From 9c69275fe64f6ab8eb29ac806fe8970e8559375c Mon Sep 17 00:00:00 2001 From: erinn Date: Fri, 11 Jun 2021 14:28:20 -0700 Subject: [PATCH] android service and notification support take one. ACTION --- android/app/build.gradle | 5 +- android/app/src/main/AndroidManifest.xml | 3 + .../kotlin/im/cwtch/flwtch/FlwtchWorker.kt | 145 ++++++++++++------ .../kotlin/im/cwtch/flwtch/MainActivity.kt | 145 +++++++++++++----- lib/cwtch/cwtch.dart | 4 +- lib/cwtch/ffi.dart | 14 +- lib/cwtch/gomobile.dart | 10 +- lib/main.dart | 37 ++++- lib/model.dart | 1 + 9 files changed, 268 insertions(+), 96 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 91220d1..58459a0 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -114,6 +114,7 @@ dependencies { // end of workmanager deps - // ipc - implementation "io.reactivex:rxkotlin:1.x.y" + // needed to prevent a ListenableFuture dependency conflict/bug + // see https://github.com/google/ExoPlayer/issues/7905#issuecomment-692637059 + implementation 'com.google.guava:guava:any' } diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index a0ea053..ec51df3 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -45,4 +45,7 @@ + + + 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 1ff7a92..97c3024 100644 --- a/android/app/src/main/kotlin/im/cwtch/flwtch/FlwtchWorker.kt +++ b/android/app/src/main/kotlin/im/cwtch/flwtch/FlwtchWorker.kt @@ -1,21 +1,19 @@ package im.cwtch.flwtch -import android.app.Notification -import android.app.NotificationChannel -import android.app.NotificationManager +import android.app.* import android.content.Context +import android.content.Context.ACTIVITY_SERVICE +import android.content.Intent +import android.graphics.BitmapFactory import android.graphics.Color import android.os.Build import android.util.Log import androidx.annotation.RequiresApi import androidx.core.app.NotificationCompat +import androidx.localbroadcastmanager.content.LocalBroadcastManager import androidx.work.* import cwtch.Cwtch -import io.flutter.embedding.engine.FlutterEngine -import io.flutter.plugin.common.MethodChannel -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.launch +import io.flutter.FlutterInjector import org.json.JSONObject @@ -27,6 +25,9 @@ class FlwtchWorker(context: Context, parameters: WorkerParameters) : context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + private var notificationID: MutableMap = mutableMapOf() + private var notificationIDnext: Int = 1; + override suspend fun doWork(): Result { val method = inputData.getString(KEY_METHOD) ?: return Result.failure() @@ -36,38 +37,96 @@ class FlwtchWorker(context: Context, parameters: WorkerParameters) : // Mark the Worker as important val progress = "Trying to do a Flwtch" setForeground(createForegroundInfo(progress)) - handleCwtch(method, args) - return Result.success() + return handleCwtch(method, args) } - private suspend fun handleCwtch(method: String, args: String) { - var a = JSONObject(args); + private fun getNotificationID(profile: String, contact: String): Int { + val k = "$profile $contact" + if (!notificationID.containsKey(k)) { + notificationID[k] = notificationIDnext++ + } + return notificationID[k] ?: -1 + } + + private suspend fun handleCwtch(method: String, args: String): Result { + val a = JSONObject(args); when (method) { "Start" -> { Log.i("FlwtchWorker.kt", "handleAppInfo Start") - val appDir = (a.get("appDir") as? String) ?: ""; - val torPath = (a.get("torPath") as? String) ?: "tor"; - Log.i("FlwtchWorker.kt", " appDir: '" + appDir + "' torPath: '" + torPath + "'") + val appDir = (a.get("appDir") as? String) ?: "" + val torPath = (a.get("torPath") as? String) ?: "tor" + Log.i("FlwtchWorker.kt", "appDir: '$appDir' torPath: '$torPath'") - Cwtch.startCwtch(appDir, torPath) + if (Cwtch.startCwtch(appDir, torPath) != 0.toByte()) return Result.failure() // infinite coroutine :) while(true) { val evt = MainActivity.AppbusEvent(Cwtch.getAppBusEvent()) - Log.i("FlwtchWorker.kt", "got appbusEvent: " + evt) + //Log.i("FlwtchWorker.kt", "got appbusEvent: " + evt) if (isStopped) { Log.i("FlwtchWorker.kt", "COROUTINEWORKER DOT ISSTOPPED TRUE OH MY") } - //todo: this elides evt.EventID which may be needed at some point? - val data = Data.Builder() - .putString("EventType", evt.EventType) - .putString("Data", evt.Data) - .putString("EventID", evt.EventID) + + if (evt.EventType == "NewMessageFromPeer") { + val data = JSONObject(evt.Data) + +// val appProcesses: List = (applicationContext.getSystemService(ACTIVITY_SERVICE) as ActivityManager).runningAppProcesses +// for (appProcess in appProcesses) { +// if (appProcess.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) { +// Log.i("Foreground App", appProcess.processName) +// } +// } + 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 clickIntent = Intent(applicationContext, MainActivity::class.java).also { intent -> + intent.setAction(Intent.ACTION_RUN)//("im.cwtch.flwtch.broadcast.SERVICE_EVENT_BUS") + //intent.setClassName("im.cwtch.flwtch", "MainActivity") + intent.putExtra("EventType", "NotificationClicked") + intent.putExtra("ProfileOnion", data.getString("ProfileOnion")) + intent.putExtra("RemotePeer", data.getString("RemotePeer")) + } + + val newNotification = NotificationCompat.Builder(applicationContext, channelId) + .setContentTitle(data.getString("Nick")) + .setContentText("New message") + .setLargeIcon(BitmapFactory.decodeStream(fh)) + .setSmallIcon(R.mipmap.knott) + .setContentIntent(PendingIntent.getActivity(applicationContext, 1, clickIntent, PendingIntent.FLAG_UPDATE_CURRENT)) + .setAutoCancel(true) .build() - setProgress(data) - Thread.sleep(200) + notificationManager.notify(getNotificationID(data.getString("ProfileOnion"), data.getString("RemotePeer")), newNotification) + } + //todo: this elides evt.EventID which may be needed at some point? +// val data = Data.Builder() +// .putString("EventType", evt.EventType) +// .putString("Data", evt.Data) +// .putString("EventID", evt.EventID) +// .build() + //setProgress(data)//progress can only hold a single undelivered value so it's possible for observers to miss rapidfire updates + //Thread.sleep(200)//this is a kludge to make it mostly-work until a proper channel is implemented + Intent().also { intent -> + intent.setAction("im.cwtch.flwtch.broadcast.SERVICE_EVENT_BUS") + intent.putExtra("EventType", evt.EventType) + intent.putExtra("Data", evt.Data) + intent.putExtra("EventID", evt.EventID) + LocalBroadcastManager.getInstance(applicationContext).sendBroadcast(intent) + } } } + "ReconnectCwtchForeground" -> { + Cwtch.reconnectCwtchForeground() + } "SelectProfile" -> { val onion = (a.get("profile") as? String) ?: ""; Cwtch.selectProfile(onion) @@ -87,7 +146,7 @@ class FlwtchWorker(context: Context, parameters: WorkerParameters) : "NumMessages" -> { val profile = (a.get("profile") as? String) ?: ""; val handle = (a.get("contact") as? String) ?: ""; - Result.success(Data.Builder().putLong("result", Cwtch.numMessages(profile, handle)).build()) + return Result.success(Data.Builder().putLong("result", Cwtch.numMessages(profile, handle)).build()) } "GetMessage" -> { //Log.i("MainActivivity.kt", (a.get("index"))); @@ -100,14 +159,14 @@ class FlwtchWorker(context: Context, parameters: WorkerParameters) : val handle = (a.get("contact") as? String) ?: ""; val indexI = a.getInt("index") ?: 0; Log.i("FlwtchWorker.kt", "indexI = " + indexI) - Result.success(Data.Builder().putString("result", Cwtch.getMessage(profile, handle, indexI.toLong())).build()) + return Result.success(Data.Builder().putString("result", Cwtch.getMessage(profile, handle, indexI.toLong())).build()) } "GetMessages" -> { val profile = (a.get("profile") as? String) ?: ""; val handle = (a.get("contact") as? String) ?: ""; val start = (a.get("start") as? Long) ?: 0; val end = (a.get("end") as? Long) ?: 0; - Result.success(Data.Builder().putString("result", Cwtch.getMessages(profile, handle, start, end)).build()) + return Result.success(Data.Builder().putString("result", Cwtch.getMessages(profile, handle, start, end)).build()) } "AcceptContact" -> { val profile = (a.get("ProfileOnion") as? String) ?: ""; @@ -177,26 +236,9 @@ class FlwtchWorker(context: Context, parameters: WorkerParameters) : val groupHandle = (a.get("groupHandle") as? String) ?: ""; Cwtch.rejectInvite(profile, groupHandle); } - else -> Result.failure() - } - } - - private suspend fun launchCwtch(appDir: String, torPath: String) { - Cwtch.startCwtch(appDir, torPath) - // seperate coroutine to poll event bus and send to dart - //Log.i("FlwtchWorker.kt", "got event chan: " + eventbus_chan + " launching corouting...") - GlobalScope.launch(Dispatchers.IO) { - while(true) { - val evt = AppbusEvent(Cwtch.getAppBusEvent()) - Log.i("FlwtchWorker.kt", "got appbusEvent: " + evt) - launch(Dispatchers.Main) { - //todo: this elides evt.EventID which may be needed at some point? - val flutterEngine = FlutterEngine(applicationContext) - val eventbus_chan = MethodChannel(flutterEngine?.dartExecutor?.binaryMessenger, CWTCH_EVENTBUS) - eventbus_chan.invokeMethod(evt.EventType, evt.Data) - } - } + else -> return Result.failure() } + return Result.success() } // Creates an instance of ForegroundInfo which can be used to update the @@ -243,6 +285,17 @@ class FlwtchWorker(context: Context, parameters: WorkerParameters) : return channelId } + + @RequiresApi(Build.VERSION_CODES.O) + private fun createMessageNotificationChannel(channelId: String, channelName: String): String{ + val chan = NotificationChannel(channelId, + channelName, NotificationManager.IMPORTANCE_HIGH) + chan.lightColor = Color.MAGENTA + chan.lockscreenVisibility = Notification.VISIBILITY_PRIVATE + notificationManager.createNotificationChannel(chan) + return channelId + } + companion object { const val KEY_METHOD = "KEY_METHOD" const val KEY_ARGS = "KEY_ARGS" 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 d50dc49..3820914 100644 --- a/android/app/src/main/kotlin/im/cwtch/flwtch/MainActivity.kt +++ b/android/app/src/main/kotlin/im/cwtch/flwtch/MainActivity.kt @@ -1,12 +1,20 @@ package im.cwtch.flwtch import SplashView +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter import androidx.annotation.NonNull import android.content.pm.PackageManager +import android.os.Build import android.os.Bundle import android.os.Looper import android.util.Log +import android.widget.Toast +import androidx.annotation.RequiresApi import androidx.lifecycle.Observer +import androidx.localbroadcastmanager.content.LocalBroadcastManager import androidx.work.* import kotlinx.coroutines.Dispatchers @@ -27,6 +35,8 @@ import kotlin.concurrent.thread import org.json.JSONObject import java.io.File +import java.time.Duration +import java.util.concurrent.TimeUnit class MainActivity: FlutterActivity() { @@ -42,11 +52,32 @@ 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 + private val CHANNEL_NOTIF_CLICK = "im.cwtch.flwtch/notificationClickHandler" + + private var methodChan: 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 + } + val profile = intent.extras!!.getString("ProfileOnion") + val handle = intent.extras!!.getString("RemotePeer") + val mappo = mapOf("ProfileOnion" to profile, "RemotePeer" to handle) + Log.i("MainActivity.kt", "onNewIntent($profile, $handle)") + val j = JSONObject(mappo) + methodChan!!.invokeMethod("NotificationClicked", j.toString()) + } + override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) { super.configureFlutterEngine(flutterEngine) // Note: this methods are invoked on the main thread. 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) } private fun handleAppInfo(@NonNull call: MethodCall, @NonNull result: Result) { @@ -61,49 +92,72 @@ class MainActivity: FlutterActivity() { val ainfo = this.applicationContext.packageManager.getApplicationInfo( "im.cwtch.flwtch", // Must be app name PackageManager.GET_SHARED_LIBRARY_FILES) - return ainfo.nativeLibraryDir } + // receives messages from the ForegroundService (which provides, ironically enough, the backend) private fun handleCwtch(@NonNull call: MethodCall, @NonNull result: Result) { + var method = call.method val argmap: Map = call.arguments as Map - val data: Data = Data.Builder().putString(FlwtchWorker.KEY_METHOD, call.method).putString(FlwtchWorker.KEY_ARGS, JSONObject(argmap).toString()).build() - var tag = "" - if (call.method == "Start") { - tag = "cwtchEventBus" - } - val workRequest: WorkRequest = OneTimeWorkRequestBuilder().setInputData(data).addTag(tag).build() - WorkManager.getInstance(this).enqueue(workRequest) - if (call.method == "Start") { - val eventbus_chan = MethodChannel(flutterEngine?.dartExecutor?.binaryMessenger, CWTCH_EVENTBUS) - WorkManager.getInstance(applicationContext) - // requestId is the WorkRequest id - .getWorkInfosByTagLiveData("cwtchEventBus") - .observe(this, Observer> { listOfWorkInfo -> - if (listOfWorkInfo.isNullOrEmpty()) { - return@Observer - } - for (workInfo in listOfWorkInfo) { - if (workInfo != null) { - val progress = workInfo.progress - val eventType = progress.getString("EventType") ?: "" - val eventData = progress.getString("Data") - val output = progress.keyValueMap.toString() - try { - if (eventType != "") { - Log.i("MainActivity.kt", "got event $output $eventType $eventData") - eventbus_chan.invokeMethod(eventType, eventData) - } - } catch (e: Exception) { - Log.i("MainActivity.kt", "event bus exception") - } - } else { - Log.i("MainActivity.kt", "got null workInfo") - } - } - }) + // the frontend calls Start every time it fires up, but we don't want to *actually* call Cwtch.Start() + // in case the ForegroundService is still running. in both cases, however, we *do* want to re-register + // the eventbus listener. + if (call.method == "Start") { + val workerTag = "cwtchEventBusWorker" + val uniqueTag = argmap["torPath"] ?: "nullEventBus" + + // note: because the ForegroundService is specified as UniquePeriodicWork, it can't actually get + // accidentally duplicated. however, we still need to manually check if it's running or not, so + // that we can divert this method call to ReconnectCwtchForeground instead if so. + val works = WorkManager.getInstance(this).getWorkInfosByTag(workerTag).get() + var alreadyRunning = false + for (workInfo in works) { + if (workInfo.tags.contains(uniqueTag)) { + if (workInfo.state == WorkInfo.State.RUNNING || workInfo.state == WorkInfo.State.ENQUEUED) { + alreadyRunning = true + } + } else { + WorkManager.getInstance(this).cancelWorkById(workInfo.id) + } + } + + // register our eventbus listener. note that we observe any/all work according to its tag, which + // results in an implicit "reconnection" to old service threads even after frontend restarts + val mc = MethodChannel(flutterEngine?.dartExecutor?.binaryMessenger, CWTCH_EVENTBUS) + val filter = IntentFilter("im.cwtch.flwtch.broadcast.SERVICE_EVENT_BUS") + LocalBroadcastManager.getInstance(applicationContext).registerReceiver(MyBroadcastReceiver(mc), filter) + + if (alreadyRunning) { + Log.i("MainActivity.kt", "diverting Start -> Reconnect") + method = "ReconnectCwtchForeground" + } else { + Log.i("MainActivity.kt", "Start() launching foregroundservice") + // this is where the eventbus ForegroundService gets launched. WorkManager should keep it alive after this + val data: Data = Data.Builder().putString(FlwtchWorker.KEY_METHOD, call.method).putString(FlwtchWorker.KEY_ARGS, JSONObject(argmap).toString()).build() + // 15 minutes is the shortest interval you can request + val workRequest = PeriodicWorkRequestBuilder(15, TimeUnit.MINUTES).setInputData(data).addTag(workerTag).addTag(uniqueTag).build() + val workResult = WorkManager.getInstance(this).enqueueUniquePeriodicWork("req_$uniqueTag", ExistingPeriodicWorkPolicy.KEEP, workRequest) + return + } } + + // ...otherwise fallthru to a normal ffi method call (and return the result using the result callback) + val data: Data = Data.Builder().putString(FlwtchWorker.KEY_METHOD, method).putString(FlwtchWorker.KEY_ARGS, JSONObject(argmap).toString()).build() + val workRequest = OneTimeWorkRequestBuilder().setInputData(data).build() + val workResult = WorkManager.getInstance(this).enqueue(workRequest) + WorkManager.getInstance(applicationContext).getWorkInfoByIdLiveData(workRequest.id).observe( + this, Observer { workInfo -> + if (workInfo.state == WorkInfo.State.SUCCEEDED) { + val res = workInfo.outputData.keyValueMap.toString() + //Log.i("MainActivity.kt", "method $method returned SUCCESS($res)") + result.success(workInfo.outputData.getString("result")) + } else { + val idk = workInfo.state.toString() + //Log.i("MainActivity.kt", "method $method returned $idk") + } + } + ) } // source: https://web.archive.org/web/20210203022531/https://stackoverflow.com/questions/41928803/how-to-parse-json-in-kotlin/50468095 @@ -125,4 +179,23 @@ class MainActivity: FlutterActivity() { val EventID = this.optString("EventID") val Data = this.optString("Data") } + + class MyBroadcastReceiver(mc: MethodChannel) : BroadcastReceiver() { + val eventBus: MethodChannel = mc + + override fun onReceive(context: Context, intent: Intent) { +// StringBuilder().apply { +// append("Action: ${intent.action}\n") +// append("URI: ${intent.toUri(Intent.URI_INTENT_SCHEME)}\n") +// toString().also { log -> +// Log.d("MyBroadcastReceiver", log) +// } +// } + val evtType = intent.getStringExtra("EventType") ?: "" + val evtData = intent.getStringExtra("Data") ?: "" + //val evtID = intent.getStringExtra("EventID") ?: ""//todo? + eventBus.invokeMethod(evtType, evtData) + } + } + } diff --git a/lib/cwtch/cwtch.dart b/lib/cwtch/cwtch.dart index cb2b0cd..3b22cde 100644 --- a/lib/cwtch/cwtch.dart +++ b/lib/cwtch/cwtch.dart @@ -1,6 +1,8 @@ abstract class Cwtch { // ignore: non_constant_identifier_names - Future Start(); + Future Start(); + // ignore: non_constant_identifier_names + Future ReconnectCwtchForeground(); // ignore: non_constant_identifier_names void SelectProfile(String onion); diff --git a/lib/cwtch/ffi.dart b/lib/cwtch/ffi.dart index 410ffba..f335178 100644 --- a/lib/cwtch/ffi.dart +++ b/lib/cwtch/ffi.dart @@ -15,8 +15,8 @@ import '../config.dart'; /// Cwtch API /// ///////////////////// -typedef start_cwtch_function = Void Function(Pointer str, Int32 length, Pointer str2, Int32 length2); -typedef StartCwtchFn = void Function(Pointer dir, int len, Pointer tor, int torLen); +typedef start_cwtch_function = Int8 Function(Pointer str, Int32 length, Pointer str2, Int32 length2); +typedef StartCwtchFn = int Function(Pointer dir, int len, Pointer tor, int torLen); typedef void_from_string_string_function = Void Function(Pointer, Int32, Pointer, Int32); typedef VoidFromStringStringFn = void Function(Pointer, int, Pointer, int); @@ -76,7 +76,7 @@ class CwtchFfi implements Cwtch { } // ignore: non_constant_identifier_names - Future Start() async { + Future Start() async { String home = ""; String bundledTor = ""; Map envVars = Platform.environment; @@ -109,6 +109,14 @@ class CwtchFfi implements Cwtch { }); } + // ignore: non_constant_identifier_names + Future ReconnectCwtchForeground() async { + var reconnectCwtch = library.lookup>("c_ReconnectCwtchForeground"); + // ignore: non_constant_identifier_names + final ReconnectCwtchForeground = reconnectCwtch.asFunction(); + ReconnectCwtchForeground(); + } + // Called on object being disposed to (presumably on app close) to close the isolate that's listening to libcwtch-go events @override void dispose() { diff --git a/lib/cwtch/gomobile.dart b/lib/cwtch/gomobile.dart index 210eb42..940fd0c 100644 --- a/lib/cwtch/gomobile.dart +++ b/lib/cwtch/gomobile.dart @@ -43,7 +43,7 @@ class CwtchGomobile implements Cwtch { } // ignore: non_constant_identifier_names - Future Start() async { + Future Start() async { print("gomobile.dart: Start()..."); var cwtchDir = path.join((await androidHomeDirectory).path, ".cwtch"); if (EnvironmentConfig.BUILD_VER == dev_version) { @@ -51,7 +51,13 @@ class CwtchGomobile implements Cwtch { } String torPath = path.join(await androidLibraryDir, "libtor.so"); print("gomobile.dart: Start invokeMethod Start($cwtchDir, $torPath)..."); - cwtchPlatform.invokeMethod("Start", {"appDir": cwtchDir, "torPath": torPath}); + return cwtchPlatform.invokeMethod("Start", {"appDir": cwtchDir, "torPath": torPath}); + } + + @override + // ignore: non_constant_identifier_names + Future ReconnectCwtchForeground() async { + cwtchPlatform.invokeMethod("ReconnectCwtchForeground", {}); } // Handle libcwtch-go events (received via kotlin) and dispatch to the cwtchNotifier diff --git a/lib/main.dart b/lib/main.dart index bfde1c1..d2da036 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,4 +1,7 @@ +import 'dart:convert'; + import 'package:cwtch/notification_manager.dart'; +import 'package:cwtch/views/messageview.dart'; import 'package:cwtch/widgets/rightshiftfixer.dart'; import 'package:flutter/foundation.dart'; import 'package:cwtch/cwtch/ffi.dart'; @@ -8,6 +11,7 @@ import 'package:cwtch/errorHandler.dart'; import 'package:cwtch/settings.dart'; import 'package:cwtch/torstatus.dart'; import 'package:cwtch/views/triplecolview.dart'; +import 'package:flutter/services.dart'; import 'package:provider/provider.dart'; import 'cwtch/cwtch.dart'; import 'cwtch/cwtchNotifier.dart'; @@ -44,6 +48,8 @@ class FlwtchState extends State { var columns = [1]; // default or 'single column' mode //var columns = [1, 1, 2]; late ProfileListState profs; + final MethodChannel notificationClickChannel = MethodChannel('im.cwtch.flwtch/notificationClickHandler'); + final GlobalKey navKey = GlobalKey(); @override initState() { @@ -51,6 +57,7 @@ class FlwtchState extends State { cwtchInit = false; profs = ProfileListState(); + notificationClickChannel.setMethodCallHandler(_externalNotificationClicked); if (Platform.isAndroid) { var cwtchNotifier = new CwtchNotifier(profs, globalSettings, globalErrorHandler, globalTorStatus, NullNotificationsManager()); @@ -63,10 +70,9 @@ class FlwtchState extends State { cwtch = CwtchFfi(cwtchNotifier); } - cwtch.Start().then((val) { - setState(() { - cwtchInit = true; - }); + cwtch.Start(); + setState(() { + cwtchInit = true; }); } @@ -92,13 +98,12 @@ class FlwtchState extends State { return Consumer( builder: (context, settings, child) => MaterialApp( key: Key('app'), + navigatorKey: navKey, locale: settings.locale, localizationsDelegates: AppLocalizations.localizationsDelegates, supportedLocales: AppLocalizations.supportedLocales, title: 'Cwtch', theme: mkThemeData(settings), - // from dan: home: cwtchInit == true ? ProfileMgrView(cwtch) : SplashView(), - // from erinn: home: columns.length == 3 ? TripleColumnView() : ProfileMgrView(), home: cwtchInit == true ? (columns.length == 3 ? TripleColumnView() : ShiftRightFixer(child: ProfileMgrView())) : SplashView(), ), ); @@ -106,6 +111,26 @@ class FlwtchState extends State { ); } + Future _externalNotificationClicked(MethodCall call) async { + var args = jsonDecode(call.arguments); + var profile = profs.getProfile(args["ProfileOnion"])!; + var contact = profile.contactList.getContact(args["RemotePeer"])!; + contact.unreadMessages = 0; + navKey.currentState?.push( + MaterialPageRoute( + builder: (BuildContext builderContext) { + return MultiProvider( + providers: [ + ChangeNotifierProvider.value(value: profile), + ChangeNotifierProvider.value(value: contact), + ], + builder: (context, child) => MessageView(), + ); + }, + ), + ); + } + @override void dispose() { cwtch.dispose(); diff --git a/lib/model.dart b/lib/model.dart index 2752e26..f0f3ef8 100644 --- a/lib/model.dart +++ b/lib/model.dart @@ -426,6 +426,7 @@ class MessageState extends ChangeNotifier { void tryLoad(BuildContext context) { Provider.of(context, listen: false).cwtch.GetMessage(profileOnion, contactHandle, messageIndex).then((jsonMessage) { try { + print("debug messageJson $jsonMessage"); dynamic messageWrapper = jsonDecode(jsonMessage); if (messageWrapper['Message'] == null || messageWrapper['Message'] == '' || messageWrapper['Message'] == '{}') { this._senderOnion = profileOnion;