notifications pass #3
|
@ -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)
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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++;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
Loading…
Reference in New Issue