A Flutter based Cwtch UI https://cwtch.im
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

207 lines
8.1 KiB

5 months ago
  1. import 'dart:convert';
  2. import 'package:cwtch/config.dart';
  3. import 'package:cwtch/notification_manager.dart';
  4. import 'package:cwtch/views/messageview.dart';
  5. import 'package:cwtch/widgets/rightshiftfixer.dart';
  6. import 'package:flutter/foundation.dart';
  7. import 'package:cwtch/cwtch/ffi.dart';
  8. import 'package:cwtch/cwtch/gomobile.dart';
  9. import 'package:flutter/material.dart';
  10. import 'package:cwtch/errorHandler.dart';
  11. import 'package:cwtch/settings.dart';
  12. import 'package:cwtch/torstatus.dart';
  13. import 'package:cwtch/views/triplecolview.dart';
  14. import 'package:flutter/services.dart';
  15. import 'package:provider/provider.dart';
  16. import 'cwtch/cwtch.dart';
  17. import 'cwtch/cwtchNotifier.dart';
  18. import 'licenses.dart';
  19. import 'model.dart';
  20. import 'views/profilemgrview.dart';
  21. import 'views/splashView.dart';
  22. import 'dart:io' show Platform, exit;
  23. import 'opaque.dart';
  24. import 'package:flutter_gen/gen_l10n/app_localizations.dart';
  25. var globalSettings = Settings(Locale("en", ''), OpaqueDark());
  26. var globalErrorHandler = ErrorHandler();
  27. var globalTorStatus = TorStatus();
  28. var globalAppState = AppState();
  29. void main() {
  30. print("Cwtch version: ${EnvironmentConfig.BUILD_VER} built on: ${EnvironmentConfig.BUILD_DATE}");
  31. LicenseRegistry.addLicense(() => licenses());
  32. WidgetsFlutterBinding.ensureInitialized();
  33. print("runApp()");
  34. runApp(Flwtch());
  35. }
  36. class Flwtch extends StatefulWidget {
  37. final Key flwtch = GlobalKey();
  38. @override
  39. FlwtchState createState() => FlwtchState();
  40. }
  41. class FlwtchState extends State<Flwtch> {
  42. final TextStyle biggerFont = const TextStyle(fontSize: 18);
  43. late Cwtch cwtch;
  44. late ProfileListState profs;
  45. final MethodChannel notificationClickChannel = MethodChannel('im.cwtch.flwtch/notificationClickHandler');
  46. final MethodChannel shutdownMethodChannel = MethodChannel('im.cwtch.flwtch/shutdownClickHandler');
  47. final GlobalKey<NavigatorState> navKey = GlobalKey<NavigatorState>();
  48. @override
  49. initState() {
  50. print("initState: running...");
  51. super.initState();
  52. print("initState: registering notification, shutdown handlers...");
  53. profs = ProfileListState();
  54. notificationClickChannel.setMethodCallHandler(_externalNotificationClicked);
  55. shutdownMethodChannel.setMethodCallHandler(modalShutdown);
  56. print("initState: creating cwtchnotifier, ffi");
  57. if (Platform.isAndroid) {
  58. var cwtchNotifier = new CwtchNotifier(profs, globalSettings, globalErrorHandler, globalTorStatus, NullNotificationsManager(), globalAppState);
  59. cwtch = CwtchGomobile(cwtchNotifier);
  60. } else if (Platform.isLinux) {
  61. var cwtchNotifier = new CwtchNotifier(profs, globalSettings, globalErrorHandler, globalTorStatus, newDesktopNotificationsManager(), globalAppState);
  62. cwtch = CwtchFfi(cwtchNotifier);
  63. } else {
  64. var cwtchNotifier = new CwtchNotifier(profs, globalSettings, globalErrorHandler, globalTorStatus, NullNotificationsManager(), globalAppState);
  65. cwtch = CwtchFfi(cwtchNotifier);
  66. }
  67. print("initState: invoking cwtch.Start()");
  68. cwtch.Start();
  69. print("initState: done!");
  70. }
  71. ChangeNotifierProvider<TorStatus> getTorStatusProvider() => ChangeNotifierProvider.value(value: globalTorStatus);
  72. ChangeNotifierProvider<ErrorHandler> getErrorHandlerProvider() => ChangeNotifierProvider.value(value: globalErrorHandler);
  73. ChangeNotifierProvider<Settings> getSettingsProvider() => ChangeNotifierProvider.value(value: globalSettings);
  74. ChangeNotifierProvider<AppState> getAppStateProvider() => ChangeNotifierProvider.value(value: globalAppState);
  75. Provider<FlwtchState> getFlwtchStateProvider() => Provider<FlwtchState>(create: (_) => this);
  76. ChangeNotifierProvider<ProfileListState> getProfileListProvider() => ChangeNotifierProvider(create: (context) => profs);
  77. @override
  78. Widget build(BuildContext context) {
  79. globalSettings.initPackageInfo();
  80. return MultiProvider(
  81. providers: [
  82. getFlwtchStateProvider(),
  83. getProfileListProvider(),
  84. getSettingsProvider(),
  85. getErrorHandlerProvider(),
  86. getTorStatusProvider(),
  87. getAppStateProvider(),
  88. ],
  89. builder: (context, widget) {
  90. return Consumer2<Settings, AppState>(
  91. builder: (context, settings, appState, child) => MaterialApp(
  92. key: Key('app'),
  93. navigatorKey: navKey,
  94. locale: settings.locale,
  95. localizationsDelegates: AppLocalizations.localizationsDelegates,
  96. supportedLocales: AppLocalizations.supportedLocales,
  97. title: 'Cwtch',
  98. theme: mkThemeData(settings),
  99. home: appState.cwtchInit == true ? ShiftRightFixer(child: ProfileMgrView()) : SplashView(),
  100. ),
  101. );
  102. },
  103. );
  104. }
  105. // invoked from either ProfileManagerView's appbar close button, or a ShutdownClicked event on
  106. // the MyBroadcastReceiver method channel
  107. Future<void> modalShutdown(MethodCall mc) async {
  108. // set up the buttons
  109. Widget cancelButton = ElevatedButton(
  110. child: Text(AppLocalizations.of(navKey.currentContext!)!.cancel),
  111. onPressed: () {
  112. Navigator.of(navKey.currentContext!).pop(); // dismiss dialog
  113. },
  114. );
  115. Widget continueButton = ElevatedButton(
  116. child: Text(AppLocalizations.of(navKey.currentContext!)!.shutdownCwtchAction),
  117. onPressed: () {
  118. // Directly call the shutdown command, Android will do this for us...
  119. Provider.of<FlwtchState>(navKey.currentContext!, listen: false).shutdown();
  120. Provider.of<AppState>(navKey.currentContext!, listen: false).cwtchIsClosing = true;
  121. });
  122. // set up the AlertDialog
  123. AlertDialog alert = AlertDialog(
  124. title: Text(AppLocalizations.of(navKey.currentContext!)!.shutdownCwtchDialogTitle),
  125. content: Text(AppLocalizations.of(navKey.currentContext!)!.shutdownCwtchDialog),
  126. actions: [
  127. cancelButton,
  128. continueButton,
  129. ],
  130. );
  131. // show the dialog
  132. showDialog(
  133. context: navKey.currentContext!,
  134. barrierDismissible: false,
  135. builder: (BuildContext context) {
  136. return alert;
  137. },
  138. );
  139. }
  140. Future<void> shutdown() async {
  141. cwtch.Shutdown();
  142. // Wait a few seconds as shutting down things takes a little time..
  143. Future.delayed(Duration(seconds: 2)).then((value) {
  144. if (Platform.isAndroid) {
  145. SystemNavigator.pop();
  146. } else if (Platform.isLinux || Platform.isWindows || Platform.isMacOS) {
  147. print("Exiting...");
  148. exit(0);
  149. }
  150. });
  151. }
  152. // Invoked via notificationClickChannel by MyBroadcastReceiver in MainActivity.kt
  153. // coder beware: args["RemotePeer"] is actually a handle, and could be eg a groupID
  154. Future<void> _externalNotificationClicked(MethodCall call) async {
  155. var args = jsonDecode(call.arguments);
  156. var profile = profs.getProfile(args["ProfileOnion"])!;
  157. var convo = profile.contactList.getContact(args["Handle"])!;
  158. Provider.of<AppState>(navKey.currentContext!, listen: false).initialScrollIndex = convo.unreadMessages;
  159. convo.unreadMessages = 0;
  160. // single pane mode pushes; double pane mode reads AppState.selectedProfile/Conversation
  161. var isLandscape = Provider.of<AppState>(navKey.currentContext!, listen: false).isLandscape(navKey.currentContext!);
  162. if (Provider.of<Settings>(navKey.currentContext!, listen: false).uiColumns(isLandscape).length == 1) {
  163. while (navKey.currentState!.canPop()) {
  164. print("messageview already open; popping before pushing replacement");
  165. navKey.currentState!.pop();
  166. }
  167. navKey.currentState?.push(
  168. MaterialPageRoute<void>(
  169. builder: (BuildContext builderContext) {
  170. return MultiProvider(
  171. providers: [
  172. ChangeNotifierProvider.value(value: profile),
  173. ChangeNotifierProvider.value(value: convo),
  174. ],
  175. builder: (context, child) => MessageView(),
  176. );
  177. },
  178. ),
  179. );
  180. } else {
  181. //dual pane
  182. Provider.of<AppState>(navKey.currentContext!, listen: false).selectedProfile = args["ProfileOnion"];
  183. Provider.of<AppState>(navKey.currentContext!, listen: false).selectedConversation = args["Handle"];
  184. }
  185. }
  186. @override
  187. void dispose() {
  188. cwtch.dispose();
  189. super.dispose();
  190. }
  191. }