From 22bf5cfe923787254300169f306213e9d2a9ac06 Mon Sep 17 00:00:00 2001 From: Sarah Jamie Lewis Date: Thu, 18 May 2023 11:15:13 -0700 Subject: [PATCH] Small UI Fixes / Font Styles / Abstractions --- .../kotlin/im/cwtch/flwtch/MainActivity.kt | 17 ++-- lib/controllers/open_link_modal.dart | 32 +++--- lib/cwtch/cwtch.dart | 4 +- lib/cwtch/cwtchNotifier.dart | 26 ++--- lib/cwtch/ffi.dart | 12 +-- lib/cwtch/gomobile.dart | 28 +++--- lib/main.dart | 4 +- lib/models/contact.dart | 9 ++ lib/models/message_draft.dart | 24 ++++- lib/notification_manager.dart | 97 +++++++------------ lib/settings.dart | 5 + lib/themes/opaque.dart | 6 ++ lib/views/messageview.dart | 60 ++++++------ lib/widgets/cwtchlabel.dart | 3 +- lib/widgets/filebubble.dart | 97 ++++++------------- lib/widgets/invitationbubble.dart | 46 +++++---- lib/widgets/messageBubbleWidgetHelpers.dart | 48 +++++++++ lib/widgets/messagebubble.dart | 53 +--------- lib/widgets/messagerow.dart | 3 +- lib/widgets/quotedmessage.dart | 49 +--------- lib/widgets/staticmessagebubble.dart | 8 +- 21 files changed, 290 insertions(+), 341 deletions(-) create mode 100644 lib/widgets/messageBubbleWidgetHelpers.dart 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 d17f83ec..c4cdda25 100644 --- a/android/app/src/main/kotlin/im/cwtch/flwtch/MainActivity.kt +++ b/android/app/src/main/kotlin/im/cwtch/flwtch/MainActivity.kt @@ -360,8 +360,7 @@ class MainActivity: FlutterActivity() { "PeerWithOnion" -> { val profile: String = call.argument("ProfileOnion") ?: "" val onion: String = call.argument("onion") ?: "" - result.success(Cwtch.peerWithOnion(profile, onion)) - return + Cwtch.peerWithOnion(profile, onion) } @@ -493,14 +492,17 @@ class MainActivity: FlutterActivity() { } "GetProfileAttribute" -> { val profile: String = call.argument("ProfileOnion") ?: "" - val key: String = call.argument("Key") ?: "" - Data.Builder().putString("result", Cwtch.getProfileAttribute(profile, key)).build() + val key: String = call.argument("key") ?: "" + var resultjson = Cwtch.getProfileAttribute(profile, key); + return result.success(resultjson) } "GetConversationAttribute" -> { val profile: String = call.argument("ProfileOnion") ?: "" val conversation: Int = call.argument("conversation") ?: 0 - val key: String = call.argument("Key") ?: "" - Data.Builder().putString("result", Cwtch.getConversationAttribute(profile, conversation.toLong(), key)).build() + val key: String = call.argument("key") ?: "" + var resultjson = Cwtch.getConversationAttribute(profile, conversation.toLong(), key); + result.success(resultjson) + return } "SetConversationAttribute" -> { val profile: String = call.argument("ProfileOnion") ?: "" @@ -512,7 +514,8 @@ class MainActivity: FlutterActivity() { "ImportProfile" -> { val file: String = call.argument("file") ?: "" val pass: String = call.argument("pass") ?: "" - Data.Builder().putString("result", Cwtch.importProfile(file, pass)).build() + result.success(Cwtch.importProfile(file, pass)) + return } "ReconnectCwtchForeground" -> { Cwtch.reconnectCwtchForeground() diff --git a/lib/controllers/open_link_modal.dart b/lib/controllers/open_link_modal.dart index 7b021f6d..c770bcb6 100644 --- a/lib/controllers/open_link_modal.dart +++ b/lib/controllers/open_link_modal.dart @@ -1,16 +1,20 @@ +import 'package:cwtch/themes/opaque.dart'; import 'package:cwtch/third_party/linkify/linkify.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:url_launcher/url_launcher.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:provider/provider.dart'; +import 'package:url_launcher/url_launcher_string.dart'; + +import '../settings.dart'; void modalOpenLink(BuildContext ctx, LinkableElement link) { showModalBottomSheet( context: ctx, builder: (BuildContext bcontext) { return Container( - height: 200, // bespoke value courtesy of the [TextField] docs + height: 200, child: Center( child: Padding( padding: EdgeInsets.all(30.0), @@ -18,17 +22,24 @@ void modalOpenLink(BuildContext ctx, LinkableElement link) { mainAxisAlignment: MainAxisAlignment.center, mainAxisSize: MainAxisSize.min, children: [ - Text(AppLocalizations.of(bcontext)!.clickableLinksWarning), + Text( + AppLocalizations.of(bcontext)!.clickableLinksWarning, + style: Provider.of(bcontext).scaleFonts(defaultTextStyle), + ), Flex(direction: Axis.horizontal, mainAxisAlignment: MainAxisAlignment.center, children: [ Container( margin: EdgeInsets.symmetric(vertical: 20, horizontal: 10), child: ElevatedButton( - child: Text(AppLocalizations.of(bcontext)!.clickableLinksCopy, semanticsLabel: AppLocalizations.of(bcontext)!.clickableLinksCopy), + child: Text(AppLocalizations.of(bcontext)!.clickableLinksCopy, + style: Provider.of(bcontext).scaleFonts(defaultTextButtonStyle), semanticsLabel: AppLocalizations.of(bcontext)!.clickableLinksCopy), onPressed: () { Clipboard.setData(new ClipboardData(text: link.url)); final snackBar = SnackBar( - content: Text(AppLocalizations.of(bcontext)!.copiedToClipboardNotification), + content: Text( + AppLocalizations.of(bcontext)!.copiedToClipboardNotification, + style: Provider.of(bcontext).scaleFonts(defaultTextButtonStyle), + ), ); Navigator.pop(bcontext); @@ -39,15 +50,14 @@ void modalOpenLink(BuildContext ctx, LinkableElement link) { Container( margin: EdgeInsets.symmetric(vertical: 20, horizontal: 10), child: ElevatedButton( - child: Text(AppLocalizations.of(bcontext)!.clickableLinkOpen, semanticsLabel: AppLocalizations.of(bcontext)!.clickableLinkOpen), + child: Text(AppLocalizations.of(bcontext)!.clickableLinkOpen, + style: Provider.of(bcontext).scaleFonts(defaultTextButtonStyle), semanticsLabel: AppLocalizations.of(bcontext)!.clickableLinkOpen), onPressed: () async { - if (await canLaunch(link.url)) { - await launch(link.url); + if (await canLaunchUrlString(link.url)) { + await launchUrlString(link.url); Navigator.pop(bcontext); } else { - final snackBar = SnackBar( - content: Text(AppLocalizations.of(bcontext)!.clickableLinkError), - ); + final snackBar = SnackBar(content: Text(AppLocalizations.of(bcontext)!.clickableLinkError, style: Provider.of(bcontext).scaleFonts(defaultTextButtonStyle))); ScaffoldMessenger.of(bcontext).showSnackBar(snackBar); } }, diff --git a/lib/cwtch/cwtch.dart b/lib/cwtch/cwtch.dart index 0ca8c851..98e4fac5 100644 --- a/lib/cwtch/cwtch.dart +++ b/lib/cwtch/cwtch.dart @@ -97,11 +97,11 @@ abstract class Cwtch { Future ImportBundle(String profile, String bundle); // ignore: non_constant_identifier_names void SetProfileAttribute(String profile, String key, String val); - String? GetProfileAttribute(String profile, String key); + Future GetProfileAttribute(String profile, String key); // ignore: non_constant_identifier_names void SetConversationAttribute(String profile, int conversation, String key, String val); // ignore: non_constant_identifier_names - String? GetConversationAttribute(String profile, int identifier, String s); + Future GetConversationAttribute(String profile, int identifier, String s); // ignore: non_constant_identifier_names void SetMessageAttribute(String profile, int conversation, int channel, int message, String key, String val); // ignore: non_constant_identifier_names diff --git a/lib/cwtch/cwtchNotifier.dart b/lib/cwtch/cwtchNotifier.dart index e2c2b4db..5788e41e 100644 --- a/lib/cwtch/cwtchNotifier.dart +++ b/lib/cwtch/cwtchNotifier.dart @@ -59,6 +59,8 @@ class CwtchNotifier { } void handleMessage(String type, dynamic data) { + EnvironmentConfig.debugLog("Handing Message $type ${data.toString()}"); + //EnvironmentConfig.debugLog("NewEvent $type $data"); switch (type) { case "CwtchStarted": @@ -68,25 +70,24 @@ class CwtchNotifier { appState.SetAppError(data["Error"]); break; case "NewPeer": - // empty events can be caused by the testing framework - if (data["Online"] == null) { - break; - } // EnvironmentConfig.debugLog("NewPeer $data"); // if tag != v1-defaultPassword then it is either encrypted OR it is an unencrypted account created during pre-beta... profileCN.add(data["Identity"], data["name"], data["picture"], data["defaultPicture"], data["ContactsJson"], data["ServerList"], data["Online"] == "true", data["autostart"] == "true", data["tag"] != "v1-defaultPassword"); // Update Profile Attributes - profileCN.getProfile(data["Identity"])?.setAttribute(0, flwtchState.cwtch.GetProfileAttribute(data["Identity"], "profile.profile-attribute-1")); - profileCN.getProfile(data["Identity"])?.setAttribute(1, flwtchState.cwtch.GetProfileAttribute(data["Identity"], "profile.profile-attribute-2")); - profileCN.getProfile(data["Identity"])?.setAttribute(2, flwtchState.cwtch.GetProfileAttribute(data["Identity"], "profile.profile-attribute-3")); - profileCN.getProfile(data["Identity"])?.setAvailabilityStatus(flwtchState.cwtch.GetProfileAttribute(data["Identity"], "profile.profile-status") ?? ""); + EnvironmentConfig.debugLog("Looking up Profile Attributes ${data["Identity"]} ${profileCN.getProfile(data["Identity"])}"); + flwtchState.cwtch.GetProfileAttribute(data["Identity"], "profile.profile-attribute-1").then((value) => profileCN.getProfile(data["Identity"])?.setAttribute(0, value)); + flwtchState.cwtch.GetProfileAttribute(data["Identity"], "profile.profile-attribute-2").then((value) => profileCN.getProfile(data["Identity"])?.setAttribute(1, value)); + flwtchState.cwtch.GetProfileAttribute(data["Identity"], "profile.profile-attribute-3").then((value) => profileCN.getProfile(data["Identity"])?.setAttribute(2, value)); + flwtchState.cwtch.GetProfileAttribute(data["Identity"], "profile.profile-status").then((value) => profileCN.getProfile(data["Identity"])?.setAvailabilityStatus(value ?? "")); + + EnvironmentConfig.debugLog("Looking up Profile Information for Contact..."); profileCN.getProfile(data["Identity"])?.contactList.contacts.forEach((contact) { - contact.setAttribute(0, flwtchState.cwtch.GetConversationAttribute(data["Identity"], contact.identifier, "public.profile.profile-attribute-1")); - contact.setAttribute(1, flwtchState.cwtch.GetConversationAttribute(data["Identity"], contact.identifier, "public.profile.profile-attribute-2")); - contact.setAttribute(2, flwtchState.cwtch.GetConversationAttribute(data["Identity"], contact.identifier, "public.profile.profile-attribute-3")); - contact.setAvailabilityStatus(flwtchState.cwtch.GetConversationAttribute(data["Identity"], contact.identifier, "public.profile.profile-status") ?? ""); + flwtchState.cwtch.GetConversationAttribute(data["Identity"], contact.identifier, "public.profile.profile-attribute-1").then((value) => contact.setAttribute(0, value)); + flwtchState.cwtch.GetConversationAttribute(data["Identity"], contact.identifier, "public.profile.profile-attribute-2").then((value) => contact.setAttribute(1, value)); + flwtchState.cwtch.GetConversationAttribute(data["Identity"], contact.identifier, "public.profile.profile-attribute-3").then((value) => contact.setAttribute(2, value)); + flwtchState.cwtch.GetConversationAttribute(data["Identity"], contact.identifier, "public.profile.profile-status").then((value) => contact.setAvailabilityStatus(value ?? "")); }); break; @@ -392,6 +393,7 @@ class CwtchNotifier { String fileKey = data['Data']; var contact = profileCN.getProfile(data["ProfileOnion"])?.contactList.findContact(data["RemotePeer"]); if (contact != null) { + EnvironmentConfig.debugLog("waiting for download from $contact"); profileCN.getProfile(data["ProfileOnion"])?.waitForDownloadComplete(contact.identifier, fileKey); } } else if (data['Path'] == "profile.profile-attribute-1" || data['Path'] == "profile.profile-attribute-2" || data['Path'] == "profile.profile-attribute-3") { diff --git a/lib/cwtch/ffi.dart b/lib/cwtch/ffi.dart index 086760c9..10a4414a 100644 --- a/lib/cwtch/ffi.dart +++ b/lib/cwtch/ffi.dart @@ -967,7 +967,7 @@ class CwtchFfi implements Cwtch { } @override - String? GetProfileAttribute(String profile, String key) { + Future GetProfileAttribute(String profile, String key) { var getProfileAttributeC = library.lookup>("c_GetProfileAttribute"); // ignore: non_constant_identifier_names final GetProfileAttribute = getProfileAttributeC.asFunction(); @@ -982,17 +982,17 @@ class CwtchFfi implements Cwtch { try { dynamic attributeResult = json.decode(jsonMessage); if (attributeResult["Exists"]) { - return attributeResult["Value"]; + return Future.value(attributeResult["Value"]); } } catch (e) { EnvironmentConfig.debugLog("error getting profile attribute: $e"); } - return null; + return Future.value(null); } @override - String? GetConversationAttribute(String profile, int conversation, String key) { + Future GetConversationAttribute(String profile, int conversation, String key) { var getConversationAttributeC = library.lookup>("c_GetConversationAttribute"); // ignore: non_constant_identifier_names final GetConversationAttribute = getConversationAttributeC.asFunction(); @@ -1007,13 +1007,13 @@ class CwtchFfi implements Cwtch { try { dynamic attributeResult = json.decode(jsonMessage); if (attributeResult["Exists"]) { - return attributeResult["Value"]; + return Future.value(attributeResult["Value"]); } } catch (e) { EnvironmentConfig.debugLog("error getting profile attribute: $e"); } - return null; + return Future.value(null); } @override diff --git a/lib/cwtch/gomobile.dart b/lib/cwtch/gomobile.dart index 17d55b9e..7278ffeb 100644 --- a/lib/cwtch/gomobile.dart +++ b/lib/cwtch/gomobile.dart @@ -390,21 +390,25 @@ class CwtchGomobile implements Cwtch { } @override - String? GetProfileAttribute(String profile, String key) { - dynamic attributeResult = cwtchPlatform.invokeMethod("GetProfileAttribute", {"ProfileOnion": profile, "key": key}); - if (attributeResult["Exists"]) { - return attributeResult["Value"]; - } - return null; + Future GetProfileAttribute(String profile, String key) async { + return await cwtchPlatform.invokeMethod("GetProfileAttribute", {"ProfileOnion": profile, "key": key}).then((dynamic json) { + var value = jsonDecode(json); + if (value["Exists"]) { + return value["Value"]; + } + return null; + }); } @override - String? GetConversationAttribute(String profile, int conversation, String key) { - dynamic attributeResult = cwtchPlatform.invokeMethod("GetProfileAttribute", {"ProfileOnion": profile, "conversation": conversation, "key": key}); - if (attributeResult["Exists"]) { - return attributeResult["Value"]; - } - return null; + Future GetConversationAttribute(String profile, int conversation, String key) async { + return await cwtchPlatform.invokeMethod("GetConversationAttribute", {"ProfileOnion": profile, "conversation": conversation, "key": key}).then((dynamic json) { + var value = jsonDecode(json); + if (value["Exists"]) { + return value["Value"]; + } + return null; + }); } @override diff --git a/lib/main.dart b/lib/main.dart index 94309a3d..b22e6b88 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -87,7 +87,6 @@ class FlwtchState extends State with WindowListener { print("initState: running..."); windowManager.addListener(this); - print("initState: registering notification, shutdown handlers..."); profs = ProfileListState(); notificationClickChannel.setMethodCallHandler(_externalNotificationClicked); @@ -307,8 +306,7 @@ class FlwtchState extends State with WindowListener { globalAppState.focus = false; } - void onWindowClose() { - } + void onWindowClose() {} @override void dispose() { diff --git a/lib/models/contact.dart b/lib/models/contact.dart index a12e19db..70121234 100644 --- a/lib/models/contact.dart +++ b/lib/models/contact.dart @@ -264,6 +264,15 @@ class ContactInfoState extends ChangeNotifier { } } + bool canSend() { + if (this.isGroup == true) { + // We now have an out of sync warning so we will mark these as online... + return this.status == "Synced"; + } else { + return this.isOnline(); + } + } + ConversationNotificationPolicy get notificationsPolicy => _notificationPolicy; set notificationsPolicy(ConversationNotificationPolicy newVal) { diff --git a/lib/models/message_draft.dart b/lib/models/message_draft.dart index 0112569e..0ffb98ab 100644 --- a/lib/models/message_draft.dart +++ b/lib/models/message_draft.dart @@ -1,24 +1,26 @@ +import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; /// A "MessageDraft" structure that stores information about in-progress message drafts. /// MessageDraft stores text, quoted replies, and attached images. /// Only one draft is stored per conversation. class MessageDraft extends ChangeNotifier { - String? _messageText; QuotedReference? _quotedReference; + TextEditingController ctrlCompose = TextEditingController(); + static MessageDraft empty() { return MessageDraft(); } bool isEmpty() { - return (this._messageText == null && this._quotedReference == null) || (this._messageText != null && this._messageText!.isEmpty); + return (this._quotedReference == null) || (this.messageText.isEmpty); } - String? get messageText => _messageText; + String get messageText => ctrlCompose.text; - set messageText(String? text) { - this._messageText = text; + set messageText(String text) { + this.ctrlCompose.text = text; notifyListeners(); } @@ -35,6 +37,18 @@ class MessageDraft extends ChangeNotifier { this._quotedReference = null; notifyListeners(); } + + void clearDraft() { + this._quotedReference = null; + this.ctrlCompose.clear(); + notifyListeners(); + } + + @override + void dispose() { + ctrlCompose.dispose(); + super.dispose(); + } } /// A QuotedReference encapsulates the state of replied-to message. diff --git a/lib/notification_manager.dart b/lib/notification_manager.dart index 95fe93ef..e3463032 100644 --- a/lib/notification_manager.dart +++ b/lib/notification_manager.dart @@ -23,8 +23,7 @@ abstract class NotificationsManager { // NullNotificationsManager ignores all notification requests class NullNotificationsManager implements NotificationsManager { @override - Future notify( - String message, String profile, int conversationId) async {} + Future notify(String message, String profile, int conversationId) async {} } // Windows Notification Manager uses https://pub.dev/packages/desktoasts to implement @@ -34,7 +33,6 @@ class WindowsNotificationManager implements NotificationsManager { bool initialized = false; late Future Function(String, int) notificationSelectConvo; - WindowsNotificationManager(Future Function(String, int) notificationSelectConvo) { this.notificationSelectConvo = notificationSelectConvo; scheduleMicrotask(() async { @@ -83,28 +81,26 @@ class WindowsNotificationManager implements NotificationsManager { * WinToast.instance().clear(); await WinToast.instance().showToast( - toast: Toast( - duration: ToastDuration.short, - children: [ - ToastChildAudio(source: ToastAudioSource.im), - ToastChildVisual( - binding: ToastVisualBinding(children: [ - ToastVisualBindingChildText( - text: message, - id: 1, - ), - ])), - ToastChildActions(children: [ - ToastAction( - content: "Open", - arguments: jsonEncode(NotificationPayload(profile, conversationId)), - ), - ToastAction( - content: "Close", - arguments: "close", - ), - ]), - ])); + toast: Toast(duration: ToastDuration.short, children: [ + ToastChildAudio(source: ToastAudioSource.im), + ToastChildVisual( + binding: ToastVisualBinding(children: [ + ToastVisualBindingChildText( + text: message, + id: 1, + ), + ])), + ToastChildActions(children: [ + ToastAction( + content: "Open", + arguments: jsonEncode(NotificationPayload(profile, conversationId)), + ), + ToastAction( + content: "Close", + arguments: "close", + ), + ]), + ])); active = false; }*/ } @@ -142,8 +138,7 @@ class NixNotificationManager implements NotificationsManager { Future detectLinuxAssetsPath() async { var devStat = FileStat.stat("assets"); var localStat = FileStat.stat("data/flutter_assets"); - var homeStat = FileStat.stat((Platform.environment["HOME"] ?? "") + - "/.local/share/cwtch/data/flutter_assets"); + var homeStat = FileStat.stat((Platform.environment["HOME"] ?? "") + "/.local/share/cwtch/data/flutter_assets"); var rootStat = FileStat.stat("/usr/share/cwtch/data/flutter_assets"); if ((await devStat).type == FileSystemEntityType.directory) { @@ -151,16 +146,14 @@ class NixNotificationManager implements NotificationsManager { } else if ((await localStat).type == FileSystemEntityType.directory) { return path.join(Directory.current.path, "data/flutter_assets/"); } else if ((await homeStat).type == FileSystemEntityType.directory) { - return (Platform.environment["HOME"] ?? "") + - "/.local/share/cwtch/data/flutter_assets/"; + return (Platform.environment["HOME"] ?? "") + "/.local/share/cwtch/data/flutter_assets/"; } else if ((await rootStat).type == FileSystemEntityType.directory) { return "/usr/share/cwtch/data/flutter_assets/"; } return ""; } - NixNotificationManager( - Future Function(String, int) notificationSelectConvo) { + NixNotificationManager(Future Function(String, int) notificationSelectConvo) { this.notificationSelectConvo = notificationSelectConvo; flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); @@ -171,26 +164,14 @@ class NixNotificationManager implements NotificationsManager { linuxAssetsPath = ""; } - var linuxIcon = - FilePathLinuxIcon(path.join(linuxAssetsPath, 'assets/knott.png')); + var linuxIcon = FilePathLinuxIcon(path.join(linuxAssetsPath, 'assets/knott.png')); - final LinuxInitializationSettings initializationSettingsLinux = - LinuxInitializationSettings( - defaultActionName: 'Open notification', - defaultIcon: linuxIcon, - defaultSuppressSound: true); + final LinuxInitializationSettings initializationSettingsLinux = LinuxInitializationSettings(defaultActionName: 'Open notification', defaultIcon: linuxIcon, defaultSuppressSound: true); final InitializationSettings initializationSettings = - InitializationSettings( - android: null, - iOS: null, - macOS: DarwinInitializationSettings(defaultPresentSound: false), - linux: initializationSettingsLinux); + InitializationSettings(android: null, iOS: null, macOS: DarwinInitializationSettings(defaultPresentSound: false), linux: initializationSettingsLinux); - flutterLocalNotificationsPlugin - .resolvePlatformSpecificImplementation< - MacOSFlutterLocalNotificationsPlugin>() - ?.requestPermissions( + flutterLocalNotificationsPlugin.resolvePlatformSpecificImplementation()?.requestPermissions( alert: true, badge: false, sound: false, @@ -202,8 +183,7 @@ class NixNotificationManager implements NotificationsManager { }); } - Future notify( - String message, String profile, int conversationId) async { + Future notify(String message, String profile, int conversationId) async { if (!globalAppState.focus) { // Warning: Only use title field on Linux, body field will render links as clickable await flutterLocalNotificationsPlugin.show( @@ -211,11 +191,7 @@ class NixNotificationManager implements NotificationsManager { message, '', NotificationDetails( - linux: LinuxNotificationDetails( - suppressSound: true, - category: LinuxNotificationCategory.imReceived, - icon: FilePathLinuxIcon( - path.join(linuxAssetsPath, 'assets/knott.png')))), + linux: LinuxNotificationDetails(suppressSound: true, category: LinuxNotificationCategory.imReceived, icon: FilePathLinuxIcon(path.join(linuxAssetsPath, 'assets/knott.png')))), payload: jsonEncode(NotificationPayload(profile, conversationId))); } } @@ -230,9 +206,7 @@ class NixNotificationManager implements NotificationsManager { } } -NotificationsManager newDesktopNotificationsManager( - Future Function(String profileOnion, int convoId) - notificationSelectConvo) { +NotificationsManager newDesktopNotificationsManager(Future Function(String profileOnion, int convoId) notificationSelectConvo) { // We don't want notifications in Dev Mode if (EnvironmentConfig.TEST_MODE) { return NullNotificationsManager(); @@ -242,22 +216,19 @@ NotificationsManager newDesktopNotificationsManager( try { return NixNotificationManager(notificationSelectConvo); } catch (e) { - EnvironmentConfig.debugLog( - "Failed to create LinuxNotificationManager. Switching off notifications."); + EnvironmentConfig.debugLog("Failed to create LinuxNotificationManager. Switching off notifications."); } } else if (Platform.isMacOS) { try { return NixNotificationManager(notificationSelectConvo); } catch (e) { - EnvironmentConfig.debugLog( - "Failed to create NixNotificationManager. Switching off notifications."); + EnvironmentConfig.debugLog("Failed to create NixNotificationManager. Switching off notifications."); } } else if (Platform.isWindows) { try { return WindowsNotificationManager(notificationSelectConvo); } catch (e) { - EnvironmentConfig.debugLog( - "Failed to create Windows desktoasts notification manager"); + EnvironmentConfig.debugLog("Failed to create Windows desktoasts notification manager"); } } diff --git a/lib/settings.dart b/lib/settings.dart index 281ecf5c..a0ba35ce 100644 --- a/lib/settings.dart +++ b/lib/settings.dart @@ -192,6 +192,11 @@ class Settings extends ChangeNotifier { double get fontScaling => _fontScaling; + // a convenience function to scale fonts dynamically... + TextStyle scaleFonts(TextStyle input) { + return input.copyWith(fontSize: (input.fontSize ?? 12) * this.fontScaling); + } + /// Switch the Locale of the App switchLocale(Locale newLocale) { locale = newLocale; diff --git a/lib/themes/opaque.dart b/lib/themes/opaque.dart index 730cff48..aa10f2c9 100644 --- a/lib/themes/opaque.dart +++ b/lib/themes/opaque.dart @@ -19,6 +19,12 @@ import 'neon2.dart'; const mode_light = "light"; const mode_dark = "dark"; +final TextStyle defaultSmallTextStyle = TextStyle(fontFamily: "Inter", fontWeight: FontWeight.normal, fontSize: 10); +final TextStyle defaultMessageTextStyle = TextStyle(fontFamily: "Inter", fontWeight: FontWeight.normal, fontSize: 12); +final TextStyle defaultFormLabelTextStyle = TextStyle(fontFamily: "Inter", fontWeight: FontWeight.bold, fontSize: 20); +final TextStyle defaultTextStyle = TextStyle(fontFamily: "Inter", fontWeight: FontWeight.w500, fontSize: 12); +final TextStyle defaultTextButtonStyle = defaultTextStyle.copyWith(fontWeight: FontWeight.bold); + final themes = { cwtch_theme: {mode_light: CwtchLight(), mode_dark: CwtchDark()}, ghost_theme: {mode_light: GhostLight(), mode_dark: GhostDark()}, diff --git a/lib/views/messageview.dart b/lib/views/messageview.dart index ac0afa7b..15bc10aa 100644 --- a/lib/views/messageview.dart +++ b/lib/views/messageview.dart @@ -44,7 +44,6 @@ class MessageView extends StatefulWidget { } class _MessageViewState extends State { - final ctrlrCompose = TextEditingController(); final focusNode = FocusNode(); int selectedContact = -1; ItemPositionsListener scrollListener = ItemPositionsListener.create(); @@ -68,7 +67,6 @@ class _MessageViewState extends State { showDown = false; } }); - ctrlrCompose.text = Provider.of(context, listen: false).messageDraft.messageText ?? ""; super.initState(); } @@ -88,7 +86,6 @@ class _MessageViewState extends State { @override void dispose() { focusNode.dispose(); - ctrlrCompose.dispose(); super.dispose(); } @@ -321,22 +318,22 @@ class _MessageViewState extends State { void _sendMessage([String? ignoredParam]) { // Do this after we trim to preserve enter-behaviour... - bool isOffline = Provider.of(context, listen: false).isOnline() == false; + bool cannotSend = Provider.of(context, listen: false).canSend() == false; bool performingAntiSpam = Provider.of(context, listen: false).antispamTickets == 0; bool isGroup = Provider.of(context, listen: false).isGroup; - if (isOffline || (isGroup && performingAntiSpam)) { + if (cannotSend || (isGroup && performingAntiSpam)) { return; } // Trim message - final messageWithoutNewLine = ctrlrCompose.value.text.trimRight(); - ctrlrCompose.value = TextEditingValue(text: messageWithoutNewLine, selection: TextSelection.fromPosition(TextPosition(offset: messageWithoutNewLine.length))); + var messageText = Provider.of(context, listen: false).messageDraft.messageText ?? ""; + final messageWithoutNewLine = messageText.trimRight(); // peers and groups currently have different length constraints (servers can store less)... - var actualMessageLength = ctrlrCompose.value.text.length; + var actualMessageLength = messageText.length; var lengthOk = (isGroup && actualMessageLength < GroupMessageLengthMax) || actualMessageLength <= P2PMessageLengthMax; - if (ctrlrCompose.value.text.isNotEmpty && lengthOk) { + if (messageWithoutNewLine.isNotEmpty && lengthOk) { if (Provider.of(context, listen: false).selectedConversation != null && Provider.of(context, listen: false).messageDraft.getQuotedMessage() != null) { var conversationId = Provider.of(context, listen: false).selectedConversation!; MessageCache? cache = Provider.of(context, listen: false).contactList.getContact(conversationId)?.messageCache; @@ -347,7 +344,7 @@ class _MessageViewState extends State { var bytes1 = utf8.encode(data!.metadata.senderHandle + data.wrapper); var digest1 = sha256.convert(bytes1); var contentHash = base64Encode(digest1.bytes); - var quotedMessage = jsonEncode(QuotedMessageStructure(contentHash, ctrlrCompose.value.text)); + var quotedMessage = jsonEncode(QuotedMessageStructure(contentHash, messageWithoutNewLine)); ChatMessage cm = new ChatMessage(o: QuotedMessageOverlay, d: quotedMessage); Provider.of(context, listen: false) .cwtch @@ -359,7 +356,7 @@ class _MessageViewState extends State { Provider.of(context, listen: false).messageDraft.clearQuotedReference(); }); } else { - ChatMessage cm = new ChatMessage(o: TextMessageOverlay, d: ctrlrCompose.value.text); + ChatMessage cm = new ChatMessage(o: TextMessageOverlay, d: messageWithoutNewLine); Provider.of(context, listen: false) .cwtch .SendMessage(Provider.of(context, listen: false).profileOnion, Provider.of(context, listen: false).identifier, jsonEncode(cm)) @@ -391,8 +388,7 @@ class _MessageViewState extends State { // At this point we have decided to send the text to the backend, failure is still possible // but it will show as an error-ed message, as such the draft can be purged. - Provider.of(context, listen: false).messageDraft = MessageDraft.empty(); - ctrlrCompose.clear(); + Provider.of(context, listen: false).messageDraft.clearDraft(); var profileOnion = Provider.of(context, listen: false).profileOnion; var identifier = Provider.of(context, listen: false).identifier; @@ -424,7 +420,7 @@ class _MessageViewState extends State { var wdgMessage = Padding( padding: EdgeInsets.all(8), child: SelectableLinkify( - text: ctrlrCompose.text + '\n', + text: Provider.of(context).messageDraft.messageText + '\n', options: LinkifyOptions(messageFormatting: true, parseLinks: showClickableLinks, looseUrl: true, defaultToHttps: true), linkifiers: [UrlLinkifier()], onOpen: showClickableLinks ? null : null, @@ -468,7 +464,7 @@ class _MessageViewState extends State { margin: EdgeInsets.all(2), // 164 minimum height + 16px for every line of text so the entire message is displayed when previewed. - height: 164 + ((ctrlrCompose.text.split("\n").length - 1) * 16), + height: 164 + ((Provider.of(context).messageDraft.messageText.split("\n").length - 1) * 16), child: Column( children: [ Row(mainAxisAlignment: MainAxisAlignment.start, mainAxisSize: MainAxisSize.max, children: [preview]), @@ -483,11 +479,11 @@ class _MessageViewState extends State { } Widget _buildComposeBox(BuildContext context) { - bool isOffline = Provider.of(context).isOnline() == false; + bool cannotSend = Provider.of(context).canSend() == false; bool isGroup = Provider.of(context).isGroup; var showToolbar = Provider.of(context).isExperimentEnabled(FormattingExperiment); - var charLength = ctrlrCompose.value.text.characters.length; - var expectedLength = ctrlrCompose.value.text.length; + var charLength = Provider.of(context).messageDraft.messageText.characters.length; + var expectedLength = Provider.of(context).messageDraft.messageText.length; var numberOfBytesMoreThanChar = (expectedLength - charLength); var bold = IconButton( @@ -495,12 +491,14 @@ class _MessageViewState extends State { tooltip: AppLocalizations.of(context)!.tooltipBoldText, onPressed: () { setState(() { + var ctrlrCompose = Provider.of(context, listen: false).messageDraft.ctrlCompose; var selected = ctrlrCompose.selection.textInside(ctrlrCompose.text); var selection = ctrlrCompose.selection; var start = ctrlrCompose.selection.start; var end = ctrlrCompose.selection.end; ctrlrCompose.text = ctrlrCompose.text.replaceRange(start, end, "**" + selected + "**"); ctrlrCompose.selection = selection.copyWith(baseOffset: selection.start + 2, extentOffset: selection.start + 2); + Provider.of(context, listen: false).messageDraft.ctrlCompose = ctrlrCompose; }); }); @@ -509,12 +507,14 @@ class _MessageViewState extends State { tooltip: AppLocalizations.of(context)!.tooltipItalicize, onPressed: () { setState(() { + var ctrlrCompose = Provider.of(context, listen: false).messageDraft.ctrlCompose; var selected = ctrlrCompose.selection.textInside(ctrlrCompose.text); var selection = ctrlrCompose.selection; var start = ctrlrCompose.selection.start; var end = ctrlrCompose.selection.end; ctrlrCompose.text = ctrlrCompose.text.replaceRange(start, end, "*" + selected + "*"); ctrlrCompose.selection = selection.copyWith(baseOffset: selection.start + 1, extentOffset: selection.start + 1); + Provider.of(context, listen: false).messageDraft.ctrlCompose = ctrlrCompose; }); }); @@ -523,12 +523,14 @@ class _MessageViewState extends State { tooltip: AppLocalizations.of(context)!.tooltipCode, onPressed: () { setState(() { + var ctrlrCompose = Provider.of(context, listen: false).messageDraft.ctrlCompose; var selected = ctrlrCompose.selection.textInside(ctrlrCompose.text); var selection = ctrlrCompose.selection; var start = ctrlrCompose.selection.start; var end = ctrlrCompose.selection.end; ctrlrCompose.text = ctrlrCompose.text.replaceRange(start, end, "`" + selected + "`"); ctrlrCompose.selection = selection.copyWith(baseOffset: selection.start + 1, extentOffset: selection.start + 1); + Provider.of(context, listen: false).messageDraft.ctrlCompose = ctrlrCompose; }); }); @@ -537,12 +539,14 @@ class _MessageViewState extends State { tooltip: AppLocalizations.of(context)!.tooltipSuperscript, onPressed: () { setState(() { + var ctrlrCompose = Provider.of(context, listen: false).messageDraft.ctrlCompose; var selected = ctrlrCompose.selection.textInside(ctrlrCompose.text); var selection = ctrlrCompose.selection; var start = ctrlrCompose.selection.start; var end = ctrlrCompose.selection.end; ctrlrCompose.text = ctrlrCompose.text.replaceRange(start, end, "^" + selected + "^"); ctrlrCompose.selection = selection.copyWith(baseOffset: selection.start + 1, extentOffset: selection.start + 1); + Provider.of(context, listen: false).messageDraft.ctrlCompose = ctrlrCompose; }); }); @@ -551,12 +555,14 @@ class _MessageViewState extends State { tooltip: AppLocalizations.of(context)!.tooltipSubscript, onPressed: () { setState(() { + var ctrlrCompose = Provider.of(context, listen: false).messageDraft.ctrlCompose; var selected = ctrlrCompose.selection.textInside(ctrlrCompose.text); var selection = ctrlrCompose.selection; var start = ctrlrCompose.selection.start; var end = ctrlrCompose.selection.end; ctrlrCompose.text = ctrlrCompose.text.replaceRange(start, end, "_" + selected + "_"); ctrlrCompose.selection = selection.copyWith(baseOffset: selection.start + 1, extentOffset: selection.start + 1); + Provider.of(context, listen: false).messageDraft.ctrlCompose = ctrlrCompose; }); }); @@ -565,12 +571,14 @@ class _MessageViewState extends State { tooltip: AppLocalizations.of(context)!.tooltipStrikethrough, onPressed: () { setState(() { + var ctrlrCompose = Provider.of(context, listen: false).messageDraft.ctrlCompose; var selected = ctrlrCompose.selection.textInside(ctrlrCompose.text); var selection = ctrlrCompose.selection; var start = ctrlrCompose.selection.start; var end = ctrlrCompose.selection.end; ctrlrCompose.text = ctrlrCompose.text.replaceRange(start, end, "~~" + selected + "~~"); ctrlrCompose.selection = selection.copyWith(baseOffset: selection.start + 2, extentOffset: selection.start + 2); + Provider.of(context, listen: false).messageDraft.ctrlCompose = ctrlrCompose; }); }); @@ -600,7 +608,7 @@ class _MessageViewState extends State { padding: EdgeInsets.all(8), child: TextFormField( key: Key('txtCompose'), - controller: ctrlrCompose, + controller: Provider.of(context).messageDraft.ctrlCompose, focusNode: focusNode, autofocus: !Platform.isAndroid, textInputAction: TextInputAction.newline, @@ -615,18 +623,17 @@ class _MessageViewState extends State { style: TextStyle( fontFamily: "Inter", fontSize: 12.0 * Provider.of(context).fontScaling, - fontWeight: FontWeight.w500, + fontWeight: FontWeight.w300, ), enabled: true, // always allow editing... onChanged: (String x) { - Provider.of(context, listen: false).messageDraft.messageText = x; setState(() { // we need to force a rerender here to update the max length count }); }, decoration: InputDecoration( - hintText: isOffline ? "" : AppLocalizations.of(context)!.placeholderEnterMessage, + hintText: AppLocalizations.of(context)!.placeholderEnterMessage, hintStyle: TextStyle(fontFamily: "Inter", fontSize: 10.0 * Provider.of(context).fontScaling, color: Provider.of(context).theme.sendHintTextColor), enabledBorder: InputBorder.none, focusedBorder: InputBorder.none, @@ -635,13 +642,13 @@ class _MessageViewState extends State { key: Key("btnSend"), style: ElevatedButton.styleFrom(padding: EdgeInsets.all(0.0), shape: new RoundedRectangleBorder(borderRadius: new BorderRadius.circular(45.0))), child: Tooltip( - message: isOffline + message: cannotSend ? (isGroup ? AppLocalizations.of(context)!.serverNotSynced : AppLocalizations.of(context)!.peerOfflineMessage) : (isGroup && Provider.of(context, listen: false).antispamTickets == 0) ? AppLocalizations.of(context)!.acquiringTicketsFromServer : AppLocalizations.of(context)!.sendMessage, child: Icon(CwtchIcons.send_24px, size: 24, color: Provider.of(context).theme.defaultButtonTextColor)), - onPressed: isOffline || (isGroup && Provider.of(context, listen: false).antispamTickets == 0) ? null : _sendMessage, + onPressed: cannotSend || (isGroup && Provider.of(context, listen: false).antispamTickets == 0) ? null : _sendMessage, ))), ))); @@ -727,7 +734,8 @@ class _MessageViewState extends State { if (event is RawKeyUpEvent) { if ((data.logicalKey == LogicalKeyboardKey.enter && !event.isShiftPressed) || data.logicalKey == LogicalKeyboardKey.numpadEnter && !event.isShiftPressed) { // Don't send when inserting a new line that is not at the end of the message - if (ctrlrCompose.selection.baseOffset != ctrlrCompose.text.length) { + if (Provider.of(context, listen: false).messageDraft.ctrlCompose.selection.baseOffset != + Provider.of(context, listen: false).messageDraft.ctrlCompose.text.length) { return; } _sendMessage(); @@ -735,8 +743,6 @@ class _MessageViewState extends State { } } - void placeHolder() => {}; - // explicitly passing BuildContext ctx here is important, change at risk to own health // otherwise some Providers will become inaccessible to subwidgets...? // https://stackoverflow.com/a/63818697 diff --git a/lib/widgets/cwtchlabel.dart b/lib/widgets/cwtchlabel.dart index 07ac3874..9cfa8b11 100644 --- a/lib/widgets/cwtchlabel.dart +++ b/lib/widgets/cwtchlabel.dart @@ -1,3 +1,4 @@ +import 'package:cwtch/themes/opaque.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../settings.dart'; @@ -18,7 +19,7 @@ class _CwtchLabelState extends State { return Consumer(builder: (context, theme, child) { return Text( widget.label, - style: TextStyle(fontSize: 20, color: theme.current().mainTextColor), + style: Provider.of(context).scaleFonts(defaultFormLabelTextStyle), ); }); } diff --git a/lib/widgets/filebubble.dart b/lib/widgets/filebubble.dart index badadf70..a8bb683b 100644 --- a/lib/widgets/filebubble.dart +++ b/lib/widgets/filebubble.dart @@ -7,7 +7,9 @@ import 'package:cwtch/models/contact.dart'; import 'package:cwtch/models/filedownloadprogress.dart'; import 'package:cwtch/models/message.dart'; import 'package:cwtch/models/profile.dart'; +import 'package:cwtch/themes/opaque.dart'; import 'package:cwtch/widgets/malformedbubble.dart'; +import 'package:cwtch/widgets/messageBubbleWidgetHelpers.dart'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; @@ -147,18 +149,10 @@ class FileBubbleState extends State { var wdgSender = Visibility( visible: widget.interactive, child: Container( - height: 14 * Provider.of(context).fontScaling, - clipBehavior: Clip.hardEdge, - decoration: BoxDecoration(), - child: SelectableText(senderDisplayStr + '\u202F', - style: TextStyle( - fontSize: 9.0 * Provider.of(context).fontScaling, - fontWeight: FontWeight.bold, - fontFamily: "Inter", - color: fromMe ? Provider.of(context).theme.messageFromMeTextColor : Provider.of(context).theme.messageFromOtherTextColor)))); + height: 14 * Provider.of(context).fontScaling, clipBehavior: Clip.hardEdge, decoration: BoxDecoration(), child: compileSenderWidget(context, fromMe, senderDisplayStr))); var isPreview = false; var wdgMessage = !showFileSharing - ? Text(AppLocalizations.of(context)!.messageEnableFileSharing) + ? Text(AppLocalizations.of(context)!.messageEnableFileSharing, style: Provider.of(context).scaleFonts(defaultTextStyle)) : fromMe ? senderFileChrome(AppLocalizations.of(context)!.messageFileSent, widget.nameSuggestion, widget.rootHash, widget.fileSize) : (fileChrome(AppLocalizations.of(context)!.messageFileOffered + ":", widget.nameSuggestion, widget.rootHash, widget.fileSize, @@ -182,11 +176,13 @@ class FileBubbleState extends State { }, ))); } else { - wdgDecorations = Visibility(visible: widget.interactive, child: Text(AppLocalizations.of(context)!.fileSavedTo + ': ' + path + '\u202F')); + wdgDecorations = Visibility( + visible: widget.interactive, child: Text(AppLocalizations.of(context)!.fileSavedTo + ': ' + path + '\u202F', style: Provider.of(context).scaleFonts(defaultTextStyle))); } } else if (downloadActive) { if (!downloadGotManifest) { - wdgDecorations = Visibility(visible: widget.interactive, child: Text(AppLocalizations.of(context)!.retrievingManifestMessage + '\u202F')); + wdgDecorations = Visibility( + visible: widget.interactive, child: Text(AppLocalizations.of(context)!.retrievingManifestMessage + '\u202F', style: Provider.of(context).scaleFonts(defaultTextStyle))); } else { wdgDecorations = Visibility( visible: widget.interactive, @@ -199,19 +195,19 @@ class FileBubbleState extends State { // in this case, the download was done in a previous application launch, // so we probably have to request an info lookup if (!downloadInterrupted) { - wdgDecorations = Text(AppLocalizations.of(context)!.fileCheckingStatus + '...' + '\u202F'); + wdgDecorations = Text(AppLocalizations.of(context)!.fileCheckingStatus + '...' + '\u202F', style: Provider.of(context).scaleFonts(defaultTextStyle)); // We should have already requested this... } else { var path = Provider.of(context).downloadFinalPath(widget.fileKey()) ?? ""; wdgDecorations = Visibility( visible: widget.interactive, child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(AppLocalizations.of(context)!.fileInterrupted + ': ' + path + '\u202F'), - ElevatedButton(onPressed: _btnResume, child: Text(AppLocalizations.of(context)!.verfiyResumeButton)) + Text(AppLocalizations.of(context)!.fileInterrupted + ': ' + path + '\u202F', style: Provider.of(context).scaleFonts(defaultTextStyle)), + ElevatedButton(onPressed: _btnResume, child: Text(AppLocalizations.of(context)!.verfiyResumeButton, style: Provider.of(context).scaleFonts(defaultTextButtonStyle))) ])); } } else if (!senderIsContact) { - wdgDecorations = Text(AppLocalizations.of(context)!.msgAddToAccept); + wdgDecorations = Text(AppLocalizations.of(context)!.msgAddToAccept, style: Provider.of(context).scaleFonts(defaultTextStyle)); } else if (!widget.isAuto || Provider.of(context).attributes["file-missing"] == "false") { //Note: we need this second case to account for scenarios where a user deletes the downloaded file, we won't automatically // fetch it again, so we need to offer the user the ability to restart.. @@ -220,7 +216,10 @@ class FileBubbleState extends State { child: Center( widthFactor: 1, child: Wrap(children: [ - Padding(padding: EdgeInsets.all(5), child: ElevatedButton(child: Text(AppLocalizations.of(context)!.downloadFileButton + '\u202F'), onPressed: _btnAccept)), + Padding( + padding: EdgeInsets.all(5), + child: ElevatedButton( + child: Text(AppLocalizations.of(context)!.downloadFileButton + '\u202F', style: Provider.of(context).scaleFonts(defaultTextButtonStyle)), onPressed: _btnAccept)), ]))); } else { wdgDecorations = Container(); @@ -278,7 +277,7 @@ class FileBubbleState extends State { } } else { try { - selectedFileName = await FilePicker.platform.saveFile( + selectedFileName = await FilePicker.platform.saveFile( fileName: widget.nameSuggestion, lockParentWindow: true, ); @@ -308,52 +307,35 @@ class FileBubbleState extends State { // Construct an file chrome for the sender Widget senderFileChrome(String chrome, String fileName, String rootHash, int fileSize) { + var settings = Provider.of(context); return ListTile( visualDensity: VisualDensity.compact, title: Wrap(direction: Axis.horizontal, alignment: WrapAlignment.start, children: [ SelectableText( chrome + '\u202F', - style: TextStyle( - color: Provider.of(context).theme.messageFromMeTextColor, - fontWeight: FontWeight.normal, - fontFamily: "Inter", - fontSize: 12 * Provider.of(context).fontScaling, - ), + style: settings.scaleFonts(defaultMessageTextStyle.copyWith(color: Provider.of(context).theme.messageFromMeTextColor)), textAlign: TextAlign.left, maxLines: 2, textWidthBasis: TextWidthBasis.longestLine, ), SelectableText( fileName + '\u202F', - style: TextStyle( - color: Provider.of(context).theme.messageFromMeTextColor, - fontWeight: FontWeight.bold, - overflow: TextOverflow.ellipsis, - fontFamily: "Inter", - fontSize: 12 * Provider.of(context).fontScaling, - ), + style: + settings.scaleFonts(defaultMessageTextStyle.copyWith(overflow: TextOverflow.ellipsis, fontWeight: FontWeight.bold, color: Provider.of(context).theme.messageFromMeTextColor)), textAlign: TextAlign.left, textWidthBasis: TextWidthBasis.parent, maxLines: 2, ), SelectableText( prettyBytes(fileSize) + '\u202F' + '\n', - style: TextStyle( - color: Provider.of(context).theme.messageFromMeTextColor, - fontSize: 10 * Provider.of(context).fontScaling, - fontFamily: "Inter", - ), + style: settings.scaleFonts(defaultSmallTextStyle.copyWith(color: Provider.of(context).theme.messageFromMeTextColor)), textAlign: TextAlign.left, maxLines: 2, ) ]), subtitle: SelectableText( 'sha512: ' + rootHash + '\u202F', - style: TextStyle( - color: Provider.of(context).theme.messageFromMeTextColor, - fontSize: 10 * Provider.of(context).fontScaling, - fontFamily: "RobotoMono", - ), + style: settings.scaleFonts(defaultSmallTextStyle.copyWith(fontFamily: "RobotoMono", color: Provider.of(context).theme.messageFromMeTextColor)), textAlign: TextAlign.left, maxLines: 4, textWidthBasis: TextWidthBasis.parent, @@ -363,50 +345,35 @@ class FileBubbleState extends State { // Construct an file chrome Widget fileChrome(String chrome, String fileName, String rootHash, int fileSize, String speed) { + var settings = Provider.of(context); return ListTile( visualDensity: VisualDensity.compact, title: Wrap(direction: Axis.horizontal, alignment: WrapAlignment.start, children: [ SelectableText( chrome + '\u202F', - style: TextStyle( - color: Provider.of(context).theme.messageFromOtherTextColor, - fontSize: 12 * Provider.of(context).fontScaling, - fontFamily: "Inter", - ), + style: settings.scaleFonts(defaultMessageTextStyle.copyWith(color: Provider.of(context).theme.messageFromOtherTextColor)), textAlign: TextAlign.left, maxLines: 2, textWidthBasis: TextWidthBasis.longestLine, ), SelectableText( fileName + '\u202F', - style: TextStyle( - color: Provider.of(context).theme.messageFromOtherTextColor, - fontWeight: FontWeight.bold, - overflow: TextOverflow.ellipsis, - fontFamily: "Inter", - ), + style: settings + .scaleFonts(defaultMessageTextStyle.copyWith(overflow: TextOverflow.ellipsis, fontWeight: FontWeight.bold, color: Provider.of(context).theme.messageFromOtherTextColor)), textAlign: TextAlign.left, textWidthBasis: TextWidthBasis.parent, maxLines: 2, ), SelectableText( AppLocalizations.of(context)!.labelFilesize + ': ' + prettyBytes(fileSize) + '\u202F' + '\n', - style: TextStyle( - color: Provider.of(context).theme.messageFromOtherTextColor, - fontFamily: "Inter", - fontSize: 10 * Provider.of(context).fontScaling, - ), + style: settings.scaleFonts(defaultSmallTextStyle.copyWith(color: Provider.of(context).theme.messageFromOtherTextColor)), textAlign: TextAlign.left, maxLines: 2, ) ]), subtitle: SelectableText( 'sha512: ' + rootHash + '\u202F', - style: TextStyle( - color: Provider.of(context).theme.messageFromMeTextColor, - fontSize: 10 * Provider.of(context).fontScaling, - fontFamily: "RobotoMono", - ), + style: settings.scaleFonts(defaultSmallTextStyle.copyWith(fontFamily: "RobotoMono", color: Provider.of(context).theme.messageFromOtherTextColor)), textAlign: TextAlign.left, maxLines: 4, textWidthBasis: TextWidthBasis.parent, @@ -416,11 +383,7 @@ class FileBubbleState extends State { visible: speed != "0 B/s", child: SelectableText( speed + '\u202F', - style: TextStyle( - color: Provider.of(context).theme.messageFromMeTextColor, - fontFamily: "Inter", - fontSize: 10 * Provider.of(context).fontScaling, - ), + style: settings.scaleFonts(defaultSmallTextStyle.copyWith(color: Provider.of(context).theme.messageFromOtherTextColor)), textAlign: TextAlign.left, maxLines: 1, textWidthBasis: TextWidthBasis.longestLine, diff --git a/lib/widgets/invitationbubble.dart b/lib/widgets/invitationbubble.dart index 9d361893..de10f11d 100644 --- a/lib/widgets/invitationbubble.dart +++ b/lib/widgets/invitationbubble.dart @@ -5,6 +5,7 @@ import 'package:cwtch/cwtch_icons_icons.dart'; import 'package:cwtch/models/contact.dart'; import 'package:cwtch/models/message.dart'; import 'package:cwtch/models/profile.dart'; +import 'package:cwtch/themes/opaque.dart'; import 'package:cwtch/widgets/malformedbubble.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; @@ -13,6 +14,7 @@ import 'package:intl/intl.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import '../settings.dart'; +import 'messageBubbleWidgetHelpers.dart'; import 'messagebubbledecorations.dart'; // Like MessageBubble but for displaying chat overlay 100/101 invitations @@ -54,15 +56,7 @@ class InvitationBubbleState extends State { } } - var wdgSender = Center( - widthFactor: 1, - child: SelectableText(senderDisplayStr + '\u202F', - style: TextStyle( - fontSize: 9.0 * Provider.of(context).fontScaling, - fontWeight: FontWeight.bold, - fontFamily: "Inter", - color: fromMe ? Provider.of(context).theme.messageFromMeTextColor : Provider.of(context).theme.messageFromOtherTextColor))); - + var wdgSender = compileSenderWidget(context, fromMe, senderDisplayStr); // If we receive an invite for ourselves, treat it as a bug. The UI no longer allows this so it could have only come from // some kind of malfeasance. var selfInvite = widget.inviteNick == Provider.of(context).onion; @@ -71,7 +65,7 @@ class InvitationBubbleState extends State { } var wdgMessage = isGroup && !showGroupInvite - ? Text(AppLocalizations.of(context)!.groupInviteSettingsWarning) + ? Text(AppLocalizations.of(context)!.groupInviteSettingsWarning, style: Provider.of(context).scaleFonts(defaultTextStyle)) : fromMe ? senderInviteChrome( AppLocalizations.of(context)!.sendAnInvitation, isGroup ? Provider.of(context).contactList.findContact(widget.inviteTarget)!.nickname : widget.inviteTarget) @@ -83,15 +77,21 @@ class InvitationBubbleState extends State { } else if (fromMe) { wdgDecorations = MessageBubbleDecoration(ackd: Provider.of(context).ackd, errored: Provider.of(context).error, fromMe: fromMe, messageDate: messageDate); } else if (isAccepted) { - wdgDecorations = Text(AppLocalizations.of(context)!.accepted + '\u202F'); + wdgDecorations = Text(AppLocalizations.of(context)!.accepted + '\u202F', style: Provider.of(context).scaleFonts(defaultTextStyle)); } else if (this.rejected) { - wdgDecorations = Text(AppLocalizations.of(context)!.rejected + '\u202F'); + wdgDecorations = Text(AppLocalizations.of(context)!.rejected + '\u202F', style: Provider.of(context).scaleFonts(defaultTextStyle)); } else { wdgDecorations = Center( widthFactor: 1, child: Wrap(children: [ - Padding(padding: EdgeInsets.all(5), child: ElevatedButton(child: Text(AppLocalizations.of(context)!.rejectGroupBtn + '\u202F'), onPressed: _btnReject)), - Padding(padding: EdgeInsets.all(5), child: ElevatedButton(child: Text(AppLocalizations.of(context)!.acceptGroupBtn + '\u202F'), onPressed: _btnAccept)), + Padding( + padding: EdgeInsets.all(5), + child: ElevatedButton( + child: Text(AppLocalizations.of(context)!.rejectGroupBtn + '\u202F', style: Provider.of(context).scaleFonts(defaultTextButtonStyle)), onPressed: _btnReject)), + Padding( + padding: EdgeInsets.all(5), + child: ElevatedButton( + child: Text(AppLocalizations.of(context)!.acceptGroupBtn + '\u202F', style: Provider.of(context).scaleFonts(defaultTextButtonStyle)), onPressed: _btnAccept)), ])); } @@ -149,21 +149,19 @@ class InvitationBubbleState extends State { // Construct an invite chrome for the sender Widget senderInviteChrome(String chrome, String targetName) { + var settings = Provider.of(context); + return Wrap(children: [ SelectableText( chrome + '\u202F', - style: TextStyle( - color: Provider.of(context).theme.messageFromMeTextColor, - ), + style: settings.scaleFonts(defaultMessageTextStyle.copyWith(color: Provider.of(context).theme.messageFromMeTextColor)), textAlign: TextAlign.left, maxLines: 2, textWidthBasis: TextWidthBasis.longestLine, ), SelectableText( targetName + '\u202F', - style: TextStyle( - color: Provider.of(context).theme.messageFromMeTextColor, - ), + style: settings.scaleFonts(defaultMessageTextStyle.copyWith(color: Provider.of(context).theme.messageFromMeTextColor)), textAlign: TextAlign.left, maxLines: 2, textWidthBasis: TextWidthBasis.longestLine, @@ -173,19 +171,19 @@ class InvitationBubbleState extends State { // Construct an invite chrome Widget inviteChrome(String chrome, String targetName, String targetId) { + var settings = Provider.of(context); + return Wrap(children: [ SelectableText( chrome + '\u202F', - style: TextStyle( - color: Provider.of(context).theme.messageFromOtherTextColor, - ), + style: settings.scaleFonts(defaultMessageTextStyle.copyWith(color: Provider.of(context).theme.messageFromOtherTextColor)), textAlign: TextAlign.left, textWidthBasis: TextWidthBasis.longestLine, maxLines: 2, ), SelectableText( targetName + '\u202F', - style: TextStyle(color: Provider.of(context).theme.messageFromOtherTextColor), + style: settings.scaleFonts(defaultMessageTextStyle.copyWith(color: Provider.of(context).theme.messageFromOtherTextColor)), textAlign: TextAlign.left, maxLines: 2, textWidthBasis: TextWidthBasis.longestLine, diff --git a/lib/widgets/messageBubbleWidgetHelpers.dart b/lib/widgets/messageBubbleWidgetHelpers.dart new file mode 100644 index 00000000..118c22b4 --- /dev/null +++ b/lib/widgets/messageBubbleWidgetHelpers.dart @@ -0,0 +1,48 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import '../controllers/open_link_modal.dart'; +import '../settings.dart'; +import '../themes/opaque.dart'; +import '../third_party/linkify/flutter_linkify.dart'; + +Widget compileSenderWidget(BuildContext context, bool fromMe, String senderDisplayStr) { + return Container( + height: 14 * Provider.of(context).fontScaling, + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration(), + child: SelectableText(senderDisplayStr, + maxLines: 1, + style: TextStyle( + fontSize: 9.0 * Provider.of(context).fontScaling, + fontWeight: FontWeight.bold, + fontFamily: "Inter", + overflow: TextOverflow.clip, + color: fromMe ? Provider.of(context).theme.messageFromMeTextColor : Provider.of(context).theme.messageFromOtherTextColor, + ))); +} + +Widget compileMessageContentWidget(BuildContext context, bool fromMe, String content, FocusNode focus, bool formatMessages, bool showClickableLinks) { + return SelectableLinkify( + text: content + '\u202F', + // TODO: onOpen breaks the "selectable" functionality. Maybe something to do with gesture handler? + options: LinkifyOptions(messageFormatting: formatMessages, parseLinks: showClickableLinks, looseUrl: true, defaultToHttps: true), + linkifiers: [UrlLinkifier()], + onOpen: showClickableLinks + ? (link) { + modalOpenLink(context, link); + } + : null, + //key: Key(myKey), + focusNode: focus, + style: Provider.of(context) + .scaleFonts(defaultMessageTextStyle.copyWith(color: fromMe ? Provider.of(context).theme.messageFromMeTextColor : Provider.of(context).theme.messageFromOtherTextColor)), + linkStyle: Provider.of(context) + .scaleFonts(defaultMessageTextStyle.copyWith(color: fromMe ? Provider.of(context).theme.messageFromMeTextColor : Provider.of(context).theme.messageFromOtherTextColor)), + codeStyle: Provider.of(context).scaleFonts(defaultMessageTextStyle.copyWith( + fontFamily: "RobotoMono", + color: fromMe ? Provider.of(context).theme.messageFromOtherTextColor : Provider.of(context).theme.messageFromMeTextColor, + backgroundColor: fromMe ? Provider.of(context).theme.messageFromOtherBackgroundColor : Provider.of(context).theme.messageFromMeBackgroundColor)), + textAlign: TextAlign.left, + textWidthBasis: TextWidthBasis.longestLine, + ); +} diff --git a/lib/widgets/messagebubble.dart b/lib/widgets/messagebubble.dart index 6fb7179d..77515db5 100644 --- a/lib/widgets/messagebubble.dart +++ b/lib/widgets/messagebubble.dart @@ -3,6 +3,7 @@ import 'dart:io'; import 'package:cwtch/controllers/open_link_modal.dart'; import 'package:cwtch/models/contact.dart'; import 'package:cwtch/models/message.dart'; +import 'package:cwtch/themes/opaque.dart'; import 'package:cwtch/third_party/linkify/flutter_linkify.dart'; import 'package:cwtch/models/profile.dart'; import 'package:cwtch/widgets/malformedbubble.dart'; @@ -10,6 +11,7 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../settings.dart'; +import 'messageBubbleWidgetHelpers.dart'; import 'messagebubbledecorations.dart'; class MessageBubble extends StatefulWidget { @@ -42,56 +44,9 @@ class MessageBubbleState extends State { senderDisplayStr = Provider.of(context).senderHandle; } } - var wdgSender = Container( - height: 14 * Provider.of(context).fontScaling, - clipBehavior: Clip.hardEdge, - decoration: BoxDecoration(), - child: SelectableText(senderDisplayStr, - maxLines: 1, - style: TextStyle( - fontSize: 9.0 * Provider.of(context).fontScaling, - fontWeight: FontWeight.bold, - fontFamily: "Inter", - overflow: TextOverflow.clip, - color: fromMe ? Provider.of(context).theme.messageFromMeTextColor : Provider.of(context).theme.messageFromOtherTextColor, - ))); - - var wdgMessage = SelectableLinkify( - text: widget.content + '\u202F', - // TODO: onOpen breaks the "selectable" functionality. Maybe something to do with gesture handler? - options: LinkifyOptions(messageFormatting: formatMessages, parseLinks: showClickableLinks, looseUrl: true, defaultToHttps: true), - linkifiers: [UrlLinkifier()], - onOpen: showClickableLinks - ? (link) { - modalOpenLink(context, link); - } - : null, - //key: Key(myKey), - focusNode: _focus, - style: TextStyle( - fontSize: 12.0 * Provider.of(context).fontScaling, - fontWeight: FontWeight.normal, - fontFamily: "Inter", - color: fromMe ? Provider.of(context).theme.messageFromMeTextColor : Provider.of(context).theme.messageFromOtherTextColor, - ), - linkStyle: TextStyle( - fontSize: 12.0 * Provider.of(context).fontScaling, - fontWeight: FontWeight.normal, - fontFamily: "Inter", - color: fromMe ? Provider.of(context).theme.messageFromMeTextColor : Provider.of(context).theme.messageFromOtherTextColor), - codeStyle: TextStyle( - fontSize: 12.0 * Provider.of(context).fontScaling, - fontWeight: FontWeight.normal, - fontFamily: "Inter", - // note: these colors are flipped - color: fromMe ? Provider.of(context).theme.messageFromOtherTextColor : Provider.of(context).theme.messageFromMeTextColor, - backgroundColor: fromMe ? Provider.of(context).theme.messageFromOtherBackgroundColor : Provider.of(context).theme.messageFromMeBackgroundColor), - textAlign: TextAlign.left, - textWidthBasis: TextWidthBasis.longestLine, - ); - + var wdgSender = compileSenderWidget(context, fromMe, senderDisplayStr); + var wdgMessage = compileMessageContentWidget(context, fromMe, widget.content, _focus, formatMessages, showClickableLinks); var wdgDecorations = MessageBubbleDecoration(ackd: Provider.of(context).ackd, errored: Provider.of(context).error, fromMe: fromMe, messageDate: messageDate); - var error = Provider.of(context).error; return LayoutBuilder(builder: (context, constraints) { diff --git a/lib/widgets/messagerow.dart b/lib/widgets/messagerow.dart index 2d3faa34..59681304 100644 --- a/lib/widgets/messagerow.dart +++ b/lib/widgets/messagerow.dart @@ -7,6 +7,7 @@ import 'package:cwtch/models/appstate.dart'; import 'package:cwtch/models/contact.dart'; import 'package:cwtch/models/message.dart'; import 'package:cwtch/models/profile.dart'; +import 'package:cwtch/themes/opaque.dart'; import 'package:cwtch/third_party/base32/base32.dart'; import 'package:cwtch/views/contactsview.dart'; import 'package:cwtch/widgets/staticmessagebubble.dart'; @@ -307,7 +308,7 @@ class MessageRowState extends State with SingleTickerProviderStateMi bottomRight: Radius.circular(8), ), ), - child: Padding(padding: EdgeInsets.all(9.0), child: Text(AppLocalizations.of(context)!.newMessagesLabel))); + child: Padding(padding: EdgeInsets.all(9.0), child: Text(AppLocalizations.of(context)!.newMessagesLabel, style: Provider.of(context).scaleFonts(defaultTextButtonStyle)))); } void _runAnimation(Offset pixelsPerSecond, Size size) { diff --git a/lib/widgets/quotedmessage.dart b/lib/widgets/quotedmessage.dart index 70567c45..dadd0f1a 100644 --- a/lib/widgets/quotedmessage.dart +++ b/lib/widgets/quotedmessage.dart @@ -10,6 +10,7 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import '../settings.dart'; +import 'messageBubbleWidgetHelpers.dart'; import 'messagebubbledecorations.dart'; class QuotedMessageBubble extends StatefulWidget { @@ -43,53 +44,11 @@ class QuotedMessageBubbleState extends State { } } - var wdgSender = Container( - height: 14 * Provider.of(context).fontScaling, - clipBehavior: Clip.hardEdge, - decoration: BoxDecoration(), - child: SelectableText(senderDisplayStr, - style: TextStyle( - fontSize: 9.0 * Provider.of(context).fontScaling, - fontWeight: FontWeight.bold, - fontFamily: "Inter", - color: fromMe ? Provider.of(context).theme.messageFromMeTextColor : Provider.of(context).theme.messageFromOtherTextColor))); + var wdgSender = compileSenderWidget(context, fromMe, senderDisplayStr); var showClickableLinks = Provider.of(context).isExperimentEnabled(ClickableLinksExperiment); var formatMessages = Provider.of(context).isExperimentEnabled(FormattingExperiment); - - var wdgMessage = SelectableLinkify( - text: widget.body + '\u202F', - // TODO: onOpen breaks the "selectable" functionality. Maybe something to do with gesture handler? - options: LinkifyOptions(messageFormatting: formatMessages, parseLinks: showClickableLinks, looseUrl: true, defaultToHttps: true), - linkifiers: [UrlLinkifier()], - onOpen: showClickableLinks - ? (link) { - modalOpenLink(context, link); - } - : null, - //key: Key(myKey), - focusNode: _focus, - style: TextStyle( - fontSize: 12.0 * Provider.of(context).fontScaling, - fontWeight: FontWeight.normal, - fontFamily: "Inter", - color: fromMe ? Provider.of(context).theme.messageFromMeTextColor : Provider.of(context).theme.messageFromOtherTextColor, - ), - linkStyle: TextStyle( - fontSize: 12.0 * Provider.of(context).fontScaling, - fontWeight: FontWeight.normal, - fontFamily: "Inter", - color: fromMe ? Provider.of(context).theme.messageFromMeTextColor : Provider.of(context).theme.messageFromOtherTextColor), - codeStyle: TextStyle( - fontSize: 12.0 * Provider.of(context).fontScaling, - fontWeight: FontWeight.normal, - fontFamily: "RobotoMono", - // note: these colors are flipped - color: fromMe ? Provider.of(context).theme.messageFromOtherTextColor : Provider.of(context).theme.messageFromMeTextColor, - backgroundColor: fromMe ? Provider.of(context).theme.messageFromOtherBackgroundColor : Provider.of(context).theme.messageFromMeBackgroundColor), - textAlign: TextAlign.left, - textWidthBasis: TextWidthBasis.longestLine, - ); + var wdgMessage = compileMessageContentWidget(context, fromMe, widget.body, _focus, formatMessages, showClickableLinks); var wdgQuote = FutureBuilder( future: widget.quotedMessage, @@ -118,7 +77,7 @@ class QuotedMessageBubbleState extends State { var wdgReplyingTo = SelectableText( AppLocalizations.of(context)!.replyingTo.replaceAll("%1", qMessageSender), - style: TextStyle(fontSize: 10, color: qTextColor.withOpacity(0.8)), + style: Provider.of(context).scaleFonts(TextStyle(fontSize: 10, color: qTextColor.withOpacity(0.8))), ); // Swap the background color for quoted tweets.. return MouseRegion( diff --git a/lib/widgets/staticmessagebubble.dart b/lib/widgets/staticmessagebubble.dart index bd8e6187..ad19bd56 100644 --- a/lib/widgets/staticmessagebubble.dart +++ b/lib/widgets/staticmessagebubble.dart @@ -6,6 +6,7 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../settings.dart'; +import 'messageBubbleWidgetHelpers.dart'; import 'messagebubbledecorations.dart'; class StaticMessageBubble extends StatefulWidget { @@ -40,12 +41,7 @@ class StaticMessageBubbleState extends State { senderDisplayStr = widget.profile.nickname; } - var wdgSender = SelectableText(senderDisplayStr, - style: TextStyle( - fontSize: 9.0 * Provider.of(context).fontScaling, - fontWeight: FontWeight.bold, - fontFamily: "Inter", - color: fromMe ? widget.settings.theme.messageFromMeTextColor : widget.settings.theme.messageFromOtherTextColor)); + var wdgSender = compileSenderWidget(context, fromMe, senderDisplayStr); var wdgDecorations = MessageBubbleDecoration(ackd: widget.metadata.ackd, errored: widget.metadata.error, fromMe: fromMe, messageDate: messageDate);