android service and notification support take one. ACTION

This commit is contained in:
erinn 2021-06-11 14:28:20 -07:00
parent 89507a8aa6
commit 9c69275fe6
9 changed files with 268 additions and 96 deletions

View File

@ -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'
}

View File

@ -45,4 +45,7 @@
<!--Needed to run in background (lol)-->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<!--Meeded to check if activity is foregrounded or if messages from the service should be queued-->
<uses-permission android:name="android.permission.GET_TASKS" />
</manifest>

View File

@ -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<String, Int> = 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<ActivityManager.RunningAppProcessInfo> = (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"

View File

@ -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<String, String> = call.arguments as Map<String, String>
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<FlwtchWorker>().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<List<WorkInfo>> { 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<FlwtchWorker>(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<FlwtchWorker>().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)
}
}
}

View File

@ -1,6 +1,8 @@
abstract class Cwtch {
// ignore: non_constant_identifier_names
Future<void> Start();
Future<dynamic> Start();
// ignore: non_constant_identifier_names
Future<void> ReconnectCwtchForeground();
// ignore: non_constant_identifier_names
void SelectProfile(String onion);

View File

@ -15,8 +15,8 @@ import '../config.dart';
/// Cwtch API ///
/////////////////////
typedef start_cwtch_function = Void Function(Pointer<Utf8> str, Int32 length, Pointer<Utf8> str2, Int32 length2);
typedef StartCwtchFn = void Function(Pointer<Utf8> dir, int len, Pointer<Utf8> tor, int torLen);
typedef start_cwtch_function = Int8 Function(Pointer<Utf8> str, Int32 length, Pointer<Utf8> str2, Int32 length2);
typedef StartCwtchFn = int Function(Pointer<Utf8> dir, int len, Pointer<Utf8> tor, int torLen);
typedef void_from_string_string_function = Void Function(Pointer<Utf8>, Int32, Pointer<Utf8>, Int32);
typedef VoidFromStringStringFn = void Function(Pointer<Utf8>, int, Pointer<Utf8>, int);
@ -76,7 +76,7 @@ class CwtchFfi implements Cwtch {
}
// ignore: non_constant_identifier_names
Future<void> Start() async {
Future<dynamic> Start() async {
String home = "";
String bundledTor = "";
Map<String, String> envVars = Platform.environment;
@ -109,6 +109,14 @@ class CwtchFfi implements Cwtch {
});
}
// ignore: non_constant_identifier_names
Future<void> ReconnectCwtchForeground() async {
var reconnectCwtch = library.lookup<NativeFunction<Void Function()>>("c_ReconnectCwtchForeground");
// ignore: non_constant_identifier_names
final ReconnectCwtchForeground = reconnectCwtch.asFunction<void Function()>();
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() {

View File

@ -43,7 +43,7 @@ class CwtchGomobile implements Cwtch {
}
// ignore: non_constant_identifier_names
Future<void> Start() async {
Future<dynamic> 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<void> ReconnectCwtchForeground() async {
cwtchPlatform.invokeMethod("ReconnectCwtchForeground", {});
}
// Handle libcwtch-go events (received via kotlin) and dispatch to the cwtchNotifier

View File

@ -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<Flwtch> {
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<NavigatorState> navKey = GlobalKey<NavigatorState>();
@override
initState() {
@ -51,6 +57,7 @@ class FlwtchState extends State<Flwtch> {
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<Flwtch> {
cwtch = CwtchFfi(cwtchNotifier);
}
cwtch.Start().then((val) {
setState(() {
cwtchInit = true;
});
cwtch.Start();
setState(() {
cwtchInit = true;
});
}
@ -92,13 +98,12 @@ class FlwtchState extends State<Flwtch> {
return Consumer<Settings>(
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<Flwtch> {
);
}
Future<void> _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<void>(
builder: (BuildContext builderContext) {
return MultiProvider(
providers: [
ChangeNotifierProvider.value(value: profile),
ChangeNotifierProvider.value(value: contact),
],
builder: (context, child) => MessageView(),
);
},
),
);
}
@override
void dispose() {
cwtch.dispose();

View File

@ -426,6 +426,7 @@ class MessageState extends ChangeNotifier {
void tryLoad(BuildContext context) {
Provider.of<FlwtchState>(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;