notifications pass
continuous-integration/drone/pr Build is passing Details

This commit is contained in:
erinn 2021-06-24 17:59:54 -07:00
parent 537f340504
commit a1e1176e90
6 changed files with 104 additions and 85 deletions

View File

@ -61,35 +61,37 @@ class FlwtchWorker(context: Context, parameters: WorkerParameters) :
val evt = MainActivity.AppbusEvent(Cwtch.getAppBusEvent()) val evt = MainActivity.AppbusEvent(Cwtch.getAppBusEvent())
if (evt.EventType == "NewMessageFromPeer" || evt.EventType == "NewMessageFromGroup") { if (evt.EventType == "NewMessageFromPeer" || evt.EventType == "NewMessageFromGroup") {
val data = JSONObject(evt.Data) val data = JSONObject(evt.Data)
val channelId = if (data["RemotePeer"] != data["ProfileOnion"]) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { val channelId =
createMessageNotificationChannel(data.getString("RemotePeer"), data.getString("RemotePeer")) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
} else { createMessageNotificationChannel(data.getString("RemotePeer"), data.getString("RemotePeer"))
// If earlier version channel ID is not used } else {
// https://developer.android.com/reference/android/support/v4/app/NotificationCompat.Builder.html#NotificationCompat.Builder(android.content.Context) // 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 loader = FlutterInjector.instance().flutterLoader()
val key = loader.getLookupKeyForAsset("assets/"+data.getString("Picture"))//"assets/profiles/001-centaur.png") val key = loader.getLookupKeyForAsset("assets/" + data.getString("Picture"))//"assets/profiles/001-centaur.png")
val fh = applicationContext.assets.open(key) val fh = applicationContext.assets.open(key)
val clickIntent = Intent(applicationContext, MainActivity::class.java).also { intent -> val clickIntent = Intent(applicationContext, MainActivity::class.java).also { intent ->
intent.action = Intent.ACTION_RUN intent.action = Intent.ACTION_RUN
intent.putExtra("EventType", "NotificationClicked") intent.putExtra("EventType", "NotificationClicked")
intent.putExtra("ProfileOnion", data.getString("ProfileOnion")) intent.putExtra("ProfileOnion", data.getString("ProfileOnion"))
intent.putExtra("RemotePeer", if (evt.EventType == "NewMessageFromPeer") data.getString("RemotePeer") else data.getString("GroupID")) 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 -> Intent().also { intent ->
@ -223,9 +225,10 @@ class FlwtchWorker(context: Context, parameters: WorkerParameters) :
"" ""
} }
// This PendingIntent can be used to cancel the worker val cancelIntent = Intent(applicationContext, MainActivity::class.java).also { intent ->
val intent = WorkManager.getInstance(applicationContext) intent.action = Intent.ACTION_RUN
.createCancelPendingIntent(getId()) intent.putExtra("EventType", "ShutdownClicked")
}
val notification = NotificationCompat.Builder(applicationContext, channelId) val notification = NotificationCompat.Builder(applicationContext, channelId)
.setContentTitle(title) .setContentTitle(title)
@ -235,7 +238,7 @@ class FlwtchWorker(context: Context, parameters: WorkerParameters) :
.setOngoing(true) .setOngoing(true)
// Add the cancel action to the notification which can // Add the cancel action to the notification which can
// be used to cancel the worker // 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() .build()
return ForegroundInfo(101, notification) return ForegroundInfo(101, notification)

View File

@ -36,27 +36,36 @@ class MainActivity: FlutterActivity() {
// Channel to send eventbus events on // Channel to send eventbus events on
private val CWTCH_EVENTBUS = "test.flutter.dev/eventBus" 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_NOTIF_CLICK = "im.cwtch.flwtch/notificationClickHandler"
private val CHANNEL_SHUTDOWN_CLICK = "im.cwtch.flwtch/shutdownClickHandler"
// WorkManager tag applied to all Start() infinite coroutines // WorkManager tag applied to all Start() infinite coroutines
val WORKER_TAG = "cwtchEventBusWorker" val WORKER_TAG = "cwtchEventBusWorker"
private var myReceiver: MyBroadcastReceiver? = null 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) { override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent) super.onNewIntent(intent)
if (methodChan == null || intent.extras == null) return if (notificationClickChannel == null || intent.extras == null) return
if (!intent.extras!!.containsKey("ProfileOnion") || !intent.extras!!.containsKey("RemotePeer")) {
Log.i("onNewIntent", "got intent with no onions") if (intent.extras!!.getString("EventType") == "NotificationClicked") {
return 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) { override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
@ -66,7 +75,8 @@ class MainActivity: FlutterActivity() {
requestWindowFeature(Window.FEATURE_NO_TITLE) requestWindowFeature(Window.FEATURE_NO_TITLE)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL_APP_INFO).setMethodCallHandler { call, result -> handleAppInfo(call, result) } 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) } 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) { private fun handleAppInfo(@NonNull call: MethodCall, @NonNull result: Result) {

View File

@ -124,7 +124,7 @@ class CwtchNotifier {
break; break;
case "NewMessageFromGroup": case "NewMessageFromGroup":
if (data["ProfileOnion"] != data["RemotePeer"]) { if (data["ProfileOnion"] != data["RemotePeer"]) {
//not from me //if not currently open
if (appState.selectedProfile != data["ProfileOnion"] || appState.selectedConversation != data["GroupID"]) { if (appState.selectedProfile != data["ProfileOnion"] || appState.selectedConversation != data["GroupID"]) {
profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["GroupID"])!.unreadMessages++; profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["GroupID"])!.unreadMessages++;
} }

View File

@ -47,7 +47,7 @@ class FlwtchState extends State<Flwtch> {
late Cwtch cwtch; late Cwtch cwtch;
late ProfileListState profs; late ProfileListState profs;
final MethodChannel notificationClickChannel = MethodChannel('im.cwtch.flwtch/notificationClickHandler'); 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<NavigatorState> navKey = GlobalKey<NavigatorState>(); final GlobalKey<NavigatorState> navKey = GlobalKey<NavigatorState>();
@override @override
@ -58,7 +58,7 @@ class FlwtchState extends State<Flwtch> {
print("initState: registering notification, shutdown handlers..."); print("initState: registering notification, shutdown handlers...");
profs = ProfileListState(); profs = ProfileListState();
notificationClickChannel.setMethodCallHandler(_externalNotificationClicked); notificationClickChannel.setMethodCallHandler(_externalNotificationClicked);
shutdownMethodChannel.setMethodCallHandler(shutdown); shutdownMethodChannel.setMethodCallHandler(modalShutdown);
print("initState: creating cwtchnotifier, ffi"); print("initState: creating cwtchnotifier, ffi");
if (Platform.isAndroid) { if (Platform.isAndroid) {
var cwtchNotifier = new CwtchNotifier(profs, globalSettings, globalErrorHandler, globalTorStatus, NullNotificationsManager(), globalAppState); var cwtchNotifier = new CwtchNotifier(profs, globalSettings, globalErrorHandler, globalTorStatus, NullNotificationsManager(), globalAppState);
@ -111,7 +111,45 @@ class FlwtchState extends State<Flwtch> {
); );
} }
Future<void> shutdown(MethodCall call) async { // invoked from either ProfileManagerView's appbar close button, or a ShutdownClicked event on
// the MyBroadcastReceiver method channel
Future<void> 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<FlwtchState>(navKey.currentContext!, listen: false).shutdown();
Provider.of<AppState>(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<void> shutdown() async {
cwtch.Shutdown(); cwtch.Shutdown();
// Wait a few seconds as shutting down things takes a little time.. // Wait a few seconds as shutting down things takes a little time..
Future.delayed(Duration(seconds: 2)).then((value) { Future.delayed(Duration(seconds: 2)).then((value) {

View File

@ -63,6 +63,7 @@ class ProfileListState extends ChangeNotifier {
class AppState extends ChangeNotifier { class AppState extends ChangeNotifier {
bool cwtchInit = false; bool cwtchInit = false;
bool cwtchIsClosing = false;
String appError = ""; String appError = "";
String? _selectedProfile; String? _selectedProfile;
String? _selectedConversation; String? _selectedConversation;

View File

@ -28,8 +28,6 @@ class ProfileMgrView extends StatefulWidget {
class _ProfileMgrViewState extends State<ProfileMgrView> { class _ProfileMgrViewState extends State<ProfileMgrView> {
final ctrlrPassword = TextEditingController(); final ctrlrPassword = TextEditingController();
bool closeApp = false;
@override @override
void dispose() { void dispose() {
ctrlrPassword.dispose(); ctrlrPassword.dispose();
@ -45,8 +43,8 @@ class _ProfileMgrViewState extends State<ProfileMgrView> {
builder: (context, settings, child) => builder: (context, settings, child) =>
WillPopScope( WillPopScope(
onWillPop: () async { onWillPop: () async {
_showShutdown(); _modalShutdown();
return closeApp; return Provider.of<AppState>(context, listen: false).cwtchIsClosing;
}, },
child: Scaffold( child: Scaffold(
backgroundColor: settings.theme.backgroundMainColor(), backgroundColor: settings.theme.backgroundMainColor(),
@ -108,45 +106,14 @@ class _ProfileMgrViewState extends State<ProfileMgrView> {
// Global Settings // Global Settings
actions.add(IconButton(icon: Icon(Icons.settings), tooltip: AppLocalizations.of(context)!.tooltipOpenSettings, onPressed: _pushGlobalSettings)); 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; return actions;
} }
_showShutdown() { void _modalShutdown() {
// set up the buttons Provider.of<FlwtchState>(context, listen: false).modalShutdown(MethodCall(""));
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<FlwtchState>(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 _pushGlobalSettings() { void _pushGlobalSettings() {