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;