Compare commits

..
Author SHA1 Message Date
Richard Ramos 47cc8a5757 test: use debug level by default 2024-01-31 15:59:51 -04:00
BalogunofAfrica eda9464b6c fix(ios): button overlap (#18620) 2024-01-30 16:34:12 +01:00
frank 8f3385fc9f fix: endless logout (#18492) 2024-01-30 20:25:53 +08:00
Paul Fitzgerald 7eb4331de1 Chore(wallet): add bridge token page (#18560) 2024-01-30 03:22:45 -08:00
Anton Iakimov 46f01f5eb3 Revert "ci: don't fail on Diawi upload failure"
This reverts commit 36168256bf.
2024-01-30 11:42:04 +01:00
Shivek Khurana 0349b0ed3d 🎡 Don't scroll on press (#18662)
- Hack fix
- Scroll on press breaks something with FlatList
- Which causes some items to not render
- Could be related to scroll view inside scroll view
- But fixing this bug by compromising the UX a tiny bit
2024-01-30 08:32:20 +00:00
Siddarth Kumar f57a26b5f5 Remove hacks from darwin nix purge script (#18656)
## Summary
In this commit we : 
- instead of `cd` to `/Library/LaunchDaemons` and then attempting to `launchctl unload` the nixos plist files we just pass the full path to unload as seen in `nix` documentation here : https://nixos.org/manual/nix/stable/installation/uninstall.html#:~:text=Stop%20and%20remove%20the%20Nix%20daemon%20services%3A

- move killing a process by prompting for confirmation to a new shell script as per feedback from previous PR : https://github.com/status-im/status-mobile/pull/18192#discussion_r1439495310

- fix few small typos

## Platforms
- macOS
2024-01-30 09:00:26 +05:30
Lungu Cristian ab509da60e fix: profile image & customization color for share-qr-code (#18643) 2024-01-29 14:14:51 +02:00
Lungu Cristian 901818492d Replace unmaintained biometrics package (#18531)
* feat: added react-native-biometrics dependency

* chore: added malli schema to auth

* fix: malli schema

* feat: using react-native-biometrics

* fix: removed biometry not-enrolled error supression

* feat: added check for enabled biometric

* fix: biometrics error handling on ios

* chore: remove touch-id library

* chore: cleanup

* removed proj.list dep

* fix: gradle get_projects regex edge-case

Handles cases when the gradle project has a description, which shows up when running `gradle projects` as (`react-native-biometrics` - react-native-biometrics), breaking `make nix-update-gradle`. Here we're just adjusting the regex to ignore everything in the line after the closing (`).

* build: ran "make nix-update-gradle"

* chore: comment typo

* chore: replaced old lib in test mocks

* fix: addressed review comments

* fix: using event for standard-auth biometrics

* ref: using ex-cause for biometric error codes

* fix: removed promesa changes

* fix: auth slide biometric success not triggered
2024-01-29 13:33:46 +02:00
Ibrahem Khalil 302020653b Add lastOpenedAt and JoinedAt for communities (#18439) 2024-01-28 14:59:10 +02:00
Yevheniia Berdnyk 205b74cd34 e2e: remived xfail marks 2024-01-26 20:03:12 +02:00
mmilad75 be618bfafe Wallet: Disable long-press feature on tokens in watch-only accounts. #18597 (#18598)
* add functionality

* update condition
2024-01-26 21:30:01 +03:30
Ulises Manuel 550fa04e33 [#18512] incomplete list of collectibles (#18577)
* Fix var naming react-native-svg namespace

* Update collectible components to show SVGs

* Display collectibles containing collection images

* Show collectibles containing SVG animations as static images

* Reduce collectibles fetching priority

* Add support to visualize SVGs in collectible detail screen
2024-01-26 09:52:04 -06:00
yqrashawnandAjay Sivan 1acf8f0d07 feat: show highest permission role when join community (#18497)
* feat: get revealed accounts for joined community

Signed-off-by: yqrashawn <namy.19@gmail.com>

* Show token requirements only when membership permissions are present.

- Add membership-permissions? to community state.

* feat: show highest permission role in overview and req to join

Signed-off-by: yqrashawn <namy.19@gmail.com>

* feat: get highest perm role in addr for perms

Signed-off-by: yqrashawn <namy.19@gmail.com>

* feat: ui for address for perm drawer

* test: unit test and spec for handle-community

Signed-off-by: yqrashawn <namy.19@gmail.com>

* fix: for #18581

* refactor: based on PR feedback

- move <-rpc to data store
- `role->translation-key`
- highest-permission-role-text -> highest-role-text
- filter out nils for handle community test

Signed-off-by: yqrashawn <namy.19@gmail.com>

---------

Signed-off-by: yqrashawn <namy.19@gmail.com>
Co-authored-by: Ajay Sivan <ajayesivan@gmail.com>
2024-01-26 15:38:10 +08:00
Parvesh Monu d82bf3365d fix bottom tabs blur overlay color (#18621) 2024-01-26 11:44:58 +05:30
Jamie Caprani d8e0d1c36b chore: remove mock data from wallet and feature flag incomplete features (#18569) 2024-01-25 13:13:33 -08:00
Yevheniia Berdnyk ad7a95ed3d e2e: updated deep/universal links tests 2024-01-25 22:20:21 +02:00
Paul Fitzgerald cc24794b74 chore (shell): add indicators to share wallet qrs (#18523) 2024-01-25 19:00:18 +00:00
Omar Basem 1fb6c60f72 Wallet: network receiver preferences (#18583)
* wallet: network receiver preferences
2024-01-25 15:45:45 +04:00
Volodymyr Kozieiev 8999b2b9e8 Implement collectible image preview (#18449)
* Lightbox refactored and moved to common screens that can be reused
* Events renamed
* Unify constants namespace importing
2024-01-25 10:58:12 +00:00
mmilad75 ed7463132a The collectibles are not supported on Optimism and Arbitrum #18507 (#18562)
* add chain id to the request

* add tests

* fix lint issues

* remove OPENSEA_API_KEY

* move subscription to a helper method for chain-ids
2024-01-24 19:17:08 +03:30
Brian Sztamfater d20f10cf8b fix: fix slide button padding on transaction confirmation page (#18585)
Signed-off-by: Brian Sztamfater <brian@status.im>
2024-01-24 10:40:53 -03:00
Brian Sztamfater ebbae051bd fix: fix button color on input amount screen (#18572)
Signed-off-by: Brian Sztamfater <brian@status.im>
2024-01-24 10:27:00 -03:00
mmilad75 521f39b6fb No ability to enter/paste assets exceeds the users's balance #18526 (#18599)
* change functionality

* fix handle-swap

* fix lint issues

* update tests

* change error to error?

* change reset-input-error

* update tests

* fix lint issues
2024-01-24 16:37:47 +03:30
127 changed files with 3269 additions and 3001 deletions
+1 -3
View File
@@ -78,9 +78,7 @@ pipeline {
}
stage('Upload') {
steps { script {
catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE') {
env.DIAWI_URL = ios.uploadToDiawi()
}
env.DIAWI_URL = ios.uploadToDiawi()
env.PKG_URL = env.DIAWI_URL
jenkins.setBuildDesc(IPA: env.PKG_URL)
} }
+2 -2
View File
@@ -144,9 +144,9 @@ used for keycard
used for svg icons, mostly collectibles are using svg
TODO: updating requires a pod update on iOS
## "react-native-touch-id": "^4.4.1"
## "react-native-biometrics": "^3.0.1"
used for touch-id identification
used for biometric authentication on Android and iOS
## "react-native-webview": "git+https://github.com/status-im/react-native-webview.git#v8.0.7_3"
+7 -7
View File
@@ -305,6 +305,8 @@ PODS:
- glog
- react-native-background-timer (2.2.0):
- React
- react-native-biometrics (3.0.1):
- React-Core
- react-native-blob-util (0.13.18):
- React-Core
- react-native-blur (4.3.3):
@@ -517,8 +519,6 @@ PODS:
- SocketRocket (0.6.1)
- SSZipArchive (2.4.3)
- TOCropViewController (2.6.1)
- TouchID (4.4.1):
- React
- Yoga (1.14.0)
DEPENDENCIES:
@@ -548,6 +548,7 @@ DEPENDENCIES:
- React-jsinspector (from `../node_modules/react-native/ReactCommon/jsinspector`)
- React-logger (from `../node_modules/react-native/ReactCommon/logger`)
- react-native-background-timer (from `../node_modules/react-native-background-timer`)
- react-native-biometrics (from `../node_modules/react-native-biometrics`)
- react-native-blob-util (from `../node_modules/react-native-blob-util`)
- "react-native-blur (from `../node_modules/@react-native-community/blur`)"
- "react-native-cameraroll (from `../node_modules/@react-native-camera-roll/camera-roll`)"
@@ -603,7 +604,6 @@ DEPENDENCIES:
- RNSVG (from `../node_modules/react-native-svg`)
- secp256k1 (from `https://github.com/status-im/secp256k1.swift.git`)
- SSZipArchive (= 2.4.3)
- TouchID (from `../node_modules/react-native-touch-id`)
- Yoga (from `../node_modules/react-native/ReactCommon/yoga`)
SPEC REPOS:
@@ -669,6 +669,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native/ReactCommon/logger"
react-native-background-timer:
:path: "../node_modules/react-native-background-timer"
react-native-biometrics:
:path: "../node_modules/react-native-biometrics"
react-native-blob-util:
:path: "../node_modules/react-native-blob-util"
react-native-blur:
@@ -778,8 +780,6 @@ EXTERNAL SOURCES:
secp256k1:
:git: https://github.com/status-im/secp256k1.swift.git
:submodules: true
TouchID:
:path: "../node_modules/react-native-touch-id"
Yoga:
:path: "../node_modules/react-native/ReactCommon/yoga"
@@ -793,7 +793,7 @@ CHECKOUT OPTIONS:
:submodules: true
SPEC CHECKSUMS:
boost: 64032b9e9b938fda23325e68a3771f0fabf414dc
boost: 57d2868c099736d80fcd648bf211b4431e51a558
BVLinearGradient: 612a04ff38e8480291f3379ee5b5a2c571f03fe0
CryptoSwift: c4f2debceb38bf44c80659afe009f71e23e4a082
DoubleConversion: 5189b271737e1565bdce30deb4a08d647e3f5f54
@@ -822,6 +822,7 @@ SPEC CHECKSUMS:
React-jsinspector: aef73cbd43b70675f572214d10fa438c89bf11ba
React-logger: 2e4aee3e11b3ec4fa6cfd8004610bbb3b8d6cca4
react-native-background-timer: 1f7d560647b40e6a60b01c452ba29c54bf581fc4
react-native-biometrics: 352e5a794bfffc46a0c86725ea7dc62deb085bdc
react-native-blob-util: 600972b1782380a5a7d5db61a3817ea32349dae9
react-native-blur: c6d0a1dc2b4b519f7afe3b14d8151998632b6d18
react-native-cameraroll: 4701ae7c3dbcd3f5e9e150ca17f250a276154b35
@@ -881,7 +882,6 @@ SPEC CHECKSUMS:
SocketRocket: f32cd54efbe0f095c4d7594881e52619cfe80b17
SSZipArchive: fe6a26b2a54d5a0890f2567b5cc6de5caa600aef
TOCropViewController: edfd4f25713d56905ad1e0b9f5be3fbe0f59c863
TouchID: ba4c656d849cceabc2e4eef722dea5e55959ecf4
Yoga: 86fed2e4d425ee4c6eab3813ba1791101ee153c6
PODFILE CHECKSUM: 3f98ce893757dfa17e609ef7cd24fa41635b73a6
@@ -0,0 +1,102 @@
package im.status.ethereum.pushnotifications;
import android.content.Context;
import android.content.Intent;
import android.app.Service;
import android.os.IBinder;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.provider.Settings;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;
import android.os.Build;
import im.status.ethereum.module.R;
public class ForegroundService extends Service {
private static final String CHANNEL_ID = "status-service";
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public int onStartCommand(Intent i, int flags, int startId) {
// NOTE: recent versions of Android require the service to display
// a sticky notification to inform the user that the service is running
Context context = getApplicationContext();
// Create the NotificationChannel, but only on API 26+ because
// the NotificationChannel class is new and not in the support library
Intent intent = null;
String notificationContentText = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationManager notificationManager =
context.getSystemService(NotificationManager.class);
NotificationChannel channel = new NotificationChannel(CHANNEL_ID,
context.getResources().getString(R.string.status_service),
NotificationManager.IMPORTANCE_HIGH);
channel.setShowBadge(false);
notificationManager.createNotificationChannel(channel);
// Create intent that takes the user to the notification channel settings so they can hide it
intent = new Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS);
intent.putExtra(Settings.EXTRA_APP_PACKAGE, getPackageName());
intent.putExtra(Settings.EXTRA_CHANNEL_ID, CHANNEL_ID);
notificationContentText = context.getResources().getString(R.string.tap_to_hide_notification);
} else {
// For older versions of android intent takes the user to the Status app
Class intentClass;
String packageName = context.getPackageName();
Intent launchIntent = context.getPackageManager().getLaunchIntentForPackage(packageName);
String className = launchIntent.getComponent().getClassName();
try {
intentClass = Class.forName(className);
} catch (ClassNotFoundException e) {
e.printStackTrace();
return 0;
}
intent = new Intent(context, intentClass);
intent.addCategory(Intent.CATEGORY_BROWSABLE);
intent.setAction(Intent.ACTION_VIEW);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
notificationContentText = context.getResources().getString(R.string.keep_status_running);
}
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_MUTABLE);
Intent stopIntent = new Intent(PushNotificationHelper.ACTION_TAP_STOP);
PendingIntent stopPendingIntent = PendingIntent.getBroadcast(context, 0, stopIntent,
PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_MUTABLE);
Notification notification = new NotificationCompat.Builder(context, CHANNEL_ID)
.setSmallIcon(R.drawable.ic_stat_notify_status)
.setContentTitle(context.getResources().getString(R.string.background_service_opened))
.setContentText(notificationContentText)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setCategory(NotificationCompat.CATEGORY_MESSAGE)
.setContentIntent(pendingIntent)
.setNumber(0)
.addAction(R.drawable.ic_stat_notify_status,
context.getResources().getString(R.string.stop),
stopPendingIntent)
.build();
// the id of the foreground notification MUST NOT be 0
startForeground(1, notification);
return START_STICKY;
}
}
@@ -1,88 +0,0 @@
package im.status.ethereum.pushnotifications
import android.content.Context
import android.content.Intent
import android.app.Service
import android.os.IBinder
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.provider.Settings
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import android.os.Build
import im.status.ethereum.module.R
class ForegroundService : Service() {
override fun onBind(intent: Intent?): IBinder? {
return null
}
override fun onStartCommand(i: Intent?, flags: Int, startId: Int): Int {
// NOTE: recent versions of Android require the service to display
// a sticky notification to inform the user that the service is running
val context: Context = getApplicationContext()
// Create the NotificationChannel, but only on API 26+ because
// the NotificationChannel class is new and not in the support library
var intent: Intent? = null
var notificationContentText: String? = null
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val notificationManager: NotificationManager = context.getSystemService(NotificationManager::class.java)
val channel = NotificationChannel(CHANNEL_ID,
context.getResources().getString(R.string.status_service),
NotificationManager.IMPORTANCE_HIGH)
channel.setShowBadge(false)
notificationManager.createNotificationChannel(channel)
// Create intent that takes the user to the notification channel settings so they can hide it
intent = Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS)
intent.putExtra(Settings.EXTRA_APP_PACKAGE, getPackageName())
intent.putExtra(Settings.EXTRA_CHANNEL_ID, CHANNEL_ID)
notificationContentText = context.getResources().getString(R.string.tap_to_hide_notification)
} else {
// For older versions of android intent takes the user to the Status app
val intentClass: java.lang.Class<*>
val packageName: String = context.getPackageName()
val launchIntent: Intent? = context.getPackageManager().getLaunchIntentForPackage(packageName)
val className: String? = launchIntent?.getComponent()?.getClassName()
if (className == null) {
return 0
}
intentClass = try {
java.lang.Class.forName(className)
} catch (e: java.lang.ClassNotFoundException) {
e.printStackTrace()
return 0
}
intent = Intent(context, intentClass)
intent.addCategory(Intent.CATEGORY_BROWSABLE)
intent.setAction(Intent.ACTION_VIEW)
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP)
notificationContentText = context.getResources().getString(R.string.keep_status_running)
}
val pendingIntent: PendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_MUTABLE)
val stopIntent = Intent(PushNotificationHelper.ACTION_TAP_STOP)
val stopPendingIntent: PendingIntent = PendingIntent.getBroadcast(context, 0, stopIntent,
PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_MUTABLE)
val notification: Notification = NotificationCompat.Builder(context, CHANNEL_ID)
.setSmallIcon(R.drawable.ic_stat_notify_status)
.setContentTitle(context.getResources().getString(R.string.background_service_opened))
.setContentText(notificationContentText)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setCategory(NotificationCompat.CATEGORY_MESSAGE)
.setContentIntent(pendingIntent)
.setNumber(0)
.addAction(R.drawable.ic_stat_notify_status,
context.getResources().getString(R.string.stop),
stopPendingIntent)
.build()
// the id of the foreground notification MUST NOT be 0
startForeground(1, notification)
return START_STICKY
}
companion object {
private const val CHANNEL_ID = "status-service"
}
}
@@ -0,0 +1,146 @@
package im.status.ethereum.pushnotifications;
import android.app.Activity;
import android.app.Application;
import android.app.NotificationManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.core.app.NotificationManagerCompat;
import java.security.SecureRandom;
import com.facebook.react.bridge.ActivityEventListener;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableMap;
import android.util.Log;
import im.status.ethereum.pushnotifications.PushNotificationJsDelivery;
public class PushNotification extends ReactContextBaseJavaModule implements ActivityEventListener {
public static final String LOG_TAG = "PushNotification";
private final SecureRandom mRandomNumberGenerator = new SecureRandom();
private PushNotificationHelper pushNotificationHelper;
private PushNotificationJsDelivery delivery;
private ReactApplicationContext reactContext;
private boolean started;
public PushNotification(ReactApplicationContext reactContext) {
super(reactContext);
this.reactContext = reactContext;
reactContext.addActivityEventListener(this);
Application applicationContext = (Application) reactContext.getApplicationContext();
IntentFilter intentFilter = new IntentFilter();
pushNotificationHelper = new PushNotificationHelper(applicationContext, intentFilter);
delivery = new PushNotificationJsDelivery(reactContext);
}
@Override
public String getName() {
return "PushNotification";
}
// removed @Override temporarily just to get it working on different versions of RN
public void onActivityResult(Activity activity, int requestCode, int resultCode, Intent data) {
onActivityResult(requestCode, resultCode, data);
}
// removed @Override temporarily just to get it working on different versions of RN
public void onActivityResult(int requestCode, int resultCode, Intent data) {
// Ignored, required to implement ActivityEventListener for RN 0.33
}
private Bundle getBundleFromIntent(Intent intent) {
Bundle bundle = null;
if (intent.hasExtra("notification")) {
bundle = intent.getBundleExtra("notification");
} else if (intent.hasExtra("google.message_id")) {
bundle = new Bundle();
bundle.putBundle("data", intent.getExtras());
}
if(null != bundle && !bundle.getBoolean("foreground", false) && !bundle.containsKey("userInteraction")) {
bundle.putBoolean("userInteraction", true);
}
return bundle;
}
@Override
public void onNewIntent(Intent intent) {
Bundle bundle = this.getBundleFromIntent(intent);
if (bundle != null) {
delivery.notifyNotification(bundle);
}
}
@ReactMethod
/**
* Creates a channel if it does not already exist. Returns whether the channel was created.
*/
public void createChannel(ReadableMap channelInfo, Callback callback) {
boolean created = pushNotificationHelper.createChannel(channelInfo);
if(callback != null) {
callback.invoke(created);
}
}
@ReactMethod
public void presentLocalNotification(ReadableMap details) {
if (!this.started) {
return;
}
Bundle bundle = Arguments.toBundle(details);
// If notification ID is not provided by the user, generate one at random
if (bundle.getString("id") == null) {
bundle.putString("id", String.valueOf(mRandomNumberGenerator.nextInt()));
}
pushNotificationHelper.sendToNotificationCentre(bundle);
}
@ReactMethod
public void clearMessageNotifications(String conversationId) {
if (this.started) {
pushNotificationHelper.clearMessageNotifications(conversationId);
}
}
@ReactMethod
public void clearAllMessageNotifications() {
pushNotificationHelper.clearAllMessageNotifications();
}
@ReactMethod
public void enableNotifications() {
this.started = true;
this.pushNotificationHelper.start();
}
@ReactMethod
public void disableNotifications() {
if (this.started) {
this.started = false;
this.pushNotificationHelper.stop();
}
}
}
@@ -1,135 +0,0 @@
package im.status.ethereum.pushnotifications
import android.app.Activity
import android.app.Application
import android.app.NotificationManager
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.Bundle
import androidx.core.app.NotificationManagerCompat
import com.facebook.react.bridge.ActivityEventListener
import com.facebook.react.bridge.Arguments
import com.facebook.react.bridge.Callback
import com.facebook.react.bridge.Promise
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReactContext
import com.facebook.react.bridge.ReactContextBaseJavaModule
import com.facebook.react.bridge.ReactMethod
import com.facebook.react.bridge.ReadableArray
import com.facebook.react.bridge.WritableArray
import com.facebook.react.bridge.ReadableMap
import com.facebook.react.bridge.WritableMap
import android.util.Log
import im.status.ethereum.pushnotifications.PushNotificationJsDelivery
class PushNotification(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext), ActivityEventListener {
companion object {
const val LOG_TAG = "PushNotification"
}
private val mRandomNumberGenerator: java.security.SecureRandom = java.security.SecureRandom()
private val pushNotificationHelper: PushNotificationHelper
private val delivery: PushNotificationJsDelivery
private val reactContext: ReactApplicationContext
private var started = false
init {
this.reactContext = reactContext
reactContext.addActivityEventListener(this)
val applicationContext: Application = reactContext.getApplicationContext() as Application
val intentFilter = IntentFilter()
pushNotificationHelper = PushNotificationHelper(applicationContext, intentFilter)
delivery = PushNotificationJsDelivery(reactContext)
}
override fun getName(): String {
return "PushNotification"
}
// removed @Override temporarily just to get it working on different versions of RN
override fun onActivityResult(activity: Activity?, requestCode: Int, resultCode: Int, data: Intent?) {
onActivityResult(requestCode, resultCode, data)
}
// removed @Override temporarily just to get it working on different versions of RN
fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
// Ignored, required to implement ActivityEventListener for RN 0.33
}
private fun getBundleFromIntent(intent: Intent): Bundle? {
var bundle: Bundle? = null
if (intent.hasExtra("notification")) {
bundle = intent.getBundleExtra("notification")
} else if (intent.hasExtra("google.message_id")) {
bundle = Bundle()
bundle.putBundle("data", intent.getExtras())
}
if (null != bundle && !bundle.getBoolean("foreground", false) && !bundle.containsKey("userInteraction")) {
bundle.putBoolean("userInteraction", true)
}
return bundle
}
override fun onNewIntent(intent: Intent) {
val bundle: Bundle? = getBundleFromIntent(intent)
if (bundle != null) {
delivery.notifyNotification(bundle)
}
}
// Creates a channel if it does not already exist. Returns whether the channel was created.
@ReactMethod
fun createChannel(channelInfo: ReadableMap?, callback: Callback?) {
if (channelInfo == null) {
return
}
val created: Boolean = pushNotificationHelper.createChannel(channelInfo)
if (callback != null) {
callback.invoke(created)
}
}
@ReactMethod
fun presentLocalNotification(details: ReadableMap?) {
if (!started) {
return
}
val bundle: Bundle? = Arguments.toBundle(details)
if (bundle == null) {
return
}
// If notification ID is not provided by the user, generate one at random
if (bundle.getString("id") == null) {
bundle.putString("id", mRandomNumberGenerator.nextInt().toString())
}
pushNotificationHelper.sendToNotificationCentre(bundle)
}
@ReactMethod
fun clearMessageNotifications(conversationId: String?) {
if (started && conversationId != null) {
pushNotificationHelper.clearMessageNotifications(conversationId)
}
}
@ReactMethod
fun clearAllMessageNotifications() {
pushNotificationHelper.clearAllMessageNotifications()
}
@ReactMethod
fun enableNotifications() {
started = true
pushNotificationHelper.start()
}
@ReactMethod
fun disableNotifications() {
if (started) {
started = false
pushNotificationHelper.stop()
}
}
}
@@ -0,0 +1,93 @@
package im.status.ethereum.pushnotifications;
import android.os.Build;
import android.app.Application;
import android.app.NotificationManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import com.facebook.react.ReactApplication;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.bridge.ReactContext;
import im.status.ethereum.pushnotifications.PushNotificationJsDelivery;
import static im.status.ethereum.pushnotifications.PushNotification.LOG_TAG;
public class PushNotificationActions extends BroadcastReceiver {
@Override
public void onReceive(final Context context, Intent intent) {
String intentActionPrefix = context.getPackageName() + ".ACTION_";
Log.i(LOG_TAG, "PushNotificationBootEventReceiver loading scheduled notifications");
if (null == intent.getAction() || !intent.getAction().startsWith(intentActionPrefix)) {
return;
}
final Bundle bundle = intent.getBundleExtra("notification");
// Dismiss the notification popup.
NotificationManager manager = (NotificationManager) context.getSystemService(context.NOTIFICATION_SERVICE);
int notificationID = Integer.parseInt(bundle.getString("id"));
boolean autoCancel = bundle.getBoolean("autoCancel", true);
if(autoCancel) {
if (bundle.containsKey("tag")) {
String tag = bundle.getString("tag");
manager.cancel(tag, notificationID);
} else {
manager.cancel(notificationID);
}
}
boolean invokeApp = bundle.getBoolean("invokeApp", true);
// Notify the action.
if(invokeApp) {
IntentFilter intentFilter = new IntentFilter();
PushNotificationHelper helper = new PushNotificationHelper((Application) context.getApplicationContext(), intentFilter);
helper.invokeApp(bundle);
} else {
// We need to run this on the main thread, as the React code assumes that is true.
// Namely, DevServerHelper constructs a Handler() without a Looper, which triggers:
// "Can't create handler inside thread that has not called Looper.prepare()"
Handler handler = new Handler(Looper.getMainLooper());
handler.post(new Runnable() {
public void run() {
// Construct and load our normal React JS code bundle
final ReactInstanceManager mReactInstanceManager = ((ReactApplication) context.getApplicationContext()).getReactNativeHost().getReactInstanceManager();
ReactContext context = mReactInstanceManager.getCurrentReactContext();
PushNotificationJsDelivery delivery = new PushNotificationJsDelivery(context);
// If it's constructed, send a notification
if (context != null) {
delivery.notifyNotificationAction(bundle);
} else {
// Otherwise wait for construction, then send the notification
mReactInstanceManager.addReactInstanceEventListener(new ReactInstanceManager.ReactInstanceEventListener() {
public void onReactContextInitialized(ReactContext context) {
PushNotificationJsDelivery delivery = new PushNotificationJsDelivery(context);
delivery.notifyNotificationAction(bundle);
mReactInstanceManager.removeReactInstanceEventListener(this);
}
});
if (!mReactInstanceManager.hasStartedCreatingInitialContext()) {
// Construct it in the background
mReactInstanceManager.createReactContextInBackground();
}
}
}
});
}
}
}
@@ -1,89 +0,0 @@
package im.status.ethereum.pushnotifications
import android.os.Build
import android.app.Application
import android.app.NotificationManager
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.util.Log
import com.facebook.react.ReactApplication
import com.facebook.react.ReactInstanceManager
import com.facebook.react.bridge.ReactContext
import im.status.ethereum.pushnotifications.PushNotificationJsDelivery
class PushNotificationActions : BroadcastReceiver() {
companion object {
const val LOG_TAG = "PushNotification"
}
override fun onReceive(context: Context, intent: Intent) {
val intentActionPrefix: String = context.getPackageName() + ".ACTION_"
Log.i(LOG_TAG, "PushNotificationBootEventReceiver loading scheduled notifications")
var intentAction: String? = intent.getAction()
if (intentAction == null || !intentAction.startsWith(intentActionPrefix)) {
return
}
val bundle: Bundle? = intent.getBundleExtra("notification")
if (bundle == null) {
return
}
// Dismiss the notification popup.
val manager: NotificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val notificationID: Int? = bundle.getString("id")?.toInt()
val autoCancel: Boolean = bundle.getBoolean("autoCancel", true)
if (notificationID != null && autoCancel) {
if (bundle.containsKey("tag")) {
val tag: String? = bundle.getString("tag")
if (tag != null) {
manager.cancel(tag, notificationID)
}
} else {
manager.cancel(notificationID)
}
}
val invokeApp: Boolean = bundle.getBoolean("invokeApp", true)
// Notify the action.
if (invokeApp) {
val intentFilter = IntentFilter()
val helper = PushNotificationHelper(context.getApplicationContext() as Application, intentFilter)
helper.invokeApp(bundle)
} else {
// We need to run this on the main thread, as the React code assumes that is true.
// Namely, DevServerHelper constructs a Handler() without a Looper, which triggers:
// "Can't create handler inside thread that has not called Looper.prepare()"
val handler = Handler(Looper.getMainLooper())
handler.post(object : java.lang.Runnable {
override fun run() {
// Construct and load our normal React JS code bundle
val mReactInstanceManager: ReactInstanceManager = (context.getApplicationContext() as ReactApplication).getReactNativeHost().getReactInstanceManager()
val context: ReactContext? = mReactInstanceManager.getCurrentReactContext()
// If it's constructed, send a notification
if (context != null) {
val delivery = PushNotificationJsDelivery(context)
delivery.notifyNotificationAction(bundle)
} else {
// Otherwise wait for construction, then send the notification
mReactInstanceManager.addReactInstanceEventListener(object : ReactInstanceManager.ReactInstanceEventListener {
override fun onReactContextInitialized(context: ReactContext) {
val delivery = PushNotificationJsDelivery(context)
delivery.notifyNotificationAction(bundle)
mReactInstanceManager.removeReactInstanceEventListener(this)
}
})
if (!mReactInstanceManager.hasStartedCreatingInitialContext()) {
// Construct it in the background
mReactInstanceManager.createReactContextInBackground()
}
}
}
})
}
}
}
@@ -0,0 +1,886 @@
// https://github.com/zo0r/react-native-push-notification/blob/bedc8f646aab67d594f291449fbfa24e07b64fe8/android/src/main/java/com/dieam/reactnativepushnotification/modules/RNPushNotificationHelper.java Copy-Paste with removed firebase
package im.status.ethereum.pushnotifications;
import android.app.ActivityManager;
import android.app.ActivityManager.RunningAppProcessInfo;
import android.app.AlarmManager;
import android.app.Application;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.BroadcastReceiver;
import android.content.SharedPreferences;
import android.content.pm.ApplicationInfo;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.RectF;
import android.media.AudioAttributes;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.service.notification.StatusBarNotification;
import android.util.Log;
import android.util.Base64;
import androidx.annotation.RequiresApi;
import androidx.core.app.NotificationCompat;
import androidx.core.app.Person;
import androidx.core.graphics.drawable.IconCompat;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap;
import org.json.JSONArray;
import org.json.JSONException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.HashMap;
import java.util.Map;
import im.status.ethereum.module.R;
import static im.status.ethereum.pushnotifications.PushNotification.LOG_TAG;
public class PushNotificationHelper {
private Context context;
private static final long DEFAULT_VIBRATION = 300L;
private static final String CHANNEL_ID = "status-im-notifications";
public static final String ACTION_DELETE_NOTIFICATION = "im.status.ethereum.module.DELETE_NOTIFICATION";
public static final String ACTION_TAP_STOP = "im.status.ethereum.module.TAP_STOP";
final int flag = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ? PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_MUTABLE : PendingIntent.FLAG_CANCEL_CURRENT;
private NotificationManager notificationManager;
private HashMap<String, Person> persons;
private HashMap<String, StatusMessageGroup> messageGroups;
private IntentFilter intentFilter;
public PushNotificationHelper(Application context, IntentFilter intentFilter) {
this.context = context;
this.intentFilter = intentFilter;
this.persons = new HashMap<String, Person>();
this.messageGroups = new HashMap<String, StatusMessageGroup>();
this.notificationManager = context.getSystemService(NotificationManager.class);
this.registerBroadcastReceiver();
}
public Intent getOpenAppIntent(String deepLink) {
Class intentClass;
String packageName = context.getPackageName();
Intent launchIntent = context.getPackageManager().getLaunchIntentForPackage(packageName);
String className = launchIntent.getComponent().getClassName();
try {
intentClass = Class.forName(className);
} catch (ClassNotFoundException e) {
e.printStackTrace();
return null;
}
Intent intent = new Intent(context, intentClass);
intent.addCategory(Intent.CATEGORY_BROWSABLE);
intent.setAction(Intent.ACTION_VIEW);
//NOTE: you might wonder, why the heck did he decide to set these flags in particular. Well,
//the answer is a simple as it can get in the Android native development world. I noticed
//that my initial setup was opening the app but wasn't triggering any events on the js side, like
//the links do from the browser. So I compared both intents and noticed that the link from
//the browser produces an intent with the flag 0x14000000. I found out that it was the following
//flags in this link:
//https://stackoverflow.com/questions/52390129/android-intent-setflags-issue
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
if (deepLink != null) {
intent.setData(Uri.parse(deepLink));
}
return intent;
}
//NOTE: we use a dynamically created BroadcastReceiver here so that we can capture
//intents from notifications and act on them. For instance when tapping/dismissing
//a chat notification we want to clear the chat so that next messages don't show
//the messages that we have seen again
private final BroadcastReceiver notificationActionReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction() == ACTION_DELETE_NOTIFICATION) {
String groupId = intent.getExtras().getString("im.status.ethereum.groupId");
if (groupId != null) {
cleanGroup(groupId);
}
}
if (intent.getAction() == ACTION_TAP_STOP) {
stop();
System.exit(0);
}
Log.e(LOG_TAG, "intent received: " + intent.getAction());
}
};
public void registerBroadcastReceiver() {
this.intentFilter.addAction(ACTION_DELETE_NOTIFICATION);
this.intentFilter.addAction(ACTION_TAP_STOP);
context.registerReceiver(notificationActionReceiver, this.intentFilter);
Log.e(LOG_TAG, "Broadcast Receiver registered");
}
private NotificationManager notificationManager() {
return (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
}
public void invokeApp(Bundle bundle) {
String packageName = context.getPackageName();
Intent launchIntent = context.getPackageManager().getLaunchIntentForPackage(packageName);
String className = launchIntent.getComponent().getClassName();
try {
Class<?> activityClass = Class.forName(className);
Intent activityIntent = new Intent(context, activityClass);
if(bundle != null) {
activityIntent.putExtra("notification", bundle);
}
activityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(activityIntent);
} catch(Exception e) {
Log.e(LOG_TAG, "Class not found", e);
return;
}
}
public void sendToNotificationCentre(final Bundle bundle) {
PushNotificationPicturesAggregator aggregator = new PushNotificationPicturesAggregator(new PushNotificationPicturesAggregator.Callback() {
public void call(Bitmap largeIconImage, Bitmap bigPictureImage) {
sendToNotificationCentreWithPicture(bundle, largeIconImage, bigPictureImage);
}
});
aggregator.setLargeIconUrl(context, bundle.getString("largeIconUrl"));
aggregator.setBigPictureUrl(context, bundle.getString("bigPictureUrl"));
}
public void handleConversation(final Bundle bundle) {
if (bundle.getBoolean("deleted")){
this.removeStatusMessage(bundle);
} else {
this.addStatusMessage(bundle);
}
}
public void clearMessageNotifications(String conversationId) {
notificationManager.cancel(conversationId.hashCode());
cleanGroup(conversationId);
}
public void clearAllMessageNotifications() {
notificationManager.cancelAll();
}
public void sendToNotificationCentreWithPicture(final Bundle bundle, Bitmap largeIconBitmap, Bitmap bigPictureBitmap) {
try {
Class intentClass = getMainActivityClass();
if (intentClass == null) {
Log.e(LOG_TAG, "No activity class found for the notification");
return;
}
if (bundle.getBoolean("isConversation")) {
this.handleConversation(bundle);
return;
}
if (bundle.getString("message") == null) {
// this happens when a 'data' notification is received - we do not synthesize a local notification in this case
Log.d(LOG_TAG, "Ignore this message if you sent data-only notification. Cannot send to notification centre because there is no 'message' field in: " + bundle);
return;
}
String notificationIdString = bundle.getString("id");
if (notificationIdString == null) {
Log.e(LOG_TAG, "No notification ID specified for the notification");
return;
}
Resources res = context.getResources();
String packageName = context.getPackageName();
String title = bundle.getString("title");
if (title == null) {
ApplicationInfo appInfo = context.getApplicationInfo();
title = context.getPackageManager().getApplicationLabel(appInfo).toString();
}
int priority = NotificationCompat.PRIORITY_HIGH;
final String priorityString = bundle.getString("priority");
if (priorityString != null) {
switch (priorityString.toLowerCase()) {
case "max":
priority = NotificationCompat.PRIORITY_MAX;
break;
case "high":
priority = NotificationCompat.PRIORITY_HIGH;
break;
case "low":
priority = NotificationCompat.PRIORITY_LOW;
break;
case "min":
priority = NotificationCompat.PRIORITY_MIN;
break;
case "default":
priority = NotificationCompat.PRIORITY_DEFAULT;
break;
default:
priority = NotificationCompat.PRIORITY_HIGH;
}
}
int visibility = NotificationCompat.VISIBILITY_PRIVATE;
final String visibilityString = bundle.getString("visibility");
if (visibilityString != null) {
switch (visibilityString.toLowerCase()) {
case "private":
visibility = NotificationCompat.VISIBILITY_PRIVATE;
break;
case "public":
visibility = NotificationCompat.VISIBILITY_PUBLIC;
break;
case "secret":
visibility = NotificationCompat.VISIBILITY_SECRET;
break;
default:
visibility = NotificationCompat.VISIBILITY_PRIVATE;
}
}
String channel_id = bundle.getString("channelId");
if(channel_id == null) {
channel_id = this.getNotificationDefaultChannelId();
}
NotificationCompat.Builder notification = new NotificationCompat.Builder(context, channel_id)
.setContentTitle(title)
.setTicker(bundle.getString("ticker"))
.setVisibility(visibility)
.setPriority(priority)
.setAutoCancel(bundle.getBoolean("autoCancel", true))
.setOnlyAlertOnce(bundle.getBoolean("onlyAlertOnce", false));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { // API 24 and higher
// Restore showing timestamp on Android 7+
// Source: https://developer.android.com/reference/android/app/Notification.Builder.html#setShowWhen(boolean)
boolean showWhen = bundle.getBoolean("showWhen", true);
notification.setShowWhen(showWhen);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { // API 26 and higher
// Changing Default mode of notification
notification.setDefaults(Notification.DEFAULT_LIGHTS);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH) { // API 20 and higher
String group = bundle.getString("group");
if (group != null) {
notification.setGroup(group);
}
if (bundle.containsKey("groupSummary") || bundle.getBoolean("groupSummary")) {
notification.setGroupSummary(bundle.getBoolean("groupSummary"));
}
}
String numberString = bundle.getString("number");
if (numberString != null) {
notification.setNumber(Integer.parseInt(numberString));
}
// Small icon
int smallIconResId = 0;
String smallIcon = bundle.getString("smallIcon");
if (smallIcon != null && !smallIcon.isEmpty()) {
smallIconResId = res.getIdentifier(smallIcon, "mipmap", packageName);
} else if(smallIcon == null) {
smallIconResId = res.getIdentifier("ic_stat_notify_status", "drawable", packageName);
}
if (smallIconResId == 0) {
smallIconResId = res.getIdentifier("ic_launcher", "mipmap", packageName);
if (smallIconResId == 0) {
smallIconResId = android.R.drawable.ic_dialog_info;
}
}
notification.setSmallIcon(smallIconResId);
// Large icon
if(largeIconBitmap == null) {
int largeIconResId = 0;
String largeIcon = bundle.getString("largeIcon");
if (largeIcon != null && !largeIcon.isEmpty()) {
largeIconResId = res.getIdentifier(largeIcon, "mipmap", packageName);
} else if(largeIcon == null) {
largeIconResId = res.getIdentifier("ic_launcher", "mipmap", packageName);
}
// Before Lolipop there was no large icon for notifications.
if (largeIconResId != 0 && (largeIcon != null || Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)) {
largeIconBitmap = BitmapFactory.decodeResource(res, largeIconResId);
}
}
Bundle author = bundle.getBundle("notificationAuthor");
if (largeIconBitmap == null && author != null) {
String base64Image = author.getString("icon").split(",")[1];
byte[] decodedString = Base64.decode(base64Image, Base64.DEFAULT);
Bitmap decodedByte = BitmapFactory.decodeByteArray(decodedString, 0, decodedString.length);
notification.setLargeIcon(getCircleBitmap(decodedByte));
} else if (largeIconBitmap != null){
notification.setLargeIcon(largeIconBitmap);
}
String message = bundle.getString("message");
notification.setContentText(message);
String subText = bundle.getString("subText");
if (subText != null) {
notification.setSubText(subText);
}
String bigText = bundle.getString("bigText");
if (bigText == null) {
bigText = message;
}
NotificationCompat.Style style;
if(bigPictureBitmap != null) {
style = new NotificationCompat.BigPictureStyle()
.bigPicture(bigPictureBitmap)
.setBigContentTitle(title)
.setSummaryText(message);
} else {
style = new NotificationCompat.BigTextStyle().bigText(bigText);
}
notification.setStyle(style);
Intent intent = new Intent(context, intentClass);
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
bundle.putBoolean("foreground", this.isApplicationInForeground());
bundle.putBoolean("userInteraction", true);
intent.putExtra("notification", bundle);
Uri soundUri = null;
if (!bundle.containsKey("playSound") || bundle.getBoolean("playSound")) {
String soundName = bundle.getString("soundName");
if (soundName == null) {
soundName = "default";
}
soundUri = getSoundUri(soundName);
notification.setSound(soundUri);
}
if (soundUri == null || Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
notification.setSound(null);
}
if (bundle.containsKey("ongoing") || bundle.getBoolean("ongoing")) {
notification.setOngoing(bundle.getBoolean("ongoing"));
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
notification.setCategory(NotificationCompat.CATEGORY_CALL);
String color = bundle.getString("color");
int defaultColor = -1;
if (color != null) {
notification.setColor(Color.parseColor(color));
} else if (defaultColor != -1) {
notification.setColor(defaultColor);
}
}
int notificationID = notificationIdString.hashCode();
notification.setContentIntent(createOnTapIntent(context, notificationID, bundle.getString("deepLink")))
.setDeleteIntent(createOnDismissedIntent(context, notificationID, bundle.getString("deepLink")));
NotificationManager notificationManager = notificationManager();
long[] vibratePattern = new long[]{0};
if (!bundle.containsKey("vibrate") || bundle.getBoolean("vibrate")) {
long vibration = bundle.containsKey("vibration") ? (long) bundle.getDouble("vibration") : DEFAULT_VIBRATION;
if (vibration == 0)
vibration = DEFAULT_VIBRATION;
vibratePattern = new long[]{0, vibration};
notification.setVibrate(vibratePattern);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// Define the shortcutId
String shortcutId = bundle.getString("shortcutId");
if (shortcutId != null) {
notification.setShortcutId(shortcutId);
}
Long timeoutAfter = (long) bundle.getDouble("timeoutAfter");
if (timeoutAfter != null && timeoutAfter >= 0) {
notification.setTimeoutAfter(timeoutAfter);
}
}
Long when = (long) bundle.getDouble("when");
if (when != null && when >= 0) {
notification.setWhen(when);
}
notification.setUsesChronometer(bundle.getBoolean("usesChronometer", false));
notification.setChannelId(channel_id);
JSONArray actionsArray = null;
try {
actionsArray = bundle.getString("actions") != null ? new JSONArray(bundle.getString("actions")) : null;
} catch (JSONException e) {
Log.e(LOG_TAG, "Exception while converting actions to JSON object.", e);
}
if (actionsArray != null) {
// No icon for now. The icon value of 0 shows no icon.
int icon = 0;
// Add button for each actions.
for (int i = 0; i < actionsArray.length(); i++) {
String action;
try {
action = actionsArray.getString(i);
} catch (JSONException e) {
Log.e(LOG_TAG, "Exception while getting action from actionsArray.", e);
continue;
}
Intent actionIntent = new Intent(context, PushNotificationActions.class);
actionIntent.setAction(packageName + ".ACTION_" + i);
actionIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
// Add "action" for later identifying which button gets pressed.
bundle.putString("action", action);
actionIntent.putExtra("notification", bundle);
actionIntent.setPackage(packageName);
PendingIntent pendingActionIntent = PendingIntent.getBroadcast(context, notificationID, actionIntent,
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
notification.addAction(new NotificationCompat.Action.Builder(icon, action, pendingActionIntent).build());
} else {
notification.addAction(icon, action, pendingActionIntent);
}
}
}
if (!(this.isApplicationInForeground() && bundle.getBoolean("ignoreInForeground"))) {
Notification info = notification.build();
info.defaults |= Notification.DEFAULT_LIGHTS;
if (bundle.containsKey("tag")) {
String tag = bundle.getString("tag");
notificationManager.notify(tag, notificationID, info);
} else {
notificationManager.notify(notificationID, info);
}
}
} catch (Exception e) {
Log.e(LOG_TAG, "failed to send push notification", e);
}
}
private boolean checkOrCreateChannel(NotificationManager manager, String channel_id, String channel_name, String channel_description, Uri soundUri, int importance, long[] vibratePattern, boolean showBadge) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O)
return false;
if (manager == null)
return false;
NotificationChannel channel = manager.getNotificationChannel(channel_id);
if (
channel == null && channel_name != null && channel_description != null ||
channel != null &&
(
channel_name != null && !channel.getName().equals(channel_name) ||
channel_description != null && !channel.getDescription().equals(channel_description)
)
) {
// If channel doesn't exist create a new one.
// If channel name or description is updated then update the existing channel.
channel = new NotificationChannel(channel_id, channel_name, importance);
channel.setDescription(channel_description);
channel.enableLights(true);
channel.enableVibration(vibratePattern != null);
channel.setVibrationPattern(vibratePattern);
channel.setShowBadge(showBadge);
if (soundUri != null) {
AudioAttributes audioAttributes = new AudioAttributes.Builder()
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
.setUsage(AudioAttributes.USAGE_NOTIFICATION)
.build();
channel.setSound(soundUri, audioAttributes);
} else {
channel.setSound(null, null);
}
manager.createNotificationChannel(channel);
return true;
}
return false;
}
public boolean createChannel(ReadableMap channelInfo) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O)
return false;
String channelId = channelInfo.getString("channelId");
String channelName = channelInfo.getString("channelName");
String channelDescription = channelInfo.hasKey("channelDescription") ? channelInfo.getString("channelDescription") : "";
String soundName = channelInfo.hasKey("soundName") ? channelInfo.getString("soundName") : "default";
int importance = channelInfo.hasKey("importance") ? channelInfo.getInt("importance") : 4;
boolean vibrate = channelInfo.hasKey("vibrate") && channelInfo.getBoolean("vibrate");
long[] vibratePattern = vibrate ? new long[] { DEFAULT_VIBRATION } : null;
boolean showBadge = channelInfo.hasKey("showBadge") && channelInfo.getBoolean("showBadge");
NotificationManager manager = notificationManager();
Uri soundUri = getSoundUri(soundName);
return checkOrCreateChannel(manager, channelId, channelName, channelDescription, soundUri, importance, vibratePattern, showBadge);
}
public String getNotificationDefaultChannelId() {
return this.CHANNEL_ID;
}
private Uri getSoundUri(String soundName) {
if (soundName == null || "default".equalsIgnoreCase(soundName)) {
return RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
} else {
// sound name can be full filename, or just the resource name.
// So the strings 'my_sound.mp3' AND 'my_sound' are accepted
// The reason is to make the iOS and android javascript interfaces compatible
int resId;
if (context.getResources().getIdentifier(soundName, "raw", context.getPackageName()) != 0) {
resId = context.getResources().getIdentifier(soundName, "raw", context.getPackageName());
} else {
soundName = soundName.substring(0, soundName.lastIndexOf('.'));
resId = context.getResources().getIdentifier(soundName, "raw", context.getPackageName());
}
return Uri.parse("android.resource://" + context.getPackageName() + "/" + resId);
}
}
public Class getMainActivityClass() {
String packageName = context.getPackageName();
Intent launchIntent = context.getPackageManager().getLaunchIntentForPackage(packageName);
String className = launchIntent.getComponent().getClassName();
try {
return Class.forName(className);
} catch (ClassNotFoundException e) {
e.printStackTrace();
return null;
}
}
public boolean isApplicationInForeground() {
ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
List<RunningAppProcessInfo> processInfos = activityManager.getRunningAppProcesses();
if (processInfos != null) {
for (RunningAppProcessInfo processInfo : processInfos) {
if (processInfo.processName.equals(context.getPackageName())
&& processInfo.importance == RunningAppProcessInfo.IMPORTANCE_FOREGROUND
&& processInfo.pkgList.length > 0) {
return true;
}
}
}
return false;
}
private Bitmap getCircleBitmap(Bitmap bitmap) {
final Bitmap output = Bitmap.createBitmap(bitmap.getWidth(),
bitmap.getHeight(), Bitmap.Config.ARGB_8888);
final Canvas canvas = new Canvas(output);
final int color = Color.RED;
final Paint paint = new Paint();
final Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
final RectF rectF = new RectF(rect);
paint.setAntiAlias(true);
canvas.drawARGB(0, 0, 0, 0);
paint.setColor(color);
canvas.drawOval(rectF, paint);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
canvas.drawBitmap(bitmap, rect, rect, paint);
bitmap.recycle();
return output;
}
private Person getPerson(Bundle bundle) {
String name = bundle.getString("name");
return new Person.Builder().setName(name).build();
}
private StatusMessage createMessage(Bundle data) {
Person author = getPerson(data.getBundle("notificationAuthor"));
long timeStampLongValue = (long) data.getDouble("timestamp");
return new StatusMessage(data.getString("id"), author, timeStampLongValue, data.getString("message"));
}
private PendingIntent createGroupOnDismissedIntent(Context context, int notificationId, String groupId, String deepLink) {
Intent intent = new Intent(ACTION_DELETE_NOTIFICATION);
intent.putExtra("im.status.ethereum.deepLink", deepLink);
intent.putExtra("im.status.ethereum.groupId", groupId);
return PendingIntent.getBroadcast(context.getApplicationContext(), notificationId, intent, flag);
}
private PendingIntent createGroupOnTapIntent(Context context, int notificationId, String groupId, String deepLink) {
Intent intent = getOpenAppIntent(deepLink);
return PendingIntent.getActivity(context.getApplicationContext(), notificationId, intent, flag);
}
private PendingIntent createOnTapIntent(Context context, int notificationId, String deepLink) {
Intent intent = getOpenAppIntent(deepLink);
return PendingIntent.getActivity(context.getApplicationContext(), notificationId, intent, flag);
}
private PendingIntent createOnDismissedIntent(Context context, int notificationId, String deepLink) {
Intent intent = new Intent(ACTION_DELETE_NOTIFICATION);
intent.putExtra("im.status.ethereum.deepLink", deepLink);
return PendingIntent.getBroadcast(context.getApplicationContext(), notificationId, intent, flag);
}
public void removeStatusMessage(Bundle bundle) {
String conversationId = bundle.getString("conversationId");
StatusMessageGroup group = this.messageGroups.get(conversationId);
NotificationManager notificationManager = notificationManager();
if (group == null) {
group = new StatusMessageGroup(conversationId);
}
this.messageGroups.put(conversationId, group);
String id = bundle.getString("id");
group.removeMessage(id);
this.showMessages(bundle);
}
public StatusMessageGroup getMessageGroup(String conversationId) {
return this.messageGroups.get(conversationId);
}
public void addStatusMessage(Bundle bundle) {
String conversationId = bundle.getString("conversationId");
StatusMessageGroup group = this.messageGroups.get(conversationId);
NotificationManager notificationManager = notificationManager();
if (group == null) {
group = new StatusMessageGroup(conversationId);
}
this.messageGroups.put(conversationId, group);
group.addMessage(createMessage(bundle));
this.showMessages(bundle);
}
public void showMessages(Bundle bundle) {
String conversationId = bundle.getString("conversationId");
StatusMessageGroup group = this.messageGroups.get(conversationId);
NotificationManager notificationManager = notificationManager();
NotificationCompat.MessagingStyle messagingStyle = new NotificationCompat.MessagingStyle("Me");
ArrayList<StatusMessage> messages = group.getMessages();
if (messages.size() == 0) {
notificationManager.cancel(conversationId.hashCode());
return;
}
for (int i = 0; i < messages.size(); i++) {
StatusMessage message = messages.get(i);
messagingStyle.addMessage(message.getText(),
message.getTimestamp(),
message.getAuthor());
}
if (bundle.getString("title") != null) {
messagingStyle.setConversationTitle(bundle.getString("title"));
}
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, CHANNEL_ID)
.setSmallIcon(R.drawable.ic_stat_notify_status)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setCategory(NotificationCompat.CATEGORY_MESSAGE)
.setStyle(messagingStyle)
.setGroup(conversationId)
.setOnlyAlertOnce(true)
.setGroupSummary(true)
.setContentIntent(createGroupOnTapIntent(context, conversationId.hashCode(), conversationId, bundle.getString("deepLink")))
.setDeleteIntent(createGroupOnDismissedIntent(context, conversationId.hashCode(), conversationId, bundle.getString("deepLink")))
.setNumber(messages.size() + 1)
.setAutoCancel(true);
if (Build.VERSION.SDK_INT >= 21) {
builder.setVibrate(new long[0]);
}
notificationManager.notify(conversationId.hashCode(), builder.build());
}
class StatusMessageGroup {
private ArrayList<StatusMessage> messages;
private String id;
StatusMessageGroup(String id) {
this.id = id;
this.messages = new ArrayList<StatusMessage>();
}
public ArrayList<StatusMessage> getMessages() {
return messages;
}
public void addMessage(StatusMessage message) {
this.messages.add(message);
}
public void removeMessage(String id) {
ArrayList<StatusMessage> newMessages = new ArrayList<StatusMessage>();
for(StatusMessage message: this.messages) {
if(!message.id.equals(id)) {
newMessages.add(message);
}
}
this.messages = newMessages;
}
public String getId() {
return this.id;
}
}
class StatusMessage {
public Person getAuthor() {
return author;
}
public long getTimestamp() {
return timestamp;
}
public String getText() {
return text;
}
private String id;
private Person author;
private long timestamp;
private String text;
StatusMessage(String id, Person author, long timestamp, String text) {
this.id = id;
this.author = author;
this.timestamp = timestamp;
this.text = text;
}
}
private void removeGroup(String groupId) {
this.messageGroups.remove(groupId);
}
private void cleanGroup(String groupId) {
removeGroup(groupId);
if (messageGroups.size() == 0) {
notificationManager.cancelAll();
}
}
public void start() {
Log.e(LOG_TAG, "Starting Foreground Service");
Intent serviceIntent = new Intent(context, ForegroundService.class);
context.startService(serviceIntent);
this.registerBroadcastReceiver();
}
public void stop() {
Log.e(LOG_TAG, "Stopping Foreground Service");
//NOTE: we cancel all the current notifications, because the intents can't be used anymore
//since the broadcast receiver will be killed as well and won't be able to handle any intent
notificationManager.cancelAll();
Intent serviceIntent = new Intent(context, ForegroundService.class);
context.stopService(serviceIntent);
context.unregisterReceiver(notificationActionReceiver);
}
}
@@ -1,771 +0,0 @@
// https://github.com/zo0r/react-native-push-notification/blob/bedc8f646aab67d594f291449fbfa24e07b64fe8/android/src/main/java/com/dieam/reactnativepushnotification/modules/RNPushNotificationHelper.java Copy-Paste with removed firebase
package im.status.ethereum.pushnotifications
import android.app.ActivityManager
import android.app.ActivityManager.RunningAppProcessInfo
import android.app.AlarmManager
import android.app.Application
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.BroadcastReceiver
import android.content.SharedPreferences
import android.content.pm.ApplicationInfo
import android.content.res.Resources
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Color
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.PorterDuff
import android.graphics.PorterDuffXfermode
import android.graphics.Rect
import android.graphics.RectF
import android.media.AudioAttributes
import android.media.RingtoneManager
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.service.notification.StatusBarNotification
import android.util.Log
import android.util.Base64
import androidx.annotation.RequiresApi
import androidx.core.app.NotificationCompat
import androidx.core.app.Person
import androidx.core.graphics.drawable.IconCompat
import com.facebook.react.bridge.Arguments
import com.facebook.react.bridge.ReadableArray
import com.facebook.react.bridge.ReadableMap
import com.facebook.react.bridge.WritableArray
import com.facebook.react.bridge.WritableMap
import org.json.JSONArray
import org.json.JSONException
import im.status.ethereum.module.R
class PushNotificationHelper(context: Application, intentFilter: IntentFilter) {
companion object {
const val LOG_TAG = "PushNotification"
private const val DEFAULT_VIBRATION: Long = 300L
private const val CHANNEL_ID = "status-im-notifications"
const val ACTION_DELETE_NOTIFICATION = "im.status.ethereum.module.DELETE_NOTIFICATION"
const val ACTION_TAP_STOP = "im.status.ethereum.module.TAP_STOP"
}
private val context: Context
val flag: Int = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_MUTABLE else PendingIntent.FLAG_CANCEL_CURRENT
private val notificationManager: NotificationManager
private val persons: HashMap<String, Person>
private val messageGroups: HashMap<String, StatusMessageGroup>
private val intentFilter: IntentFilter
fun getOpenAppIntent(deepLink: String?): Intent? {
val intentClass: java.lang.Class<*>
val packageName: String = context.getPackageName()
val launchIntent: Intent? = context.getPackageManager().getLaunchIntentForPackage(packageName)
if (launchIntent == null) {
return null
}
val className: String? = launchIntent.getComponent()?.getClassName()
if (className == null) {
return null
}
intentClass = try {
java.lang.Class.forName(className)
} catch (e: java.lang.ClassNotFoundException) {
e.printStackTrace()
return null
}
val intent = Intent(context, intentClass)
intent.addCategory(Intent.CATEGORY_BROWSABLE)
intent.setAction(Intent.ACTION_VIEW)
//NOTE: you might wonder, why the heck did he decide to set these flags in particular. Well,
//the answer is a simple as it can get in the Android native development world. I noticed
//that my initial setup was opening the app but wasn't triggering any events on the js side, like
//the links do from the browser. So I compared both intents and noticed that the link from
//the browser produces an intent with the flag 0x14000000. I found out that it was the following
//flags in this link:
//https://stackoverflow.com/questions/52390129/android-intent-setflags-issue
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP)
if (deepLink != null) {
intent.setData(Uri.parse(deepLink))
}
return intent
}
//NOTE: we use a dynamically created BroadcastReceiver here so that we can capture
//intents from notifications and act on them. For instance when tapping/dismissing
//a chat notification we want to clear the chat so that next messages don't show
//the messages that we have seen again
private val notificationActionReceiver: BroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent) {
if (intent.getAction() === ACTION_DELETE_NOTIFICATION) {
val groupId: String? = intent.getExtras()?.getString("im.status.ethereum.groupId")
if (groupId != null) {
cleanGroup(groupId)
}
}
if (intent.getAction() === ACTION_TAP_STOP) {
stop()
java.lang.System.exit(0)
}
Log.e(LOG_TAG, "intent received: " + intent.getAction())
}
}
init {
this.context = context
this.intentFilter = intentFilter
persons = HashMap<String, Person>()
messageGroups = HashMap<String, StatusMessageGroup>()
notificationManager = context.getSystemService(NotificationManager::class.java)
registerBroadcastReceiver()
}
fun registerBroadcastReceiver() {
intentFilter.addAction(ACTION_DELETE_NOTIFICATION)
intentFilter.addAction(ACTION_TAP_STOP)
context.registerReceiver(notificationActionReceiver, intentFilter)
Log.e(LOG_TAG, "Broadcast Receiver registered")
}
private fun notificationManager(): NotificationManager {
return context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
}
fun invokeApp(bundle: Bundle?) {
val packageName: String = context.getPackageName()
val launchIntent: Intent? = context.getPackageManager().getLaunchIntentForPackage(packageName)
if (launchIntent == null) {
return
}
val className: String? = launchIntent.getComponent()?.getClassName()
if (className == null) {
return
}
try {
val activityClass: java.lang.Class<*> = java.lang.Class.forName(className)
val activityIntent = Intent(context, activityClass)
if (bundle != null) {
activityIntent.putExtra("notification", bundle)
}
activityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
context.startActivity(activityIntent)
} catch (e: java.lang.Exception) {
Log.e(LOG_TAG, "Class not found", e)
return
}
}
fun sendToNotificationCentre(bundle: Bundle) {
val aggregator = PushNotificationPicturesAggregator(object : PushNotificationPicturesAggregator.Callback {
override fun call(largeIconImage: Bitmap?, bigPictureImage: Bitmap?) {
sendToNotificationCentreWithPicture(bundle, largeIconImage, bigPictureImage)
}
})
aggregator.setLargeIconUrl(context, bundle.getString("largeIconUrl"))
aggregator.setBigPictureUrl(context, bundle.getString("bigPictureUrl"))
}
fun handleConversation(bundle: Bundle) {
if (bundle.getBoolean("deleted")) {
removeStatusMessage(bundle)
} else {
addStatusMessage(bundle)
}
}
fun clearMessageNotifications(conversationId: String) {
notificationManager.cancel(conversationId.hashCode())
cleanGroup(conversationId)
}
fun clearAllMessageNotifications() {
notificationManager.cancelAll()
}
fun sendToNotificationCentreWithPicture(bundle: Bundle, largeIconBitmap: Bitmap?, bigPictureBitmap: Bitmap?) {
var largeIconBitmap: Bitmap? = largeIconBitmap
try {
val intentClass: java.lang.Class<*>? = mainActivityClass
if (intentClass == null) {
Log.e(LOG_TAG, "No activity class found for the notification")
return
}
if (bundle.getBoolean("isConversation")) {
handleConversation(bundle)
return
}
if (bundle.getString("message") == null) {
// this happens when a 'data' notification is received - we do not synthesize a local notification in this case
Log.d(LOG_TAG, "Ignore this message if you sent data-only notification. Cannot send to notification centre because there is no 'message' field in: $bundle")
return
}
val notificationIdString: String? = bundle.getString("id")
if (notificationIdString == null) {
Log.e(LOG_TAG, "No notification ID specified for the notification")
return
}
val res: Resources = context.getResources()
val packageName: String = context.getPackageName()
var title: String? = bundle.getString("title")
if (title == null) {
val appInfo: ApplicationInfo = context.getApplicationInfo()
title = context.getPackageManager().getApplicationLabel(appInfo).toString()
}
var priority: Int = NotificationCompat.PRIORITY_HIGH
val priorityString: String? = bundle.getString("priority")
if (priorityString != null) {
priority = when (priorityString.lowercase()) {
"max" -> NotificationCompat.PRIORITY_MAX
"high" -> NotificationCompat.PRIORITY_HIGH
"low" -> NotificationCompat.PRIORITY_LOW
"min" -> NotificationCompat.PRIORITY_MIN
"default" -> NotificationCompat.PRIORITY_DEFAULT
else -> NotificationCompat.PRIORITY_HIGH
}
}
var visibility: Int = NotificationCompat.VISIBILITY_PRIVATE
val visibilityString: String? = bundle.getString("visibility")
if (visibilityString != null) {
visibility = when (visibilityString.lowercase()) {
"private" -> NotificationCompat.VISIBILITY_PRIVATE
"public" -> NotificationCompat.VISIBILITY_PUBLIC
"secret" -> NotificationCompat.VISIBILITY_SECRET
else -> NotificationCompat.VISIBILITY_PRIVATE
}
}
var channel_id: String? = bundle.getString("channelId")
if (channel_id == null) {
channel_id = notificationDefaultChannelId
}
val notification: NotificationCompat.Builder = NotificationCompat.Builder(context, channel_id)
.setContentTitle(title)
.setTicker(bundle.getString("ticker"))
.setVisibility(visibility)
.setPriority(priority)
.setAutoCancel(bundle.getBoolean("autoCancel", true))
.setOnlyAlertOnce(bundle.getBoolean("onlyAlertOnce", false))
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { // API 24 and higher
// Restore showing timestamp on Android 7+
// Source: https://developer.android.com/reference/android/app/Notification.Builder.html#setShowWhen(boolean)
val showWhen: Boolean = bundle.getBoolean("showWhen", true)
notification.setShowWhen(showWhen)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { // API 26 and higher
// Changing Default mode of notification
notification.setDefaults(Notification.DEFAULT_LIGHTS)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH) { // API 20 and higher
val group: String? = bundle.getString("group")
if (group != null) {
notification.setGroup(group)
}
if (bundle.containsKey("groupSummary") || bundle.getBoolean("groupSummary")) {
notification.setGroupSummary(bundle.getBoolean("groupSummary"))
}
}
val numberString: String? = bundle.getString("number")
if (numberString != null) {
notification.setNumber(numberString.toInt())
}
// Small icon
var smallIconResId = 0
val smallIcon: String? = bundle.getString("smallIcon")
if (smallIcon != null && !smallIcon.isEmpty()) {
smallIconResId = res.getIdentifier(smallIcon, "mipmap", packageName)
} else if (smallIcon == null) {
smallIconResId = res.getIdentifier("ic_stat_notify_status", "drawable", packageName)
}
if (smallIconResId == 0) {
smallIconResId = res.getIdentifier("ic_launcher", "mipmap", packageName)
if (smallIconResId == 0) {
smallIconResId = android.R.drawable.ic_dialog_info
}
}
notification.setSmallIcon(smallIconResId)
// Large icon
if (largeIconBitmap == null) {
var largeIconResId = 0
val largeIcon: String? = bundle.getString("largeIcon")
if (largeIcon != null && !largeIcon.isEmpty()) {
largeIconResId = res.getIdentifier(largeIcon, "mipmap", packageName)
} else if (largeIcon == null) {
largeIconResId = res.getIdentifier("ic_launcher", "mipmap", packageName)
}
// Before Lolipop there was no large icon for notifications.
if (largeIconResId != 0 && (largeIcon != null || Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)) {
largeIconBitmap = BitmapFactory.decodeResource(res, largeIconResId)
}
}
val author: Bundle? = bundle.getBundle("notificationAuthor")
if (largeIconBitmap == null && author != null) {
val base64Image: String? = author.getString("icon")?.split(",")?.get(1)
if (base64Image != null) {
val decodedString: ByteArray = Base64.decode(base64Image, Base64.DEFAULT)
val decodedByte: Bitmap = BitmapFactory.decodeByteArray(decodedString, 0, decodedString.size)
notification.setLargeIcon(getCircleBitmap(decodedByte))
}
} else if (largeIconBitmap != null) {
notification.setLargeIcon(largeIconBitmap)
}
val message: String? = bundle.getString("message")
if (message != null) {
notification.setContentText(message)
}
val subText: String? = bundle.getString("subText")
if (subText != null) {
notification.setSubText(subText)
}
var bigText: String? = bundle.getString("bigText")
if (bigText == null) {
bigText = message
}
val style: NotificationCompat.Style
style = if (bigPictureBitmap != null) {
NotificationCompat.BigPictureStyle()
.bigPicture(bigPictureBitmap)
.setBigContentTitle(title)
.setSummaryText(message)
} else {
NotificationCompat.BigTextStyle().bigText(bigText)
}
notification.setStyle(style)
val intent = Intent(context, intentClass)
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
bundle.putBoolean("foreground", isApplicationInForeground)
bundle.putBoolean("userInteraction", true)
intent.putExtra("notification", bundle)
var soundUri: Uri? = null
if (!bundle.containsKey("playSound") || bundle.getBoolean("playSound")) {
var soundName: String? = bundle.getString("soundName")
if (soundName == null) {
soundName = "default"
}
soundUri = getSoundUri(soundName)
notification.setSound(soundUri)
}
if (soundUri == null || Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
notification.setSound(null)
}
if (bundle.containsKey("ongoing") || bundle.getBoolean("ongoing")) {
notification.setOngoing(bundle.getBoolean("ongoing"))
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
notification.setCategory(NotificationCompat.CATEGORY_CALL)
val color: String? = bundle.getString("color")
val defaultColor = -1
if (color != null) {
notification.setColor(Color.parseColor(color))
} else if (defaultColor != -1) {
notification.setColor(defaultColor)
}
}
val notificationID = notificationIdString.hashCode()
var deepLink: String? = bundle.getString("deepLink")
if (deepLink != null) {
notification
.setContentIntent(createOnTapIntent(context, notificationID, deepLink))
.setDeleteIntent(createOnDismissedIntent(context, notificationID, deepLink))
}
val notificationManager: NotificationManager = notificationManager()
if (!bundle.containsKey("vibrate") || bundle.getBoolean("vibrate")) {
var vibration = if (bundle.containsKey("vibration")) bundle.getDouble("vibration") as Long else DEFAULT_VIBRATION
if (vibration == 0L) {
vibration = DEFAULT_VIBRATION
}
val vibrationPattern: LongArray = longArrayOf(0L, vibration)
notification.setVibrate(vibrationPattern)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// Define the shortcutId
val shortcutId: String? = bundle.getString("shortcutId")
if (shortcutId != null) {
notification.setShortcutId(shortcutId)
}
val timeoutAfter = bundle.getDouble("timeoutAfter") as Long
if (timeoutAfter != null && timeoutAfter >= 0) {
notification.setTimeoutAfter(timeoutAfter)
}
}
val `when` = bundle.getDouble("when") as Long
if (`when` != null && `when` >= 0) {
notification.setWhen(`when`)
}
notification.setUsesChronometer(bundle.getBoolean("usesChronometer", false))
notification.setChannelId(channel_id)
var actionsArray: JSONArray? = null
try {
actionsArray = if (bundle.getString("actions") != null) JSONArray(bundle.getString("actions")) else null
} catch (e: JSONException) {
Log.e(LOG_TAG, "Exception while converting actions to JSON object.", e)
}
if (actionsArray != null) {
// No icon for now. The icon value of 0 shows no icon.
val icon = 0
// Add button for each actions.
for (i in 0 until actionsArray.length()) {
var action: String
action = try {
actionsArray.getString(i)
} catch (e: JSONException) {
Log.e(LOG_TAG, "Exception while getting action from actionsArray.", e)
continue
}
val actionIntent = Intent(context, PushNotificationActions::class.java)
actionIntent.setAction("$packageName.ACTION_$i")
actionIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
// Add "action" for later identifying which button gets pressed.
bundle.putString("action", action)
actionIntent.putExtra("notification", bundle)
actionIntent.setPackage(packageName)
val pendingActionIntent: PendingIntent = PendingIntent.getBroadcast(context, notificationID, actionIntent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
notification.addAction(NotificationCompat.Action.Builder(icon, action, pendingActionIntent).build())
} else {
notification.addAction(icon, action, pendingActionIntent)
}
}
}
if (!(isApplicationInForeground && bundle.getBoolean("ignoreInForeground"))) {
val info: Notification = notification.build()
info.defaults = info.defaults or Notification.DEFAULT_LIGHTS
if (bundle.containsKey("tag")) {
val tag: String? = bundle.getString("tag")
if (tag != null) {
notificationManager.notify(tag, notificationID, info)
}
} else {
notificationManager.notify(notificationID, info)
}
}
} catch (e: java.lang.Exception) {
Log.e(LOG_TAG, "failed to send push notification", e)
}
}
private fun checkOrCreateChannel(manager: NotificationManager?, channel_id: String, channel_name: String?, channel_description: String?, soundUri: Uri?, importance: Int, vibratePattern: LongArray?, showBadge: Boolean): Boolean {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return false
if (manager == null) return false
var channel: NotificationChannel? = manager.getNotificationChannel(channel_id)
if (channel == null && channel_name != null && channel_description != null ||
channel != null &&
(channel_name != null && !channel.getName().equals(channel_name) ||
channel_description != null && !channel.getDescription().equals(channel_description))) {
// If channel doesn't exist create a new one.
// If channel name or description is updated then update the existing channel.
channel = NotificationChannel(channel_id, channel_name, importance)
channel.setDescription(channel_description)
channel.enableLights(true)
channel.enableVibration(vibratePattern != null)
channel.setVibrationPattern(vibratePattern)
channel.setShowBadge(showBadge)
if (soundUri != null) {
val audioAttributes: AudioAttributes = AudioAttributes.Builder()
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
.setUsage(AudioAttributes.USAGE_NOTIFICATION)
.build()
channel.setSound(soundUri, audioAttributes)
} else {
channel.setSound(null, null)
}
manager.createNotificationChannel(channel)
return true
}
return false
}
fun createChannel(channelInfo: ReadableMap): Boolean {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
return false
}
val channelId: String? = channelInfo.getString("channelId")
val channelName: String? = channelInfo.getString("channelName")
val channelDescription = if (channelInfo.hasKey("channelDescription")) channelInfo.getString("channelDescription") else ""
val soundName = if (channelInfo.hasKey("soundName")) channelInfo.getString("soundName") else "default"
val importance = if (channelInfo.hasKey("importance")) channelInfo.getInt("importance") else 4
val vibrate = channelInfo.hasKey("vibrate") && channelInfo.getBoolean("vibrate")
val vibratePattern = if (vibrate) longArrayOf(DEFAULT_VIBRATION) else null
val showBadge = channelInfo.hasKey("showBadge") && channelInfo.getBoolean("showBadge")
val manager: NotificationManager = notificationManager()
val soundUri: Uri? = if (soundName != null) getSoundUri(soundName) else null
if (channelId != null) {
return checkOrCreateChannel(manager, channelId, channelName, channelDescription, soundUri, importance, vibratePattern, showBadge)
}
return false
}
val notificationDefaultChannelId: String
get() = CHANNEL_ID
private fun getSoundUri(soundName: String): Uri {
var soundName: String? = soundName
return if (soundName == null || "default".equals(soundName, ignoreCase = true)) {
RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
} else {
// sound name can be full filename, or just the resource name.
// So the strings 'my_sound.mp3' AND 'my_sound' are accepted
// The reason is to make the iOS and android javascript interfaces compatible
val resId: Int
if (context.getResources().getIdentifier(soundName, "raw", context.getPackageName()) !== 0) {
resId = context.getResources().getIdentifier(soundName, "raw", context.getPackageName())
} else {
soundName = soundName.substring(0, soundName.lastIndexOf('.'))
resId = context.getResources().getIdentifier(soundName, "raw", context.getPackageName())
}
Uri.parse("android.resource://" + context.getPackageName() + "/" + resId)
}
}
val mainActivityClass: java.lang.Class<*>?
get() {
val packageName: String = context.getPackageName()
val launchIntent: Intent? = context.getPackageManager().getLaunchIntentForPackage(packageName)
if (launchIntent == null) {
return null
}
val className: String? = launchIntent.getComponent()?.getClassName()
if (className == null) {
return null
}
return try {
java.lang.Class.forName(className)
} catch (e: java.lang.ClassNotFoundException) {
e.printStackTrace()
null
}
}
val isApplicationInForeground: Boolean
get() {
val activityManager: ActivityManager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
val processInfos: List<RunningAppProcessInfo> = activityManager.getRunningAppProcesses()
if (processInfos != null) {
for (processInfo in processInfos) {
if (processInfo.processName.equals(context.getPackageName()) && processInfo.importance === RunningAppProcessInfo.IMPORTANCE_FOREGROUND && processInfo.pkgList.size > 0) {
return true
}
}
}
return false
}
private fun getCircleBitmap(bitmap: Bitmap): Bitmap {
val output: Bitmap = Bitmap.createBitmap(bitmap.getWidth(),
bitmap.getHeight(), Bitmap.Config.ARGB_8888)
val canvas = Canvas(output)
val color: Int = Color.RED
val paint = Paint()
val rect = Rect(0, 0, bitmap.getWidth(), bitmap.getHeight())
val rectF = RectF(rect)
paint.setAntiAlias(true)
canvas.drawARGB(0, 0, 0, 0)
paint.setColor(color)
canvas.drawOval(rectF, paint)
paint.setXfermode(PorterDuffXfermode(PorterDuff.Mode.SRC_IN))
canvas.drawBitmap(bitmap, rect, rect, paint)
bitmap.recycle()
return output
}
private fun getPerson(bundle: Bundle?): Person {
val builder = Person.Builder()
val name: String? = bundle?.getString("name")
if (name != null) {
builder.setName(name)
}
return builder.build()
}
private fun createMessage(data: Bundle): StatusMessage {
val notificationAuthor: Bundle? = data.getBundle("notificationAuthor")
val author: Person = getPerson(notificationAuthor)
val timeStampLongValue = data.getDouble("timestamp") as Long
val id: String? = data.getString("id")
val message: String? = data.getString("message")
return StatusMessage(id ?: "", author, timeStampLongValue, message ?: "")
}
private fun createGroupOnDismissedIntent(context: Context, notificationId: Int, groupId: String, deepLink: String?): PendingIntent {
val intent = Intent(ACTION_DELETE_NOTIFICATION)
if (deepLink != null) {
intent.putExtra("im.status.ethereum.deepLink", deepLink)
}
intent.putExtra("im.status.ethereum.groupId", groupId)
return PendingIntent.getBroadcast(context.getApplicationContext(), notificationId, intent, flag)
}
private fun createGroupOnTapIntent(context: Context, notificationId: Int, groupId: String, deepLink: String?): PendingIntent {
val intent: Intent? = getOpenAppIntent(deepLink)
return PendingIntent.getActivity(context.getApplicationContext(), notificationId, intent, flag)
}
private fun createOnTapIntent(context: Context, notificationId: Int, deepLink: String?): PendingIntent {
val intent: Intent? = getOpenAppIntent(deepLink)
return PendingIntent.getActivity(context.getApplicationContext(), notificationId, intent, flag)
}
private fun createOnDismissedIntent(context: Context, notificationId: Int, deepLink: String): PendingIntent {
val intent = Intent(ACTION_DELETE_NOTIFICATION)
intent.putExtra("im.status.ethereum.deepLink", deepLink)
return PendingIntent.getBroadcast(context.getApplicationContext(), notificationId, intent, flag)
}
fun removeStatusMessage(bundle: Bundle) {
val conversationId: String? = bundle.getString("conversationId")
if (conversationId == null) {
return
}
var group: StatusMessageGroup? = if (conversationId != null) messageGroups.get(conversationId) else null
val notificationManager: NotificationManager = notificationManager()
if (group == null) {
group = StatusMessageGroup(conversationId)
}
messageGroups.put(conversationId, group)
val id: String? = bundle.getString("id")
if (id != null) {
group.removeMessage(id)
}
showMessages(bundle)
}
fun getMessageGroup(conversationId: String?): StatusMessageGroup? {
return messageGroups.get(conversationId)
}
fun addStatusMessage(bundle: Bundle) {
val conversationId: String? = bundle.getString("conversationId")
if (conversationId == null) {
return
}
var group: StatusMessageGroup? = if (conversationId != null) messageGroups.get(conversationId) else null
val notificationManager: NotificationManager = notificationManager()
if (group == null) {
group = StatusMessageGroup(conversationId)
}
messageGroups.put(conversationId, group)
group.addMessage(createMessage(bundle))
showMessages(bundle)
}
fun showMessages(bundle: Bundle) {
val conversationId: String? = bundle.getString("conversationId")
if (conversationId == null) {
return
}
val group: StatusMessageGroup? = if (conversationId != null) messageGroups.get(conversationId) else null
val notificationManager: NotificationManager = notificationManager()
val messagingStyle: NotificationCompat.MessagingStyle = NotificationCompat.MessagingStyle("Me")
var messages = if (group != null) group.getMessages() else null
if (messages == null || messages.size == 0) {
notificationManager.cancel(conversationId.hashCode())
return
}
for (i in messages.indices) {
val message: StatusMessage = messages.get(i)
messagingStyle.addMessage(message.text, message.timestamp, message.getAuthor())
}
val title: String? = bundle.getString("title")
if (title != null) {
messagingStyle.setConversationTitle(title)
}
val builder: NotificationCompat.Builder = NotificationCompat.Builder(context, CHANNEL_ID)
.setSmallIcon(R.drawable.ic_stat_notify_status)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setCategory(NotificationCompat.CATEGORY_MESSAGE)
.setStyle(messagingStyle)
.setGroup(conversationId)
.setOnlyAlertOnce(true)
.setGroupSummary(true)
.setContentIntent(createGroupOnTapIntent(context, conversationId.hashCode(), conversationId, bundle.getString("deepLink")))
.setDeleteIntent(createGroupOnDismissedIntent(context, conversationId.hashCode(), conversationId, bundle.getString("deepLink")))
.setNumber(messages.size + 1)
.setAutoCancel(true)
if (Build.VERSION.SDK_INT >= 21) {
builder.setVibrate(LongArray(0))
}
notificationManager.notify(conversationId.hashCode(), builder.build())
}
inner class StatusMessageGroup(val id: String) {
private var messages: java.util.ArrayList<StatusMessage>
init {
messages = java.util.ArrayList<StatusMessage>()
}
fun getMessages(): java.util.ArrayList<StatusMessage> {
return messages
}
fun addMessage(message: StatusMessage) {
messages.add(message)
}
fun removeMessage(id: String) {
val newMessages: java.util.ArrayList<StatusMessage> = java.util.ArrayList<StatusMessage>()
for (message in messages) {
if (message.id != id) {
newMessages.add(message)
}
}
messages = newMessages
}
}
inner class StatusMessage(val id: String, author: Person, timestamp: Long, text: String) {
fun getAuthor(): Person {
return author
}
private val author: Person
val timestamp: Long
val text: String
init {
this.author = author
this.timestamp = timestamp
this.text = text
}
}
private fun removeGroup(groupId: String) {
messageGroups.remove(groupId)
}
private fun cleanGroup(groupId: String) {
removeGroup(groupId)
if (messageGroups.size == 0) {
notificationManager.cancelAll()
}
}
fun start() {
Log.e(LOG_TAG, "Starting Foreground Service")
val serviceIntent = Intent(context, ForegroundService::class.java)
context.startService(serviceIntent)
registerBroadcastReceiver()
}
fun stop() {
Log.e(LOG_TAG, "Stopping Foreground Service")
//NOTE: we cancel all the current notifications, because the intents can't be used anymore
//since the broadcast receiver will be killed as well and won't be able to handle any intent
notificationManager.cancelAll()
val serviceIntent = Intent(context, ForegroundService::class.java)
context.stopService(serviceIntent)
context.unregisterReceiver(notificationActionReceiver)
}
}
@@ -0,0 +1,86 @@
package im.status.ethereum.pushnotifications;
import android.os.Build;
import android.app.Application;
import android.app.NotificationManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.Set;
import static im.status.ethereum.pushnotifications.PushNotification.LOG_TAG;
class PushNotificationJsDelivery {
private ReactContext reactContext;
PushNotificationJsDelivery(ReactContext context){
reactContext = context;
}
String convertJSON(Bundle bundle) {
try {
JSONObject json = convertJSONObject(bundle);
return json.toString();
} catch (JSONException e) {
return null;
}
}
// a Bundle is not a map, so we have to convert it explicitly
private JSONObject convertJSONObject(Bundle bundle) throws JSONException {
JSONObject json = new JSONObject();
Set<String> keys = bundle.keySet();
for (String key : keys) {
Object value = bundle.get(key);
if (value instanceof Bundle) {
json.put(key, convertJSONObject((Bundle)value));
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
json.put(key, JSONObject.wrap(value));
} else {
json.put(key, value);
}
}
return json;
}
void notifyNotification(Bundle bundle) {
String bundleString = convertJSON(bundle);
WritableMap params = Arguments.createMap();
params.putString("dataJSON", bundleString);
sendEvent("remoteNotificationReceived", params);
}
void notifyNotificationAction(Bundle bundle) {
String bundleString = convertJSON(bundle);
WritableMap params = Arguments.createMap();
params.putString("dataJSON", bundleString);
sendEvent("notificationActionReceived", params);
}
void sendEvent(String eventName, Object params) {
if (reactContext.hasActiveCatalystInstance()) {
reactContext
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit(eventName, params);
}
}
}
@@ -1,81 +0,0 @@
package im.status.ethereum.pushnotifications
import android.os.Build
import android.app.Application
import android.app.NotificationManager
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.util.Log
import com.facebook.react.bridge.ReactContext
import com.facebook.react.bridge.Arguments
import com.facebook.react.bridge.WritableMap
import com.facebook.react.modules.core.DeviceEventManagerModule
import org.json.JSONException
import org.json.JSONObject
internal class PushNotificationJsDelivery(context: ReactContext) {
companion object {
const val LOG_TAG = "PushNotification"
}
private val reactContext: ReactContext
init {
reactContext = context
}
fun convertJSON(bundle: Bundle): String? {
return try {
val json: JSONObject = convertJSONObject(bundle)
json.toString()
} catch (e: JSONException) {
null
}
}
// a Bundle is not a map, so we have to convert it explicitly
@kotlin.Throws(JSONException::class)
private fun convertJSONObject(bundle: Bundle): JSONObject {
val json = JSONObject()
val keys: Set<String> = bundle.keySet()
for (key in keys) {
val value: Any? = bundle.get(key)
if (value == null) {
continue
} else if (value is Bundle) {
json.put(key, convertJSONObject(value as Bundle))
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
json.put(key, JSONObject.wrap(value))
} else {
json.put(key, value)
}
}
return json
}
fun notifyNotification(bundle: Bundle) {
val bundleString = convertJSON(bundle)
val params: WritableMap = Arguments.createMap()
params.putString("dataJSON", bundleString)
sendEvent("remoteNotificationReceived", params)
}
fun notifyNotificationAction(bundle: Bundle) {
val bundleString = convertJSON(bundle)
val params: WritableMap = Arguments.createMap()
params.putString("dataJSON", bundleString)
sendEvent("notificationActionReceived", params)
}
fun sendEvent(eventName: String, params: Any?) {
if (reactContext.hasActiveCatalystInstance()) {
reactContext
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
.emit(eventName, params)
}
}
}
@@ -0,0 +1,27 @@
package im.status.ethereum.pushnotifications;
import im.status.ethereum.pushnotifications.PushNotification;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.JavaScriptModule;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import java.util.Collections;
import java.util.List;
public class PushNotificationPackage implements ReactPackage {
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
return Collections.<NativeModule>singletonList(new PushNotification(reactContext));
}
public List<Class<? extends JavaScriptModule>> createJSModules() {
return Collections.emptyList();
}
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
}
@@ -1,22 +0,0 @@
package im.status.ethereum.pushnotifications
import im.status.ethereum.pushnotifications.PushNotification
import com.facebook.react.ReactPackage
import com.facebook.react.bridge.JavaScriptModule
import com.facebook.react.bridge.NativeModule
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.uimanager.ViewManager
class PushNotificationPackage : ReactPackage {
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
return listOf<NativeModule>(PushNotification(reactContext))
}
fun createJSModules(): List<java.lang.Class<out JavaScriptModule?>> {
return emptyList<java.lang.Class<out JavaScriptModule?>>()
}
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
return emptyList()
}
}
@@ -0,0 +1,136 @@
package im.status.ethereum.pushnotifications;
import androidx.annotation.Nullable;
import com.facebook.common.executors.CallerThreadExecutor;
import com.facebook.common.references.CloseableReference;
import com.facebook.datasource.DataSource;
import com.facebook.drawee.backends.pipeline.Fresco;
import com.facebook.imagepipeline.common.Priority;
import com.facebook.imagepipeline.core.ImagePipeline;
import com.facebook.imagepipeline.datasource.BaseBitmapDataSubscriber;
import com.facebook.imagepipeline.image.CloseableImage;
import com.facebook.imagepipeline.request.ImageRequest;
import com.facebook.imagepipeline.request.ImageRequestBuilder;
import android.util.Log;
import android.content.Context;
import android.graphics.Bitmap;
import android.net.Uri;
import java.util.concurrent.atomic.AtomicInteger;
import static im.status.ethereum.pushnotifications.PushNotification.LOG_TAG;
public class PushNotificationPicturesAggregator {
interface Callback {
public void call(Bitmap largeIconImage, Bitmap bigPictureImage);
}
private AtomicInteger count = new AtomicInteger(0);
private Bitmap largeIconImage;
private Bitmap bigPictureImage;
private Callback callback;
public PushNotificationPicturesAggregator(Callback callback) {
this.callback = callback;
}
public void setBigPicture(Bitmap bitmap) {
this.bigPictureImage = bitmap;
this.finished();
}
public void setBigPictureUrl(Context context, String url) {
if(null == url) {
this.setBigPicture(null);
return;
}
Uri uri = null;
try {
uri = Uri.parse(url);
} catch(Exception ex) {
Log.e(LOG_TAG, "Failed to parse bigPictureUrl", ex);
this.setBigPicture(null);
return;
}
final PushNotificationPicturesAggregator aggregator = this;
this.downloadRequest(context, uri, new BaseBitmapDataSubscriber() {
@Override
public void onNewResultImpl(@Nullable Bitmap bitmap) {
aggregator.setBigPicture(bitmap);
}
@Override
public void onFailureImpl(DataSource dataSource) {
aggregator.setBigPicture(null);
}
});
}
public void setLargeIcon(Bitmap bitmap) {
this.largeIconImage = bitmap;
this.finished();
}
public void setLargeIconUrl(Context context, String url) {
if(null == url) {
this.setLargeIcon(null);
return;
}
Uri uri = null;
try {
uri = Uri.parse(url);
} catch(Exception ex) {
Log.e(LOG_TAG, "Failed to parse largeIconUrl", ex);
this.setLargeIcon(null);
return;
}
final PushNotificationPicturesAggregator aggregator = this;
this.downloadRequest(context, uri, new BaseBitmapDataSubscriber() {
@Override
public void onNewResultImpl(@Nullable Bitmap bitmap) {
aggregator.setLargeIcon(bitmap);
}
@Override
public void onFailureImpl(DataSource dataSource) {
aggregator.setLargeIcon(null);
}
});
}
private void downloadRequest(Context context, Uri uri, BaseBitmapDataSubscriber subscriber) {
ImageRequest imageRequest = ImageRequestBuilder
.newBuilderWithSource(uri)
.setRequestPriority(Priority.HIGH)
.setLowestPermittedRequestLevel(ImageRequest.RequestLevel.FULL_FETCH)
.build();
if(!Fresco.hasBeenInitialized()) {
Fresco.initialize(context);
}
DataSource<CloseableReference<CloseableImage>> dataSource = Fresco.getImagePipeline().fetchDecodedImage(imageRequest, context);
dataSource.subscribe(subscriber, CallerThreadExecutor.getInstance());
}
private void finished() {
synchronized(this.count) {
int val = this.count.incrementAndGet();
if(val >= 2 && this.callback != null) {
this.callback.call(this.largeIconImage, this.bigPictureImage);
}
}
}
}
@@ -1,112 +0,0 @@
package im.status.ethereum.pushnotifications
import com.facebook.common.executors.CallerThreadExecutor
import com.facebook.common.references.CloseableReference
import com.facebook.datasource.DataSource
import com.facebook.drawee.backends.pipeline.Fresco
import com.facebook.imagepipeline.common.Priority
import com.facebook.imagepipeline.core.ImagePipeline
import com.facebook.imagepipeline.datasource.BaseBitmapDataSubscriber
import com.facebook.imagepipeline.image.CloseableImage
import com.facebook.imagepipeline.request.ImageRequest
import com.facebook.imagepipeline.request.ImageRequestBuilder
import android.util.Log
import android.content.Context
import android.graphics.Bitmap
import android.net.Uri
import java.util.concurrent.atomic.AtomicInteger
class PushNotificationPicturesAggregator(private val callback: Callback?) {
companion object {
const val LOG_TAG = "PushNotification"
}
interface Callback {
fun call(largeIconImage: Bitmap?, bigPictureImage: Bitmap?)
}
private val count: AtomicInteger = AtomicInteger(0)
private var largeIconImage: Bitmap? = null
private var bigPictureImage: Bitmap? = null
fun setBigPicture(bitmap: Bitmap?) {
bigPictureImage = bitmap
finished()
}
fun setBigPictureUrl(context: Context, url: String?) {
if (null == url) {
setBigPicture(null)
return
}
var uri: Uri? = null
uri = try {
Uri.parse(url)
} catch (ex: java.lang.Exception) {
Log.e(LOG_TAG, "Failed to parse bigPictureUrl", ex)
setBigPicture(null)
return
}
val aggregator = this
downloadRequest(context, uri, object : BaseBitmapDataSubscriber() {
override fun onNewResultImpl(bitmap: Bitmap?) {
aggregator.setBigPicture(bitmap)
}
override fun onFailureImpl(dataSource: DataSource<CloseableReference<CloseableImage>>) {
aggregator.setBigPicture(null)
}
})
}
fun setLargeIcon(bitmap: Bitmap?) {
largeIconImage = bitmap
finished()
}
fun setLargeIconUrl(context: Context, url: String?) {
if (null == url) {
setLargeIcon(null)
return
}
var uri: Uri? = null
uri = try {
Uri.parse(url)
} catch (ex: java.lang.Exception) {
Log.e(LOG_TAG, "Failed to parse largeIconUrl", ex)
setLargeIcon(null)
return
}
val aggregator = this
downloadRequest(context, uri, object : BaseBitmapDataSubscriber() {
override fun onNewResultImpl(bitmap: Bitmap?) {
aggregator.setLargeIcon(bitmap)
}
override fun onFailureImpl(dataSource: DataSource<CloseableReference<CloseableImage>>) {
aggregator.setLargeIcon(null)
}
})
}
private fun downloadRequest(context: Context, uri: Uri?, subscriber: BaseBitmapDataSubscriber) {
val imageRequest: ImageRequest = ImageRequestBuilder
.newBuilderWithSource(uri)
.setRequestPriority(Priority.HIGH)
.setLowestPermittedRequestLevel(ImageRequest.RequestLevel.FULL_FETCH)
.build()
if (!Fresco.hasBeenInitialized()) {
Fresco.initialize(context)
}
val dataSource: DataSource<CloseableReference<CloseableImage>> = Fresco.getImagePipeline().fetchDecodedImage(imageRequest, context)
dataSource.subscribe(subscriber, CallerThreadExecutor.getInstance())
}
private fun finished() {
synchronized(count) {
val `val`: Int = count.incrementAndGet()
if (`val` >= 2 && callback != null) {
callback.call(largeIconImage, bigPictureImage)
}
}
}
}
+25 -531
View File
@@ -1055,21 +1055,6 @@
}
},
{
"path": "androidx/databinding/databinding-common/3.3.1",
"repo": "https://dl.google.com/dl/android/maven2",
"files": {
"databinding-common-3.3.1.pom": {
"sha1": "2e932d3219ea5f6d1078b24a8a3ce24b209c9309",
"sha256": "sha256-J5Qv7Gy8ROM19MN5Rx/lo2BsA7Zwt+NG6lXxYtjrF10="
},
"databinding-common-3.3.1.jar": {
"sha1": "45795e2f688474b219d37beb7f9f6756578e6454",
"sha256": "sha256-mq9hUr+3QZCgovJ1St/aoqkP7Ef50KwR811ej5iYkyw="
}
}
},
{
"path": "androidx/databinding/databinding-common/3.5.4",
"repo": "https://dl.google.com/dl/android/maven2",
@@ -1179,21 +1164,6 @@
}
},
{
"path": "androidx/databinding/databinding-compiler-common/3.3.1",
"repo": "https://dl.google.com/dl/android/maven2",
"files": {
"databinding-compiler-common-3.3.1.pom": {
"sha1": "123c2b1dd44ec6e2441be9441bdf393462936928",
"sha256": "sha256-na2hPeqxkcTcC9T7EvFs12+r1ygfSfnOHIirb3kXyzI="
},
"databinding-compiler-common-3.3.1.jar": {
"sha1": "e96aa159e1ed5c5a3461d15adfc42ef5b1f68b59",
"sha256": "sha256-sfLOroi0d73GMp2r8SCNhCHFrT/HzPpEOXEOVBcvxh0="
}
}
},
{
"path": "androidx/databinding/databinding-compiler-common/3.5.4",
"repo": "https://dl.google.com/dl/android/maven2",
@@ -2755,21 +2725,6 @@
}
},
{
"path": "com/android/databinding/baseLibrary/3.3.1",
"repo": "https://dl.google.com/dl/android/maven2",
"files": {
"baseLibrary-3.3.1.pom": {
"sha1": "611fd96b47fdd27dd88afc23829975278d47774c",
"sha256": "sha256-dKLQobB7s6ExmWyEIG/CHmDQaT4pa7EI07c9B7hnUj8="
},
"baseLibrary-3.3.1.jar": {
"sha1": "702acc804e85563d3ae7b8ee49122e4f857dde83",
"sha256": "sha256-5QRyEqpp1J0FULCvlrQQC4xslwG4PD+24ZzGbzxK6cQ="
}
}
},
{
"path": "com/android/databinding/baseLibrary/3.5.4",
"repo": "https://dl.google.com/dl/android/maven2",
@@ -3288,21 +3243,6 @@
}
},
{
"path": "com/android/tools/analytics-library/crash/26.3.1",
"repo": "https://dl.google.com/dl/android/maven2",
"files": {
"crash-26.3.1.pom": {
"sha1": "7b667eb48f02b8ba2a981a962e9c1137e251c1e0",
"sha256": "sha256-m0FH4hP4dmWwzUXF6w47Q79EWJf5r9rKF/c8wjjF01c="
},
"crash-26.3.1.jar": {
"sha1": "46163f2cf0e16a1ba9be2ad6dddc39f1a1d6b613",
"sha256": "sha256-k5OoPOLz+pZ4LCU98yROaZo7PfGG3pJssTiVj6+VYpM="
}
}
},
{
"path": "com/android/tools/analytics-library/crash/26.5.4",
"repo": "https://dl.google.com/dl/android/maven2",
@@ -3442,21 +3382,6 @@
}
},
{
"path": "com/android/tools/analytics-library/protos/26.3.1",
"repo": "https://dl.google.com/dl/android/maven2",
"files": {
"protos-26.3.1.pom": {
"sha1": "d6015a3a017dcb62d9a9a56c28b5c47bb6727a91",
"sha256": "sha256-IP1qlda7IElg7qdLFBTEWy78J0+4Tf90MnqRYezfc3k="
},
"protos-26.3.1.jar": {
"sha1": "f35a0accd93cfbb4306268ea748e1ac49920a0a8",
"sha256": "sha256-70/09xLY//RrI3jBwQh5l1hXdvpsGhwgBpbXd0eNnUw="
}
}
},
{
"path": "com/android/tools/analytics-library/protos/26.5.4",
"repo": "https://dl.google.com/dl/android/maven2",
@@ -3596,21 +3521,6 @@
}
},
{
"path": "com/android/tools/analytics-library/shared/26.3.1",
"repo": "https://dl.google.com/dl/android/maven2",
"files": {
"shared-26.3.1.pom": {
"sha1": "af1e1e7d120696bfee452247b5224290d91d828f",
"sha256": "sha256-3Hxmxmp5mFLNaGRaEXOJSRxiTU1U+OzdX5QENgHtys4="
},
"shared-26.3.1.jar": {
"sha1": "3457faee7a68d84dcbd258889567153acfc731a2",
"sha256": "sha256-bSe5quJj4rwth0SR72dh86WuH34wzEq2v7ktd/2tO/A="
}
}
},
{
"path": "com/android/tools/analytics-library/shared/26.5.4",
"repo": "https://dl.google.com/dl/android/maven2",
@@ -3750,21 +3660,6 @@
}
},
{
"path": "com/android/tools/analytics-library/tracker/26.3.1",
"repo": "https://dl.google.com/dl/android/maven2",
"files": {
"tracker-26.3.1.pom": {
"sha1": "6775a4eeafd2ef5270750388ee53503eeafa688b",
"sha256": "sha256-+mgVCnuDFGnXWVQ1ZGPkmcJjl1ubk/YmTQ3qjRHlKwg="
},
"tracker-26.3.1.jar": {
"sha1": "26248cad2525e003752322cfc6bd86182df7daac",
"sha256": "sha256-JG6AgEtANgzjGNp7nRKUjgrUHQwpzHvbOGF9weaWnYg="
}
}
},
{
"path": "com/android/tools/analytics-library/tracker/26.5.4",
"repo": "https://dl.google.com/dl/android/maven2",
@@ -3904,21 +3799,6 @@
}
},
{
"path": "com/android/tools/annotations/26.3.1",
"repo": "https://dl.google.com/dl/android/maven2",
"files": {
"annotations-26.3.1.pom": {
"sha1": "bce2159e811cceee12498cb34812cbae1e7b8eda",
"sha256": "sha256-Y7XWCr1wC4On07xPXk1+cRxZsOB6WsMmd2FOP/1xJLE="
},
"annotations-26.3.1.jar": {
"sha1": "3d46d5da6d5dd17df682a22b914cdc6b84677731",
"sha256": "sha256-U0rc5Q64KdT4yXW8NOpVs49oxjAOAA4rR8Q6xnLQxyE="
}
}
},
{
"path": "com/android/tools/annotations/26.5.4",
"repo": "https://dl.google.com/dl/android/maven2",
@@ -4346,21 +4226,6 @@
}
},
{
"path": "com/android/tools/build/apksig/3.3.1",
"repo": "https://dl.google.com/dl/android/maven2",
"files": {
"apksig-3.3.1.pom": {
"sha1": "e916d810d293ce4ab620435b41d2e3bc67972cd3",
"sha256": "sha256-cGliMgmhK5sal8XJypuekagqDkDG4h4LlMc5barHh80="
},
"apksig-3.3.1.jar": {
"sha1": "5dc32667e573a18b05119685ac3f2b8c0f17182d",
"sha256": "sha256-NXs57dinjUgKlT5rl8CYWf1AyXLBpJtvnTvT1LmYyy0="
}
}
},
{
"path": "com/android/tools/build/apksig/3.5.4",
"repo": "https://dl.google.com/dl/android/maven2",
@@ -4470,21 +4335,6 @@
}
},
{
"path": "com/android/tools/build/apkzlib/3.3.1",
"repo": "https://dl.google.com/dl/android/maven2",
"files": {
"apkzlib-3.3.1.pom": {
"sha1": "02214cdec00c2553430192a7f36e9a28cf96bf6f",
"sha256": "sha256-IVhIC03ar9F8K15CEeAPstojFB93BY+VjRqUlFBZ0IU="
},
"apkzlib-3.3.1.jar": {
"sha1": "0563a5b71668b4fe7da03a0a3054c9fdd973d3cf",
"sha256": "sha256-ZAVmwOT1BZATm7z7sP90TB9tzZ8mL48tAVn8QZQTgcw="
}
}
},
{
"path": "com/android/tools/build/apkzlib/3.5.4",
"repo": "https://dl.google.com/dl/android/maven2",
@@ -4624,21 +4474,6 @@
}
},
{
"path": "com/android/tools/build/builder-model/3.3.1",
"repo": "https://dl.google.com/dl/android/maven2",
"files": {
"builder-model-3.3.1.pom": {
"sha1": "4011d22709777c4063e84d39dbfe4ed535a31905",
"sha256": "sha256-XVNezIVtl4YN/ZElCFrqki4FTrfZ4SRPg3p0n76b++M="
},
"builder-model-3.3.1.jar": {
"sha1": "2c47cf0451da2eaad484ccd2d580a8cfd23fa064",
"sha256": "sha256-BXtSlcP+jNze+uaN47+/nHbTnaI+BMw0ZdZgA3oxfwA="
}
}
},
{
"path": "com/android/tools/build/builder-model/3.5.4",
"repo": "https://dl.google.com/dl/android/maven2",
@@ -4790,21 +4625,6 @@
}
},
{
"path": "com/android/tools/build/builder-test-api/3.3.1",
"repo": "https://dl.google.com/dl/android/maven2",
"files": {
"builder-test-api-3.3.1.pom": {
"sha1": "e79a310b9c89fcb62f343b727f5747783c778861",
"sha256": "sha256-heZof7B9lqEFRE1Rouwnrz9798H5w72QcGZ9QblfZAY="
},
"builder-test-api-3.3.1.jar": {
"sha1": "c1a1ce91c0f34c43a52c54c1a2f6fdbaafadd3da",
"sha256": "sha256-KYnV5vE5YwFoEC40dwJCFbnKvew+Gqc4saBze0tfyww="
}
}
},
{
"path": "com/android/tools/build/builder-test-api/3.5.4",
"repo": "https://dl.google.com/dl/android/maven2",
@@ -4956,21 +4776,6 @@
}
},
{
"path": "com/android/tools/build/builder/3.3.1",
"repo": "https://dl.google.com/dl/android/maven2",
"files": {
"builder-3.3.1.pom": {
"sha1": "fbca96adc8069b8fcb01c7271c3b4be4f901b517",
"sha256": "sha256-pg/e6U4EqMemXDi6m2UFhcOkv0cisGFnuI85cO4UA+Q="
},
"builder-3.3.1.jar": {
"sha1": "e4d2ad1ef694fca700b3ab1f4df4b79313d17c98",
"sha256": "sha256-zoj1PYZLXN+oW1cC5yiz1u0NOqkoy2HghCRtSlUBXFI="
}
}
},
{
"path": "com/android/tools/build/builder/3.5.4",
"repo": "https://dl.google.com/dl/android/maven2",
@@ -5092,21 +4897,6 @@
}
},
{
"path": "com/android/tools/build/bundletool/0.6.0",
"repo": "https://dl.google.com/dl/android/maven2",
"files": {
"bundletool-0.6.0.pom": {
"sha1": "5a6e5b5af1687285125f43e00a8e5a8efe6f76a4",
"sha256": "sha256-YDqi6WyRrkPynACNA16mdo0fJya6qjAqXwzYBFfgRiI="
},
"bundletool-0.6.0.jar": {
"sha1": "fb944be26f692e6ac937924ad5be7faa4093dd80",
"sha256": "sha256-QAU1QDlstRlZ0GYLSihk7mqBaXi/AT/UwB3L7IlcS7o="
}
}
},
{
"path": "com/android/tools/build/bundletool/0.9.0",
"repo": "https://dl.google.com/dl/android/maven2",
@@ -5242,21 +5032,6 @@
}
},
{
"path": "com/android/tools/build/gradle-api/3.3.1",
"repo": "https://dl.google.com/dl/android/maven2",
"files": {
"gradle-api-3.3.1.pom": {
"sha1": "c9a6cecdb5e233c2ffde76507bddc6f26ade5528",
"sha256": "sha256-3BUSdrg4w9TdT8QdcwRoOkTGotjb3ngpdN9p+1g8dRQ="
},
"gradle-api-3.3.1.jar": {
"sha1": "93aac19942998e480db4195212150fb30f7a78cd",
"sha256": "sha256-SMmx/OFMswX3/3XdL97Ds/qtZK/pFQbviYoEsIbmtBY="
}
}
},
{
"path": "com/android/tools/build/gradle-api/3.5.4",
"repo": "https://dl.google.com/dl/android/maven2",
@@ -5457,21 +5232,6 @@
}
},
{
"path": "com/android/tools/build/gradle/3.3.1",
"repo": "https://dl.google.com/dl/android/maven2",
"files": {
"gradle-3.3.1.pom": {
"sha1": "1769370bffda87132eb79580bf54ccb9eb339020",
"sha256": "sha256-9eitp3Q6B3or5f+qF0Q0VmkSQa7ZEoYVgAXzUcRA/3w="
},
"gradle-3.3.1.jar": {
"sha1": "7d642f14aa35443dd7aeecd0600c17da37d3bccc",
"sha256": "sha256-9WSZq8Ux35uuhlFo5y8kCHAtSgH0bXuMGgowDWLcEb0="
}
}
},
{
"path": "com/android/tools/build/gradle/3.5.4",
"repo": "https://dl.google.com/dl/android/maven2",
@@ -5593,21 +5353,6 @@
}
},
{
"path": "com/android/tools/build/jetifier/jetifier-core/1.0.0-beta02",
"repo": "https://dl.google.com/dl/android/maven2",
"files": {
"jetifier-core-1.0.0-beta02.pom": {
"sha1": "db878af76000b681798b114a5169163b1efe59ea",
"sha256": "sha256-kJGGklY7MCjkeLrxyUM3jfSIOTpSeJWMbzaWO2jfw7k="
},
"jetifier-core-1.0.0-beta02.jar": {
"sha1": "fa93fdb83bdfd51e80e09a808fb2515a7134d406",
"sha256": "sha256-72H4QwL4tB3OOFjB/H56kOx0omOgITsfZegMVhRaR5M="
}
}
},
{
"path": "com/android/tools/build/jetifier/jetifier-core/1.0.0-beta04",
"repo": "https://dl.google.com/dl/android/maven2",
@@ -5672,21 +5417,6 @@
}
},
{
"path": "com/android/tools/build/jetifier/jetifier-processor/1.0.0-beta02",
"repo": "https://dl.google.com/dl/android/maven2",
"files": {
"jetifier-processor-1.0.0-beta02.pom": {
"sha1": "3bf006f5a449806a9ad493a99c9f9417b3040ef0",
"sha256": "sha256-+zYwiX233O8PE6VViKrYxkvcGPZ64KHizNR1igcioa8="
},
"jetifier-processor-1.0.0-beta02.jar": {
"sha1": "624d187319c8dfd6bc8281b4afbab87c39bbb005",
"sha256": "sha256-J+UFZWvlNLNIYHQy/EEeKVQLCE0NTkhlIyqnYswe9Aw="
}
}
},
{
"path": "com/android/tools/build/jetifier/jetifier-processor/1.0.0-beta04",
"repo": "https://dl.google.com/dl/android/maven2",
@@ -5781,21 +5511,6 @@
}
},
{
"path": "com/android/tools/build/manifest-merger/26.3.1",
"repo": "https://dl.google.com/dl/android/maven2",
"files": {
"manifest-merger-26.3.1.pom": {
"sha1": "1aad18d13e7389cf148b1c6c468b2947ca23cbbd",
"sha256": "sha256-YNhLNKJlpgVeZhxpOvIFB1mgbYpuXLpk370esi0vpPU="
},
"manifest-merger-26.3.1.jar": {
"sha1": "aa94f738e318fc73059d9207f433f13556072d05",
"sha256": "sha256-hNrba75FBJgCeexO/2RdkaGgsPpagAQjmjS5vOZ1crU="
}
}
},
{
"path": "com/android/tools/build/manifest-merger/26.5.4",
"repo": "https://dl.google.com/dl/android/maven2",
@@ -5947,21 +5662,6 @@
}
},
{
"path": "com/android/tools/common/26.3.1",
"repo": "https://dl.google.com/dl/android/maven2",
"files": {
"common-26.3.1.pom": {
"sha1": "b79360c83ea68ba7f0ac0fadf7c48e1a53511352",
"sha256": "sha256-mvrBYjeX9GBEevDT9gMuQfLdXx1N7pSbd3OBSR6NxVM="
},
"common-26.3.1.jar": {
"sha1": "b0e3aa846dd94f9608018f7ef6501d1069f6488c",
"sha256": "sha256-QashymLYGxzYciSbVSlD1rzTZmN32f1/cY3rQd7HM9Y="
}
}
},
{
"path": "com/android/tools/common/26.5.4",
"repo": "https://dl.google.com/dl/android/maven2",
@@ -6101,21 +5801,6 @@
}
},
{
"path": "com/android/tools/ddms/ddmlib/26.3.1",
"repo": "https://dl.google.com/dl/android/maven2",
"files": {
"ddmlib-26.3.1.pom": {
"sha1": "14405d92c982c96ace23b7a4c505b0d9de58cd8d",
"sha256": "sha256-MhUp9OEUJ8VtX0uLtcJkLAHza1LkO1wwnEk81IM9sNE="
},
"ddmlib-26.3.1.jar": {
"sha1": "61ca2873fcce1012ba9782adf7e9b0e164fb6007",
"sha256": "sha256-oTYJ+lhJr4+Os0JzaOG8KMp7DwSvKGwLqVoAfrkkHro="
}
}
},
{
"path": "com/android/tools/ddms/ddmlib/26.5.4",
"repo": "https://dl.google.com/dl/android/maven2",
@@ -6255,21 +5940,6 @@
}
},
{
"path": "com/android/tools/dvlib/26.3.1",
"repo": "https://dl.google.com/dl/android/maven2",
"files": {
"dvlib-26.3.1.pom": {
"sha1": "c438f3c025bd878ac3b54368e012b3792f9c8928",
"sha256": "sha256-3lL/6GFK8OCRXxbiIujskxfriVuL9GtfE7hU8B2mDdY="
},
"dvlib-26.3.1.jar": {
"sha1": "971430b6163a834656d07434c46fee64d109ad8e",
"sha256": "sha256-cLMOL4Sc+kKqE7+GWenhTAKtfLSkbLKB4CKOuC5XM4s="
}
}
},
{
"path": "com/android/tools/dvlib/26.5.4",
"repo": "https://dl.google.com/dl/android/maven2",
@@ -6514,21 +6184,6 @@
}
},
{
"path": "com/android/tools/layoutlib/layoutlib-api/26.3.1",
"repo": "https://dl.google.com/dl/android/maven2",
"files": {
"layoutlib-api-26.3.1.pom": {
"sha1": "0355e0146c59a91fbb303d7faaa6d2d0413f5dde",
"sha256": "sha256-az/hSE+y4dWr/4VYc0KxX5tM3W6E/Vkjm9r7ShTtLXg="
},
"layoutlib-api-26.3.1.jar": {
"sha1": "969ef3a7c0c3b245a3efe8aff7a2f6f15ff4e824",
"sha256": "sha256-Tk6yS7wTU2sl1t063kHZJWr1rnuBpBe5a1WEMLUKQV4="
}
}
},
{
"path": "com/android/tools/layoutlib/layoutlib-api/26.5.4",
"repo": "https://dl.google.com/dl/android/maven2",
@@ -6728,21 +6383,6 @@
}
},
{
"path": "com/android/tools/lint/lint-gradle-api/26.3.1",
"repo": "https://dl.google.com/dl/android/maven2",
"files": {
"lint-gradle-api-26.3.1.pom": {
"sha1": "dbdbed4b5fa948b991335771c7005fc8aa8be73b",
"sha256": "sha256-6xds70C7k1Yy8gaACv2XSeMP+suuYIgzflDCP2vG5CA="
},
"lint-gradle-api-26.3.1.jar": {
"sha1": "d55e1bcbe4e7c0bb97f5c385a003c4339e38d07f",
"sha256": "sha256-RcBjcp/izzXWOh/9Ih17gtPkpxWay2AvqWuc/5BN5sk="
}
}
},
{
"path": "com/android/tools/lint/lint-gradle-api/26.5.4",
"repo": "https://dl.google.com/dl/android/maven2",
@@ -7017,21 +6657,6 @@
}
},
{
"path": "com/android/tools/repository/26.3.1",
"repo": "https://dl.google.com/dl/android/maven2",
"files": {
"repository-26.3.1.pom": {
"sha1": "ab5a2f6cbedccf4ff9efbd5301fe26c9d1aaea2a",
"sha256": "sha256-VRlYrn5H3gUTtUim8TrCf8Bv8/Ww/x6aQ8SAjzRz6zc="
},
"repository-26.3.1.jar": {
"sha1": "0fe4c713437ebfb8c68e95c717c063843bd63758",
"sha256": "sha256-UZR25PiBJMbbndC98J8MSqXMi/Hvq4Dd3uL3Wmug784="
}
}
},
{
"path": "com/android/tools/repository/26.5.4",
"repo": "https://dl.google.com/dl/android/maven2",
@@ -7171,21 +6796,6 @@
}
},
{
"path": "com/android/tools/sdklib/26.3.1",
"repo": "https://dl.google.com/dl/android/maven2",
"files": {
"sdklib-26.3.1.pom": {
"sha1": "d6cfc0d8a0f49620138cfaea6c51201c7c678633",
"sha256": "sha256-gnUcqRaojEMoL7ZX1kJerqAstwsEAJlroYEZr6GRRCE="
},
"sdklib-26.3.1.jar": {
"sha1": "85c3c1de8824ca1f5ff491690931256c23a7297b",
"sha256": "sha256-aEN9Fbfmle8G6Yu88HEQQJlVtjsBijR7pofwGuxQfQo="
}
}
},
{
"path": "com/android/tools/sdklib/26.5.4",
"repo": "https://dl.google.com/dl/android/maven2",
@@ -7325,21 +6935,6 @@
}
},
{
"path": "com/android/tools/sdk-common/26.3.1",
"repo": "https://dl.google.com/dl/android/maven2",
"files": {
"sdk-common-26.3.1.pom": {
"sha1": "9b7b4248e21db9c342dba7c3e891f740246b97c3",
"sha256": "sha256-jFp1bExxaochJmmIvOMohkjrj3uDF3DFct1Pcp85iqg="
},
"sdk-common-26.3.1.jar": {
"sha1": "c9bed0f05024f919fe6168ef381afbe72074a272",
"sha256": "sha256-s2dcStd5EUHIfzaADxkUIz8q9Rx2b+F67BsVIsgOL8g="
}
}
},
{
"path": "com/android/tools/sdk-common/26.5.4",
"repo": "https://dl.google.com/dl/android/maven2",
@@ -11342,17 +10937,6 @@
}
},
{
"path": "com/google/guava/guava-parent/26.0-jre",
"repo": "https://repo.maven.apache.org/maven2",
"files": {
"guava-parent-26.0-jre.pom": {
"sha1": "3a638a46e3277ff6a3d43d2a50bcccfac2966815",
"sha256": "sha256-u31c1BfA19714+Jwkv3SNSLT/CUa2KB5VsE84Fc66qM="
}
}
},
{
"path": "com/google/guava/guava-parent/27.0.1-jre",
"repo": "https://repo.maven.apache.org/maven2",
@@ -11505,21 +11089,6 @@
}
},
{
"path": "com/google/guava/guava/26.0-jre",
"repo": "https://repo.maven.apache.org/maven2",
"files": {
"guava-26.0-jre.pom": {
"sha1": "7883e19f8b0cca8c4498d476743e31b107674155",
"sha256": "sha256-HDN63JpKtLhE2oHahZNlgbiUbHy3EoTF3L2vHqp+4Qk="
},
"guava-26.0-jre.jar": {
"sha1": "6a806eff209f36f635f943e16d97491f00f6bfab",
"sha256": "sha256-oOnKutZlvCC80rAfEI5fwD91bhOuqAq6rbn0BwM76iw="
}
}
},
{
"path": "com/google/guava/guava/27.0.1-jre",
"repo": "https://repo.maven.apache.org/maven2",
@@ -14121,16 +13690,16 @@
},
{
"path": "net/bytebuddy/byte-buddy-agent/1.14.10",
"path": "net/bytebuddy/byte-buddy-agent/1.14.11",
"repo": "https://repo.maven.apache.org/maven2",
"files": {
"byte-buddy-agent-1.14.10.pom": {
"sha1": "ab24aa41b81d9496ec9ccd188a0449327d93bfdf",
"sha256": "sha256-U7yX6ca/fC0OX5y6x9Xeaykk2RbPS4F+ajo1kpx5a0o="
"byte-buddy-agent-1.14.11.pom": {
"sha1": "00a2165ce3253489dd9fd0836a169dfce74e2042",
"sha256": "sha256-VBMQ/3WsTurkYFUJn8Ec3b5TqcBe6vry9wMgZQ2zzOk="
},
"byte-buddy-agent-1.14.10.jar": {
"sha1": "90ed94ac044ea8953b224304c762316e91fd6b31",
"sha256": "sha256-Z5k6idR8pY/4aIAqREjd0VDl/k5aVkXe2ZDXtNVXprk="
"byte-buddy-agent-1.14.11.jar": {
"sha1": "f9cb566608fbac6bc7bf54901a7aa11543a989ee",
"sha256": "sha256-L1N6Yhpk+nAT1oxpWnajTujXna105jXKyhbdViV664A="
}
}
},
@@ -14158,12 +13727,12 @@
},
{
"path": "net/bytebuddy/byte-buddy-parent/1.14.10",
"path": "net/bytebuddy/byte-buddy-parent/1.14.11",
"repo": "https://repo.maven.apache.org/maven2",
"files": {
"byte-buddy-parent-1.14.10.pom": {
"sha1": "4c560a925c30d5911dc875af5af4737e21b397be",
"sha256": "sha256-xpghkGD7DlZaDyyWCU6Ssg7+FG/WFjayyeTAO9m6Sns="
"byte-buddy-parent-1.14.11.pom": {
"sha1": "696bf3e007e88bd1c0c2d877d74e0f3c56f2b96f",
"sha256": "sha256-RsDxP8l3jcLrtpdIgjqixMvwfqt9GMaqGRxSnRZQiSs="
}
}
},
@@ -14199,16 +13768,16 @@
},
{
"path": "net/bytebuddy/byte-buddy/1.14.10",
"path": "net/bytebuddy/byte-buddy/1.14.11",
"repo": "https://repo.maven.apache.org/maven2",
"files": {
"byte-buddy-1.14.10.pom": {
"sha1": "8806052a8770a6b3e14362ed0ce6ef263e6a20ab",
"sha256": "sha256-zVh4KEX/ju3y7YOgw+30Xu65nhTAWvii6QGGDqCAzq8="
"byte-buddy-1.14.11.pom": {
"sha1": "5ecfcd2f75aa8c7b6347d864a4d0a5212c360301",
"sha256": "sha256-NYRJ1sc1OFhFCN2K5s/eVrr0o0t2e3HZzEZE8PH0IRo="
},
"byte-buddy-1.14.10.jar": {
"sha1": "8117daf4a612122eb4f517f66adff778cb8b4737",
"sha256": "sha256-MObgRGQ3pn2zfit/fTP1B4fd/ZcDWTGd/QVGnaoty84="
"byte-buddy-1.14.11.jar": {
"sha1": "725602eb7c8c56b51b9c21f273f9df5c909d9e7d",
"sha256": "sha256-Yq4oGH7SsGKBPaap1We/7nM8NBWCaZti3ZgCMHKaAxM="
}
}
},
@@ -17822,21 +17391,6 @@
}
},
{
"path": "org/jetbrains/kotlin/kotlin-reflect/1.3.20",
"repo": "https://repo.maven.apache.org/maven2",
"files": {
"kotlin-reflect-1.3.20.pom": {
"sha1": "7cc0e8dc8e9bf863127aee4501449e25e85a6f78",
"sha256": "sha256-ajZdjTUpX69X8t86ssuQQgnaqsiQb8Y3R9Pjv6sJPeI="
},
"kotlin-reflect-1.3.20.jar": {
"sha1": "cd49eec32cf964333faf59e04b4085eac7008477",
"sha256": "sha256-7hqSbtZY594ObfAkLNV0s7TgjmT+emgngibG8tM3Mjg="
}
}
},
{
"path": "org/jetbrains/kotlin/kotlin-reflect/1.3.50",
"repo": "https://repo.maven.apache.org/maven2",
@@ -18227,21 +17781,6 @@
}
},
{
"path": "org/jetbrains/kotlin/kotlin-stdlib-common/1.3.20",
"repo": "https://repo.maven.apache.org/maven2",
"files": {
"kotlin-stdlib-common-1.3.20.pom": {
"sha1": "507fa9faf5120c5c5247bc1b23476ad91760c09c",
"sha256": "sha256-BIjdHCYv5hfXc2K8Sa+BjYztQJQ6pbA8GITb8kdRCjA="
},
"kotlin-stdlib-common-1.3.20.jar": {
"sha1": "7d7934e26ce34da1a0a8d00e38038d7cf3375e89",
"sha256": "sha256-Br3Yrto0fvbvPk6diKASVMzbcHhLaXSV9qQh/WY6tkk="
}
}
},
{
"path": "org/jetbrains/kotlin/kotlin-stdlib-common/1.3.31",
"repo": "https://repo.maven.apache.org/maven2",
@@ -18527,21 +18066,6 @@
}
},
{
"path": "org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.3.20",
"repo": "https://repo.maven.apache.org/maven2",
"files": {
"kotlin-stdlib-jdk7-1.3.20.pom": {
"sha1": "665178dfa4d04de64edecf5f0dbda61b55f8f4e3",
"sha256": "sha256-SdtmBiZNOmpI+QgzXASEYirVyPTfjEjdSgAmdA4eFsU="
},
"kotlin-stdlib-jdk7-1.3.20.jar": {
"sha1": "aa17d6fd473ce53061a7b2b9d2ae96f547cae93d",
"sha256": "sha256-/Sr3C90rwCT7A8vtYzyhQ4cso2pqFuIYzGfIkLefsx0="
}
}
},
{
"path": "org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.3.50",
"repo": "https://repo.maven.apache.org/maven2",
@@ -18722,21 +18246,6 @@
}
},
{
"path": "org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.3.20",
"repo": "https://repo.maven.apache.org/maven2",
"files": {
"kotlin-stdlib-jdk8-1.3.20.pom": {
"sha1": "5c65523f57e3918ef722a15faae5173fc9a31331",
"sha256": "sha256-DF+73Rh7hSpRoKP7YskCSdpOdN//jf/5RphOL3AlCkI="
},
"kotlin-stdlib-jdk8-1.3.20.jar": {
"sha1": "b1f3cb184c4ce4139741454df2f8fca5320f822d",
"sha256": "sha256-bAgx5+OsqjkL8i952B8y8hLcj94ZgHo1Sk7k9migxHg="
}
}
},
{
"path": "org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.3.50",
"repo": "https://repo.maven.apache.org/maven2",
@@ -18962,21 +18471,6 @@
}
},
{
"path": "org/jetbrains/kotlin/kotlin-stdlib/1.3.20",
"repo": "https://repo.maven.apache.org/maven2",
"files": {
"kotlin-stdlib-1.3.20.pom": {
"sha1": "9d1e01f424795aa471a8def0b5dc8aeeb537aafd",
"sha256": "sha256-Mu+dGkDXXOr0F5KW7VTdylp+ekpl4aAZ5kmOusg14Rw="
},
"kotlin-stdlib-1.3.20.jar": {
"sha1": "eb2a232734e09fcd1b958a5c7520a93c6de38b32",
"sha256": "sha256-YB+RDalo+z2o6te2Tta/XJcQ2DzqN+SmMYR+fWiOM2E="
}
}
},
{
"path": "org/jetbrains/kotlin/kotlin-stdlib/1.3.31",
"repo": "https://repo.maven.apache.org/maven2",
@@ -19666,16 +19160,16 @@
},
{
"path": "org/mockito/mockito-core/5.8.0",
"path": "org/mockito/mockito-core/5.9.0",
"repo": "https://repo.maven.apache.org/maven2",
"files": {
"mockito-core-5.8.0.pom": {
"sha1": "a2b7b33c5d649a2f48b1e7aab95d81671f66875f",
"sha256": "sha256-dJWKzd4UjzC/ox/8CFi2L3H2PMws602KjGfX9CjUOi0="
"mockito-core-5.9.0.pom": {
"sha1": "2054dcc15b9ede899cb9ec6e8e960e36c68b4574",
"sha256": "sha256-BJDEL+Yu7WRzQdpAojcHGW2BO0TbKj3KMB2IFz56yPc="
},
"mockito-core-5.8.0.jar": {
"sha1": "df310627f77f859114ad31e1376fff0dce263cca",
"sha256": "sha256-wyD2YqA8KVHtzFGkS2TXmo6ZQ3P/VIRTtf2vad6+RoI="
"mockito-core-5.9.0.jar": {
"sha1": "faa88b96db3aeb96a93e83dec7491345bfbfc414",
"sha256": "sha256-u62Rhe1zSWX6x+Nn8OUVlvaVMeUdiyy87BBI3W+0Hyw="
}
}
},
-33
View File
@@ -49,7 +49,6 @@ androidx.core:core:1.7.0
androidx.cursoradapter:cursoradapter:1.0.0
androidx.customview:customview:1.0.0
androidx.databinding:databinding-common:3.2.1
androidx.databinding:databinding-common:3.3.1
androidx.databinding:databinding-common:3.5.4
androidx.databinding:databinding-common:4.1.0
androidx.databinding:databinding-common:7.0.4
@@ -57,7 +56,6 @@ androidx.databinding:databinding-common:7.2.1
androidx.databinding:databinding-common:7.3.1
androidx.databinding:databinding-common:7.4.2
androidx.databinding:databinding-compiler-common:3.2.1
androidx.databinding:databinding-compiler-common:3.3.1
androidx.databinding:databinding-compiler-common:3.5.4
androidx.databinding:databinding-compiler-common:4.1.0
androidx.databinding:databinding-compiler-common:7.0.4
@@ -159,7 +157,6 @@ com.android.databinding:baseLibrary:1.0-rc5
com.android.databinding:baseLibrary:3.0.0
com.android.databinding:baseLibrary:3.0.1
com.android.databinding:baseLibrary:3.2.1
com.android.databinding:baseLibrary:3.3.1
com.android.databinding:baseLibrary:3.5.4
com.android.databinding:baseLibrary:4.1.0
com.android.databinding:baseLibrary:7.0.4
@@ -170,7 +167,6 @@ com.android.databinding:compilerCommon:1.0-rc5
com.android.databinding:compilerCommon:3.0.0
com.android.databinding:compilerCommon:3.0.1
com.android.tools.analytics-library:crash:26.2.1
com.android.tools.analytics-library:crash:26.3.1
com.android.tools.analytics-library:crash:26.5.4
com.android.tools.analytics-library:crash:27.1.0
com.android.tools.analytics-library:crash:30.0.4
@@ -180,7 +176,6 @@ com.android.tools.analytics-library:crash:30.4.2
com.android.tools.analytics-library:protos:26.0.0
com.android.tools.analytics-library:protos:26.0.1
com.android.tools.analytics-library:protos:26.2.1
com.android.tools.analytics-library:protos:26.3.1
com.android.tools.analytics-library:protos:26.5.4
com.android.tools.analytics-library:protos:27.1.0
com.android.tools.analytics-library:protos:30.0.4
@@ -190,7 +185,6 @@ com.android.tools.analytics-library:protos:30.4.2
com.android.tools.analytics-library:shared:26.0.0
com.android.tools.analytics-library:shared:26.0.1
com.android.tools.analytics-library:shared:26.2.1
com.android.tools.analytics-library:shared:26.3.1
com.android.tools.analytics-library:shared:26.5.4
com.android.tools.analytics-library:shared:27.1.0
com.android.tools.analytics-library:shared:30.0.4
@@ -200,7 +194,6 @@ com.android.tools.analytics-library:shared:30.4.2
com.android.tools.analytics-library:tracker:26.0.0
com.android.tools.analytics-library:tracker:26.0.1
com.android.tools.analytics-library:tracker:26.2.1
com.android.tools.analytics-library:tracker:26.3.1
com.android.tools.analytics-library:tracker:26.5.4
com.android.tools.analytics-library:tracker:27.1.0
com.android.tools.analytics-library:tracker:30.0.4
@@ -208,12 +201,10 @@ com.android.tools.analytics-library:tracker:30.2.1
com.android.tools.analytics-library:tracker:30.3.1
com.android.tools.analytics-library:tracker:30.4.2
com.android.tools.build.jetifier:jetifier-core:1.0.0-alpha10
com.android.tools.build.jetifier:jetifier-core:1.0.0-beta02
com.android.tools.build.jetifier:jetifier-core:1.0.0-beta04
com.android.tools.build.jetifier:jetifier-core:1.0.0-beta09
com.android.tools.build.jetifier:jetifier-core:1.0.0-beta10
com.android.tools.build.jetifier:jetifier-processor:1.0.0-alpha10
com.android.tools.build.jetifier:jetifier-processor:1.0.0-beta02
com.android.tools.build.jetifier:jetifier-processor:1.0.0-beta04
com.android.tools.build.jetifier:jetifier-processor:1.0.0-beta09
com.android.tools.build.jetifier:jetifier-processor:1.0.0-beta10
@@ -232,7 +223,6 @@ com.android.tools.build:aaptcompiler:7.4.2
com.android.tools.build:apksig:3.0.0
com.android.tools.build:apksig:3.0.1
com.android.tools.build:apksig:3.2.1
com.android.tools.build:apksig:3.3.1
com.android.tools.build:apksig:3.5.4
com.android.tools.build:apksig:4.1.0
com.android.tools.build:apksig:7.0.4
@@ -240,7 +230,6 @@ com.android.tools.build:apksig:7.2.1
com.android.tools.build:apksig:7.3.1
com.android.tools.build:apksig:7.4.2
com.android.tools.build:apkzlib:3.2.1
com.android.tools.build:apkzlib:3.3.1
com.android.tools.build:apkzlib:3.5.4
com.android.tools.build:apkzlib:4.1.0
com.android.tools.build:apkzlib:7.0.4
@@ -252,7 +241,6 @@ com.android.tools.build:builder-model:1.5.0
com.android.tools.build:builder-model:3.0.0
com.android.tools.build:builder-model:3.0.1
com.android.tools.build:builder-model:3.2.1
com.android.tools.build:builder-model:3.3.1
com.android.tools.build:builder-model:3.5.4
com.android.tools.build:builder-model:4.1.0
com.android.tools.build:builder-model:7.0.4
@@ -264,7 +252,6 @@ com.android.tools.build:builder-test-api:1.5.0
com.android.tools.build:builder-test-api:3.0.0
com.android.tools.build:builder-test-api:3.0.1
com.android.tools.build:builder-test-api:3.2.1
com.android.tools.build:builder-test-api:3.3.1
com.android.tools.build:builder-test-api:3.5.4
com.android.tools.build:builder-test-api:4.1.0
com.android.tools.build:builder-test-api:7.0.4
@@ -276,7 +263,6 @@ com.android.tools.build:builder:1.5.0
com.android.tools.build:builder:3.0.0
com.android.tools.build:builder:3.0.1
com.android.tools.build:builder:3.2.1
com.android.tools.build:builder:3.3.1
com.android.tools.build:builder:3.5.4
com.android.tools.build:builder:4.1.0
com.android.tools.build:builder:7.0.4
@@ -284,7 +270,6 @@ com.android.tools.build:builder:7.2.1
com.android.tools.build:builder:7.3.1
com.android.tools.build:builder:7.4.2
com.android.tools.build:bundletool:0.5.0
com.android.tools.build:bundletool:0.6.0
com.android.tools.build:bundletool:0.9.0
com.android.tools.build:bundletool:0.14.0
com.android.tools.build:bundletool:1.6.0
@@ -294,7 +279,6 @@ com.android.tools.build:bundletool:1.11.4
com.android.tools.build:gradle-api:3.0.0
com.android.tools.build:gradle-api:3.0.1
com.android.tools.build:gradle-api:3.2.1
com.android.tools.build:gradle-api:3.3.1
com.android.tools.build:gradle-api:3.5.4
com.android.tools.build:gradle-api:4.1.0
com.android.tools.build:gradle-api:7.0.4
@@ -311,7 +295,6 @@ com.android.tools.build:gradle:1.5.0
com.android.tools.build:gradle:3.0.0
com.android.tools.build:gradle:3.0.1
com.android.tools.build:gradle:3.2.1
com.android.tools.build:gradle:3.3.1
com.android.tools.build:gradle:3.5.4
com.android.tools.build:gradle:4.1.0
com.android.tools.build:gradle:7.0.4
@@ -322,7 +305,6 @@ com.android.tools.build:manifest-merger:24.5.0
com.android.tools.build:manifest-merger:26.0.0
com.android.tools.build:manifest-merger:26.0.1
com.android.tools.build:manifest-merger:26.2.1
com.android.tools.build:manifest-merger:26.3.1
com.android.tools.build:manifest-merger:26.5.4
com.android.tools.build:manifest-merger:27.1.0
com.android.tools.build:manifest-merger:30.0.4
@@ -336,7 +318,6 @@ com.android.tools.ddms:ddmlib:24.5.0
com.android.tools.ddms:ddmlib:26.0.0
com.android.tools.ddms:ddmlib:26.0.1
com.android.tools.ddms:ddmlib:26.2.1
com.android.tools.ddms:ddmlib:26.3.1
com.android.tools.ddms:ddmlib:26.5.4
com.android.tools.ddms:ddmlib:27.1.0
com.android.tools.ddms:ddmlib:30.0.4
@@ -355,7 +336,6 @@ com.android.tools.layoutlib:layoutlib-api:24.5.0
com.android.tools.layoutlib:layoutlib-api:26.0.0
com.android.tools.layoutlib:layoutlib-api:26.0.1
com.android.tools.layoutlib:layoutlib-api:26.2.1
com.android.tools.layoutlib:layoutlib-api:26.3.1
com.android.tools.layoutlib:layoutlib-api:26.5.4
com.android.tools.layoutlib:layoutlib-api:27.1.0
com.android.tools.layoutlib:layoutlib-api:30.0.4
@@ -371,7 +351,6 @@ com.android.tools.lint:lint-checks:24.5.0
com.android.tools.lint:lint-checks:26.0.0
com.android.tools.lint:lint-checks:26.0.1
com.android.tools.lint:lint-gradle-api:26.2.1
com.android.tools.lint:lint-gradle-api:26.3.1
com.android.tools.lint:lint-gradle-api:26.5.4
com.android.tools.lint:lint-gradle-api:27.1.0
com.android.tools.lint:lint-model:27.1.0
@@ -412,7 +391,6 @@ com.android.tools:annotations:24.5.0
com.android.tools:annotations:26.0.0
com.android.tools:annotations:26.0.1
com.android.tools:annotations:26.2.1
com.android.tools:annotations:26.3.1
com.android.tools:annotations:26.5.4
com.android.tools:annotations:27.1.0
com.android.tools:annotations:30.0.4
@@ -424,7 +402,6 @@ com.android.tools:common:24.5.0
com.android.tools:common:26.0.0
com.android.tools:common:26.0.1
com.android.tools:common:26.2.1
com.android.tools:common:26.3.1
com.android.tools:common:26.5.4
com.android.tools:common:27.1.0
com.android.tools:common:30.0.4
@@ -436,7 +413,6 @@ com.android.tools:dvlib:24.5.0
com.android.tools:dvlib:26.0.0
com.android.tools:dvlib:26.0.1
com.android.tools:dvlib:26.2.1
com.android.tools:dvlib:26.3.1
com.android.tools:dvlib:26.5.4
com.android.tools:dvlib:27.1.0
com.android.tools:dvlib:30.0.4
@@ -446,7 +422,6 @@ com.android.tools:dvlib:30.4.2
com.android.tools:repository:26.0.0
com.android.tools:repository:26.0.1
com.android.tools:repository:26.2.1
com.android.tools:repository:26.3.1
com.android.tools:repository:26.5.4
com.android.tools:repository:27.1.0
com.android.tools:repository:30.0.4
@@ -458,7 +433,6 @@ com.android.tools:sdklib:24.5.0
com.android.tools:sdklib:26.0.0
com.android.tools:sdklib:26.0.1
com.android.tools:sdklib:26.2.1
com.android.tools:sdklib:26.3.1
com.android.tools:sdklib:26.5.4
com.android.tools:sdklib:27.1.0
com.android.tools:sdklib:30.0.4
@@ -470,7 +444,6 @@ com.android.tools:sdk-common:24.5.0
com.android.tools:sdk-common:26.0.0
com.android.tools:sdk-common:26.0.1
com.android.tools:sdk-common:26.2.1
com.android.tools:sdk-common:26.3.1
com.android.tools:sdk-common:26.5.4
com.android.tools:sdk-common:27.1.0
com.android.tools:sdk-common:30.0.4
@@ -590,7 +563,6 @@ com.google.guava:failureaccess:1.0.1
com.google.guava:guava:17.0
com.google.guava:guava:22.0
com.google.guava:guava:23.0
com.google.guava:guava:26.0-jre
com.google.guava:guava:27.0.1-jre
com.google.guava:guava:28.1-jre
com.google.guava:guava:29.0-jre
@@ -817,7 +789,6 @@ org.jetbrains.kotlin:kotlin-project-model:1.6.20
org.jetbrains.kotlin:kotlin-project-model:1.9.0
org.jetbrains.kotlin:kotlin-reflect:1.1.3-2
org.jetbrains.kotlin:kotlin-reflect:1.2.0
org.jetbrains.kotlin:kotlin-reflect:1.3.20
org.jetbrains.kotlin:kotlin-reflect:1.3.50
org.jetbrains.kotlin:kotlin-reflect:1.3.72
org.jetbrains.kotlin:kotlin-reflect:1.4.32
@@ -834,7 +805,6 @@ org.jetbrains.kotlin:kotlin-scripting-jvm:1.6.20
org.jetbrains.kotlin:kotlin-scripting-jvm:1.9.0
org.jetbrains.kotlin:kotlin-script-runtime:1.9.0
org.jetbrains.kotlin:kotlin-stdlib-common:1.2.71
org.jetbrains.kotlin:kotlin-stdlib-common:1.3.20
org.jetbrains.kotlin:kotlin-stdlib-common:1.3.50
org.jetbrains.kotlin:kotlin-stdlib-common:1.3.72
org.jetbrains.kotlin:kotlin-stdlib-common:1.4.32
@@ -843,7 +813,6 @@ org.jetbrains.kotlin:kotlin-stdlib-common:1.6.20
org.jetbrains.kotlin:kotlin-stdlib-common:1.7.22
org.jetbrains.kotlin:kotlin-stdlib-common:1.9.0
org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.2.71
org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.20
org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.50
org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.72
org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.4.32
@@ -852,7 +821,6 @@ org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.6.20
org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.7.22
org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.9.0
org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.2.71
org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.20
org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.50
org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.72
org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.4.32
@@ -861,7 +829,6 @@ org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.7.22
org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.0
org.jetbrains.kotlin:kotlin-stdlib:1.1.3-2
org.jetbrains.kotlin:kotlin-stdlib:1.2.71
org.jetbrains.kotlin:kotlin-stdlib:1.3.20
org.jetbrains.kotlin:kotlin-stdlib:1.3.50
org.jetbrains.kotlin:kotlin-stdlib:1.3.72
org.jetbrains.kotlin:kotlin-stdlib:1.4.32
+4 -38
View File
@@ -62,7 +62,6 @@ https://dl.google.com/dl/android/maven2/androidx/core/core/1.7.0/core-1.7.0.pom
https://dl.google.com/dl/android/maven2/androidx/cursoradapter/cursoradapter/1.0.0/cursoradapter-1.0.0.pom
https://dl.google.com/dl/android/maven2/androidx/customview/customview/1.0.0/customview-1.0.0.pom
https://dl.google.com/dl/android/maven2/androidx/databinding/databinding-common/3.2.1/databinding-common-3.2.1.pom
https://dl.google.com/dl/android/maven2/androidx/databinding/databinding-common/3.3.1/databinding-common-3.3.1.pom
https://dl.google.com/dl/android/maven2/androidx/databinding/databinding-common/3.5.4/databinding-common-3.5.4.pom
https://dl.google.com/dl/android/maven2/androidx/databinding/databinding-common/4.1.0/databinding-common-4.1.0.pom
https://dl.google.com/dl/android/maven2/androidx/databinding/databinding-common/7.0.4/databinding-common-7.0.4.pom
@@ -70,7 +69,6 @@ https://dl.google.com/dl/android/maven2/androidx/databinding/databinding-common/
https://dl.google.com/dl/android/maven2/androidx/databinding/databinding-common/7.3.1/databinding-common-7.3.1.pom
https://dl.google.com/dl/android/maven2/androidx/databinding/databinding-common/7.4.2/databinding-common-7.4.2.pom
https://dl.google.com/dl/android/maven2/androidx/databinding/databinding-compiler-common/3.2.1/databinding-compiler-common-3.2.1.pom
https://dl.google.com/dl/android/maven2/androidx/databinding/databinding-compiler-common/3.3.1/databinding-compiler-common-3.3.1.pom
https://dl.google.com/dl/android/maven2/androidx/databinding/databinding-compiler-common/3.5.4/databinding-compiler-common-3.5.4.pom
https://dl.google.com/dl/android/maven2/androidx/databinding/databinding-compiler-common/4.1.0/databinding-compiler-common-4.1.0.pom
https://dl.google.com/dl/android/maven2/androidx/databinding/databinding-compiler-common/7.0.4/databinding-compiler-common-7.0.4.pom
@@ -170,7 +168,6 @@ https://dl.google.com/dl/android/maven2/android/arch/lifecycle/runtime/1.0.0/run
https://dl.google.com/dl/android/maven2/com/android/databinding/baseLibrary/3.0.0/baseLibrary-3.0.0.pom
https://dl.google.com/dl/android/maven2/com/android/databinding/baseLibrary/3.0.1/baseLibrary-3.0.1.pom
https://dl.google.com/dl/android/maven2/com/android/databinding/baseLibrary/3.2.1/baseLibrary-3.2.1.pom
https://dl.google.com/dl/android/maven2/com/android/databinding/baseLibrary/3.3.1/baseLibrary-3.3.1.pom
https://dl.google.com/dl/android/maven2/com/android/databinding/baseLibrary/3.5.4/baseLibrary-3.5.4.pom
https://dl.google.com/dl/android/maven2/com/android/databinding/baseLibrary/4.1.0/baseLibrary-4.1.0.pom
https://dl.google.com/dl/android/maven2/com/android/databinding/baseLibrary/7.0.4/baseLibrary-7.0.4.pom
@@ -205,7 +202,6 @@ https://dl.google.com/dl/android/maven2/com/android/support/support-v4/26.0.2/su
https://dl.google.com/dl/android/maven2/com/android/support/support-vector-drawable/26.0.2/support-vector-drawable-26.0.2.pom
https://dl.google.com/dl/android/maven2/com/android/support/support-vector-drawable/27.0.1/support-vector-drawable-27.0.1.pom
https://dl.google.com/dl/android/maven2/com/android/tools/analytics-library/crash/26.2.1/crash-26.2.1.pom
https://dl.google.com/dl/android/maven2/com/android/tools/analytics-library/crash/26.3.1/crash-26.3.1.pom
https://dl.google.com/dl/android/maven2/com/android/tools/analytics-library/crash/26.5.4/crash-26.5.4.pom
https://dl.google.com/dl/android/maven2/com/android/tools/analytics-library/crash/27.1.0/crash-27.1.0.pom
https://dl.google.com/dl/android/maven2/com/android/tools/analytics-library/crash/30.0.4/crash-30.0.4.pom
@@ -215,7 +211,6 @@ https://dl.google.com/dl/android/maven2/com/android/tools/analytics-library/cras
https://dl.google.com/dl/android/maven2/com/android/tools/analytics-library/protos/26.0.0/protos-26.0.0.pom
https://dl.google.com/dl/android/maven2/com/android/tools/analytics-library/protos/26.0.1/protos-26.0.1.pom
https://dl.google.com/dl/android/maven2/com/android/tools/analytics-library/protos/26.2.1/protos-26.2.1.pom
https://dl.google.com/dl/android/maven2/com/android/tools/analytics-library/protos/26.3.1/protos-26.3.1.pom
https://dl.google.com/dl/android/maven2/com/android/tools/analytics-library/protos/26.5.4/protos-26.5.4.pom
https://dl.google.com/dl/android/maven2/com/android/tools/analytics-library/protos/27.1.0/protos-27.1.0.pom
https://dl.google.com/dl/android/maven2/com/android/tools/analytics-library/protos/30.0.4/protos-30.0.4.pom
@@ -225,7 +220,6 @@ https://dl.google.com/dl/android/maven2/com/android/tools/analytics-library/prot
https://dl.google.com/dl/android/maven2/com/android/tools/analytics-library/shared/26.0.0/shared-26.0.0.pom
https://dl.google.com/dl/android/maven2/com/android/tools/analytics-library/shared/26.0.1/shared-26.0.1.pom
https://dl.google.com/dl/android/maven2/com/android/tools/analytics-library/shared/26.2.1/shared-26.2.1.pom
https://dl.google.com/dl/android/maven2/com/android/tools/analytics-library/shared/26.3.1/shared-26.3.1.pom
https://dl.google.com/dl/android/maven2/com/android/tools/analytics-library/shared/26.5.4/shared-26.5.4.pom
https://dl.google.com/dl/android/maven2/com/android/tools/analytics-library/shared/27.1.0/shared-27.1.0.pom
https://dl.google.com/dl/android/maven2/com/android/tools/analytics-library/shared/30.0.4/shared-30.0.4.pom
@@ -235,7 +229,6 @@ https://dl.google.com/dl/android/maven2/com/android/tools/analytics-library/shar
https://dl.google.com/dl/android/maven2/com/android/tools/analytics-library/tracker/26.0.0/tracker-26.0.0.pom
https://dl.google.com/dl/android/maven2/com/android/tools/analytics-library/tracker/26.0.1/tracker-26.0.1.pom
https://dl.google.com/dl/android/maven2/com/android/tools/analytics-library/tracker/26.2.1/tracker-26.2.1.pom
https://dl.google.com/dl/android/maven2/com/android/tools/analytics-library/tracker/26.3.1/tracker-26.3.1.pom
https://dl.google.com/dl/android/maven2/com/android/tools/analytics-library/tracker/26.5.4/tracker-26.5.4.pom
https://dl.google.com/dl/android/maven2/com/android/tools/analytics-library/tracker/27.1.0/tracker-27.1.0.pom
https://dl.google.com/dl/android/maven2/com/android/tools/analytics-library/tracker/30.0.4/tracker-30.0.4.pom
@@ -245,7 +238,6 @@ https://dl.google.com/dl/android/maven2/com/android/tools/analytics-library/trac
https://dl.google.com/dl/android/maven2/com/android/tools/annotations/26.0.0/annotations-26.0.0.pom
https://dl.google.com/dl/android/maven2/com/android/tools/annotations/26.0.1/annotations-26.0.1.pom
https://dl.google.com/dl/android/maven2/com/android/tools/annotations/26.2.1/annotations-26.2.1.pom
https://dl.google.com/dl/android/maven2/com/android/tools/annotations/26.3.1/annotations-26.3.1.pom
https://dl.google.com/dl/android/maven2/com/android/tools/annotations/26.5.4/annotations-26.5.4.pom
https://dl.google.com/dl/android/maven2/com/android/tools/annotations/27.1.0/annotations-27.1.0.pom
https://dl.google.com/dl/android/maven2/com/android/tools/annotations/30.0.4/annotations-30.0.4.pom
@@ -271,7 +263,6 @@ https://dl.google.com/dl/android/maven2/com/android/tools/build/aaptcompiler/7.4
https://dl.google.com/dl/android/maven2/com/android/tools/build/apksig/3.0.0/apksig-3.0.0.pom
https://dl.google.com/dl/android/maven2/com/android/tools/build/apksig/3.0.1/apksig-3.0.1.pom
https://dl.google.com/dl/android/maven2/com/android/tools/build/apksig/3.2.1/apksig-3.2.1.pom
https://dl.google.com/dl/android/maven2/com/android/tools/build/apksig/3.3.1/apksig-3.3.1.pom
https://dl.google.com/dl/android/maven2/com/android/tools/build/apksig/3.5.4/apksig-3.5.4.pom
https://dl.google.com/dl/android/maven2/com/android/tools/build/apksig/4.1.0/apksig-4.1.0.pom
https://dl.google.com/dl/android/maven2/com/android/tools/build/apksig/7.0.4/apksig-7.0.4.pom
@@ -279,7 +270,6 @@ https://dl.google.com/dl/android/maven2/com/android/tools/build/apksig/7.2.1/apk
https://dl.google.com/dl/android/maven2/com/android/tools/build/apksig/7.3.1/apksig-7.3.1.pom
https://dl.google.com/dl/android/maven2/com/android/tools/build/apksig/7.4.2/apksig-7.4.2.pom
https://dl.google.com/dl/android/maven2/com/android/tools/build/apkzlib/3.2.1/apkzlib-3.2.1.pom
https://dl.google.com/dl/android/maven2/com/android/tools/build/apkzlib/3.3.1/apkzlib-3.3.1.pom
https://dl.google.com/dl/android/maven2/com/android/tools/build/apkzlib/3.5.4/apkzlib-3.5.4.pom
https://dl.google.com/dl/android/maven2/com/android/tools/build/apkzlib/4.1.0/apkzlib-4.1.0.pom
https://dl.google.com/dl/android/maven2/com/android/tools/build/apkzlib/7.0.4/apkzlib-7.0.4.pom
@@ -289,7 +279,6 @@ https://dl.google.com/dl/android/maven2/com/android/tools/build/apkzlib/7.4.2/ap
https://dl.google.com/dl/android/maven2/com/android/tools/build/builder-model/3.0.0/builder-model-3.0.0.pom
https://dl.google.com/dl/android/maven2/com/android/tools/build/builder-model/3.0.1/builder-model-3.0.1.pom
https://dl.google.com/dl/android/maven2/com/android/tools/build/builder-model/3.2.1/builder-model-3.2.1.pom
https://dl.google.com/dl/android/maven2/com/android/tools/build/builder-model/3.3.1/builder-model-3.3.1.pom
https://dl.google.com/dl/android/maven2/com/android/tools/build/builder-model/3.5.4/builder-model-3.5.4.pom
https://dl.google.com/dl/android/maven2/com/android/tools/build/builder-model/4.1.0/builder-model-4.1.0.pom
https://dl.google.com/dl/android/maven2/com/android/tools/build/builder-model/7.0.4/builder-model-7.0.4.pom
@@ -299,7 +288,6 @@ https://dl.google.com/dl/android/maven2/com/android/tools/build/builder-model/7.
https://dl.google.com/dl/android/maven2/com/android/tools/build/builder-test-api/3.0.0/builder-test-api-3.0.0.pom
https://dl.google.com/dl/android/maven2/com/android/tools/build/builder-test-api/3.0.1/builder-test-api-3.0.1.pom
https://dl.google.com/dl/android/maven2/com/android/tools/build/builder-test-api/3.2.1/builder-test-api-3.2.1.pom
https://dl.google.com/dl/android/maven2/com/android/tools/build/builder-test-api/3.3.1/builder-test-api-3.3.1.pom
https://dl.google.com/dl/android/maven2/com/android/tools/build/builder-test-api/3.5.4/builder-test-api-3.5.4.pom
https://dl.google.com/dl/android/maven2/com/android/tools/build/builder-test-api/4.1.0/builder-test-api-4.1.0.pom
https://dl.google.com/dl/android/maven2/com/android/tools/build/builder-test-api/7.0.4/builder-test-api-7.0.4.pom
@@ -309,7 +297,6 @@ https://dl.google.com/dl/android/maven2/com/android/tools/build/builder-test-api
https://dl.google.com/dl/android/maven2/com/android/tools/build/builder/3.0.0/builder-3.0.0.pom
https://dl.google.com/dl/android/maven2/com/android/tools/build/builder/3.0.1/builder-3.0.1.pom
https://dl.google.com/dl/android/maven2/com/android/tools/build/builder/3.2.1/builder-3.2.1.pom
https://dl.google.com/dl/android/maven2/com/android/tools/build/builder/3.3.1/builder-3.3.1.pom
https://dl.google.com/dl/android/maven2/com/android/tools/build/builder/3.5.4/builder-3.5.4.pom
https://dl.google.com/dl/android/maven2/com/android/tools/build/builder/4.1.0/builder-4.1.0.pom
https://dl.google.com/dl/android/maven2/com/android/tools/build/builder/7.0.4/builder-7.0.4.pom
@@ -317,7 +304,6 @@ https://dl.google.com/dl/android/maven2/com/android/tools/build/builder/7.2.1/bu
https://dl.google.com/dl/android/maven2/com/android/tools/build/builder/7.3.1/builder-7.3.1.pom
https://dl.google.com/dl/android/maven2/com/android/tools/build/builder/7.4.2/builder-7.4.2.pom
https://dl.google.com/dl/android/maven2/com/android/tools/build/bundletool/0.5.0/bundletool-0.5.0.pom
https://dl.google.com/dl/android/maven2/com/android/tools/build/bundletool/0.6.0/bundletool-0.6.0.pom
https://dl.google.com/dl/android/maven2/com/android/tools/build/bundletool/0.9.0/bundletool-0.9.0.pom
https://dl.google.com/dl/android/maven2/com/android/tools/build/bundletool/0.14.0/bundletool-0.14.0.pom
https://dl.google.com/dl/android/maven2/com/android/tools/build/bundletool/1.6.0/bundletool-1.6.0.pom
@@ -327,7 +313,6 @@ https://dl.google.com/dl/android/maven2/com/android/tools/build/bundletool/1.11.
https://dl.google.com/dl/android/maven2/com/android/tools/build/gradle-api/3.0.0/gradle-api-3.0.0.pom
https://dl.google.com/dl/android/maven2/com/android/tools/build/gradle-api/3.0.1/gradle-api-3.0.1.pom
https://dl.google.com/dl/android/maven2/com/android/tools/build/gradle-api/3.2.1/gradle-api-3.2.1.pom
https://dl.google.com/dl/android/maven2/com/android/tools/build/gradle-api/3.3.1/gradle-api-3.3.1.pom
https://dl.google.com/dl/android/maven2/com/android/tools/build/gradle-api/3.5.4/gradle-api-3.5.4.pom
https://dl.google.com/dl/android/maven2/com/android/tools/build/gradle-api/4.1.0/gradle-api-4.1.0.pom
https://dl.google.com/dl/android/maven2/com/android/tools/build/gradle-api/7.0.4/gradle-api-7.0.4.pom
@@ -340,7 +325,6 @@ https://dl.google.com/dl/android/maven2/com/android/tools/build/gradle-settings-
https://dl.google.com/dl/android/maven2/com/android/tools/build/gradle/3.0.0/gradle-3.0.0.pom
https://dl.google.com/dl/android/maven2/com/android/tools/build/gradle/3.0.1/gradle-3.0.1.pom
https://dl.google.com/dl/android/maven2/com/android/tools/build/gradle/3.2.1/gradle-3.2.1.pom
https://dl.google.com/dl/android/maven2/com/android/tools/build/gradle/3.3.1/gradle-3.3.1.pom
https://dl.google.com/dl/android/maven2/com/android/tools/build/gradle/3.5.4/gradle-3.5.4.pom
https://dl.google.com/dl/android/maven2/com/android/tools/build/gradle/4.1.0/gradle-4.1.0.pom
https://dl.google.com/dl/android/maven2/com/android/tools/build/gradle/7.0.4/gradle-7.0.4.pom
@@ -348,19 +332,16 @@ https://dl.google.com/dl/android/maven2/com/android/tools/build/gradle/7.2.1/gra
https://dl.google.com/dl/android/maven2/com/android/tools/build/gradle/7.3.1/gradle-7.3.1.pom
https://dl.google.com/dl/android/maven2/com/android/tools/build/gradle/7.4.2/gradle-7.4.2.pom
https://dl.google.com/dl/android/maven2/com/android/tools/build/jetifier/jetifier-core/1.0.0-alpha10/jetifier-core-1.0.0-alpha10.pom
https://dl.google.com/dl/android/maven2/com/android/tools/build/jetifier/jetifier-core/1.0.0-beta02/jetifier-core-1.0.0-beta02.pom
https://dl.google.com/dl/android/maven2/com/android/tools/build/jetifier/jetifier-core/1.0.0-beta04/jetifier-core-1.0.0-beta04.pom
https://dl.google.com/dl/android/maven2/com/android/tools/build/jetifier/jetifier-core/1.0.0-beta09/jetifier-core-1.0.0-beta09.pom
https://dl.google.com/dl/android/maven2/com/android/tools/build/jetifier/jetifier-core/1.0.0-beta10/jetifier-core-1.0.0-beta10.pom
https://dl.google.com/dl/android/maven2/com/android/tools/build/jetifier/jetifier-processor/1.0.0-alpha10/jetifier-processor-1.0.0-alpha10.pom
https://dl.google.com/dl/android/maven2/com/android/tools/build/jetifier/jetifier-processor/1.0.0-beta02/jetifier-processor-1.0.0-beta02.pom
https://dl.google.com/dl/android/maven2/com/android/tools/build/jetifier/jetifier-processor/1.0.0-beta04/jetifier-processor-1.0.0-beta04.pom
https://dl.google.com/dl/android/maven2/com/android/tools/build/jetifier/jetifier-processor/1.0.0-beta09/jetifier-processor-1.0.0-beta09.pom
https://dl.google.com/dl/android/maven2/com/android/tools/build/jetifier/jetifier-processor/1.0.0-beta10/jetifier-processor-1.0.0-beta10.pom
https://dl.google.com/dl/android/maven2/com/android/tools/build/manifest-merger/26.0.0/manifest-merger-26.0.0.pom
https://dl.google.com/dl/android/maven2/com/android/tools/build/manifest-merger/26.0.1/manifest-merger-26.0.1.pom
https://dl.google.com/dl/android/maven2/com/android/tools/build/manifest-merger/26.2.1/manifest-merger-26.2.1.pom
https://dl.google.com/dl/android/maven2/com/android/tools/build/manifest-merger/26.3.1/manifest-merger-26.3.1.pom
https://dl.google.com/dl/android/maven2/com/android/tools/build/manifest-merger/26.5.4/manifest-merger-26.5.4.pom
https://dl.google.com/dl/android/maven2/com/android/tools/build/manifest-merger/27.1.0/manifest-merger-27.1.0.pom
https://dl.google.com/dl/android/maven2/com/android/tools/build/manifest-merger/30.0.4/manifest-merger-30.0.4.pom
@@ -370,7 +351,6 @@ https://dl.google.com/dl/android/maven2/com/android/tools/build/manifest-merger/
https://dl.google.com/dl/android/maven2/com/android/tools/common/26.0.0/common-26.0.0.pom
https://dl.google.com/dl/android/maven2/com/android/tools/common/26.0.1/common-26.0.1.pom
https://dl.google.com/dl/android/maven2/com/android/tools/common/26.2.1/common-26.2.1.pom
https://dl.google.com/dl/android/maven2/com/android/tools/common/26.3.1/common-26.3.1.pom
https://dl.google.com/dl/android/maven2/com/android/tools/common/26.5.4/common-26.5.4.pom
https://dl.google.com/dl/android/maven2/com/android/tools/common/27.1.0/common-27.1.0.pom
https://dl.google.com/dl/android/maven2/com/android/tools/common/30.0.4/common-30.0.4.pom
@@ -380,7 +360,6 @@ https://dl.google.com/dl/android/maven2/com/android/tools/common/30.4.2/common-3
https://dl.google.com/dl/android/maven2/com/android/tools/ddms/ddmlib/26.0.0/ddmlib-26.0.0.pom
https://dl.google.com/dl/android/maven2/com/android/tools/ddms/ddmlib/26.0.1/ddmlib-26.0.1.pom
https://dl.google.com/dl/android/maven2/com/android/tools/ddms/ddmlib/26.2.1/ddmlib-26.2.1.pom
https://dl.google.com/dl/android/maven2/com/android/tools/ddms/ddmlib/26.3.1/ddmlib-26.3.1.pom
https://dl.google.com/dl/android/maven2/com/android/tools/ddms/ddmlib/26.5.4/ddmlib-26.5.4.pom
https://dl.google.com/dl/android/maven2/com/android/tools/ddms/ddmlib/27.1.0/ddmlib-27.1.0.pom
https://dl.google.com/dl/android/maven2/com/android/tools/ddms/ddmlib/30.0.4/ddmlib-30.0.4.pom
@@ -390,7 +369,6 @@ https://dl.google.com/dl/android/maven2/com/android/tools/ddms/ddmlib/30.4.2/ddm
https://dl.google.com/dl/android/maven2/com/android/tools/dvlib/26.0.0/dvlib-26.0.0.pom
https://dl.google.com/dl/android/maven2/com/android/tools/dvlib/26.0.1/dvlib-26.0.1.pom
https://dl.google.com/dl/android/maven2/com/android/tools/dvlib/26.2.1/dvlib-26.2.1.pom
https://dl.google.com/dl/android/maven2/com/android/tools/dvlib/26.3.1/dvlib-26.3.1.pom
https://dl.google.com/dl/android/maven2/com/android/tools/dvlib/26.5.4/dvlib-26.5.4.pom
https://dl.google.com/dl/android/maven2/com/android/tools/dvlib/27.1.0/dvlib-27.1.0.pom
https://dl.google.com/dl/android/maven2/com/android/tools/dvlib/30.0.4/dvlib-30.0.4.pom
@@ -407,7 +385,6 @@ https://dl.google.com/dl/android/maven2/com/android/tools/external/org-jetbrains
https://dl.google.com/dl/android/maven2/com/android/tools/layoutlib/layoutlib-api/26.0.0/layoutlib-api-26.0.0.pom
https://dl.google.com/dl/android/maven2/com/android/tools/layoutlib/layoutlib-api/26.0.1/layoutlib-api-26.0.1.pom
https://dl.google.com/dl/android/maven2/com/android/tools/layoutlib/layoutlib-api/26.2.1/layoutlib-api-26.2.1.pom
https://dl.google.com/dl/android/maven2/com/android/tools/layoutlib/layoutlib-api/26.3.1/layoutlib-api-26.3.1.pom
https://dl.google.com/dl/android/maven2/com/android/tools/layoutlib/layoutlib-api/26.5.4/layoutlib-api-26.5.4.pom
https://dl.google.com/dl/android/maven2/com/android/tools/layoutlib/layoutlib-api/27.1.0/layoutlib-api-27.1.0.pom
https://dl.google.com/dl/android/maven2/com/android/tools/layoutlib/layoutlib-api/30.0.4/layoutlib-api-30.0.4.pom
@@ -421,7 +398,6 @@ https://dl.google.com/dl/android/maven2/com/android/tools/lint/lint-checks/26.0.
https://dl.google.com/dl/android/maven2/com/android/tools/lint/lint-checks/26.0.1/lint-checks-26.0.1.pom
https://dl.google.com/dl/android/maven2/com/android/tools/lint/lint-checks/30.4.2/lint-checks-30.4.2.pom
https://dl.google.com/dl/android/maven2/com/android/tools/lint/lint-gradle-api/26.2.1/lint-gradle-api-26.2.1.pom
https://dl.google.com/dl/android/maven2/com/android/tools/lint/lint-gradle-api/26.3.1/lint-gradle-api-26.3.1.pom
https://dl.google.com/dl/android/maven2/com/android/tools/lint/lint-gradle-api/26.5.4/lint-gradle-api-26.5.4.pom
https://dl.google.com/dl/android/maven2/com/android/tools/lint/lint-gradle-api/27.1.0/lint-gradle-api-27.1.0.pom
https://dl.google.com/dl/android/maven2/com/android/tools/lint/lint-gradle/30.4.2/lint-gradle-30.4.2.pom
@@ -440,7 +416,6 @@ https://dl.google.com/dl/android/maven2/com/android/tools/play-sdk-proto/30.4.2/
https://dl.google.com/dl/android/maven2/com/android/tools/repository/26.0.0/repository-26.0.0.pom
https://dl.google.com/dl/android/maven2/com/android/tools/repository/26.0.1/repository-26.0.1.pom
https://dl.google.com/dl/android/maven2/com/android/tools/repository/26.2.1/repository-26.2.1.pom
https://dl.google.com/dl/android/maven2/com/android/tools/repository/26.3.1/repository-26.3.1.pom
https://dl.google.com/dl/android/maven2/com/android/tools/repository/26.5.4/repository-26.5.4.pom
https://dl.google.com/dl/android/maven2/com/android/tools/repository/27.1.0/repository-27.1.0.pom
https://dl.google.com/dl/android/maven2/com/android/tools/repository/30.0.4/repository-30.0.4.pom
@@ -450,7 +425,6 @@ https://dl.google.com/dl/android/maven2/com/android/tools/repository/30.4.2/repo
https://dl.google.com/dl/android/maven2/com/android/tools/sdklib/26.0.0/sdklib-26.0.0.pom
https://dl.google.com/dl/android/maven2/com/android/tools/sdklib/26.0.1/sdklib-26.0.1.pom
https://dl.google.com/dl/android/maven2/com/android/tools/sdklib/26.2.1/sdklib-26.2.1.pom
https://dl.google.com/dl/android/maven2/com/android/tools/sdklib/26.3.1/sdklib-26.3.1.pom
https://dl.google.com/dl/android/maven2/com/android/tools/sdklib/26.5.4/sdklib-26.5.4.pom
https://dl.google.com/dl/android/maven2/com/android/tools/sdklib/27.1.0/sdklib-27.1.0.pom
https://dl.google.com/dl/android/maven2/com/android/tools/sdklib/30.0.4/sdklib-30.0.4.pom
@@ -460,7 +434,6 @@ https://dl.google.com/dl/android/maven2/com/android/tools/sdklib/30.4.2/sdklib-3
https://dl.google.com/dl/android/maven2/com/android/tools/sdk-common/26.0.0/sdk-common-26.0.0.pom
https://dl.google.com/dl/android/maven2/com/android/tools/sdk-common/26.0.1/sdk-common-26.0.1.pom
https://dl.google.com/dl/android/maven2/com/android/tools/sdk-common/26.2.1/sdk-common-26.2.1.pom
https://dl.google.com/dl/android/maven2/com/android/tools/sdk-common/26.3.1/sdk-common-26.3.1.pom
https://dl.google.com/dl/android/maven2/com/android/tools/sdk-common/26.5.4/sdk-common-26.5.4.pom
https://dl.google.com/dl/android/maven2/com/android/tools/sdk-common/27.1.0/sdk-common-27.1.0.pom
https://dl.google.com/dl/android/maven2/com/android/tools/sdk-common/30.0.4/sdk-common-30.0.4.pom
@@ -715,7 +688,6 @@ https://repo.maven.apache.org/maven2/com/google/guava/guava-parent/17.0/guava-pa
https://repo.maven.apache.org/maven2/com/google/guava/guava-parent/22.0/guava-parent-22.0.pom
https://repo.maven.apache.org/maven2/com/google/guava/guava-parent/23.0/guava-parent-23.0.pom
https://repo.maven.apache.org/maven2/com/google/guava/guava-parent/26.0-android/guava-parent-26.0-android.pom
https://repo.maven.apache.org/maven2/com/google/guava/guava-parent/26.0-jre/guava-parent-26.0-jre.pom
https://repo.maven.apache.org/maven2/com/google/guava/guava-parent/27.0.1-jre/guava-parent-27.0.1-jre.pom
https://repo.maven.apache.org/maven2/com/google/guava/guava-parent/28.1-jre/guava-parent-28.1-jre.pom
https://repo.maven.apache.org/maven2/com/google/guava/guava-parent/29.0-jre/guava-parent-29.0-jre.pom
@@ -728,7 +700,6 @@ https://repo.maven.apache.org/maven2/com/google/guava/guava/17.0/guava-17.0.pom
https://repo.maven.apache.org/maven2/com/google/guava/guava/22.0/guava-22.0.pom
https://repo.maven.apache.org/maven2/com/google/guava/guava/23.0/guava-23.0.pom
https://repo.maven.apache.org/maven2/com/google/guava/guava/26.0-android/guava-26.0-android.pom
https://repo.maven.apache.org/maven2/com/google/guava/guava/26.0-jre/guava-26.0-jre.pom
https://repo.maven.apache.org/maven2/com/google/guava/guava/27.0.1-jre/guava-27.0.1-jre.pom
https://repo.maven.apache.org/maven2/com/google/guava/guava/28.1-jre/guava-28.1-jre.pom
https://repo.maven.apache.org/maven2/com/google/guava/guava/29.0-jre/guava-29.0-jre.pom
@@ -913,13 +884,13 @@ https://repo.maven.apache.org/maven2/junit/junit/4.13.2/junit-4.13.2.pom
https://repo.maven.apache.org/maven2/me/zhanghai/android/materialprogressbar/library/1.4.2/library-1.4.2.pom
https://repo.maven.apache.org/maven2/net/bytebuddy/byte-buddy-agent/1.11.13/byte-buddy-agent-1.11.13.pom
https://repo.maven.apache.org/maven2/net/bytebuddy/byte-buddy-agent/1.11.19/byte-buddy-agent-1.11.19.pom
https://repo.maven.apache.org/maven2/net/bytebuddy/byte-buddy-agent/1.14.10/byte-buddy-agent-1.14.10.pom
https://repo.maven.apache.org/maven2/net/bytebuddy/byte-buddy-agent/1.14.11/byte-buddy-agent-1.14.11.pom
https://repo.maven.apache.org/maven2/net/bytebuddy/byte-buddy-parent/1.11.13/byte-buddy-parent-1.11.13.pom
https://repo.maven.apache.org/maven2/net/bytebuddy/byte-buddy-parent/1.11.19/byte-buddy-parent-1.11.19.pom
https://repo.maven.apache.org/maven2/net/bytebuddy/byte-buddy-parent/1.14.10/byte-buddy-parent-1.14.10.pom
https://repo.maven.apache.org/maven2/net/bytebuddy/byte-buddy-parent/1.14.11/byte-buddy-parent-1.14.11.pom
https://repo.maven.apache.org/maven2/net/bytebuddy/byte-buddy/1.11.13/byte-buddy-1.11.13.pom
https://repo.maven.apache.org/maven2/net/bytebuddy/byte-buddy/1.11.19/byte-buddy-1.11.19.pom
https://repo.maven.apache.org/maven2/net/bytebuddy/byte-buddy/1.14.10/byte-buddy-1.14.10.pom
https://repo.maven.apache.org/maven2/net/bytebuddy/byte-buddy/1.14.11/byte-buddy-1.14.11.pom
https://repo.maven.apache.org/maven2/net/java/dev/jna/jna-platform/5.6.0/jna-platform-5.6.0.pom
https://repo.maven.apache.org/maven2/net/java/dev/jna/jna/5.6.0/jna-5.6.0.pom
https://repo.maven.apache.org/maven2/net/java/jvnet-parent/1/jvnet-parent-1.pom
@@ -1167,7 +1138,6 @@ https://repo.maven.apache.org/maven2/org/jetbrains/kotlin/kotlin-project-model/1
https://repo.maven.apache.org/maven2/org/jetbrains/kotlin/kotlin-project-model/1.9.0/kotlin-project-model-1.9.0.pom
https://repo.maven.apache.org/maven2/org/jetbrains/kotlin/kotlin-reflect/1.1.3-2/kotlin-reflect-1.1.3-2.pom
https://repo.maven.apache.org/maven2/org/jetbrains/kotlin/kotlin-reflect/1.2.0/kotlin-reflect-1.2.0.pom
https://repo.maven.apache.org/maven2/org/jetbrains/kotlin/kotlin-reflect/1.3.20/kotlin-reflect-1.3.20.pom
https://repo.maven.apache.org/maven2/org/jetbrains/kotlin/kotlin-reflect/1.3.50/kotlin-reflect-1.3.50.pom
https://repo.maven.apache.org/maven2/org/jetbrains/kotlin/kotlin-reflect/1.3.72/kotlin-reflect-1.3.72.pom
https://repo.maven.apache.org/maven2/org/jetbrains/kotlin/kotlin-reflect/1.4.32/kotlin-reflect-1.4.32.pom
@@ -1194,7 +1164,6 @@ https://repo.maven.apache.org/maven2/org/jetbrains/kotlin/kotlin-script-runtime/
https://repo.maven.apache.org/maven2/org/jetbrains/kotlin/kotlin-script-runtime/1.9.0/kotlin-script-runtime-1.9.0.pom
https://repo.maven.apache.org/maven2/org/jetbrains/kotlin/kotlin-stdlib-common/1.2.71/kotlin-stdlib-common-1.2.71.pom
https://repo.maven.apache.org/maven2/org/jetbrains/kotlin/kotlin-stdlib-common/1.3.0/kotlin-stdlib-common-1.3.0.pom
https://repo.maven.apache.org/maven2/org/jetbrains/kotlin/kotlin-stdlib-common/1.3.20/kotlin-stdlib-common-1.3.20.pom
https://repo.maven.apache.org/maven2/org/jetbrains/kotlin/kotlin-stdlib-common/1.3.31/kotlin-stdlib-common-1.3.31.pom
https://repo.maven.apache.org/maven2/org/jetbrains/kotlin/kotlin-stdlib-common/1.3.50/kotlin-stdlib-common-1.3.50.pom
https://repo.maven.apache.org/maven2/org/jetbrains/kotlin/kotlin-stdlib-common/1.3.60/kotlin-stdlib-common-1.3.60.pom
@@ -1214,7 +1183,6 @@ https://repo.maven.apache.org/maven2/org/jetbrains/kotlin/kotlin-stdlib-common/1
https://repo.maven.apache.org/maven2/org/jetbrains/kotlin/kotlin-stdlib-common/1.7.22/kotlin-stdlib-common-1.7.22.pom
https://repo.maven.apache.org/maven2/org/jetbrains/kotlin/kotlin-stdlib-common/1.9.0/kotlin-stdlib-common-1.9.0.pom
https://repo.maven.apache.org/maven2/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.2.71/kotlin-stdlib-jdk7-1.2.71.pom
https://repo.maven.apache.org/maven2/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.3.20/kotlin-stdlib-jdk7-1.3.20.pom
https://repo.maven.apache.org/maven2/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.3.50/kotlin-stdlib-jdk7-1.3.50.pom
https://repo.maven.apache.org/maven2/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.3.72/kotlin-stdlib-jdk7-1.3.72.pom
https://repo.maven.apache.org/maven2/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.4.10/kotlin-stdlib-jdk7-1.4.10.pom
@@ -1227,7 +1195,6 @@ https://repo.maven.apache.org/maven2/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.7
https://repo.maven.apache.org/maven2/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.7.22/kotlin-stdlib-jdk7-1.7.22.pom
https://repo.maven.apache.org/maven2/org/jetbrains/kotlin/kotlin-stdlib-jdk7/1.9.0/kotlin-stdlib-jdk7-1.9.0.pom
https://repo.maven.apache.org/maven2/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.2.71/kotlin-stdlib-jdk8-1.2.71.pom
https://repo.maven.apache.org/maven2/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.3.20/kotlin-stdlib-jdk8-1.3.20.pom
https://repo.maven.apache.org/maven2/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.3.50/kotlin-stdlib-jdk8-1.3.50.pom
https://repo.maven.apache.org/maven2/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.3.72/kotlin-stdlib-jdk8-1.3.72.pom
https://repo.maven.apache.org/maven2/org/jetbrains/kotlin/kotlin-stdlib-jdk8/1.4.10/kotlin-stdlib-jdk8-1.4.10.pom
@@ -1243,7 +1210,6 @@ https://repo.maven.apache.org/maven2/org/jetbrains/kotlin/kotlin-stdlib/1.2.0/ko
https://repo.maven.apache.org/maven2/org/jetbrains/kotlin/kotlin-stdlib/1.2.20/kotlin-stdlib-1.2.20.pom
https://repo.maven.apache.org/maven2/org/jetbrains/kotlin/kotlin-stdlib/1.2.71/kotlin-stdlib-1.2.71.pom
https://repo.maven.apache.org/maven2/org/jetbrains/kotlin/kotlin-stdlib/1.3.0/kotlin-stdlib-1.3.0.pom
https://repo.maven.apache.org/maven2/org/jetbrains/kotlin/kotlin-stdlib/1.3.20/kotlin-stdlib-1.3.20.pom
https://repo.maven.apache.org/maven2/org/jetbrains/kotlin/kotlin-stdlib/1.3.31/kotlin-stdlib-1.3.31.pom
https://repo.maven.apache.org/maven2/org/jetbrains/kotlin/kotlin-stdlib/1.3.50/kotlin-stdlib-1.3.50.pom
https://repo.maven.apache.org/maven2/org/jetbrains/kotlin/kotlin-stdlib/1.3.60/kotlin-stdlib-1.3.60.pom
@@ -1288,7 +1254,7 @@ https://repo.maven.apache.org/maven2/org/jvnet/staxex/stax-ex/1.8/stax-ex-1.8.po
https://repo.maven.apache.org/maven2/org/jvnet/staxex/stax-ex/2.1.0/stax-ex-2.1.0.pom
https://repo.maven.apache.org/maven2/org/mockito/mockito-core/3.12.4/mockito-core-3.12.4.pom
https://repo.maven.apache.org/maven2/org/mockito/mockito-core/4.0.0/mockito-core-4.0.0.pom
https://repo.maven.apache.org/maven2/org/mockito/mockito-core/5.8.0/mockito-core-5.8.0.pom
https://repo.maven.apache.org/maven2/org/mockito/mockito-core/5.9.0/mockito-core-5.9.0.pom
https://repo.maven.apache.org/maven2/org/mockito/mockito-inline/4.0.0/mockito-inline-4.0.0.pom
https://repo.maven.apache.org/maven2/org/objenesis/objenesis-parent/3.2/objenesis-parent-3.2.pom
https://repo.maven.apache.org/maven2/org/objenesis/objenesis-parent/3.3/objenesis-parent-3.3.pom
+1 -1
View File
@@ -19,4 +19,4 @@ trap show_gradle_log ERR
./gradlew projects --no-daemon --console plain 2>&1 \
| tee "${GRADLE_LOG_FILE}" \
| grep "Project ':" \
| sed -E "s;^.--- Project '\:([@_a-zA-Z0-9\-]+)';\1;"
| sed -E "s;^.--- Project '\:([@_a-zA-Z0-9\-]+)'.*;\1;"
+1 -1
View File
@@ -1,6 +1,7 @@
app
react-native-async-storage_async-storage
react-native-background-timer
react-native-biometrics
react-native-blob-util
react-native-blur
react-native-camera-kit
@@ -36,6 +37,5 @@ react-native-static-safe-area-insets
react-native-status
react-native-status-keycard
react-native-svg
react-native-touch-id
react-native-transparent-video
react-native-webview
+14
View File
@@ -0,0 +1,14 @@
#!/usr/bin/env bash
set -Eeuo pipefail
# script to prompt user for terminating a process
pid="${1}"
read -p "Do you want to terminate this process? (y/n): " choice
if [[ "${choice}" == "y" ]]; then
sudo kill "${pid}"
echo "Process ${pid} terminated."
else
echo "Process not terminated. Please close it manually and retry."
exit 1
fi
+11 -21
View File
@@ -22,12 +22,10 @@ nix_purge_linux_multi_user_users() {
}
nix_purge_darwin_multi_user_service() {
cd /Library/LaunchDaemons
NIX_SERVICES=(org.nixos.darwin-store.plist org.nixos.nix-daemon.plist)
for NIX_SERVICE in "${NIX_SERVICES[@]}"; do
sudo launchctl unload "${NIX_SERVICE}" || true
sudo launchctl remove "${NIX_SERVICE}" || true
done
sudo launchctl unload /Library/LaunchDaemons/org.nixos.nix-daemon.plist
sudo rm /Library/LaunchDaemons/org.nixos.nix-daemon.plist
sudo launchctl unload /Library/LaunchDaemons/org.nixos.darwin-store.plist
sudo rm /Library/LaunchDaemons/org.nixos.darwin-store.plist
}
nix_purge_darwin_multi_user_users() {
@@ -48,18 +46,10 @@ nix_purge_darwin_multi_user_volumes() {
# Identify the process using the volume
local pid=$(lsof +D /nix | awk 'NR==2{print $2}')
if [ -n "$pid" ]; then
if [[ -n "$pid" ]]; then
echo "The volume /nix is in use by process ID $pid."
"${GIT_ROOT}/nix/scripts/kill_proc_prompt.sh" "${pid}" || return 1
# Ask the user whether to terminate the process
read -p "Do you want to terminate this process? (y/n): " choice
if [[ $choice == "y" ]]; then
sudo kill $pid
echo "Process $pid terminated."
else
echo "Process not terminated. Please close it manually and retry."
return 1
fi
else
echo "No process found using /nix. Manual intervention required."
return 1
@@ -71,9 +61,9 @@ nix_purge_darwin_multi_user_volumes() {
nix_purge_multi_user() {
if [[ $(uname -s) == "Darwin" ]]; then
nix_purge_darwin_multi_user_service || true
nix_purge_darwin_multi_user_users || true
nix_purge_darwin_multi_user_volumes || true
nix_purge_darwin_multi_user_service
nix_purge_darwin_multi_user_users
nix_purge_darwin_multi_user_volumes
else
nix_purge_linux_multi_user_service
nix_purge_linux_multi_user_users
@@ -117,7 +107,7 @@ if (return 0 2>/dev/null); then
return
fi
# Confirm user decission, unless --force is used.
# Confirm user decision, unless --force is used.
if [[ "${1}" != "--force" ]]; then
echo -e "${YLW}Are you sure you want to purge Nix?${RST}" >&2
read -p "[y/n]: " -n 1 -r
@@ -129,7 +119,7 @@ if [[ "${1}" != "--force" ]]; then
fi
NIX_INSTALL_TYPE=$(nix_install_type)
# Purging /nix on NixOS would be disasterous.
# Purging /nix on NixOS would be disastrous.
if [[ "${NIX_INSTALL_TYPE}" == "nixos" ]]; then
echo -e "${RED}You should not purge Nix files on NixOS!${RST}" >&2
exit
+1 -1
View File
@@ -35,6 +35,7 @@
"react-dom": "18.0.0",
"react-native": "0.72.5",
"react-native-background-timer": "^2.1.1",
"react-native-biometrics": "^3.0.1",
"react-native-blob-util": "^0.13.18",
"react-native-camera-kit": "14.0.0-beta13",
"react-native-config": "^1.5.0",
@@ -65,7 +66,6 @@
"react-native-static-safe-area-insets": "^2.2.0",
"react-native-status-keycard": "git+https://github.com/status-im/react-native-status-keycard.git#refs/tags/v2.5.39",
"react-native-svg": "13.10.0",
"react-native-touch-id": "^4.4.1",
"react-native-transparent-video": "git+https://github.com/status-im/react-native-transparent-video.git#refs/tags/0.1.1",
"react-native-webview": "13.6.3",
"react-syntax-highlighter": "^15.5.0",
@@ -1,5 +1,8 @@
(ns legacy.status-im.data-store.communities
(:require [clojure.set :as set]))
(:require
[clojure.set :as set]
[clojure.walk :as walk]
[status-im.constants :as constants]))
(defn rpc->channel-permissions
[rpc-channels-permissions]
@@ -7,3 +10,68 @@
(fn [{:keys [viewAndPostPermissions viewOnlyPermissions]}]
{:view-only (set/rename-keys viewOnlyPermissions {:satisfied :satisfied?})
:view-and-post (set/rename-keys viewAndPostPermissions {:satisfied :satisfied?})})))
(defn <-revealed-accounts-rpc
[accounts]
(mapv
#(set/rename-keys % {:isAirdropAddress :airdrop-address?})
(js->clj accounts :keywordize-keys true)))
(defn <-request-to-join-community-rpc
[r]
(set/rename-keys r
{:communityId :community-id
:publicKey :public-key
:chatId :chat-id}))
(defn <-requests-to-join-community-rpc
[requests key-fn]
(reduce #(assoc %1 (key-fn %2) (<-request-to-join-community-rpc %2)) {} requests))
(defn <-chats-rpc
[chats]
(reduce-kv (fn [acc k v]
(assoc acc
(name k)
(-> v
(assoc :can-post? (:canPost v))
(dissoc :canPost)
(update :members walk/stringify-keys))))
{}
chats))
(defn <-categories-rpc
[categ]
(reduce-kv #(assoc %1 (name %2) %3) {} categ))
(defn <-rpc
[c]
(-> c
(set/rename-keys {:canRequestAccess :can-request-access?
:canManageUsers :can-manage-users?
:canDeleteMessageForEveryone :can-delete-message-for-everyone?
:canJoin :can-join?
:requestedToJoinAt :requested-to-join-at
:isMember :is-member?
:adminSettings :admin-settings
:tokenPermissions :token-permissions
:communityTokensMetadata :tokens-metadata
:introMessage :intro-message
:muteTill :muted-till
:lastOpenedAt :last-opened-at
:joinedAt :joined-at})
(update :admin-settings
set/rename-keys
{:pinMessageAllMembersEnabled :pin-message-all-members-enabled?})
(update :members walk/stringify-keys)
(update :chats <-chats-rpc)
(update :token-permissions seq)
(update :categories <-categories-rpc)
(assoc :membership-permissions?
(some #(= (:type %) constants/community-token-permission-become-member)
(vals (:tokenPermissions c))))
(assoc :token-images
(reduce (fn [acc {sym :symbol image :image}]
(assoc acc sym image))
{}
(:communityTokensMetadata c)))))
+2 -2
View File
@@ -164,7 +164,7 @@
{:MediaStates {}
:PlaybackCategories {}})
(def net-info #js {})
(def touchid #js {})
(def react-native-biometrics #js {:default {}})
(def react-native-image-viewing #js {:default {}})
(def react-native-static-safe-area-insets #js {:default {}})
@@ -391,7 +391,7 @@
"react-native-fetch-polyfill" fetch
"react-native-status-keycard" status-keycard
"react-native-keychain" keychain
"react-native-touch-id" touchid
"react-native-biometrics" react-native-biometrics
"@react-native-community/netinfo" net-info
"react-native-dialogs" dialogs
"react-native" react-native
@@ -45,7 +45,7 @@
[:fiat-value :string]
[:token-value :string]
[:customization-color {:optional true} [:maybe :schema.common/customization-color]]
[:state [:enum :pressed :active :default]]
[:state {:optional true} [:enum :pressed :active :default]]
[:on-press {:optional true} [:maybe fn?]]
[:theme :schema.common/theme]]]]
:any])
@@ -2,7 +2,9 @@
(:require
[quo.components.markdown.text :as text]
[quo.components.profile.collectible.style :as style]
[react-native.core :as rn]))
[react-native.core :as rn]
[react-native.fast-image :as fast-image]
[react-native.svg :as svg]))
(defn remaining-tiles
[amount]
@@ -14,12 +16,21 @@
(str "+" amount)]])
(defn tile
[{:keys [style resource size]}]
(let [source (if (string? resource) {:uri resource} resource)]
[{:keys [style size resource]}]
(let [svg? (and (map? resource) (:svg? resource))
image-style (style/tile-style-by-size size)]
[rn/view {:style style}
[rn/image
{:style (style/tile-style-by-size size)
:source source}]]))
(if svg?
[rn/view
{:style {:border-radius (:border-radius image-style)
:overflow :hidden}}
[svg/svg-uri (assoc image-style :uri (:uri resource))]]
[fast-image/fast-image
{:style image-style
:source (if (string? resource)
{:uri resource
:priority :low}
resource)}])]))
(defn two-tiles
[{:keys [images size]}]
+1 -1
View File
@@ -27,7 +27,7 @@
9.213 11.877 8.436 C 11.787 7.866 11.649 7.313 11.468 6.781 Z"
:clip-path "url(#clip0_5514_84289)"}]
[svg/defs
[svg/clippath {:id "clip0_5514_84289"}
[svg/clip-path {:id "clip0_5514_84289"}
[svg/rect {:width width :height height :fill :none}]]]])
(defn- content
+1 -1
View File
@@ -15,7 +15,7 @@
[:token {:optional true} [:or keyword? string?]]
[:style {:optional true} map?]
;; Ignores `token` and uses this as parameter to `rn/image`'s source.
[:image-source {:optional true} [:or :schema.common/image-source :string]]]]
[:image-source {:optional true} [:maybe [:or :schema.common/image-source :string]]]]]
:any])
(defn- size->number
@@ -4,7 +4,7 @@
(defn container
[network state theme]
{:flex 1
{:width 136
:height 44
:border-width 1
:border-radius 12
@@ -10,8 +10,12 @@
[react-native.core :as rn]))
(defn network-bridge-add
[{:keys [network state theme]}]
[rn/view {:style (merge (style/container network state theme) (style/add-container theme))}
[{:keys [network state theme container-style on-press]}]
[rn/pressable
{:style (merge (style/container network state theme)
(style/add-container theme)
container-style)
:on-press on-press}
[icon/icon :i/add-circle {:size 12 :no-color true}]])
(defn- network->text
@@ -21,13 +25,14 @@
:else (string/capitalize (name network))))
(defn view-internal
[{:keys [theme network status amount container-style] :as args}]
[{:keys [theme network status amount container-style on-press] :as args}]
(if (= status :add)
[network-bridge-add args]
[rn/view
[rn/pressable
{:style (merge (style/container network status theme) container-style)
:accessible true
:accessibility-label :container}
:accessibility-label :container
:on-press on-press}
(if (= status :loading)
[rn/view
{:style (style/loading-skeleton theme)
@@ -49,8 +49,12 @@
:height "100%")))
(defn text-input
[theme]
(assoc text-input-dimensions :color (colors/theme-colors colors/neutral-100 colors/white theme)))
[theme error?]
(assoc text-input-dimensions
:color
(if error?
(colors/resolve-color :danger theme)
(colors/theme-colors colors/neutral-100 colors/white theme))))
(defn placeholder-text
[theme]
@@ -32,9 +32,12 @@
(crypto-format num-value conversion crypto-decimals token))))
(defn- data-info
[{:keys [theme token crypto-decimals conversion networks title crypto? currency amount]}]
[{:keys [theme token crypto-decimals conversion networks title crypto? currency amount error?]}]
[rn/view {:style style/data-container}
[network-tag/view {:networks networks :title title}]
[network-tag/view
{:networks networks
:title title
:status (when error? :error)}]
[text/text
{:size :paragraph-2
:weight :medium
@@ -80,7 +83,7 @@
(reset! value-atom v))
(when on-change-text
(on-change-text v)))]
(fn [{:keys [theme token customization-color show-keyboard? crypto? currency value]
(fn [{:keys [theme token customization-color show-keyboard? crypto? currency value error?]
:or {show-keyboard? true}}]
[rn/pressable
{:on-press focus-input
@@ -90,7 +93,7 @@
:size :size-32}]
[rn/view {:style style/text-input-container}
[rn/text-input
(cond-> {:style (style/text-input theme)
(cond-> {:style (style/text-input theme error?)
:placeholder-text-color (style/placeholder-text theme)
:auto-focus true
:ref set-ref
+76
View File
@@ -0,0 +1,76 @@
(ns react-native.biometrics
(:require
["react-native-biometrics" :default Biometrics]
[clojure.string :as string]
[oops.core :as oops]
[react-native.platform :as platform]
[schema.core :as schema]))
(def ^:private ^:const android-not-available-error-message "Biometric hardware unavailable")
(def ^:private ^:const android-not-enrolled-error-message "No fingerprints enrolled.")
(def ^:private ^:const ios-not-enrolled-error-message "No identities are enrolled")
(defn get-supported-type
"Returns a JS promise that resolves with the biometrics types supported by the
device, regardless of whether it's enabled or disabled.
Resolved values: `:Biometrics` `:FaceID` `:TouchID`"
[]
(-> (Biometrics.)
(.isSensorAvailable)
(.then (fn [result]
(-> result
(oops/oget :biometryType)
keyword)))
(.catch (constantly nil))))
(defn get-available
"Returns a JS promise that resolves to a boolean, which signifies whether
biometrics is enabled/disabled on the device."
[]
(-> (Biometrics.)
(.isSensorAvailable)
(.then (fn [result]
(oops/oget result :available)))))
;; NOTE: the react-native-biometrics package error codes/messages differ across platforms.
;; On android we get error messages while on iOS it's a stringified Obj-C error object.
(defn- convert-auth-error-message
[message]
(let [cause (if platform/android?
(condp = message
android-not-enrolled-error-message ::not-enrolled
android-not-available-error-message ::not-available
::unknown)
(condp #(string/includes? %2 %1) message
ios-not-enrolled-error-message ::not-enrolled
::unknown))]
(ex-info "Failed to authenticate with biometrics"
{:orig-error-message message}
cause)))
(defn authenticate
"Returns a JS promise that resolves with a boolean auth success state: `true` for
success and `false` when canceled by user."
[{:keys [prompt-message fallback-prompt-message cancel-button-text]}]
(-> (Biometrics.)
(.simplePrompt #js
{"promptMessage" prompt-message
"fallbackPromptMessage" fallback-prompt-message
"cancelButtonText" cancel-button-text})
(.then (fn [result]
(oops/oget result :success)))
(.catch (fn [error]
(-> (.-message error)
convert-auth-error-message
throw)))))
(schema/=> authenticate
[:=>
[:cat
[:map {:closed true}
[:prompt-message string?]
[:fallback-prompt-message {:optional true} string?]
[:cancel-button-text {:optional true} string?]]]
:any])
+2 -1
View File
@@ -20,7 +20,8 @@
(merge
props
{:source (if (string? source)
{:uri source}
{:uri source
:priority :high}
source)
:on-error (fn [e]
(when-let [on-error (:on-error props)]
+5 -3
View File
@@ -72,9 +72,11 @@
(defn get-credentials
"Gets the credentials for a specified server from the Keychain"
[server callback]
(-> (.getInternetCredentials ^js react-native-keychain (string/lower-case server))
(.then callback)))
([server]
(get-credentials server identity))
([server callback]
(-> (.getInternetCredentials ^js react-native-keychain (string/lower-case server))
(.then callback))))
(defn reset-credentials
[server]
+3 -2
View File
@@ -6,10 +6,11 @@
(def svg (reagent/adapt-react-class Svg/default))
(def path (reagent/adapt-react-class Svg/Path))
(def rect (reagent/adapt-react-class Svg/Rect))
(def clippath (reagent/adapt-react-class Svg/ClipPath))
(def clip-path (reagent/adapt-react-class Svg/ClipPath))
(def defs (reagent/adapt-react-class Svg/Defs))
(def circle (reagent/adapt-react-class Svg/Circle))
(def svgxml (reagent/adapt-react-class Svg/SvgXml))
(def svg-xml (reagent/adapt-react-class Svg/SvgXml))
(def svg-uri (reagent/adapt-react-class Svg/SvgUri))
(def g (reagent/adapt-react-class Svg/G))
(def linear-gradient (reagent/adapt-react-class Svg/LinearGradient))
(def stop (reagent/adapt-react-class Svg/Stop))
-20
View File
@@ -1,20 +0,0 @@
(ns react-native.touch-id
(:require
["react-native-touch-id" :default touchid]))
;; currently, for android, react-native-touch-id
;; is not returning supported biometric type
;; defaulting to :fingerprint
(def android-default-support :fingerprint)
(defn get-supported-type
[callback]
(-> (.isSupported ^js touchid)
(.then #(callback (or (keyword %) android-default-support)))
(.catch #(callback nil))))
(defn authenticate
[{:keys [on-success on-fail reason options]}]
(-> (.authenticate ^js touchid reason (clj->js options))
(.then #(when on-success (on-success %)))
(.catch #(when on-fail (on-fail (aget % "code"))))))
+51 -88
View File
@@ -2,9 +2,8 @@
(:require
[native-module.core :as native-module]
[re-frame.core :as re-frame]
[react-native.async-storage :as async-storage]
[react-native.biometrics :as biometrics]
[react-native.platform :as platform]
[react-native.touch-id :as touch-id]
[status-im.common.keychain.events :as keychain]
[status-im.constants :as constants]
[taoensso.timbre :as log]
@@ -12,27 +11,19 @@
[utils.re-frame :as rf]))
(def android-device-blacklisted?
(= (:brand (native-module/get-device-model-info)) "bannedbrand"))
(defn get-supported-type
[callback]
(cond platform/ios? (touch-id/get-supported-type callback)
platform/android? (if android-device-blacklisted?
(callback nil)
(touch-id/get-supported-type callback))
:else (callback nil)))
(and platform/android? (= (:brand (native-module/get-device-model-info)) "bannedbrand")))
(defn get-label-by-type
[biometric-type]
(condp = biometric-type
:fingerprint (i18n/label :t/biometric-fingerprint)
:FaceID (i18n/label :t/biometric-faceid)
constants/biometrics-type-android (i18n/label :t/biometric-fingerprint)
constants/biometrics-type-face-id (i18n/label :t/biometric-faceid)
(i18n/label :t/biometric-touchid)))
(defn get-icon-by-type
[biometric-type]
(condp = biometric-type
:FaceID :i/face-id
constants/biometrics-type-face-id :i/face-id
:i/touch-id))
(re-frame/reg-fx
@@ -41,8 +32,10 @@
;;NOTE: if we can't save user password, we can't use biometric
(keychain/can-save-user-password?
(fn [can-save?]
(when can-save?
(get-supported-type #(rf/dispatch [:biometric/get-supported-biometric-type-success %])))))))
(when (and can-save? (not android-device-blacklisted?))
(-> (biometrics/get-supported-type)
(.then (fn [type]
(rf/dispatch [:biometric/get-supported-biometric-type-success type])))))))))
(rf/defn get-supported-biometric-auth-success
{:events [:biometric/get-supported-biometric-type-success]}
@@ -51,82 +44,32 @@
(rf/defn show-message
{:events [:biometric/show-message]}
[_ code]
(let [handle-error? (and code
(not (contains? #{constants/biometric-error-user-canceled
constants/biometric-error-user-fallback}
code)))
content (if (#{constants/biometric-error-not-available
constants/biometric-error-not-enrolled}
code)
(i18n/label :t/grant-face-id-permissions)
(i18n/label :t/biometric-auth-error {:code code}))]
(when handle-error?
{:effects.utils/show-popup
{:title (i18n/label :t/biometric-auth-login-error-title)
:content content}})))
[_ error]
(let [code (ex-cause error)
content (if (#{::biometrics/not-enrolled
::biometrics/not-available}
code)
(i18n/label :t/grant-face-id-permissions)
(i18n/label :t/biometric-auth-error {:code code}))]
{:effects.utils/show-popup
{:title (i18n/label :t/biometric-auth-login-error-title)
:content content}}))
(defn- supress-biometry-error-key
[key-uid]
(keyword (str "biometric/supress-not-enrolled-error-" key-uid)))
;; NOTE: if the account had biometrics registered, but it's not enrolled at the moment,
;; we should show the error message only once and supress further "NOT_ENROLLED" errors
;; until biometry is enrolled again. Note that we can only know that when :biometric/authenticate
;; is dispatched and fails with "NOT_ENROLLED", since :biometric/get-supported-biometric-type
;; only tells us what kind of biometric is available on the device, but it doesn't know of its
;; enrollment status.
(re-frame/reg-fx
:biometric/supress-not-enrolled-error
(fn [[key-uid dispatch-event]]
(let [storage-key (supress-biometry-error-key key-uid)]
(-> (async-storage/get-item storage-key identity)
(.then (fn [item]
(when (not item)
(rf/dispatch dispatch-event)
(async-storage/set-item! storage-key true))))
(.catch (fn [err]
(log/error "Couldn't supress biometry NOT_ENROLLED error"
{:key-uid key-uid
:event :biometric/supress-not-enrolled-error
:error err})))))))
;; NOTE: when biometrics is re-enrolled, we erase the flag in async-storage to assure
;; the "NOT_ENROLLED" error message will be shown again if biometrics is un-enrolled
;; in the future.
(re-frame/reg-fx
:biometric/reset-not-enrolled-error
(fn [key-uid]
(let [storage-key (supress-biometry-error-key key-uid)]
(-> (async-storage/get-item storage-key identity)
(.then (fn [supress?]
(when supress?
(async-storage/set-item! storage-key nil))))
(.catch (fn [err]
(log/error "Couldn't reset supressing biometry NOT_ENROLLED error"
{:key-uid key-uid
:event :biometric/reset-not-enrolled-error
:error err})))))))
(re-frame/reg-fx
:biometric/authenticate
(fn [options]
(touch-id/authenticate
(merge
{:reason (i18n/label :t/biometric-auth-reason-login)
:options (merge
{:unifiedErrors true}
(when platform/ios?
{:passcodeFallback false
:fallbackLabel (i18n/label :t/biometric-auth-login-ios-fallback-label)})
(when platform/android?
{:title (i18n/label :t/biometric-auth-android-title)
:imageColor :blue
:imageErrorColor :red
:sensorDescription (i18n/label :t/biometric-auth-android-sensor-desc)
:sensorErrorDescription (i18n/label :t/biometric-auth-android-sensor-error-desc)
:cancelText (i18n/label :t/cancel)}))}
options))))
(fn [{:keys [on-success on-fail prompt-message]}]
(-> (biometrics/authenticate
{:prompt-message (or prompt-message (i18n/label :t/biometric-auth-reason-login))
:fallback-prompt-message (i18n/label
:t/biometric-auth-login-ios-fallback-label)
:cancel-button-text (i18n/label :t/cancel)})
(.then (fn [not-canceled?]
(when (and on-success not-canceled?)
(on-success))))
(.catch (fn [err]
(when on-fail
(on-fail err)))))))
(rf/defn authenticate
{:events [:biometric/authenticate]}
@@ -155,3 +98,23 @@
(let [key-uid (get-in db [:profile/profile :key-uid])]
{:db (assoc db :auth-method constants/auth-method-none)
:keychain/clear-user-password key-uid})))
(rf/reg-fx
:biometric/check-if-available
(fn [[key-uid callback]]
(keychain/can-save-user-password?
(fn [can-save?]
(when can-save?
(-> (biometrics/get-available)
(.then (fn [available?]
(when-not available?
(throw (js/Error. "biometric-not-available")))))
(.then #(keychain/get-auth-method! key-uid))
(.then (fn [auth-method]
(when auth-method (callback auth-method))))
(.catch (fn [err]
(when-not (= (.-message err) "biometric-not-available")
(log/error "Failed to check if biometrics is available"
{:error err
:key-uid key-uid
:event :profile.login/check-biometric}))))))))))
@@ -52,7 +52,8 @@
:remove-listeners remove-listeners}))
(defn view
[{:keys [header footer customization-color gradient-cover?]} &
[{:keys [header footer customization-color footer-container-padding gradient-cover?]
:or {footer-container-padding (safe-area/get-top)}} &
children]
(reagent/with-let [window-height (:height (rn/get-window))
footer-container-height (reagent/atom 0)
@@ -94,7 +95,7 @@
children)]
[rn/keyboard-avoiding-view
{:style style/keyboard-avoiding-view
:keyboard-vertical-offset (if platform/ios? (safe-area/get-top) 0)
:keyboard-vertical-offset (if platform/ios? footer-container-padding 0)
:pointer-events :box-none}
[floating-container/view
{:on-layout set-footer-container-height
+8 -12
View File
@@ -70,17 +70,13 @@
{:db (assoc db :auth-method method)
:keychain/save-auth-method [key-uid method]})
(re-frame/reg-fx
:keychain/get-auth-method
(fn [[key-uid callback]]
(can-save-user-password?
(fn [can-save?]
(if can-save?
(keychain/get-credentials
(str key-uid "-auth")
(fn [value]
(callback (if value (oops/oget value "password") auth-method-none))))
(callback nil))))))
(defn get-auth-method!
[key-uid]
(-> (str key-uid "-auth")
(keychain/get-credentials)
(.then (fn [value]
(if value (oops/oget value "password") auth-method-none)))
(.catch (constantly auth-method-none))))
(defn save-user-password!
[key-uid password]
@@ -101,7 +97,7 @@
(get-user-password! key-uid callback)))
(rf/defn get-user-password
[_ key-uid callback]
[_ [key-uid callback]]
{:keychain/get-user-password [key-uid callback]})
(defn- password-migration-key-name
@@ -1,4 +1,4 @@
(ns status-im.contexts.chat.messenger.lightbox.animations
(ns status-im.common.lightbox.animations
(:require
[react-native.reanimated :as reanimated]))
@@ -1,25 +1,25 @@
(ns status-im.contexts.chat.messenger.lightbox.bottom-view
(ns status-im.common.lightbox.bottom-view
(:require
[quo.foundations.colors :as colors]
[react-native.core :as rn]
[react-native.platform :as platform]
[react-native.reanimated :as reanimated]
[status-im.contexts.chat.messenger.lightbox.animations :as anim]
[status-im.contexts.chat.messenger.lightbox.constants :as c]
[status-im.contexts.chat.messenger.lightbox.style :as style]
[status-im.contexts.chat.messenger.lightbox.text-sheet.view :as text-sheet]
[status-im.common.lightbox.animations :as anim]
[status-im.common.lightbox.constants :as constants]
[status-im.common.lightbox.style :as style]
[status-im.common.lightbox.text-sheet.view :as text-sheet]
[utils.re-frame :as rf]))
(defn get-small-item-layout
[_ index]
#js
{:length c/small-image-size
:offset (* (+ c/small-image-size 8) index)
{:length constants/small-image-size
:offset (* (+ constants/small-image-size 8) index)
:index index})
(defn- f-small-image
[item index _ {:keys [scroll-index props]}]
(let [size (if (= @scroll-index index) c/focused-image-size c/small-image-size)
(let [size (if (= @scroll-index index) constants/focused-image-size constants/small-image-size)
size-value (anim/use-val size)
{:keys [scroll-index-lock? small-list-ref flat-list-ref]}
props]
@@ -27,7 +27,7 @@
[rn/touchable-opacity
{:active-opacity 1
:on-press (fn []
(rf/dispatch [:chat.ui/zoom-out-signal @scroll-index])
(rf/dispatch [:lightbox/zoom-out-signal @scroll-index])
(reset! scroll-index-lock? true)
(js/setTimeout #(reset! scroll-index-lock? false) 500)
(js/setTimeout
@@ -38,9 +38,9 @@
(.scrollToIndex ^js @flat-list-ref
#js {:animated true :index index}))
(if platform/ios? 50 150))
(rf/dispatch [:chat.ui/update-shared-element-id (:message-id item)]))}
(rf/dispatch [:lightbox/update-animation-shared-element-id (:id item)]))}
[reanimated/fast-image
{:source {:uri (:image (:content item))}
{:source {:uri (:image item)}
:style (reanimated/apply-animations-to-style {:width size-value
:height size-value}
{:border-radius 10})}]]))
@@ -50,20 +50,26 @@
[:f> f-small-image item index _ render-data])
(defn bottom-view
[messages index scroll-index insets animations derived item-width props state transparent?]
(let [padding-horizontal (- (/ item-width 2) (/ c/focused-image-size 2))]
[{:keys [images index scroll-index insets animations derived item-width props state transparent?
bottom-text-component]}]
(let [padding-horizontal (- (/ item-width 2) (/ constants/focused-image-size 2))]
[reanimated/linear-gradient
{:colors [colors/neutral-100-opa-100 colors/neutral-100-opa-80 colors/neutral-100-opa-0]
:location [0.2 0.9]
:start {:x 0 :y 1}
:end {:x 0 :y 0}
:style (style/gradient-container insets animations derived transparent?)}
[text-sheet/view messages animations state props]
(when bottom-text-component
[text-sheet/view
{:overlay-opacity (:overlay-opacity animations)
:overlay-z-index (:overlay-z-index state)
:text-sheet-lock? (:text-sheet-lock? props)
:text-component bottom-text-component}])
[rn/flat-list
{:ref #(reset! (:small-list-ref props) %)
:key-fn :message-id
:style {:height c/small-list-height}
:data messages
:key-fn :id
:style {:height constants/small-list-height}
:data images
:render-fn small-image
:render-data {:scroll-index scroll-index
:props props}
@@ -1,4 +1,4 @@
(ns status-im.contexts.chat.messenger.lightbox.constants)
(ns status-im.common.lightbox.constants)
(def ^:const small-image-size 40)
(def ^:const focused-extra-size 16)
@@ -1,4 +1,4 @@
(ns status-im.contexts.chat.messenger.lightbox.effects
(ns status-im.common.lightbox.effects
(:require [react-native.blob :as blob]
[react-native.cameraroll :as cameraroll]
[react-native.fs :as fs]
@@ -10,7 +10,7 @@
{:trusty platform/ios?
:path (str (fs/cache-dir) "/StatusIm_Image.jpeg")})
(rf/reg-fx :effects.chat/share-image
(rf/reg-fx :effects.lightbox/share-image
(fn [uri]
(blob/fetch uri
config
@@ -20,7 +20,7 @@
#(fs/unlink downloaded-url)
#(fs/unlink downloaded-url))))))
(rf/reg-fx :effects.chat/save-image-to-gallery
(rf/reg-fx :effects.lightbox/save-image-to-gallery
(fn [[uri on-success]]
(blob/fetch uri
config
+37
View File
@@ -0,0 +1,37 @@
(ns status-im.common.lightbox.events
(:require [reagent.core :as reagent]
status-im.common.lightbox.effects
[utils.re-frame :as rf]))
(rf/reg-event-fx :lightbox/navigate-to-lightbox
(fn [{:keys [db]} [animation-shared-element-id screen-params]]
(reagent/next-tick #(rf/dispatch [:navigate-to :lightbox screen-params]))
{:db (assoc db :animation-shared-element-id animation-shared-element-id)}))
(rf/reg-event-fx :lightbox/update-animation-shared-element-id
(fn [{:keys [db]} [animation-shared-element-id]]
{:db (assoc db :animation-shared-element-id animation-shared-element-id)}))
(rf/reg-event-fx :lightbox/exit-lightbox-signal
(fn [{:keys [db]} [value]]
{:db (assoc db :lightbox/exit-signal value)}))
(rf/reg-event-fx :lightbox/zoom-out-signal
(fn [{:keys [db]} [value]]
{:db (assoc db :lightbox/zoom-out-signal value)}))
(rf/reg-event-fx :lightbox/orientation-change
(fn [{:keys [db]} [value]]
{:db (assoc db :lightbox/orientation value)}))
(rf/reg-event-fx :lightbox/lightbox-scale
(fn [{:keys [db]} [value]]
{:db (assoc db :lightbox/scale value)}))
(rf/reg-event-fx :lightbox/share-image
(fn [_ [uri]]
{:effects.lightbox/share-image uri}))
(rf/reg-event-fx :lightbox/save-image-to-gallery
(fn [_ [uri on-success]]
{:effects.lightbox/save-image-to-gallery [uri on-success]}))
@@ -1,9 +1,9 @@
(ns status-im.contexts.chat.messenger.lightbox.style
(ns status-im.common.lightbox.style
(:require
[quo.foundations.colors :as colors]
[react-native.platform :as platform]
[react-native.reanimated :as reanimated]
[status-im.contexts.chat.messenger.lightbox.constants :as c]))
[status-im.common.lightbox.constants :as constants]))
;;;; VIEW
(defn image
@@ -33,7 +33,7 @@
{:position :absolute
:padding-horizontal 20
:top (if (or platform/ios? (not landscape?)) top-inset 0)
:height c/top-view-height
:height constants/top-view-height
:z-index 4
:flex-direction :row
:justify-content :space-between
@@ -44,7 +44,7 @@
(defn top-gradient
[insets]
{:position :absolute
:height (+ c/top-view-height (:top insets) 0)
:height (+ constants/top-view-height (:top insets) 0)
:top (- (:top insets))
:left 0
:right 0})
@@ -71,12 +71,12 @@
:display (if @transparent? :none :flex)
:bottom 0
:padding-bottom (:bottom insets)
:padding-top c/text-min-height
:padding-top constants/text-min-height
:z-index 3}))
(defn content-container
[padding-horizontal]
{:padding-vertical c/small-list-padding-vertical
{:padding-vertical constants/small-list-padding-vertical
:padding-horizontal padding-horizontal
:align-items :center
:justify-content :center})
@@ -1,8 +1,8 @@
(ns status-im.contexts.chat.messenger.lightbox.text-sheet.style
(ns status-im.common.lightbox.text-sheet.style
(:require
[quo.foundations.colors :as colors]
[react-native.reanimated :as reanimated]
[status-im.contexts.chat.messenger.lightbox.constants :as constants]))
[status-im.common.lightbox.constants :as constants]))
(defn sheet-container
[{:keys [height top]}]
@@ -14,10 +14,10 @@
:right 0}))
(defn text-style
[expanding-message?]
[expandable-text?]
{:color colors/white
:margin-horizontal 20
:align-items (when-not expanding-message? :center)
:align-items (when-not expandable-text? :center)
:flex-grow 1})
(def bar-container
@@ -1,10 +1,10 @@
(ns status-im.contexts.chat.messenger.lightbox.text-sheet.utils
(ns status-im.common.lightbox.text-sheet.utils
(:require
[oops.core :as oops]
[react-native.gesture :as gesture]
[react-native.reanimated :as reanimated]
[reagent.core :as r]
[status-im.contexts.chat.messenger.lightbox.constants :as constants]))
[status-im.common.lightbox.constants :as constants]))
(defn- collapse-sheet
[{:keys [derived-value overlay-opacity saved-top expanded? overlay-z-index]}]
@@ -16,10 +16,10 @@
(defn sheet-gesture
[{:keys [derived-value saved-top overlay-opacity gradient-opacity]}
expanded-height max-height full-height overlay-z-index expanded? dragging? expanding-message?]
expanded-height max-height full-height overlay-z-index expanded? dragging? expandable-text?]
(let [disable-gesture-update (r/atom false)]
(-> (gesture/gesture-pan)
(gesture/enabled expanding-message?)
(gesture/enabled expandable-text?)
(gesture/on-start (fn []
(reset! overlay-z-index 1)
(reset! dragging? true)
@@ -1,4 +1,4 @@
(ns status-im.contexts.chat.messenger.lightbox.text-sheet.view
(ns status-im.common.lightbox.text-sheet.view
(:require
[quo.foundations.colors :as colors]
[react-native.core :as rn]
@@ -8,33 +8,31 @@
[react-native.reanimated :as reanimated]
[react-native.safe-area :as safe-area]
[reagent.core :as reagent]
[status-im.contexts.chat.messenger.lightbox.constants :as constants]
[status-im.contexts.chat.messenger.lightbox.text-sheet.style :as style]
[status-im.contexts.chat.messenger.lightbox.text-sheet.utils :as utils]
[status-im.contexts.chat.messenger.messages.content.text.view :as message-view]))
[status-im.common.lightbox.constants :as constants]
[status-im.common.lightbox.text-sheet.style :as style]
[status-im.common.lightbox.text-sheet.utils :as utils]))
(defn- text-sheet
[messages overlay-opacity overlay-z-index text-sheet-lock?]
[_]
(let [text-height (reagent/atom 0)
expanded? (reagent/atom false)
dragging? (atom false)]
(fn []
(let [{:keys [chat-id content]} (first messages)
insets (safe-area/get-insets)
window-height (:height (rn/get-window))
max-height (- window-height
constants/text-min-height
constants/top-view-height
(:bottom insets)
(when platform/ios? (:top insets)))
full-height (+ constants/bar-container-height
constants/text-margin
constants/line-height
@text-height)
expanded-height (min max-height full-height)
animations (utils/init-animations overlay-opacity)
derived (utils/init-derived-animations animations)
expanding-message? (> @text-height (* constants/line-height 2))]
(fn [{:keys [overlay-opacity overlay-z-index text-sheet-lock? text-component]}]
(let [insets (safe-area/get-insets)
window-height (:height (rn/get-window))
max-height (- window-height
constants/text-min-height
constants/top-view-height
(:bottom insets)
(when platform/ios? (:top insets)))
full-height (+ constants/bar-container-height
constants/text-margin
constants/line-height
@text-height)
expanded-height (min max-height full-height)
animations (utils/init-animations overlay-opacity)
derived (utils/init-derived-animations animations)
expandable-text? (> @text-height (* constants/line-height 2))]
[rn/view
[reanimated/linear-gradient
{:colors [colors/neutral-100-opa-0 colors/neutral-100]
@@ -51,10 +49,10 @@
overlay-z-index
expanded?
dragging?
expanding-message?)}
expandable-text?)}
[gesture/gesture-detector
{:gesture (-> (gesture/gesture-tap)
(gesture/enabled (and expanding-message? (not @expanded?)))
(gesture/enabled (and expandable-text? (not @expanded?)))
(gesture/on-start (fn []
(utils/expand-sheet animations
expanded-height
@@ -63,7 +61,7 @@
expanded?
text-sheet-lock?))))}
[reanimated/view {:style (style/sheet-container derived)}
(when expanding-message?
(when expandable-text?
[rn/view {:style style/bar-container}
[rn/view {:style style/bar}]])
[linear-gradient/linear-gradient
@@ -72,19 +70,17 @@
:end {:x 0 :y 0}
:locations [0.7 0.8 1]
:style (style/bottom-gradient (:bottom insets))}]
[gesture/scroll-view
{:scroll-enabled false
:scroll-event-throttle 16
:bounces false
:style {:height (- max-height constants/bar-container-height)}
:content-container-style {:padding-top (when (not expanding-message?)
:content-container-style {:padding-top (when (not expandable-text?)
constants/bar-container-height)}}
[message-view/render-parsed-text
{:content content
:chat-id chat-id
:style-override (style/text-style expanding-message?)
:on-layout #(utils/on-layout % text-height)}]]]]]]))))
[rn/view {:on-layout #(utils/on-layout % text-height)}
text-component]]]]]]))))
(defn view
[messages {:keys [overlay-opacity]} {:keys [overlay-z-index]} {:keys [text-sheet-lock?]}]
[:f> text-sheet messages overlay-opacity overlay-z-index text-sheet-lock?])
[props]
[:f> text-sheet props])
@@ -1,4 +1,4 @@
(ns status-im.contexts.chat.messenger.lightbox.top-view
(ns status-im.common.lightbox.top-view
(:require
[quo.core :as quo]
[quo.foundations.colors :as colors]
@@ -6,10 +6,9 @@
[react-native.orientation :as orientation]
[react-native.platform :as platform]
[react-native.reanimated :as reanimated]
[status-im.contexts.chat.messenger.lightbox.animations :as anim]
[status-im.contexts.chat.messenger.lightbox.constants :as c]
[status-im.contexts.chat.messenger.lightbox.style :as style]
[utils.datetime :as datetime]
[status-im.common.lightbox.animations :as anim]
[status-im.common.lightbox.constants :as constants]
[status-im.common.lightbox.style :as style]
[utils.i18n :as i18n]
[utils.re-frame :as rf]
[utils.url :as url]))
@@ -17,7 +16,7 @@
(defn animate-rotation
[result screen-width screen-height insets
{:keys [rotate top-view-y top-view-x top-view-width top-view-bg]}]
(let [top-x (+ (/ c/top-view-height 2) (:top insets))]
(let [top-x (+ (/ constants/top-view-height 2) (:top insets))]
(cond
(= result orientation/landscape-left)
(do
@@ -42,9 +41,9 @@
(anim/animate top-view-bg colors/neutral-100-opa-0)))))
(defn drawer
[messages index]
(let [{:keys [content]} (nth messages index)
uri (url/replace-port (:image content) (rf/sub [:mediaserver/port]))]
[images index]
(let [{:keys [image]} (nth images index)
uri (url/replace-port image (rf/sub [:mediaserver/port]))]
[quo/action-drawer
[[{:icon :i/save
:accessibility-label :save-image
@@ -52,7 +51,7 @@
:on-press (fn []
(rf/dispatch [:hide-bottom-sheet])
(rf/dispatch
[:chat.ui/save-image-to-gallery
[:lightbox/save-image-to-gallery
uri
#(rf/dispatch [:toasts/upsert
{:id :random-id
@@ -61,20 +60,19 @@
:text (i18n/label :t/photo-saved)}])]))}]]]))
(defn share-image
[messages index]
(let [{:keys [content]} (nth messages index)
uri (url/replace-port (:image content) (rf/sub [:mediaserver/port]))]
(rf/dispatch [:chat.ui/share-image uri])))
[images index]
(let [{:keys [image]} (nth images index)
uri (url/replace-port image (rf/sub [:mediaserver/port]))]
(rf/dispatch [:lightbox/share-image uri])))
(defn top-view
[messages insets index animations derived landscape? screen-width]
(let [{:keys [from timestamp]} (first messages)
[primary-name _] (rf/sub [:contacts/contact-two-names-by-identity from])
bg-color (if landscape?
colors/neutral-100-opa-70
colors/neutral-100-opa-0)
[images insets index animations derived landscape? screen-width]
(let [{:keys [description header]} (nth images @index)
bg-color (if landscape?
colors/neutral-100-opa-70
colors/neutral-100-opa-0)
{:keys [background-color opacity
overlay-opacity]} animations]
overlay-opacity]} animations]
[reanimated/view
{:style
(style/top-view-container (:top insets) screen-width bg-color landscape? animations derived)}
@@ -92,7 +90,7 @@
(anim/animate opacity 0)
(anim/animate overlay-opacity 0)
(rf/dispatch (if platform/ios?
[:chat.ui/exit-lightbox-signal @index]
[:lightbox/exit-lightbox-signal @index]
[:navigate-back])))
:style style/close-container}
[quo/icon :close {:size 20 :color colors/white}]]
@@ -100,22 +98,22 @@
[quo/text
{:weight :semi-bold
:size :paragraph-1
:style {:color colors/white}} primary-name]
:style {:color colors/white}} header]
[quo/text
{:weight :medium
:size :paragraph-2
:style {:color colors/neutral-40}} (when timestamp (datetime/to-short-str timestamp))]]]
:style {:color colors/neutral-40}} description]]]
[rn/view {:style style/top-right-buttons}
[rn/touchable-opacity
{:active-opacity 1
:accessibility-label :share-image
:on-press #(share-image messages @index)
:on-press #(share-image images @index)
:style (merge style/close-container {:margin-right 12})}
[quo/icon :share {:size 20 :color colors/white}]]
[rn/touchable-opacity
{:active-opacity 1
:accessibility-label :image-options
:on-press #(rf/dispatch [:show-bottom-sheet
{:content (fn [] [drawer messages @index])}])
{:content (fn [] [drawer images @index])}])
:style style/close-container}
[quo/icon :options {:size 20 :color colors/white}]]]]))
@@ -1,4 +1,4 @@
(ns status-im.contexts.chat.messenger.lightbox.utils
(ns status-im.common.lightbox.utils
(:require
[clojure.string :as string]
[oops.core :as oops]
@@ -9,9 +9,9 @@
[react-native.platform :as platform]
[react-native.safe-area :as safe-area]
[reagent.core :as reagent]
[status-im.contexts.chat.messenger.lightbox.animations :as anim]
[status-im.contexts.chat.messenger.lightbox.constants :as constants]
[status-im.contexts.chat.messenger.lightbox.top-view :as top-view]
[status-im.common.lightbox.animations :as anim]
[status-im.common.lightbox.constants :as constants]
[status-im.common.lightbox.top-view :as top-view]
[utils.re-frame :as rf]
[utils.worklets.chat.messenger.lightbox :as worklet]))
@@ -46,7 +46,7 @@
(if platform/ios? 250 100)))
(swap! timers assoc :mount-index-lock (js/setTimeout #(reset! scroll-index-lock? false) 300))
(fn []
(rf/dispatch [:chat.ui/zoom-out-signal nil])
(rf/dispatch [:lightbox/zoom-out-signal nil])
(when platform/android?
(rf/dispatch [:chat.ui/lightbox-scale 1]))
(clear-timers timers)))))
@@ -69,7 +69,7 @@
landscape? (string/includes? result orientation/landscape)
item-width (if (and landscape? platform/ios?) screen-height screen-width)]
(when (or landscape? (= result orientation/portrait))
(rf/dispatch [:chat.ui/orientation-change result]))
(rf/dispatch [:lightbox/orientation-change result]))
(cond
landscape?
(orientation/lock-to-landscape "lightbox")
@@ -148,11 +148,11 @@
:timers (atom {})})
(defn init-state
[messages index]
[images index]
;; The initial value of data is the image that was pressed (and not the whole album) in order
;; for the transition animation to execute properly, otherwise it would animate towards
;; outside the screen (even if we have `initialScrollIndex` set).
{:data (reagent/atom (if (number? index) [(nth messages index)] []))
{:data (reagent/atom (if (number? index) [(nth images index)] []))
:scroll-index (reagent/atom index)
:transparent? (reagent/atom false)
:set-full-height? (reagent/atom false)
@@ -1,4 +1,4 @@
(ns status-im.contexts.chat.messenger.lightbox.view
(ns status-im.common.lightbox.view
(:require
[clojure.string :as string]
[oops.core :as oops]
@@ -9,13 +9,13 @@
[react-native.platform :as platform]
[react-native.reanimated :as reanimated]
[react-native.safe-area :as safe-area]
[status-im.contexts.chat.messenger.lightbox.animations :as anim]
[status-im.contexts.chat.messenger.lightbox.bottom-view :as bottom-view]
[status-im.contexts.chat.messenger.lightbox.constants :as constants]
[status-im.contexts.chat.messenger.lightbox.style :as style]
[status-im.contexts.chat.messenger.lightbox.top-view :as top-view]
[status-im.contexts.chat.messenger.lightbox.utils :as utils]
[status-im.contexts.chat.messenger.lightbox.zoomable-image.view :as zoomable-image]
[status-im.common.lightbox.animations :as anim]
[status-im.common.lightbox.bottom-view :as bottom-view]
[status-im.common.lightbox.constants :as constants]
[status-im.common.lightbox.style :as style]
[status-im.common.lightbox.top-view :as top-view]
[status-im.common.lightbox.utils :as utils]
[status-im.common.lightbox.zoomable-image.view :as zoomable-image]
[utils.re-frame :as rf]))
(defn get-item-layout
@@ -33,9 +33,10 @@
(reset! scroll-index index)
(when @small-list-ref
(.scrollToIndex ^js @small-list-ref #js {:animated true :index index}))
(rf/dispatch [:chat.ui/update-shared-element-id (:message-id (oops/oget changed :item))]))))
(rf/dispatch [:lightbox/update-animation-shared-element-id
(:id (oops/oget changed :item))]))))
(defn image
(defn image-view
[message index _ {:keys [screen-width screen-height] :as args}]
[rn/view
{:style (style/image (+ screen-width constants/separator-width) screen-height)}
@@ -43,30 +44,31 @@
[rn/view {:style {:width constants/separator-width}}]])
(defn lightbox-content
[props {:keys [data transparent? scroll-index set-full-height?] :as state}
animations derived messages index handle-items-changed]
(let [insets (safe-area/get-insets)
window (rn/get-window)
window-width (:width window)
window-height (if platform/android?
(+ (:height window) (:top insets))
(:height window))
curr-orientation (or (rf/sub [:lightbox/orientation]) orientation/portrait)
landscape? (string/includes? curr-orientation orientation/landscape)
horizontal? (or platform/android? (not landscape?))
inverted? (and platform/ios? (= curr-orientation orientation/landscape-right))
screen-width (if (or platform/ios? (= curr-orientation orientation/portrait))
window-width
window-height)
screen-height (if (or platform/ios? (= curr-orientation orientation/portrait))
window-height
window-width)
item-width (if (and landscape? platform/ios?) screen-height screen-width)]
[{:keys [props state animations derived images index handle-items-changed bottom-text-component]}]
(let [{:keys [data transparent? scroll-index
set-full-height?]} state
insets (safe-area/get-insets)
window (rn/get-window)
window-width (:width window)
window-height (if platform/android?
(+ (:height window) (:top insets))
(:height window))
curr-orientation (or (rf/sub [:lightbox/orientation]) orientation/portrait)
landscape? (string/includes? curr-orientation orientation/landscape)
horizontal? (or platform/android? (not landscape?))
inverted? (and platform/ios? (= curr-orientation orientation/landscape-right))
screen-width (if (or platform/ios? (= curr-orientation orientation/portrait))
window-width
window-height)
screen-height (if (or platform/ios? (= curr-orientation orientation/portrait))
window-height
window-width)
item-width (if (and landscape? platform/ios?) screen-height screen-width)]
[reanimated/view
{:style (reanimated/apply-animations-to-style {:background-color (:background-color animations)}
{:height screen-height})}
(when-not @transparent?
[:f> top-view/top-view messages insets scroll-index animations derived landscape?
[:f> top-view/top-view images insets scroll-index animations derived landscape?
screen-width])
[gesture/gesture-detector
{:gesture (utils/drag-gesture animations (and landscape? platform/ios?) set-full-height?)}
@@ -92,7 +94,7 @@
:scroll-event-throttle 8
:style {:width (+ screen-width constants/separator-width)}
:data @data
:render-fn image
:render-fn image-view
:render-data {:opacity-value (:opacity animations)
:overlay-opacity (:overlay-opacity animations)
:border-value (:border animations)
@@ -118,26 +120,47 @@
;; NOTE: not un-mounting bottom-view based on `transparent?` (like we do with the top-view
;; above), since we need to save the state of the text-sheet position. Instead, we use
;; the `:display` style property to hide the bottom-sheet.
(when (not landscape?)
[:f> bottom-view/bottom-view messages index scroll-index insets animations derived
item-width props state transparent?])]))
(when (and (not landscape?) (or bottom-text-component (> (count images) 1)))
[:f> bottom-view/bottom-view
{:images images
:index index
:scroll-index scroll-index
:insets insets
:animations animations
:derived derived
:item-width item-width
:props props
:state state
:transparent? transparent?
:bottom-text-component bottom-text-component}])]))
(defn- f-lightbox
[]
(let [{:keys [messages index]} (rf/sub [:get-screen-params])
props (utils/init-props)
state (utils/init-state messages index)
handle-items-changed (fn [e]
(on-viewable-items-changed e props state))]
(let [{:keys [images index bottom-text-component]} (rf/sub [:get-screen-params])
props
(utils/init-props)
state
(utils/init-state images index)
handle-items-changed
(fn [e]
(on-viewable-items-changed e props state))]
(fn []
(let [animations (utils/init-animations (count messages) index)
(let [animations (utils/init-animations (count images) index)
derived (utils/init-derived-animations animations)]
(anim/animate (:background-color animations) colors/neutral-100)
(reset! (:data state) messages)
(reset! (:data state) images)
(when platform/ios? ; issue: https://github.com/wix/react-native-navigation/issues/7726
(utils/orientation-change props state animations))
(utils/effect props animations index)
[:f> lightbox-content props state animations derived messages index handle-items-changed]))))
[:f> lightbox-content
{:props props
:state state
:animations animations
:derived derived
:images images
:index index
:handle-items-changed handle-items-changed
:bottom-text-component bottom-text-component}]))))
(defn lightbox
[]
@@ -1,4 +1,4 @@
(ns status-im.contexts.chat.messenger.lightbox.zoomable-image.constants)
(ns status-im.common.lightbox.zoomable-image.constants)
(def ^:const min-scale 1)
@@ -1,4 +1,4 @@
(ns status-im.contexts.chat.messenger.lightbox.zoomable-image.style
(ns status-im.common.lightbox.zoomable-image.style
(:require
[react-native.platform :as platform]
[react-native.reanimated :as reanimated]))
@@ -1,4 +1,4 @@
(ns status-im.contexts.chat.messenger.lightbox.zoomable-image.utils
(ns status-im.common.lightbox.zoomable-image.utils
(:require
[clojure.string :as string]
[react-native.core :as rn]
@@ -6,8 +6,8 @@
[react-native.orientation :as orientation]
[react-native.platform :as platform]
[reagent.core :as reagent]
[status-im.contexts.chat.messenger.lightbox.animations :as anim]
[status-im.contexts.chat.messenger.lightbox.zoomable-image.constants :as constants]
[status-im.common.lightbox.animations :as anim]
[status-im.common.lightbox.zoomable-image.constants :as constants]
[utils.re-frame :as rf]))
;;; Helpers
@@ -97,7 +97,7 @@
(rescale constants/min-scale true)
(js/setTimeout #(rf/dispatch [:navigate-back]) 70))
(rf/dispatch [:navigate-back]))
(js/setTimeout #(rf/dispatch [:chat.ui/exit-lightbox-signal nil]) 500)))
(js/setTimeout #(rf/dispatch [:lightbox/exit-lightbox-signal nil]) 500)))
(defn handle-zoom-out-signal
"Zooms out when pressing on another photo from the small bottom list"
@@ -1,4 +1,4 @@
(ns status-im.contexts.chat.messenger.lightbox.zoomable-image.view
(ns status-im.common.lightbox.zoomable-image.view
(:require
[oops.core :refer [oget]]
[react-native.core :as rn]
@@ -7,10 +7,10 @@
[react-native.platform :as platform]
[react-native.reanimated :as reanimated]
[reagent.core :as r]
[status-im.contexts.chat.messenger.lightbox.animations :as anim]
[status-im.contexts.chat.messenger.lightbox.zoomable-image.constants :as c]
[status-im.contexts.chat.messenger.lightbox.zoomable-image.style :as style]
[status-im.contexts.chat.messenger.lightbox.zoomable-image.utils :as utils]
[status-im.common.lightbox.animations :as anim]
[status-im.common.lightbox.zoomable-image.constants :as c]
[status-im.common.lightbox.zoomable-image.style :as style]
[status-im.common.lightbox.zoomable-image.utils :as utils]
[utils.re-frame :as rf]
[utils.url :as url]))
@@ -206,7 +206,7 @@
(anim/animate-decay pan-y-start velocity [lower-bound upper-bound]))))))))
(defn- f-zoomable-image
[dimensions animations state rescale curr-orientation content focused? index render-data
[dimensions animations state rescale curr-orientation image focused? index render-data
image-dimensions-nil?]
(let [{:keys [transparent? set-full-height?]} render-data
portrait? (= curr-orientation orientation/portrait)
@@ -229,7 +229,7 @@
(= curr-orientation orientation/portrait))}
[reanimated/fast-image
(merge
{:source {:uri (url/replace-port (:image content) (rf/sub [:mediaserver/port]))}
{:source {:uri (url/replace-port image (rf/sub [:mediaserver/port]))}
:native-ID (when focused? :shared-element)
:style (style/image dimensions animations render-data index)}
(when image-dimensions-nil?
@@ -238,12 +238,12 @@
(defn zoomable-image
[]
(let [state (utils/init-state)]
(fn [{:keys [image-width image-height content message-id]} index render-data]
(let [shared-element-id (rf/sub [:shared-element-id])
(fn [{:keys [image-width image-height image id]} index render-data]
(let [animation-shared-element-id (rf/sub [:animation-shared-element-id])
exit-lightbox-signal (rf/sub [:lightbox/exit-signal])
zoom-out-signal (rf/sub [:lightbox/zoom-out-signal])
{:keys [set-full-height? curr-orientation]} render-data
focused? (= shared-element-id message-id)
focused? (= animation-shared-element-id id)
;; TODO - remove `image-dimensions` check, once
;; https://github.com/status-im/status-desktop/issues/10944 is fixed
image-dimensions-nil? (not (and image-width image-height))
@@ -269,5 +269,5 @@
rescale
set-full-height?))
(utils/handle-zoom-out-signal zoom-out-signal index (anim/get-val (:scale animations)) rescale)
[:f> f-zoomable-image dimensions animations state rescale curr-orientation content focused?
[:f> f-zoomable-image dimensions animations state rescale curr-orientation image focused?
index render-data image-dimensions-nil?]))))
+2 -1
View File
@@ -4,6 +4,7 @@
[quo.core :as quo]
[react-native.core :as rn]
[react-native.reanimated :as reanimated]
[react-native.safe-area :as safe-area]
[reagent.core :as reagent]
[status-im.common.scroll-page.style :as style]
[utils.re-frame :as rf]))
@@ -59,7 +60,7 @@
[rn/view {:style {:margin-top 0}}
top-nav]
[quo/page-nav
(cond-> {:margin-top 44
(cond-> {:margin-top (safe-area/get-top)
:type :no-title
:background (if (= 1 (reanimated/get-shared-value opacity-animation))
:blur
@@ -1,6 +1,6 @@
(ns status-im.common.standard-authentication.standard-auth.authorize
(:require
[react-native.touch-id :as biometric]
[react-native.biometrics :as biometrics]
[status-im.common.standard-authentication.enter-password.view :as enter-password]
[taoensso.timbre :as log]
[utils.i18n :as i18n]
@@ -39,25 +39,29 @@
; to retrigger biometric auth, so they can endlessly repeat this cycle.
biometrics-login (fn [on-press-biometrics]
(rf/dispatch [:dismiss-keyboard])
(biometric/authenticate
{:reason (i18n/label :t/biometric-auth-confirm-message)
:on-success (fn [_response]
(on-close)
(rf/dispatch [:standard-auth/on-biometric-success
(handle-auth-success true)]))
:on-fail (fn [error]
(on-close)
(log/error "Authentication Failed. Error:" error)
(when on-auth-fail (on-auth-fail error))
(password-login {:on-press-biometrics
#(on-press-biometrics
on-press-biometrics)}))}))]
(rf/dispatch
[:biometric/authenticate
{:prompt-message (i18n/label :t/biometric-auth-confirm-message)
:on-success (fn []
(on-close)
(rf/dispatch [:standard-auth/on-biometric-success
(handle-auth-success true)]))
:on-fail (fn [error]
(on-close)
(log/error
(ex-message error)
(-> error ex-data (assoc :code (ex-cause error))))
(when on-auth-fail (on-auth-fail error))
(password-login {:on-press-biometrics
#(on-press-biometrics
on-press-biometrics)}))}]))]
(if biometric-auth?
(biometric/get-supported-type
(fn [biometric-type]
(if biometric-type
(biometrics-login biometrics-login)
(do
(reset-password)
(password-login {})))))
(-> (biometrics/get-supported-type)
(.then (fn [biometric-type]
(if biometric-type
(biometrics-login biometrics-login)
(do
(reset-password)
(password-login {})))))
(.catch #(password-login {})))
(password-login {}))))
+4
View File
@@ -162,3 +162,7 @@
(def community-accounts-selection-enabled? false)
(def fetch-messages-enabled? (enabled? (get-config :FETCH_MESSAGES_ENABLED "1")))
(def wallet-feature-flags
{:edit-default-keypair false
:bridge-token false})
+4 -4
View File
@@ -378,10 +378,10 @@
(def ^:const auth-method-biometric-prepare "biometric-prepare")
(def ^:const auth-method-none "none")
(def ^:const biometric-error-user-canceled "USER_CANCELED")
(def ^:const biometric-error-user-fallback "USER_USER_FALLBACK")
(def ^:const biometric-error-not-available "NOT_AVAILABLE")
(def ^:const biometric-error-not-enrolled "NOT_ENROLLED")
;; NOTE all android biometrics falls under :Biometrics
(def ^:const biometrics-type-android :Biometrics)
(def ^:const biometrics-type-touch-id :TouchID)
(def ^:const biometrics-type-face-id :FaceID)
(def ^:const onboarding-generating-keys-animation-duration-ms 7000)
(def ^:const onboarding-generating-keys-navigation-retry-ms 3000)
+13 -34
View File
@@ -4,13 +4,12 @@
[legacy.status-im.chat.models.loading :as loading]
[legacy.status-im.data-store.chats :as chats-store]
[re-frame.core :as re-frame]
[reagent.core :as reagent]
status-im.common.lightbox.events
[status-im.common.muting.helpers :refer [format-mute-till]]
[status-im.constants :as constants]
[status-im.contexts.chat.contacts.events :as contacts-store]
status-im.contexts.chat.effects
[status-im.contexts.chat.messenger.composer.link-preview.events :as link-preview]
status-im.contexts.chat.messenger.lightbox.events
status-im.contexts.chat.messenger.messages.content.reactions.events
[status-im.contexts.chat.messenger.messages.delete-message-for-me.events :as delete-for-me]
[status-im.contexts.chat.messenger.messages.delete-message.events :as delete-message]
@@ -394,38 +393,6 @@
[cofx chat-id]
(navigation/navigate-to cofx :chat-pinned-messages {:chat-id chat-id}))
(rf/defn update-shared-element-id
{:events [:chat.ui/update-shared-element-id]}
[{:keys [db]} shared-element-id]
{:db (assoc db :shared-element-id shared-element-id)})
(rf/defn navigate-to-lightbox
{:events [:chat.ui/navigate-to-lightbox]}
[{:keys [db]} shared-element-id screen-params]
(reagent/next-tick #(rf/dispatch [:navigate-to :lightbox screen-params]))
{:db (assoc db :shared-element-id shared-element-id)})
(rf/defn exit-lightbox-signal
{:events [:chat.ui/exit-lightbox-signal]}
[{:keys [db]} value]
{:db (assoc db :lightbox/exit-signal value)})
(rf/defn zoom-out-signal
{:events [:chat.ui/zoom-out-signal]}
[{:keys [db]} value]
{:db (assoc db :lightbox/zoom-out-signal value)})
(rf/defn orientation-change
{:events [:chat.ui/orientation-change]}
[{:keys [db]} value]
{:db (assoc db :lightbox/orientation value)})
(rf/defn lightbox-scale
{:events [:chat.ui/lightbox-scale]}
[{:keys [db]} value]
{:db (assoc db :lightbox/scale value)})
(rf/defn check-last-chat
{:events [:chat/check-last-chat]}
[{:keys [db]}]
@@ -452,3 +419,15 @@
{:events [:chat.ui/scroll-to-bottom]}
[_]
{:effects.chat/scroll-to-bottom nil})
(rf/reg-event-fx :chat.ui/clear-sending-images
(fn [{:keys [db]}]
{:db (update-in db [:chat/inputs (:current-chat-id db) :metadata] assoc :sending-image {})}))
(rf/reg-event-fx :chat.ui/image-unselected
(fn [{:keys [db]} [original]]
(let [current-chat-id (:current-chat-id db)]
{:db (update-in db
[:chat/inputs current-chat-id :metadata :sending-image]
dissoc
(:uri original))})))
@@ -1,23 +0,0 @@
(ns status-im.contexts.chat.messenger.lightbox.events
(:require status-im.contexts.chat.messenger.lightbox.effects
[utils.re-frame :as rf]))
(rf/reg-event-fx :chat.ui/clear-sending-images
(fn [{:keys [db]}]
{:db (update-in db [:chat/inputs (:current-chat-id db) :metadata] assoc :sending-image {})}))
(rf/reg-event-fx :chat.ui/image-unselected
(fn [{:keys [db]} [original]]
(let [current-chat-id (:current-chat-id db)]
{:db (update-in db
[:chat/inputs current-chat-id :metadata :sending-image]
dissoc
(:uri original))})))
(rf/reg-event-fx :chat.ui/share-image
(fn [_ [uri]]
{:effects.chat/share-image uri}))
(rf/reg-event-fx :chat.ui/save-image-to-gallery
(fn [_ [uri on-success]]
{:effects.chat/save-image-to-gallery [uri on-success]}))
@@ -7,6 +7,8 @@
[status-im.constants :as constants]
[status-im.contexts.chat.messenger.messages.content.album.style :as style]
[status-im.contexts.chat.messenger.messages.content.image.view :as image]
[status-im.contexts.chat.messenger.messages.content.lightbox.utils :as lightbox-utils]
[status-im.contexts.chat.messenger.messages.content.lightbox.view :as lightbox]
[status-im.contexts.chat.messenger.messages.content.text.view :as text]
[utils.re-frame :as rf]
[utils.url :as url]))
@@ -21,17 +23,19 @@
(defn album-message
[{:keys [albumize?] :as message} context on-long-press message-container-data]
(let [shared-element-id (rf/sub [:shared-element-id])
media-server-port (rf/sub [:mediaserver/port])
first-image (first (:album message))
album-style (if (> (:image-width first-image) (:image-height first-image))
:landscape
:portrait)
images-count (count (:album message))
(let [animation-shared-element-id (rf/sub [:animation-shared-element-id])
media-server-port (rf/sub [:mediaserver/port])
album-messages (:album message)
first-image (first album-messages)
album-style (if (> (:image-width first-image) (:image-height first-image))
:landscape
:portrait)
images-count (count album-messages)
;; album images are always square, except when we have 3 images, then they must be
;; rectangular
;; (portrait or landscape)
portrait? (and (= images-count rectangular-style-count) (= album-style :portrait))]
portrait? (and (= images-count rectangular-style-count)
(= album-style :portrait))]
(if (and albumize? (> images-count 1))
[:<>
[rn/view {:style {:margin-bottom 4}} [text/text-content first-image]]
@@ -52,14 +56,21 @@
{:key (:message-id item)
:active-opacity 1
:on-long-press #(on-long-press message context)
:on-press #(rf/dispatch [:chat.ui/navigate-to-lightbox
(:message-id item)
{:messages (:album message)
:index index}])}
:on-press #(rf/dispatch
[:lightbox/navigate-to-lightbox
(:message-id item)
{:index index
:images (into []
(map
lightbox-utils/convert-message-to-lightbox-image
album-messages))
:bottom-text-component
[lightbox/bottom-text-for-lightbox
first-image]}])}
[fast-image/fast-image
{:style (style/image dimensions index portrait? images-count)
:source {:uri (url/replace-port (:image (:content item)) media-server-port)}
:native-ID (when (and (= shared-element-id (:message-id item))
:native-ID (when (and (= animation-shared-element-id (:message-id item))
(< index constants/max-album-photos))
:shared-element)}]
(when (and (> images-count constants/max-album-photos)
@@ -71,11 +82,11 @@
:size :heading-2
:style {:color colors/white}}
(str "+" (- images-count (dec constants/max-album-photos)))]])]))
(:album message))]]
album-messages)]]
[:<>
(map-indexed
(fn [index item]
[:<> {:key (:message-id item)}
[image/image-message index item {:on-long-press #(on-long-press message context)}
message-container-data]])
(:album message))])))
album-messages)])))
@@ -4,6 +4,8 @@
[react-native.fast-image :as fast-image]
[react-native.safe-area :as safe-area]
[status-im.constants :as constants]
[status-im.contexts.chat.messenger.messages.content.lightbox.utils :as lightbox-utils]
[status-im.contexts.chat.messenger.messages.content.lightbox.view :as lightbox]
[status-im.contexts.chat.messenger.messages.content.text.view :as text]
[utils.re-frame :as rf]
[utils.url :as url]))
@@ -39,7 +41,7 @@
image-height
max-container-width
max-container-height)
shared-element-id (rf/sub [:shared-element-id])
animation-shared-element-id (rf/sub [:animation-shared-element-id])
image-local-url (url/replace-port (:image content) (rf/sub [:mediaserver/port]))]
[:<>
(when (= index 0)
@@ -48,13 +50,16 @@
{:active-opacity 1
:style {:margin-top 4}
:on-long-press on-long-press
:on-press #(rf/dispatch [:chat.ui/navigate-to-lightbox
:on-press #(rf/dispatch [:lightbox/navigate-to-lightbox
message-id
{:messages [message]
:index 0
:insets insets}])}
{:images [(lightbox-utils/convert-message-to-lightbox-image
message)]
:index 0
:insets insets
:bottom-text-component
[lightbox/bottom-text-for-lightbox message]}])}
[fast-image/fast-image
{:source {:uri image-local-url}
:style (merge dimensions {:border-radius 12})
:native-ID (when (= shared-element-id message-id) :shared-element)
:native-ID (when (= animation-shared-element-id message-id) :shared-element)
:accessibility-label :image-message}]]]))
@@ -0,0 +1,10 @@
(ns status-im.contexts.chat.messenger.messages.content.lightbox.style
(:require
[quo.foundations.colors :as colors]))
(defn bottom-text
[expandable-text?]
{:color colors/white
:margin-horizontal 20
:align-items (when-not expandable-text? :center)
:flex-grow 1})
@@ -0,0 +1,14 @@
(ns status-im.contexts.chat.messenger.messages.content.lightbox.utils
(:require
[utils.datetime :as datetime]
[utils.re-frame :as rf]))
(defn convert-message-to-lightbox-image
[{:keys [timestamp image-width image-height message-id from content]}]
(let [[primary-name _] (rf/sub [:contacts/contact-two-names-by-identity from])]
{:image (:image content)
:image-width image-width
:image-height image-height
:id message-id
:header primary-name
:description (when timestamp (datetime/to-short-str timestamp))}))
@@ -0,0 +1,18 @@
(ns status-im.contexts.chat.messenger.messages.content.lightbox.view
(:require
[oops.core :as oops]
[reagent.core :as reagent]
[status-im.common.lightbox.constants :as constants]
[status-im.contexts.chat.messenger.messages.content.lightbox.style :as style]
[status-im.contexts.chat.messenger.messages.content.text.view :as message-view]))
(defn bottom-text-for-lightbox
[_]
(let [text-height (reagent/atom 0)]
(fn [{:keys [content chat-id] :as _message}]
(let [expandable-text? (> @text-height (* constants/line-height 2))]
[message-view/render-parsed-text
{:content content
:chat-id chat-id
:style-override (style/bottom-text expandable-text?)
:on-layout #(reset! text-height (oops/oget % "nativeEvent.layout.height"))}]))))
@@ -6,6 +6,7 @@
[legacy.status-im.chat.models.message :as models.message]
[legacy.status-im.data-store.activities :as data-store.activities]
[legacy.status-im.data-store.chats :as data-store.chats]
[legacy.status-im.data-store.communities :as data-store.communities]
[legacy.status-im.data-store.invitations :as data-store.invitations]
[legacy.status-im.group-chats.core :as models.group]
[legacy.status-im.multiaccounts.update.core :as update.core]
@@ -127,7 +128,7 @@
(seq requests-to-join-community)
(let [requests (->> requests-to-join-community
types/js->clj
(map communities/<-request-to-join-community-rpc))]
(map data-store.communities/<-request-to-join-community-rpc))]
(js-delete response-js "requestsToJoinCommunity")
(rf/merge cofx
(process-next response-js sync-handler)
@@ -6,25 +6,28 @@
[status-im.common.password-authentication.view :as password-authentication]
[status-im.contexts.communities.actions.accounts-selection.style :as style]
[status-im.contexts.communities.actions.community-rules.view :as community-rules]
[status-im.contexts.communities.utils :as communities.utils]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
(defn- join-community-and-navigate-back
[id]
(rf/dispatch [:password-authentication/show
{:content (fn [] [password-authentication/view])}
(rf/dispatch [:password-authentication/show {:content (fn [] [password-authentication/view])}
{:label (i18n/label :t/join-open-community)
:on-press #(rf/dispatch [:communities/request-to-join-with-addresses
{:community-id id
:password %}])}])
{:community-id id :password %}])}])
(rf/dispatch [:navigate-back]))
(defn f-view-internal
[]
(let [{id :community-id} (rf/sub [:get-screen-params])
{:keys [name color images]} (rf/sub [:communities/community id])
airdrop-account (rf/sub [:communities/airdrop-account id])
selected-accounts (rf/sub [:communities/selected-permission-accounts id])]
(let [{id :community-id} (rf/sub [:get-screen-params])
{:keys [name color images]} (rf/sub [:communities/community id])
airdrop-account (rf/sub [:communities/airdrop-account id])
selected-accounts (rf/sub [:communities/selected-permission-accounts id])
{:keys [highest-permission-role]} (rf/sub [:community/token-gated-overview id])
highest-role-text (i18n/label (communities.utils/role->translation-key
highest-permission-role
:t/member))]
[rn/view {:style style/container}
[quo/page-nav
{:text-align :left
@@ -48,7 +51,7 @@
(i18n/label :t/address-to-share)]
[quo/category
{:list-type :settings
:data [{:title (i18n/label :t/join-as-a-member)
:data [{:title (i18n/label :t/join-as-a {:role highest-role-text})
:on-press #(rf/dispatch [:open-modal :addresses-for-permissions
{:community-id id}])
:description :text
@@ -13,3 +13,10 @@
:gap 4
:justify-content :center
:margin-bottom 8})
(def highest-role
{:flex-direction :row
:gap 4
:justify-content :center
:align-items :center
:margin-bottom 8})
@@ -4,6 +4,7 @@
[react-native.core :as rn]
[status-im.common.not-implemented :as not-implemented]
[status-im.contexts.communities.actions.addresses-for-permissions.style :as style]
[status-im.contexts.communities.utils :as communities.utils]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
@@ -22,10 +23,14 @@
(defn view
[]
(let [{id :community-id} (rf/sub [:get-screen-params])
(let [{id :community-id} (rf/sub [:get-screen-params])
{:keys [name color images]} (rf/sub [:communities/community id])
accounts (rf/sub [:wallet/accounts-with-customization-color])
selected-addresses (rf/sub [:communities/selected-permission-addresses id])]
{:keys [highest-permission-role]} (rf/sub [:community/token-gated-overview id])
accounts (rf/sub [:wallet/accounts-with-customization-color])
selected-addresses (rf/sub [:communities/selected-permission-addresses id])
highest-role-text
(i18n/label
(communities.utils/role->translation-key highest-permission-role))]
[rn/safe-area-view {:style style/container}
[quo/drawer-top
{:type :context-tag
@@ -43,6 +48,19 @@
:key-fn :address
:data accounts}]
(when (and highest-permission-role (seq selected-addresses))
[rn/view
{:style style/highest-role}
[quo/text
{:size :paragraph-2
:style {:color colors/neutral-50}}
(i18n/label :t/eligible-to-join-as {:role ""})]
[quo/context-tag
{:type :icon
:icon :i/members
:size 24
:context highest-role-text}]])
(when (empty? selected-addresses)
[rn/view
{:style style/error-message}
@@ -1,13 +1,16 @@
(ns status-im.contexts.communities.discover.style
(:require
[react-native.platform :as platform]
[status-im.contexts.shell.jump-to.constants :as jump-to.constants]))
(def screen-title-container
{:height 56
(def header-height 56)
(defn screen-title-container
[safe-area-top]
{:height header-height
:padding-vertical 12
:justify-content :center
:margin-horizontal 20})
:margin-horizontal 20
:margin-top (+ header-height safe-area-top)})
(def featured-communities-header
{:flex-direction :row
@@ -40,7 +43,7 @@
[fixed?]
(merge
{:padding-vertical 12
:height 56
:height header-height
:background-color :transparent}
(when-not fixed?
{:margin-top 12
@@ -60,15 +63,12 @@
{:align-items :center
:justify-content :center})
(def render-communities-container
{:margin-top (if platform/ios? 57 104)})
(defn blur-tabs-header
[]
[safe-area-top]
{:padding-horizontal 20
:position :absolute
:top (if platform/ios? 100 104)
:height 56
:top (+ header-height safe-area-top)
:height header-height
:right 0
:left 0
:justify-content :center
@@ -5,6 +5,7 @@
[quo.foundations.colors :as colors]
[quo.theme :as quo.theme]
[react-native.core :as rn]
[react-native.safe-area :as safe-area]
[reagent.core :as reagent]
[status-im.common.scroll-page.view :as scroll-page]
[status-im.contexts.communities.actions.community-options.view :as options]
@@ -33,7 +34,7 @@
(defn screen-title
[]
[rn/view
{:style style/screen-title-container}
{:style (style/screen-title-container (safe-area/get-top))}
[quo/text
{:accessibility-label :communities-screen-title
:weight :semi-bold
@@ -183,7 +184,6 @@
view-type]
(fn []
[rn/view
{:style style/render-communities-container}
[discover-communities-header
{:selected-tab selected-tab
:view-type view-type
@@ -196,7 +196,7 @@
(fn []
(when (> @scroll-height 360)
[rn/view
{:style (style/blur-tabs-header)}
{:style (style/blur-tabs-header (safe-area/get-top))}
[discover-communities-segments selected-tab true]])))
(defn discover-screen-content
@@ -209,6 +209,7 @@
[scroll-page/scroll-page
{:theme theme
:on-scroll #(reset! scroll-height %)
:page-nav-props {:background :blur}
:navigate-back? :true
:height (if (> @scroll-height 360)
208
+133 -99
View File
@@ -1,96 +1,56 @@
(ns status-im.contexts.communities.events
(:require [clojure.set :as set]
[clojure.string :as string]
[clojure.walk :as walk]
[legacy.status-im.data-store.chats :as data-store.chats]
[legacy.status-im.mailserver.core :as mailserver]
[react-native.platform :as platform]
[react-native.share :as share]
[schema.core :as schema]
[status-im.constants :as constants]
[status-im.contexts.chat.messenger.messages.link-preview.events :as link-preview.events]
status-im.contexts.communities.actions.community-options.events
status-im.contexts.communities.actions.leave.events
[status-im.navigation.events :as navigation]
[taoensso.timbre :as log]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
(:require
[clojure.string :as string]
[legacy.status-im.data-store.chats :as data-store.chats]
[legacy.status-im.data-store.communities :as data-store.communities]
[legacy.status-im.mailserver.core :as mailserver]
[react-native.platform :as platform]
[react-native.share :as share]
[schema.core :as schema]
[status-im.constants :as constants]
[status-im.contexts.chat.messenger.messages.link-preview.events :as link-preview.events]
status-im.contexts.communities.actions.community-options.events
status-im.contexts.communities.actions.leave.events
[status-im.navigation.events :as navigation]
[taoensso.timbre :as log]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
(defn <-request-to-join-community-rpc
[r]
(set/rename-keys r
{:communityId :community-id
:publicKey :public-key
:chatId :chat-id}))
(defn handle-community
[{:keys [db]} [community-js]]
(when community-js
(let [{:keys [token-permissions
token-permissions-check joined id]
:as community} (data-store.communities/<-rpc community-js)
has-channel-perm? (fn [id-perm-tuple]
(let [{:keys [type]} (second id-perm-tuple)]
(or (= type constants/community-token-permission-can-view-channel)
(=
type
constants/community-token-permission-can-view-and-post-channel))))]
{:db (assoc-in db [:communities id] community)
:fx [[:dispatch [:communities/initialize-permission-addresses id]]
(when (not joined)
[:dispatch [:chat.ui/spectate-community id]])
(when (nil? token-permissions-check)
[:dispatch [:communities/check-permissions-to-join-community id]])
(when (some has-channel-perm? token-permissions)
[:dispatch [:communities/check-all-community-channels-permissions id]])
(when joined
[:dispatch [:communities/get-revealed-accounts id]])]})))
(defn <-requests-to-join-community-rpc
[requests key-fn]
(reduce #(assoc %1 (key-fn %2) (<-request-to-join-community-rpc %2)) {} requests))
(rf/reg-event-fx :communities/handle-community handle-community)
(defn <-chats-rpc
[chats]
(reduce-kv (fn [acc k v]
(assoc acc
(name k)
(-> v
(assoc :can-post? (:canPost v))
(dissoc :canPost)
(update :members walk/stringify-keys))))
{}
chats))
(defn <-categories-rpc
[categ]
(reduce-kv #(assoc %1 (name %2) %3) {} categ))
(defn <-rpc
[c]
(-> c
(set/rename-keys {:canRequestAccess :can-request-access?
:canManageUsers :can-manage-users?
:canDeleteMessageForEveryone :can-delete-message-for-everyone?
:canJoin :can-join?
:requestedToJoinAt :requested-to-join-at
:isMember :is-member?
:adminSettings :admin-settings
:tokenPermissions :token-permissions
:communityTokensMetadata :tokens-metadata
:introMessage :intro-message
:muteTill :muted-till})
(update :admin-settings
set/rename-keys
{:pinMessageAllMembersEnabled :pin-message-all-members-enabled?})
(update :members walk/stringify-keys)
(update :chats <-chats-rpc)
(update :token-permissions seq)
(update :categories <-categories-rpc)
(assoc :token-images
(reduce (fn [acc {sym :symbol image :image}]
(assoc acc sym image))
{}
(:communityTokensMetadata c)))))
(rf/reg-event-fx :communities/handle-community
(fn [{:keys [db]}
[community-js]]
(when community-js
(let [{:keys [token-permissions
token-permissions-check joined id]
:as community} (<-rpc community-js)
has-channel-perm? (fn [id-perm-tuple]
(let [{:keys [type]} (second id-perm-tuple)]
(or (= type constants/community-token-permission-can-view-channel)
(=
type
constants/community-token-permission-can-view-and-post-channel))))]
{:db (assoc-in db [:communities id] community)
:fx [[:dispatch [:communities/initialize-permission-addresses id]]
(when (not joined)
[:dispatch [:chat.ui/spectate-community id]])
(when (nil? token-permissions-check)
[:dispatch [:communities/check-permissions-to-join-community id]])
(when (some has-channel-perm? token-permissions)
[:dispatch [:communities/check-all-community-channels-permissions id]])]}))))
(schema/=> handle-community
[:=>
[:catn
[:cofx :schema.re-frame/cofx]
[:args
[:schema [:catn [:community-js map?]]]]]
[:maybe
[:map
[:db [:map [:communities map?]]]
[:fx vector?]]]])
(rf/defn handle-removed-chats
[{:keys [db]} chat-ids]
@@ -164,7 +124,7 @@
(fn [{:keys [db]} [requests]]
{:db (assoc db
:communities/my-pending-requests-to-join
(<-requests-to-join-community-rpc requests :communityId))}))
(data-store.communities/<-requests-to-join-community-rpc requests :communityId))}))
(rf/reg-event-fx :communities/get-user-requests-to-join
(fn [_]
@@ -240,12 +200,19 @@
(defn toggle-selected-permission-address
[{:keys [db]} [address community-id]]
{:db (update-in db
[:communities community-id :selected-permission-addresses]
(fn [selected-addresses]
(if (contains? selected-addresses address)
(disj selected-addresses address)
(conj selected-addresses address))))})
(let [selected-permission-addresses
(get-in db [:communities community-id :selected-permission-addresses])
updated-selected-permission-addresses
(if (contains? selected-permission-addresses address)
(disj selected-permission-addresses address)
(conj selected-permission-addresses address))]
{:db (assoc-in db
[:communities community-id :selected-permission-addresses]
updated-selected-permission-addresses)
:fx [(when community-id
[:dispatch
[:communities/check-permissions-to-join-community community-id
updated-selected-permission-addresses :based-on-client-selection]])]}))
(rf/reg-event-fx :communities/toggle-selected-permission-address
toggle-selected-permission-address)
@@ -255,7 +222,8 @@
(when community-id
{:db (assoc-in db
[:communities community-id :selected-permission-addresses]
(get-in db [:communities community-id :previous-permission-addresses]))})))
(get-in db [:communities community-id :previous-permission-addresses]))
:fx [[:dispatch [:communities/check-permissions-to-join-community community-id]]]})))
(rf/reg-event-fx :communities/share-community-channel-url-with-data
(fn [_ [chat-id]]
@@ -394,14 +362,80 @@
(rf/merge
cofx
{:fx [[:dispatch [:communities/fetch-community deserialized-key]]
[:dispatch [:navigate-to :community-overview deserialized-key]]]}
[:dispatch [:navigate-to :community-overview deserialized-key]]
[:dispatch [:communities/update-last-opened-at deserialized-key]]]}
(navigation/pop-to-root :shell-stack)))))
(rf/reg-event-fx :communities/navigate-to-community-chat
(fn [{:keys [db]} [chat-id pop-to-root?]]
(let [{:keys [community-id]} (get-in db [:chats chat-id])]
{:fx [(when community-id
[:dispatch [:communities/fetch-community community-id]])
[:dispatch [:communities/fetch-community community-id]]
[:dispatch [:communities/update-last-opened-at community-id]])
(if pop-to-root?
[:dispatch [:chat/pop-to-root-and-navigate-to-chat chat-id]]
[:dispatch [:chat/navigate-to-chat chat-id]])]})))
(defn get-revealed-accounts
[{:keys [db]} [community-id]]
(let [{:keys [joined fetching-revealed-accounts]
:as community} (get-in db [:communities community-id])]
(when (and community joined (not fetching-revealed-accounts))
{:db (assoc-in db [:communities community-id :fetching-revealed-accounts] true)
:json-rpc/call
[{:method "wakuext_getRevealedAccounts"
:params [community-id (get-in db [:profile/profile :public-key])]
:js-response true
:on-success [:communities/get-revealed-accounts-success community-id]
:on-error (fn [err]
(log/error {:message "failed to fetch revealed accounts"
:community-id community-id
:err err})
(rf/dispatch [:communities/get-revealed-accounts-failed community-id]))}]})))
(rf/reg-event-fx :communities/get-revealed-accounts get-revealed-accounts)
(schema/=> get-revealed-accounts
[:=>
[:catn
[:cofx :schema.re-frame/cofx]
[:args
[:schema [:catn [:community-id [:? :string]]]]]]
[:maybe
[:map
[:db map?]
[:json-rpc/call :schema.common/rpc-call]]]])
(rf/reg-event-fx :communities/get-revealed-accounts-success
(fn [{:keys [db]} [community-id revealed-accounts-js]]
(when-let [community (get-in db [:communities community-id])]
(let [revealed-accounts
(reduce
(fn [acc {:keys [address] :as revealed-account}]
(assoc acc address (dissoc revealed-account :address)))
{}
(data-store.communities/<-revealed-accounts-rpc revealed-accounts-js))
community-with-revealed-accounts
(-> community
(assoc :revealed-accounts revealed-accounts)
(dissoc :fetching-revealed-accounts))]
{:db (assoc-in db [:communities community-id] community-with-revealed-accounts)}))))
(rf/reg-event-fx :communities/get-revealed-accounts-failed
(fn [{:keys [db]} [community-id]]
(when (get-in db [:communities community-id])
{:db (update-in db [:communities community-id] dissoc :fetching-revealed-accounts)})))
(rf/reg-event-fx :communities/update-last-opened-at
(fn [_ [community-id]]
{:json-rpc/call [{:method "wakuext_communityUpdateLastOpenedAt"
:params [community-id]
:on-success #(rf/dispatch [:communities/update-last-opened-at-success community-id
%])
:on-error #(log/error (str "failed to update last opened at for community "
%))}]}))
(rf/reg-event-fx :communities/update-last-opened-at-success
(fn [{:keys [db]} [community-id last-opened-at]]
{:db (assoc-in db [:communities community-id :last-opened-at] last-opened-at)}))
@@ -2,6 +2,7 @@
(:require [cljs.test :refer [deftest is testing]]
[legacy.status-im.mailserver.core :as mailserver]
matcher-combinators.test
[status-im.constants :as constants]
[status-im.contexts.chat.messenger.messages.link-preview.events :as link-preview.events]
[status-im.contexts.communities.events :as events]))
@@ -193,3 +194,79 @@
(is (match?
nil
(events/spectate-community-success {} []))))))
(deftest get-revealed-accounts
(let [community {:id community-id}]
(testing "given a unjoined community"
(is (match?
nil
(events/get-revealed-accounts {:db {:communities {community-id community}}} [community-id]))))
(testing "given a already :fetching-revealed-accounts community"
(is (match?
nil
(events/get-revealed-accounts
{:db {:communities {community-id (assoc community :fetching-revealed-accounts true)}}}
[community-id]))))
(testing "given joined community"
(let [community (assoc community :joined true)
db {:communities {community-id community}
:profile/profile {:public-key "profile-public-key"}}
effects (events/get-revealed-accounts {:db db} [community-id])]
(is (match? (assoc-in db [:communities community-id :fetching-revealed-accounts] true)
(:db effects)))
(is (match? {:method "wakuext_getRevealedAccounts"
:params [community-id "profile-public-key"]}
(-> effects :json-rpc/call first (select-keys [:method :params]))))))))
(deftest handle-community
(let [community {:id community-id}]
(testing "given a unjoined community"
(let [effects (events/handle-community {} [community])]
(is (match? community-id
(-> effects :db :communities (get community-id) :id)))
(is (match?
[[:dispatch [:communities/initialize-permission-addresses community-id]]
[:dispatch [:chat.ui/spectate-community community-id]]
[:dispatch [:communities/check-permissions-to-join-community community-id]]]
(filter some? (:fx effects))))))
(testing "given a joined community"
(let [community (assoc community :joined true)
effects (events/handle-community {} [community])]
(is (match?
[[:dispatch [:communities/initialize-permission-addresses community-id]]
[:dispatch [:communities/check-permissions-to-join-community community-id]]
[:dispatch [:communities/get-revealed-accounts community-id]]]
(filter some? (:fx effects))))))
(testing "given a community with token-permissions-check"
(let [community (assoc community :token-permissions-check :fake-token-permissions-check)
effects (events/handle-community {} [community])]
(is (match?
[[:dispatch [:communities/initialize-permission-addresses community-id]]
[:dispatch [:chat.ui/spectate-community community-id]]]
(filter some? (:fx effects))))))
(testing "given a community with view channel permission"
(let [community (assoc community
:token-permissions
[["perm-id" {:type constants/community-token-permission-can-view-channel}]])
effects (events/handle-community {} [community])]
(is (match?
[[:dispatch [:communities/initialize-permission-addresses community-id]]
[:dispatch [:chat.ui/spectate-community community-id]]
[:dispatch [:communities/check-permissions-to-join-community community-id]]
[:dispatch
[:communities/check-all-community-channels-permissions community-id]]]
(filter some? (:fx effects))))))
(testing "given a community with post in channel permission"
(let [community (assoc community
:token-permissions
[["perm-id"
{:type constants/community-token-permission-can-view-and-post-channel}]])
effects (events/handle-community {} [community])]
(is (match?
[[:dispatch [:communities/initialize-permission-addresses community-id]]
[:dispatch [:chat.ui/spectate-community community-id]]
[:dispatch [:communities/check-permissions-to-join-community community-id]]
[:dispatch
[:communities/check-all-community-channels-permissions community-id]]]
(filter some? (:fx effects))))))))
@@ -34,26 +34,31 @@
:communities/check-all-community-channels-permissions}))}]]]})))
(rf/reg-event-fx :communities/check-permissions-to-join-community-success
(fn [{:keys [db]} [community-id result]]
{:db (-> db
(assoc-in [:communities community-id :checking-permissions?] false)
(assoc-in [:communities community-id :token-permissions-check] result))}))
(fn [{:keys [db]} [community-id based-on-client-selection? result]]
(let [token-permissions-check (cond-> result
based-on-client-selection? (assoc :based-on-client-selection? true))]
{:db (-> db
(assoc-in [:communities community-id :checking-permissions?] false)
(assoc-in [:communities community-id :token-permissions-check]
token-permissions-check))})))
(rf/reg-event-fx :communities/check-permissions-to-join-community-failed
(fn [{:keys [db]} [community-id]]
{:db (assoc-in db [:communities community-id :checking-permissions?] false)}))
(rf/reg-event-fx :communities/check-permissions-to-join-community
(fn [{:keys [db]} [community-id]]
(fn [{:keys [db]} [community-id addresses based-on-client-selection?]]
(when-let [community (get-in db [:communities community-id])]
(when-not (:checking-permissions? community)
{:db (-> db
(assoc-in [:communities community-id :checking-permissions?] true)
(assoc-in [:communities community-id :can-request-access?] false))
:json-rpc/call [{:method "wakuext_checkPermissionsToJoinCommunity"
:params [{:communityId community-id}]
:params [(cond-> {:communityId community-id}
addresses
(assoc :addresses addresses))]
:on-success [:communities/check-permissions-to-join-community-success
community-id]
community-id based-on-client-selection?]
:on-error (fn [err]
(rf/dispatch
[:communities/check-permissions-to-join-community-failed
@@ -16,6 +16,7 @@
[status-im.contexts.communities.actions.community-options.view :as options]
[status-im.contexts.communities.overview.style :as style]
[status-im.contexts.communities.overview.utils :as utils]
[status-im.contexts.communities.utils :as communities.utils]
[utils.debounce :as debounce]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
@@ -140,7 +141,11 @@
[]
(fn [{:keys [id color]}]
(let [{:keys [can-request-access?
number-of-hold-tokens tokens]} (rf/sub [:community/token-gated-overview id])]
number-of-hold-tokens tokens
highest-permission-role]} (rf/sub [:community/token-gated-overview id])
highest-role-text
(i18n/label
(communities.utils/role->translation-key highest-permission-role :t/member))]
[rn/view {:style (style/token-gated-container)}
[rn/view
{:style {:padding-horizontal 12
@@ -150,7 +155,7 @@
:flex 1}}
[quo/text {:weight :medium}
(if can-request-access?
(i18n/label :t/you-eligible-to-join)
(i18n/label :t/you-eligible-to-join-as {:role highest-role-text})
(i18n/label :t/you-not-eligible-to-join))]
[info-button]]
(when (pos? number-of-hold-tokens)
@@ -163,23 +168,26 @@
{:tokens tokens
:padding? true}]
[quo/button
{:on-press #(join-gated-community id)
{:on-press
(if config/community-accounts-selection-enabled?
#(rf/dispatch [:open-modal :community-account-selection {:community-id id}])
#(join-gated-community id))
:accessibility-label :join-community-button
:customization-color color
:container-style {:margin-horizontal 12 :margin-top 12 :margin-bottom 12}
:disabled? (not can-request-access?)
:icon-left (if can-request-access? :i/unlocked :i/locked)}
:container-style {:margin-horizontal 12 :margin-top 12 :margin-bottom 12}
:disabled? (not can-request-access?)
:icon-left (if can-request-access? :i/unlocked :i/locked)}
(i18n/label :t/join-open-community)]])))
(defn join-community
[{:keys [joined color permissions token-permissions id] :as community}
[{:keys [joined color permissions token-permissions membership-permissions? id] :as community}
pending?]
(let [access-type (get-access-type (:access permissions))
unknown-access? (= access-type :unknown-access)
invite-only? (= access-type :invite-only)]
[:<>
(when-not (or joined pending? invite-only? unknown-access?)
(if (seq token-permissions)
(if membership-permissions?
[token-gates community]
[quo/button
{:on-press
@@ -190,7 +198,7 @@
:accessibility-label :show-request-to-join-screen-button
:customization-color color
:icon-left :i/communities}
(i18n/label :t/request-to-join-community)]))
(i18n/label :t/request-to-join)]))
(when (not (or joined pending? token-permissions))
[quo/text
@@ -0,0 +1,13 @@
(ns status-im.contexts.communities.utils
(:require
[status-im.constants :as constants]))
(defn role->translation-key
([role] (role->translation-key role nil))
([role fallback-to]
(condp = role
constants/community-token-permission-become-token-owner :t/token-owner
constants/community-token-permission-become-token-master :t/token-master
constants/community-token-permission-become-admin :t/admin
constants/community-token-permission-become-member :t/member
fallback-to)))
@@ -21,7 +21,9 @@
{:key :currency
:type :select
:options [{:key :usd}
{:key :eur}]}])
{:key :eur}]}
{:key :error?
:type :boolean}])
(defn view
[]
@@ -16,7 +16,6 @@
[status-im.common.log :as logging]
[status-im.common.universal-links :as universal-links]
[status-im.config :as config]
[status-im.constants :as constants]
[status-im.contexts.chat.contacts.events :as contacts]
[status-im.contexts.chat.messenger.messages.link-preview.events :as link-preview]
[status-im.contexts.profile.config :as profile.config]
@@ -178,11 +177,11 @@
(rf/defn login-with-biometric-if-available
{:events [:profile.login/login-with-biometric-if-available]}
[_ key-uid]
{:keychain/get-auth-method [key-uid
#(rf/dispatch [:profile.login/get-auth-method-success % key-uid])]})
{:biometric/check-if-available [key-uid
#(rf/dispatch [:profile.login/check-biometric-success % key-uid])]})
(rf/defn get-auth-method-success
{:events [:profile.login/get-auth-method-success]}
(rf/defn check-biometric-success
{:events [:profile.login/check-biometric-success]}
[{:keys [db]} auth-method key-uid]
(merge {:db (assoc db :auth-method auth-method)}
(when (= auth-method keychain/auth-method-biometric)
@@ -194,7 +193,6 @@
:on-fail #(rf/dispatch
[:profile.login/biometric-auth-fail %])}]))}})))
;; result of :keychain/get-auth-method above
(rf/defn get-user-password-success
{:events [:profile.login/get-user-password-success]}
[{:keys [db] :as cofx} password]
@@ -209,22 +207,18 @@
:profile.login/biometric-success
(fn [{:keys [db]}]
(let [key-uid (get-in db [:profile/login :key-uid])]
{:db db
:fx [[:biometric/reset-not-enrolled-error key-uid]
[:keychain/get-user-password
[key-uid #(rf/dispatch [:profile.login/get-user-password-success %])]]]})))
{:keychain/get-user-password [key-uid
#(rf/dispatch [:profile.login/get-user-password-success %])]})))
(rf/reg-event-fx
:profile.login/biometric-auth-fail
(fn [{:keys [db]} [code]]
(let [key-uid (get-in db [:profile/login :key-uid])]
{:db db
:fx [(if (= code constants/biometric-error-not-enrolled)
[:biometric/supress-not-enrolled-error
[key-uid
[:biometric/show-message code]]]
[:dispatch [:biometric/show-message code]])]})))
(fn [_ [error]]
(log/error (ex-message error)
(-> error
ex-data
(assoc :code (ex-cause error)
:event :profile.login/biometric-auth-fail)))
{:dispatch [:biometric/show-message error]}))
(rf/defn verify-database-password
{:events [:profile.login/verify-database-password]}
@@ -2,6 +2,7 @@
(:require
[quo.foundations.colors :as colors]
[react-native.platform :as platform]
[react-native.reanimated :as reanimated]
[status-im.contexts.shell.jump-to.utils :as utils]))
(defn bottom-tabs-container
@@ -28,10 +29,11 @@
(defn bottom-tabs-blur-overlay
[height]
[{:height height}
(reanimated/apply-animations-to-style
{:height height}
{:position :absolute
:left 0
:right 0
:bottom 0
:height (utils/bottom-tabs-container-height)
:background-color colors/neutral-100-opa-70-blur}])
:background-color colors/neutral-100-opa-70-blur}))
@@ -15,12 +15,12 @@
(defn profile-tab
[]
(let [{:keys [emoji-hash
customization-color
universal-profile-url]
:as profile} (rf/sub [:profile/profile])
abbreviated-url (address/get-abbreviated-profile-url
universal-profile-url)
emoji-hash-string (string/join emoji-hash)]
:as profile} (rf/sub [:profile/profile-with-image])
customization-color (rf/sub [:profile/customization-color])
abbreviated-url (address/get-abbreviated-profile-url
universal-profile-url)
emoji-hash-string (string/join emoji-hash)]
[rn/scroll-view
{:content-container-style {:padding-bottom 16}}
[rn/view {:style style/qr-code-container}
@@ -0,0 +1,18 @@
(ns status-im.contexts.shell.share.wallet.style
(:require
[quo.foundations.colors :as colors]))
(defn indicator-wrapper-style
[active?]
{:width 8
:height 8
:border-radius 4
:background-color colors/white
:opacity (if active? 1.0 0.5)})
(def indicator-list-style
{:display :flex
:flex-direction :row
:align-items :center
:justify-content :center
:gap 8})
@@ -6,6 +6,7 @@
[react-native.share :as share]
[reagent.core :as reagent]
[status-im.contexts.shell.share.style :as style]
[status-im.contexts.shell.share.wallet.style :as wallet-style]
[status-im.contexts.wallet.common.sheets.network-preferences.view :as network-preferences]
[status-im.contexts.wallet.common.utils :as utils]
[utils.i18n :as i18n]
@@ -43,10 +44,11 @@
(rf/dispatch [:hide-bottom-sheet])
(reset! selected-networks (map #(get utils/id->network %)
chain-ids)))}])}]))
(defn wallet-qr-code-item
[account width index]
(let [selected-networks (reagent/atom [:ethereum :optimism :arbitrum])
wallet-type (reagent/atom :wallet-legacy)]
(defn- wallet-qr-code-item-internal
[props]
(let [{:keys [account width index]} props
selected-networks (reagent/atom [:ethereum :optimism :arbitrum])
wallet-type (reagent/atom :wallet-legacy)]
(fn []
(let [share-title (str (:name account) " " (i18n/label :t/address))
qr-url (utils/get-wallet-qr {:wallet-type @wallet-type
@@ -57,7 +59,7 @@
:port (rf/sub [:mediaserver/port])
:qr-size qr-size
:error-level :highest})]
[rn/view {:style {:width width :margin-left (if (zero? index) 0 -30)}}
[rn/view {:style {:height qr-size :width width :margin-left (if (zero? index) 0 -30)}}
[rn/view {:style style/qr-code-container}
[quo/share-qr-code
{:type @wallet-type
@@ -73,18 +75,54 @@
:on-legacy-press #(reset! wallet-type :wallet-legacy)
:on-settings-press #(open-preferences @selected-networks)}]]]))))
(def wallet-qr-code-item (memoize wallet-qr-code-item-internal))
(defn- indicator
[active?]
[rn/view
{:style (wallet-style/indicator-wrapper-style active?)}])
(defn- indicator-list
[indicator-count current-index]
[rn/view
{:style wallet-style/indicator-list-style}
(for [i (range indicator-count)]
(let [current-index (cond (<= current-index 0) 0
(>= current-index (dec indicator-count)) (dec indicator-count)
:else current-index)]
^{:key i} [indicator (= current-index i)]))])
(defn render-item
[item]
(let [width (rf/sub [:dimensions/window-width])]
[wallet-qr-code-item
{:account item
:index (:position item)
:width width}]))
(defn wallet-tab
[]
(let [accounts (rf/sub [:wallet/accounts])
width (rf/sub [:dimensions/window-width])]
[rn/flat-list
{:horizontal true
:deceleration-rate 0.9
:snap-to-alignment "start"
:snap-to-interval (- width 30)
:disable-interval-momentum true
:scroll-event-throttle 64
:data accounts
:directional-lock-enabled true
:render-fn (fn [account index]
(wallet-qr-code-item account width index))}]))
(let [accounts (rf/sub [:wallet/accounts])
width (rf/sub [:dimensions/window-width])
current-index (reagent/atom 0)]
(fn []
[rn/view
[rn/flat-list
{:horizontal true
:deceleration-rate 0.9
:snap-to-alignment :start
:snap-to-interval (- width 30)
:disable-interval-momentum true
:scroll-event-throttle 64
:data accounts
:directional-lock-enabled true
:shows-horizontal-scroll-indicator false
:on-scroll (fn [e]
(reset! current-index (js/Math.ceil
(/ e.nativeEvent.contentOffset.x
width))))
:render-fn render-item}]
(when (> (count accounts) 1)
[rn/view
{:style {:margin-top 20}}
(indicator-list (count accounts) @current-index)])])))
@@ -13,6 +13,14 @@
[item]
{:source (quo.resources/get-network (:network-name item))})
(defn- bridge-token-component
[]
(fn [token]
(let [on-press #(rf/dispatch [:wallet/select-bridge-to
{:token token
:stack-id :wallet-bridge}])]
[quo/token-network (assoc token :on-press on-press)])))
(defn view
[]
(let [networks (rf/sub [:wallet/network-details])
@@ -30,5 +38,5 @@
:placeholder (i18n/label :t/search-assets)}]
[rn/flat-list
{:data (temp/bridge-token-list networks-logos)
:render-fn quo/token-network
:render-fn bridge-token-component
:content-container-style style/list-content-container}]]))
@@ -0,0 +1,9 @@
(ns status-im.contexts.wallet.account.bridge-to.style)
(def header-container
{:padding-horizontal 20
:padding-vertical 12})
(def content-container
{:padding-horizontal 8})
@@ -0,0 +1,64 @@
(ns status-im.contexts.wallet.account.bridge-to.view
(:require
[clojure.string :as string]
[quo.core :as quo]
[quo.foundations.resources :as quo.resources]
[quo.theme :as quo.theme]
[react-native.core :as rn]
[status-im.contexts.wallet.account.bridge-to.style :as style]
[status-im.contexts.wallet.common.account-switcher.view :as account-switcher]
[status-im.contexts.wallet.common.utils :as utils]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
(defn- bridge-token-component
[]
(fn [{:keys [chain-id network-name]} token]
(let [network (rf/sub [:wallet/network-details-by-chain-id chain-id])
currency (rf/sub [:profile/currency])
currency-symbol (rf/sub [:profile/currency-symbol])
all-balances (:balances-per-chain token)
balance-for-chain (utils/get-balance-for-chain all-balances chain-id)
crypto-formatted (or (:balance balance-for-chain) "0.00")
fiat-value (utils/token-fiat-value currency
(or (:balance balance-for-chain) 0)
token)
fiat-formatted (utils/get-standard-fiat-format crypto-formatted currency-symbol fiat-value)]
[quo/network-list
{:label (name network-name)
:network-image (quo.resources/get-network (:network-name network))
:token-value (str crypto-formatted " " (:symbol token))
:fiat-value fiat-formatted}])))
(defn- view-internal
[]
(let [send-bridge-data (rf/sub [:wallet/wallet-send])
network-details (rf/sub [:wallet/network-details])
account (rf/sub [:wallet/current-viewing-account])
token (:token send-bridge-data)
token-symbol (:symbol token)
tokens (:tokens account)
mainnet (first network-details)
layer-2-networks (rest network-details)
account-token (some #(when (= token-symbol (:symbol %)) %) tokens)]
[rn/view
[account-switcher/view
{:on-press #(rf/dispatch [:navigate-back-within-stack :wallet-bridge-to])
:icon-name :i/arrow-left
:accessibility-label :top-bar}]
[quo/text-combinations
{:container-style style/header-container
:title (i18n/label :t/bridge-to {:name (string/upper-case (str (:label token)))})}]
[rn/view style/content-container
[bridge-token-component (assoc mainnet :network-name :t/mainnet) account-token]]
[quo/divider-label (i18n/label :t/layer-2)]
[rn/flat-list
{:data layer-2-networks
:render-fn (fn [network]
[bridge-token-component network account-token])
:content-container-style style/content-container}]]))
(def view (quo.theme/with-theme view-internal))
@@ -3,6 +3,7 @@
[quo.core :as quo]
[react-native.core :as rn]
[reagent.core :as reagent]
[status-im.config :as config]
[status-im.contexts.wallet.account.style :as style]
[status-im.contexts.wallet.account.tabs.view :as tabs]
[status-im.contexts.wallet.common.account-switcher.view :as account-switcher]
@@ -52,7 +53,9 @@
:receive-action #(rf/dispatch [:open-modal :wallet-share-address {:status :receive}])
:buy-action #(rf/dispatch [:show-bottom-sheet
{:content buy-drawer}])
:bridge-action #(rf/dispatch [:open-modal :wallet-bridge])}])
:bridge-action (if (:bridge-token config/wallet-feature-flags)
#(rf/dispatch [:open-modal :wallet-bridge])
#(js/alert "feature disabled in config file"))}])
[quo/tabs
{:style style/tabs
:size 32
@@ -122,17 +122,17 @@
(rf/dispatch [:wallet/clean-scanned-address])
(rf/dispatch [:wallet/clear-address-activity-check])
(rf/dispatch [:navigate-back]))}]
:footer
[quo/button
{:customization-color customization-color
:disabled? (or (string/blank? @input-value) (some? (validate @input-value)))
:on-press (fn []
(rf/dispatch [:navigate-to
:confirm-address-to-watch
{:address @input-value}])
(clear-input))
:container-style {:z-index 2}}
(i18n/label :t/continue)]}
:footer [quo/button
{:customization-color customization-color
:disabled? (or (string/blank? @input-value)
(some? (validate @input-value)))
:on-press (fn []
(rf/dispatch [:navigate-to
:confirm-address-to-watch
{:address @input-value}])
(clear-input))
:container-style {:z-index 2}}
(i18n/label :t/continue)]}
[quo/text-combinations
{:container-style style/header-container
:title (i18n/label :t/add-address)
@@ -3,7 +3,6 @@
[quo.theme]
[react-native.core :as rn]
[status-im.contexts.wallet.collectible.tabs.about.style :as style]
[status-im.contexts.wallet.temp :as temp]
[utils.i18n :as i18n]
[utils.re-frame :as rf]))
@@ -14,7 +13,8 @@
(let [window-width (rf/sub [:dimensions/window-width])
item-width (- (/ window-width 2) link-card-space)
{:keys [collectible-data]} (rf/sub [:wallet/last-collectible-details])
link-card-container-style (style/link-card item-width)]
link-card-container-style (style/link-card item-width)
collectible-about {:cards []}]
[:<>
[rn/view {:style style/title}
[quo/text
@@ -25,12 +25,13 @@
[quo/text
{:size :paragraph-2}
(:description collectible-data)]]
[quo/section-label
{:container-style style/section-label
:section (i18n/label :t/on-the-web)}]
[rn/view {:style style/link-cards-container}
(for [item (:cards temp/collectible-about)]
^{:key (:title item)}
[quo/link-card (assoc item :container-style link-card-container-style)])]]))
(when (count collectible-about)
[quo/section-label
{:container-style style/section-label
:section (i18n/label :t/on-the-web)}]
[rn/view {:style style/link-cards-container}
(for [item (:cards collectible-about)]
^{:key (:title item)}
[quo/link-card (assoc item :container-style link-card-container-style)])])]))
(def view (quo.theme/with-theme view-internal))
@@ -1,7 +1,6 @@
(ns status-im.contexts.wallet.collectible.tabs.activity.view
(:require [quo.core :as quo]
[react-native.core :as rn]
[status-im.contexts.wallet.temp :as temp]))
[react-native.core :as rn]))
(defn activity-item
[item]
@@ -12,6 +11,6 @@
(defn view
[]
[rn/flat-list
{:data temp/collectible-activities
{:data nil
:style {:flex 1}
:render-fn activity-item}])
@@ -3,6 +3,7 @@
[quo.core :as quo]
[quo.theme :as quo.theme]
[react-native.core :as rn]
[react-native.svg :as svg]
[reagent.core :as reagent]
[status-im.common.scroll-page.view :as scroll-page]
[status-im.contexts.wallet.collectible.style :as style]
@@ -75,12 +76,18 @@
(let [selected-tab (reagent/atom :overview)
on-tab-change #(reset! selected-tab %)]
(fn []
(let [collectible (rf/sub [:wallet/last-collectible-details])
{:keys [collectible-data preview-url
collection-data]} collectible
(let [collectible (rf/sub [:wallet/last-collectible-details])
animation-shared-element-id (rf/sub [:animation-shared-element-id])
{:keys [id
preview-url
collection-data
collectible-data]} collectible
{svg? :svg?
preview-uri :uri} preview-url
token-id (:token-id id)
{collection-image :image-url
collection-name :name} collection-data
{collectible-name :name} collectible-data]
collection-name :name} collection-data
{collectible-name :name} collectible-data]
[scroll-page/scroll-page
{:navigate-back? true
:height 148
@@ -92,12 +99,38 @@
[:show-bottom-sheet
{:content collectible-actions-sheet
:theme theme}])}]
:picture preview-url}}
:picture preview-uri}}
[rn/view {:style style/container}
[rn/view {:style style/preview-container}
[rn/image
{:source preview-url
:style style/preview}]]
[rn/touchable-opacity
{:active-opacity 1
:on-press (fn []
(if svg?
(js/alert "Can't visualize SVG images in lightbox")
(rf/dispatch [:lightbox/navigate-to-lightbox
token-id
{:images [{:image preview-uri
:image-width 300 ; collectibles don't have
; width/height but we need
; to pass something
:image-height 300 ; without it animation
; doesn't work smoothly and
; :border-radius not
; applied
:id token-id
:header collectible-name
:description collection-name}]
:index 0}])))}
(if svg?
[rn/view
{:style (assoc style/preview :overflow :hidden)
:native-ID (when (= animation-shared-element-id token-id)
:shared-element)}
[svg/svg-uri (assoc style/preview :uri preview-uri)]]
[rn/image
{:source preview-uri
:style style/preview
:native-ID (when (= animation-shared-element-id token-id) :shared-element)}])]]
[header collectible-name collection-name collection-image]
[cta-buttons]
[quo/tabs
@@ -52,6 +52,7 @@
:fiat-value "€0.00"
:networks networks-list
:state :default
:symbol "STT"
:customization-color :blue}
{:token :eth
:label "Ethereum"
@@ -59,6 +60,7 @@
:fiat-value "€0.00"
:networks networks-list
:state :default
:symbol "ETH"
:customization-color :blue}
{:token :dai
:label "Dai"
@@ -66,6 +68,7 @@
:fiat-value "€0.00"
:networks networks-list
:state :default
:symbol "DAI"
:customization-color :blue}])
(def secret-phrase
@@ -41,10 +41,12 @@
(defn view
[item]
[quo/token-value
(merge item
{:on-long-press
#(rf/dispatch
[:show-bottom-sheet
{:content (fn [] [token-value-drawer item])
:selected-item (fn [] [quo/token-value item])}])})])
(let [{:keys [watch-only?]} (rf/sub [:wallet/current-viewing-account])]
[quo/token-value
(cond-> item
(not watch-only?)
(assoc :on-long-press
#(rf/dispatch
[:show-bottom-sheet
{:content (fn [] [token-value-drawer item])
:selected-item (fn [] [quo/token-value item])}])))]))
@@ -106,6 +106,16 @@
total-units-in-all-chains (total-token-units-in-all-chains token)]
(money/crypto->fiat total-units-in-all-chains price)))
(defn token-fiat-value
"Returns the fiat value for a single token on a given network."
[currency raw-balance {:keys [market-values-per-currency]}]
(let [price (or (get-in market-values-per-currency
[currency :price])
(get-in market-values-per-currency
[constants/profile-default-currency :price])
0)]
(money/crypto->fiat raw-balance price)))
(defn calculate-balance-for-account
[currency {:keys [tokens] :as _account}]
(->> tokens
@@ -185,6 +195,11 @@
constants/arbitrum-chain-id :arbitrum
constants/arbitrum-test-chain-id :arbitrum})
(def short-name->id
{:eth constants/mainnet-chain-id
:opt constants/optimism-chain-id
:arb1 constants/arbitrum-chain-id})
(defn get-standard-fiat-format
[crypto-value currency-symbol fiat-value]
(if (string/includes? crypto-value "<")
@@ -226,3 +241,7 @@
[input-string]
(let [split-result (string/split input-string #"0x")]
[(first split-result) (str "0x" (second split-result))]))
(defn get-balance-for-chain
[data chain-id]
(some #(when (= chain-id (:chain-id %)) %) (vals data)))
@@ -9,6 +9,7 @@
[reagent.core :as reagent]
[status-im.common.emoji-picker.utils :as emoji-picker.utils]
[status-im.common.standard-authentication.core :as standard-auth]
[status-im.config :as config]
[status-im.constants :as constants]
[status-im.contexts.wallet.common.utils :as utils]
[status-im.contexts.wallet.create-account.style :as style]
@@ -31,7 +32,9 @@
:size :xxs
:customization-color account-color}
:action :button
:action-props {:on-press #(rf/dispatch [:navigate-to :wallet-select-keypair])
:action-props {:on-press (if (:edit-default-keypair config/wallet-feature-flags)
#(rf/dispatch [:navigate-to :wallet-select-keypair])
#(js/alert "feature disabled in config file"))
:button-text (i18n/label :t/edit)
:alignment :flex-start}
:description :text
@@ -40,7 +43,7 @@
:image :icon
:image-props :i/derivated-path
:action :button
:action-props {:on-press #(js/alert "Button pressed!")
:action-props {:on-press #(js/alert "Coming soon!")
:button-text (i18n/label :t/edit)
:icon-left :i/placeholder
:alignment :flex-start}
+10 -21
View File
@@ -6,7 +6,6 @@
[status-im.contexts.wallet.data-store :as data-store]
[status-im.contexts.wallet.events.collectibles]
[status-im.contexts.wallet.item-types :as item-types]
[status-im.contexts.wallet.temp :as temp]
[taoensso.timbre :as log]
[utils.ethereum.chain :as chain]
[utils.ethereum.eip.eip55 :as eip55]
@@ -179,6 +178,11 @@
(first derived-address-details)]))]
{:fx [[:dispatch [:wallet/create-derived-addresses account-details on-success]]]})))
(rf/reg-event-fx :wallet/select-bridge-to
(fn [{:keys [db]} [{:keys [token stack-id]}]]
{:db (assoc-in db [:wallet :ui :send :token] token)
:fx [[:navigate-to-within-stack [:wallet-bridge-to stack-id]]]}))
(rf/reg-event-fx
:wallet/get-ethereum-chains
(fn [_]
@@ -242,30 +246,15 @@
(rf/reg-event-fx :wallet/fetch-address-suggestions
(fn [{:keys [db]} [address]]
(fn [{:keys [db]} [_address]]
{:db (assoc db
:wallet/local-suggestions
(cond
(= address
(get-in
temp/address-local-suggestion-saved-contact-address-mock
[:accounts 0 :address]))
[temp/address-local-suggestion-saved-contact-address-mock]
(= address
(get temp/address-local-suggestion-saved-address-mock
:address))
[temp/address-local-suggestion-saved-address-mock]
:else (temp/find-matching-addresses address))
:wallet/valid-ens-or-address?
false)}))
:wallet/local-suggestions nil
:wallet/valid-ens-or-address? false)}))
(rf/reg-event-fx :wallet/ens-validation-success
(fn [{:keys [db]} [ens]]
(fn [{:keys [db]} [_ens]]
{:db (assoc db
:wallet/local-suggestions (if (= ens
(:ens temp/ens-local-suggestion-saved-address-mock))
[temp/ens-local-suggestion-saved-address-mock]
[temp/ens-local-suggestion-mock])
:wallet/local-suggestions nil
:wallet/valid-ens-or-address? true)}))
(rf/reg-event-fx :wallet/address-validation-success

Some files were not shown because too many files have changed in this diff Show More