From a1e1176e903f6cfb3f755644b3b9cd0bc2042d5f Mon Sep 17 00:00:00 2001 From: erinn Date: Thu, 24 Jun 2021 17:59:54 -0700 Subject: [PATCH] notifications pass --- .../kotlin/im/cwtch/flwtch/FlwtchWorker.kt | 63 ++++++++++--------- .../kotlin/im/cwtch/flwtch/MainActivity.kt | 34 ++++++---- lib/cwtch/cwtchNotifier.dart | 2 +- lib/main.dart | 44 ++++++++++++- lib/model.dart | 1 + lib/views/profilemgrview.dart | 45 ++----------- 6 files changed, 104 insertions(+), 85 deletions(-) diff --git a/android/app/src/main/kotlin/im/cwtch/flwtch/FlwtchWorker.kt b/android/app/src/main/kotlin/im/cwtch/flwtch/FlwtchWorker.kt index 90d751cf..3936ecc1 100644 --- a/android/app/src/main/kotlin/im/cwtch/flwtch/FlwtchWorker.kt +++ b/android/app/src/main/kotlin/im/cwtch/flwtch/FlwtchWorker.kt @@ -61,35 +61,37 @@ class FlwtchWorker(context: Context, parameters: WorkerParameters) : val evt = MainActivity.AppbusEvent(Cwtch.getAppBusEvent()) if (evt.EventType == "NewMessageFromPeer" || evt.EventType == "NewMessageFromGroup") { val data = JSONObject(evt.Data) - val channelId = - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - createMessageNotificationChannel(data.getString("RemotePeer"), data.getString("RemotePeer")) - } else { - // If earlier version channel ID is not used - // https://developer.android.com/reference/android/support/v4/app/NotificationCompat.Builder.html#NotificationCompat.Builder(android.content.Context) - "" - } + if (data["RemotePeer"] != data["ProfileOnion"]) { + val channelId = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + createMessageNotificationChannel(data.getString("RemotePeer"), data.getString("RemotePeer")) + } else { + // If earlier version channel ID is not used + // https://developer.android.com/reference/android/support/v4/app/NotificationCompat.Builder.html#NotificationCompat.Builder(android.content.Context) + "" + } - val loader = FlutterInjector.instance().flutterLoader() - val key = loader.getLookupKeyForAsset("assets/"+data.getString("Picture"))//"assets/profiles/001-centaur.png") - val fh = applicationContext.assets.open(key) + val loader = FlutterInjector.instance().flutterLoader() + val key = loader.getLookupKeyForAsset("assets/" + data.getString("Picture"))//"assets/profiles/001-centaur.png") + val fh = applicationContext.assets.open(key) - val clickIntent = Intent(applicationContext, MainActivity::class.java).also { intent -> - intent.action = Intent.ACTION_RUN - intent.putExtra("EventType", "NotificationClicked") - intent.putExtra("ProfileOnion", data.getString("ProfileOnion")) - intent.putExtra("RemotePeer", if (evt.EventType == "NewMessageFromPeer") data.getString("RemotePeer") else data.getString("GroupID")) + val clickIntent = Intent(applicationContext, MainActivity::class.java).also { intent -> + intent.action = Intent.ACTION_RUN + intent.putExtra("EventType", "NotificationClicked") + intent.putExtra("ProfileOnion", data.getString("ProfileOnion")) + intent.putExtra("RemotePeer", if (evt.EventType == "NewMessageFromPeer") data.getString("RemotePeer") else data.getString("GroupID")) + } + + val newNotification = NotificationCompat.Builder(applicationContext, channelId) + .setContentTitle(data.getString("Nick")) + .setContentText("New message")//todo: translate + .setLargeIcon(BitmapFactory.decodeStream(fh)) + .setSmallIcon(R.mipmap.knott) + .setContentIntent(PendingIntent.getActivity(applicationContext, 1, clickIntent, PendingIntent.FLAG_UPDATE_CURRENT)) + .setAutoCancel(true) + .build() + notificationManager.notify(getNotificationID(data.getString("ProfileOnion"), data.getString("RemotePeer")), newNotification) } - - val newNotification = NotificationCompat.Builder(applicationContext, channelId) - .setContentTitle(data.getString("Nick")) - .setContentText("New message")//todo: translate - .setLargeIcon(BitmapFactory.decodeStream(fh)) - .setSmallIcon(R.mipmap.knott) - .setContentIntent(PendingIntent.getActivity(applicationContext, 1, clickIntent, PendingIntent.FLAG_UPDATE_CURRENT)) - .setAutoCancel(true) - .build() - notificationManager.notify(getNotificationID(data.getString("ProfileOnion"), data.getString("RemotePeer")), newNotification) } Intent().also { intent -> @@ -223,9 +225,10 @@ class FlwtchWorker(context: Context, parameters: WorkerParameters) : "" } - // This PendingIntent can be used to cancel the worker - val intent = WorkManager.getInstance(applicationContext) - .createCancelPendingIntent(getId()) + val cancelIntent = Intent(applicationContext, MainActivity::class.java).also { intent -> + intent.action = Intent.ACTION_RUN + intent.putExtra("EventType", "ShutdownClicked") + } val notification = NotificationCompat.Builder(applicationContext, channelId) .setContentTitle(title) @@ -235,7 +238,7 @@ class FlwtchWorker(context: Context, parameters: WorkerParameters) : .setOngoing(true) // Add the cancel action to the notification which can // be used to cancel the worker - .addAction(android.R.drawable.ic_delete, cancel, intent) + .addAction(android.R.drawable.ic_delete, cancel, PendingIntent.getActivity(applicationContext, 1, cancelIntent, PendingIntent.FLAG_UPDATE_CURRENT)) .build() return ForegroundInfo(101, notification) diff --git a/android/app/src/main/kotlin/im/cwtch/flwtch/MainActivity.kt b/android/app/src/main/kotlin/im/cwtch/flwtch/MainActivity.kt index c82d2e42..9f2ff6e6 100644 --- a/android/app/src/main/kotlin/im/cwtch/flwtch/MainActivity.kt +++ b/android/app/src/main/kotlin/im/cwtch/flwtch/MainActivity.kt @@ -36,27 +36,36 @@ class MainActivity: FlutterActivity() { // Channel to send eventbus events on private val CWTCH_EVENTBUS = "test.flutter.dev/eventBus" - // Channel to trigger contactview when an external notification is clicked + // Channels to trigger actions when an external notification is clicked private val CHANNEL_NOTIF_CLICK = "im.cwtch.flwtch/notificationClickHandler" + private val CHANNEL_SHUTDOWN_CLICK = "im.cwtch.flwtch/shutdownClickHandler" // WorkManager tag applied to all Start() infinite coroutines val WORKER_TAG = "cwtchEventBusWorker" private var myReceiver: MyBroadcastReceiver? = null - private var methodChan: MethodChannel? = null + private var notificationClickChannel: MethodChannel? = null + private var shutdownClickChannel: MethodChannel? = null override fun onNewIntent(intent: Intent) { super.onNewIntent(intent) - if (methodChan == null || intent.extras == null) return - if (!intent.extras!!.containsKey("ProfileOnion") || !intent.extras!!.containsKey("RemotePeer")) { - Log.i("onNewIntent", "got intent with no onions") - return + if (notificationClickChannel == null || intent.extras == null) return + + if (intent.extras!!.getString("EventType") == "NotificationClicked") { + if (!intent.extras!!.containsKey("ProfileOnion") || !intent.extras!!.containsKey("RemotePeer")) { + Log.i("onNewIntent", "got notification clicked intent with no onions") + return + } + val profile = intent.extras!!.getString("ProfileOnion") + val handle = intent.extras!!.getString("RemotePeer") + val mappo = mapOf("ProfileOnion" to profile, "RemotePeer" to handle) + val j = JSONObject(mappo) + notificationClickChannel!!.invokeMethod("NotificationClicked", j.toString()) + } else if (intent.extras!!.getString("EventType") == "ShutdownClicked") { + shutdownClickChannel!!.invokeMethod("ShutdownClicked", "") + } else { + print("warning: received intent with unknown method; ignoring") } - val profile = intent.extras!!.getString("ProfileOnion") - val handle = intent.extras!!.getString("RemotePeer") - val mappo = mapOf("ProfileOnion" to profile, "RemotePeer" to handle) - val j = JSONObject(mappo) - methodChan!!.invokeMethod("NotificationClicked", j.toString()) } override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) { @@ -66,7 +75,8 @@ class MainActivity: FlutterActivity() { requestWindowFeature(Window.FEATURE_NO_TITLE) MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL_APP_INFO).setMethodCallHandler { call, result -> handleAppInfo(call, result) } MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL_CWTCH).setMethodCallHandler { call, result -> handleCwtch(call, result) } - methodChan = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL_NOTIF_CLICK) + notificationClickChannel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL_NOTIF_CLICK) + shutdownClickChannel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL_SHUTDOWN_CLICK) } private fun handleAppInfo(@NonNull call: MethodCall, @NonNull result: Result) { diff --git a/lib/cwtch/cwtchNotifier.dart b/lib/cwtch/cwtchNotifier.dart index c3ebc615..ce52a441 100644 --- a/lib/cwtch/cwtchNotifier.dart +++ b/lib/cwtch/cwtchNotifier.dart @@ -124,7 +124,7 @@ class CwtchNotifier { break; case "NewMessageFromGroup": if (data["ProfileOnion"] != data["RemotePeer"]) { - //not from me + //if not currently open if (appState.selectedProfile != data["ProfileOnion"] || appState.selectedConversation != data["GroupID"]) { profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["GroupID"])!.unreadMessages++; } diff --git a/lib/main.dart b/lib/main.dart index bb9fd44e..fddc0ec0 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -47,7 +47,7 @@ class FlwtchState extends State { late Cwtch cwtch; late ProfileListState profs; final MethodChannel notificationClickChannel = MethodChannel('im.cwtch.flwtch/notificationClickHandler'); - final MethodChannel shutdownMethodChannel = MethodChannel('im.cwtch.flwtch/shutdown'); + final MethodChannel shutdownMethodChannel = MethodChannel('im.cwtch.flwtch/shutdownClickHandler'); final GlobalKey navKey = GlobalKey(); @override @@ -58,7 +58,7 @@ class FlwtchState extends State { print("initState: registering notification, shutdown handlers..."); profs = ProfileListState(); notificationClickChannel.setMethodCallHandler(_externalNotificationClicked); - shutdownMethodChannel.setMethodCallHandler(shutdown); + shutdownMethodChannel.setMethodCallHandler(modalShutdown); print("initState: creating cwtchnotifier, ffi"); if (Platform.isAndroid) { var cwtchNotifier = new CwtchNotifier(profs, globalSettings, globalErrorHandler, globalTorStatus, NullNotificationsManager(), globalAppState); @@ -111,7 +111,45 @@ class FlwtchState extends State { ); } - Future shutdown(MethodCall call) async { + // invoked from either ProfileManagerView's appbar close button, or a ShutdownClicked event on + // the MyBroadcastReceiver method channel + Future modalShutdown(MethodCall mc) async { + // set up the buttons + Widget cancelButton = TextButton( + child: Text(AppLocalizations.of(navKey.currentContext!)!.cancel), + onPressed: () { + Navigator.of(navKey.currentContext!).pop(); // dismiss dialog + }, + ); + Widget continueButton = TextButton( + child: Text(AppLocalizations.of(navKey.currentContext!)!.shutdownCwtchAction), + onPressed: () { + // Directly call the shutdown command, Android will do this for us... + Provider.of(navKey.currentContext!, listen: false).shutdown(); + Provider.of(navKey.currentContext!, listen: false).cwtchIsClosing = true; + }); + + // set up the AlertDialog + AlertDialog alert = AlertDialog( + title: Text(AppLocalizations.of(navKey.currentContext!)!.shutdownCwtchDialogTitle), + content: Text(AppLocalizations.of(navKey.currentContext!)!.shutdownCwtchDialog), + actions: [ + cancelButton, + continueButton, + ], + ); + + // show the dialog + showDialog( + context: navKey.currentContext!, + barrierDismissible: false, + builder: (BuildContext context) { + return alert; + }, + ); + } + + Future shutdown() async { cwtch.Shutdown(); // Wait a few seconds as shutting down things takes a little time.. Future.delayed(Duration(seconds: 2)).then((value) { diff --git a/lib/model.dart b/lib/model.dart index 566ab5e4..4fdeb076 100644 --- a/lib/model.dart +++ b/lib/model.dart @@ -63,6 +63,7 @@ class ProfileListState extends ChangeNotifier { class AppState extends ChangeNotifier { bool cwtchInit = false; + bool cwtchIsClosing = false; String appError = ""; String? _selectedProfile; String? _selectedConversation; diff --git a/lib/views/profilemgrview.dart b/lib/views/profilemgrview.dart index be700df5..2e65a2f8 100644 --- a/lib/views/profilemgrview.dart +++ b/lib/views/profilemgrview.dart @@ -28,8 +28,6 @@ class ProfileMgrView extends StatefulWidget { class _ProfileMgrViewState extends State { final ctrlrPassword = TextEditingController(); - bool closeApp = false; - @override void dispose() { ctrlrPassword.dispose(); @@ -45,8 +43,8 @@ class _ProfileMgrViewState extends State { builder: (context, settings, child) => WillPopScope( onWillPop: () async { - _showShutdown(); - return closeApp; + _modalShutdown(); + return Provider.of(context, listen: false).cwtchIsClosing; }, child: Scaffold( backgroundColor: settings.theme.backgroundMainColor(), @@ -108,45 +106,14 @@ class _ProfileMgrViewState extends State { // Global Settings actions.add(IconButton(icon: Icon(Icons.settings), tooltip: AppLocalizations.of(context)!.tooltipOpenSettings, onPressed: _pushGlobalSettings)); - actions.add(IconButton(icon: Icon(Icons.close), tooltip: AppLocalizations.of(context)!.shutdownCwtchTooltip, onPressed: _showShutdown)); + // shutdown cwtch + actions.add(IconButton(icon: Icon(Icons.close), tooltip: AppLocalizations.of(context)!.shutdownCwtchTooltip, onPressed: _modalShutdown)); return actions; } - _showShutdown() { - // set up the buttons - Widget cancelButton = TextButton( - child: Text(AppLocalizations.of(context)!.cancel), - onPressed: () { - Navigator.of(context).pop(); // dismiss dialog - }, - ); - Widget continueButton = TextButton( - child: Text(AppLocalizations.of(context)!.shutdownCwtchAction), - onPressed: () { - // Directly call the shutdown command, Android will do this for us... - Provider.of(context, listen: false).shutdown(MethodCall("")); - closeApp = true; - }); - - // set up the AlertDialog - AlertDialog alert = AlertDialog( - title: Text(AppLocalizations.of(context)!.shutdownCwtchDialogTitle), - content: Text(AppLocalizations.of(context)!.shutdownCwtchDialog), - actions: [ - cancelButton, - continueButton, - ], - ); - - // show the dialog - showDialog( - context: context, - barrierDismissible: false, - builder: (BuildContext context) { - return alert; - }, - ); + void _modalShutdown() { + Provider.of(context, listen: false).modalShutdown(MethodCall("")); } void _pushGlobalSettings() {