Small UI Fixes / Font Styles / Abstractions

This commit is contained in:
Sarah Jamie Lewis 2023-05-18 11:15:13 -07:00 committed by Gitea
parent 7f50036968
commit 22bf5cfe92
21 changed files with 290 additions and 341 deletions

View File

@ -360,8 +360,7 @@ class MainActivity: FlutterActivity() {
"PeerWithOnion" -> { "PeerWithOnion" -> {
val profile: String = call.argument("ProfileOnion") ?: "" val profile: String = call.argument("ProfileOnion") ?: ""
val onion: String = call.argument("onion") ?: "" val onion: String = call.argument("onion") ?: ""
result.success(Cwtch.peerWithOnion(profile, onion)) Cwtch.peerWithOnion(profile, onion)
return
} }
@ -493,14 +492,17 @@ class MainActivity: FlutterActivity() {
} }
"GetProfileAttribute" -> { "GetProfileAttribute" -> {
val profile: String = call.argument("ProfileOnion") ?: "" val profile: String = call.argument("ProfileOnion") ?: ""
val key: String = call.argument("Key") ?: "" val key: String = call.argument("key") ?: ""
Data.Builder().putString("result", Cwtch.getProfileAttribute(profile, key)).build() var resultjson = Cwtch.getProfileAttribute(profile, key);
return result.success(resultjson)
} }
"GetConversationAttribute" -> { "GetConversationAttribute" -> {
val profile: String = call.argument("ProfileOnion") ?: "" val profile: String = call.argument("ProfileOnion") ?: ""
val conversation: Int = call.argument("conversation") ?: 0 val conversation: Int = call.argument("conversation") ?: 0
val key: String = call.argument("Key") ?: "" val key: String = call.argument("key") ?: ""
Data.Builder().putString("result", Cwtch.getConversationAttribute(profile, conversation.toLong(), key)).build() var resultjson = Cwtch.getConversationAttribute(profile, conversation.toLong(), key);
result.success(resultjson)
return
} }
"SetConversationAttribute" -> { "SetConversationAttribute" -> {
val profile: String = call.argument("ProfileOnion") ?: "" val profile: String = call.argument("ProfileOnion") ?: ""
@ -512,7 +514,8 @@ class MainActivity: FlutterActivity() {
"ImportProfile" -> { "ImportProfile" -> {
val file: String = call.argument("file") ?: "" val file: String = call.argument("file") ?: ""
val pass: String = call.argument("pass") ?: "" val pass: String = call.argument("pass") ?: ""
Data.Builder().putString("result", Cwtch.importProfile(file, pass)).build() result.success(Cwtch.importProfile(file, pass))
return
} }
"ReconnectCwtchForeground" -> { "ReconnectCwtchForeground" -> {
Cwtch.reconnectCwtchForeground() Cwtch.reconnectCwtchForeground()

View File

@ -1,16 +1,20 @@
import 'package:cwtch/themes/opaque.dart';
import 'package:cwtch/third_party/linkify/linkify.dart'; import 'package:cwtch/third_party/linkify/linkify.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.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) { void modalOpenLink(BuildContext ctx, LinkableElement link) {
showModalBottomSheet<void>( showModalBottomSheet<void>(
context: ctx, context: ctx,
builder: (BuildContext bcontext) { builder: (BuildContext bcontext) {
return Container( return Container(
height: 200, // bespoke value courtesy of the [TextField] docs height: 200,
child: Center( child: Center(
child: Padding( child: Padding(
padding: EdgeInsets.all(30.0), padding: EdgeInsets.all(30.0),
@ -18,17 +22,24 @@ void modalOpenLink(BuildContext ctx, LinkableElement link) {
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: <Widget>[ children: <Widget>[
Text(AppLocalizations.of(bcontext)!.clickableLinksWarning), Text(
AppLocalizations.of(bcontext)!.clickableLinksWarning,
style: Provider.of<Settings>(bcontext).scaleFonts(defaultTextStyle),
),
Flex(direction: Axis.horizontal, mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ Flex(direction: Axis.horizontal, mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[
Container( Container(
margin: EdgeInsets.symmetric(vertical: 20, horizontal: 10), margin: EdgeInsets.symmetric(vertical: 20, horizontal: 10),
child: ElevatedButton( child: ElevatedButton(
child: Text(AppLocalizations.of(bcontext)!.clickableLinksCopy, semanticsLabel: AppLocalizations.of(bcontext)!.clickableLinksCopy), child: Text(AppLocalizations.of(bcontext)!.clickableLinksCopy,
style: Provider.of<Settings>(bcontext).scaleFonts(defaultTextButtonStyle), semanticsLabel: AppLocalizations.of(bcontext)!.clickableLinksCopy),
onPressed: () { onPressed: () {
Clipboard.setData(new ClipboardData(text: link.url)); Clipboard.setData(new ClipboardData(text: link.url));
final snackBar = SnackBar( final snackBar = SnackBar(
content: Text(AppLocalizations.of(bcontext)!.copiedToClipboardNotification), content: Text(
AppLocalizations.of(bcontext)!.copiedToClipboardNotification,
style: Provider.of<Settings>(bcontext).scaleFonts(defaultTextButtonStyle),
),
); );
Navigator.pop(bcontext); Navigator.pop(bcontext);
@ -39,15 +50,14 @@ void modalOpenLink(BuildContext ctx, LinkableElement link) {
Container( Container(
margin: EdgeInsets.symmetric(vertical: 20, horizontal: 10), margin: EdgeInsets.symmetric(vertical: 20, horizontal: 10),
child: ElevatedButton( child: ElevatedButton(
child: Text(AppLocalizations.of(bcontext)!.clickableLinkOpen, semanticsLabel: AppLocalizations.of(bcontext)!.clickableLinkOpen), child: Text(AppLocalizations.of(bcontext)!.clickableLinkOpen,
style: Provider.of<Settings>(bcontext).scaleFonts(defaultTextButtonStyle), semanticsLabel: AppLocalizations.of(bcontext)!.clickableLinkOpen),
onPressed: () async { onPressed: () async {
if (await canLaunch(link.url)) { if (await canLaunchUrlString(link.url)) {
await launch(link.url); await launchUrlString(link.url);
Navigator.pop(bcontext); Navigator.pop(bcontext);
} else { } else {
final snackBar = SnackBar( final snackBar = SnackBar(content: Text(AppLocalizations.of(bcontext)!.clickableLinkError, style: Provider.of<Settings>(bcontext).scaleFonts(defaultTextButtonStyle)));
content: Text(AppLocalizations.of(bcontext)!.clickableLinkError),
);
ScaffoldMessenger.of(bcontext).showSnackBar(snackBar); ScaffoldMessenger.of(bcontext).showSnackBar(snackBar);
} }
}, },

View File

@ -97,11 +97,11 @@ abstract class Cwtch {
Future<dynamic> ImportBundle(String profile, String bundle); Future<dynamic> ImportBundle(String profile, String bundle);
// ignore: non_constant_identifier_names // ignore: non_constant_identifier_names
void SetProfileAttribute(String profile, String key, String val); void SetProfileAttribute(String profile, String key, String val);
String? GetProfileAttribute(String profile, String key); Future<String?> GetProfileAttribute(String profile, String key);
// ignore: non_constant_identifier_names // ignore: non_constant_identifier_names
void SetConversationAttribute(String profile, int conversation, String key, String val); void SetConversationAttribute(String profile, int conversation, String key, String val);
// ignore: non_constant_identifier_names // ignore: non_constant_identifier_names
String? GetConversationAttribute(String profile, int identifier, String s); Future<String?> GetConversationAttribute(String profile, int identifier, String s);
// ignore: non_constant_identifier_names // ignore: non_constant_identifier_names
void SetMessageAttribute(String profile, int conversation, int channel, int message, String key, String val); void SetMessageAttribute(String profile, int conversation, int channel, int message, String key, String val);
// ignore: non_constant_identifier_names // ignore: non_constant_identifier_names

View File

@ -59,6 +59,8 @@ class CwtchNotifier {
} }
void handleMessage(String type, dynamic data) { void handleMessage(String type, dynamic data) {
EnvironmentConfig.debugLog("Handing Message $type ${data.toString()}");
//EnvironmentConfig.debugLog("NewEvent $type $data"); //EnvironmentConfig.debugLog("NewEvent $type $data");
switch (type) { switch (type) {
case "CwtchStarted": case "CwtchStarted":
@ -68,25 +70,24 @@ class CwtchNotifier {
appState.SetAppError(data["Error"]); appState.SetAppError(data["Error"]);
break; break;
case "NewPeer": case "NewPeer":
// empty events can be caused by the testing framework
if (data["Online"] == null) {
break;
}
// EnvironmentConfig.debugLog("NewPeer $data"); // EnvironmentConfig.debugLog("NewPeer $data");
// if tag != v1-defaultPassword then it is either encrypted OR it is an unencrypted account created during pre-beta... // 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", profileCN.add(data["Identity"], data["name"], data["picture"], data["defaultPicture"], data["ContactsJson"], data["ServerList"], data["Online"] == "true", data["autostart"] == "true",
data["tag"] != "v1-defaultPassword"); data["tag"] != "v1-defaultPassword");
// Update Profile Attributes // Update Profile Attributes
profileCN.getProfile(data["Identity"])?.setAttribute(0, flwtchState.cwtch.GetProfileAttribute(data["Identity"], "profile.profile-attribute-1")); EnvironmentConfig.debugLog("Looking up Profile Attributes ${data["Identity"]} ${profileCN.getProfile(data["Identity"])}");
profileCN.getProfile(data["Identity"])?.setAttribute(1, flwtchState.cwtch.GetProfileAttribute(data["Identity"], "profile.profile-attribute-2")); flwtchState.cwtch.GetProfileAttribute(data["Identity"], "profile.profile-attribute-1").then((value) => profileCN.getProfile(data["Identity"])?.setAttribute(0, value));
profileCN.getProfile(data["Identity"])?.setAttribute(2, flwtchState.cwtch.GetProfileAttribute(data["Identity"], "profile.profile-attribute-3")); flwtchState.cwtch.GetProfileAttribute(data["Identity"], "profile.profile-attribute-2").then((value) => profileCN.getProfile(data["Identity"])?.setAttribute(1, value));
profileCN.getProfile(data["Identity"])?.setAvailabilityStatus(flwtchState.cwtch.GetProfileAttribute(data["Identity"], "profile.profile-status") ?? ""); 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) { profileCN.getProfile(data["Identity"])?.contactList.contacts.forEach((contact) {
contact.setAttribute(0, flwtchState.cwtch.GetConversationAttribute(data["Identity"], contact.identifier, "public.profile.profile-attribute-1")); flwtchState.cwtch.GetConversationAttribute(data["Identity"], contact.identifier, "public.profile.profile-attribute-1").then((value) => contact.setAttribute(0, value));
contact.setAttribute(1, flwtchState.cwtch.GetConversationAttribute(data["Identity"], contact.identifier, "public.profile.profile-attribute-2")); flwtchState.cwtch.GetConversationAttribute(data["Identity"], contact.identifier, "public.profile.profile-attribute-2").then((value) => contact.setAttribute(1, value));
contact.setAttribute(2, flwtchState.cwtch.GetConversationAttribute(data["Identity"], contact.identifier, "public.profile.profile-attribute-3")); flwtchState.cwtch.GetConversationAttribute(data["Identity"], contact.identifier, "public.profile.profile-attribute-3").then((value) => contact.setAttribute(2, value));
contact.setAvailabilityStatus(flwtchState.cwtch.GetConversationAttribute(data["Identity"], contact.identifier, "public.profile.profile-status") ?? ""); flwtchState.cwtch.GetConversationAttribute(data["Identity"], contact.identifier, "public.profile.profile-status").then((value) => contact.setAvailabilityStatus(value ?? ""));
}); });
break; break;
@ -392,6 +393,7 @@ class CwtchNotifier {
String fileKey = data['Data']; String fileKey = data['Data'];
var contact = profileCN.getProfile(data["ProfileOnion"])?.contactList.findContact(data["RemotePeer"]); var contact = profileCN.getProfile(data["ProfileOnion"])?.contactList.findContact(data["RemotePeer"]);
if (contact != null) { if (contact != null) {
EnvironmentConfig.debugLog("waiting for download from $contact");
profileCN.getProfile(data["ProfileOnion"])?.waitForDownloadComplete(contact.identifier, fileKey); 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") { } else if (data['Path'] == "profile.profile-attribute-1" || data['Path'] == "profile.profile-attribute-2" || data['Path'] == "profile.profile-attribute-3") {

View File

@ -967,7 +967,7 @@ class CwtchFfi implements Cwtch {
} }
@override @override
String? GetProfileAttribute(String profile, String key) { Future<String?> GetProfileAttribute(String profile, String key) {
var getProfileAttributeC = library.lookup<NativeFunction<get_json_blob_from_str_str_function>>("c_GetProfileAttribute"); var getProfileAttributeC = library.lookup<NativeFunction<get_json_blob_from_str_str_function>>("c_GetProfileAttribute");
// ignore: non_constant_identifier_names // ignore: non_constant_identifier_names
final GetProfileAttribute = getProfileAttributeC.asFunction<GetJsonBlobFromStrStrFn>(); final GetProfileAttribute = getProfileAttributeC.asFunction<GetJsonBlobFromStrStrFn>();
@ -982,17 +982,17 @@ class CwtchFfi implements Cwtch {
try { try {
dynamic attributeResult = json.decode(jsonMessage); dynamic attributeResult = json.decode(jsonMessage);
if (attributeResult["Exists"]) { if (attributeResult["Exists"]) {
return attributeResult["Value"]; return Future.value(attributeResult["Value"]);
} }
} catch (e) { } catch (e) {
EnvironmentConfig.debugLog("error getting profile attribute: $e"); EnvironmentConfig.debugLog("error getting profile attribute: $e");
} }
return null; return Future.value(null);
} }
@override @override
String? GetConversationAttribute(String profile, int conversation, String key) { Future<String?> GetConversationAttribute(String profile, int conversation, String key) {
var getConversationAttributeC = library.lookup<NativeFunction<get_json_blob_from_str_int_string_function>>("c_GetConversationAttribute"); var getConversationAttributeC = library.lookup<NativeFunction<get_json_blob_from_str_int_string_function>>("c_GetConversationAttribute");
// ignore: non_constant_identifier_names // ignore: non_constant_identifier_names
final GetConversationAttribute = getConversationAttributeC.asFunction<GetJsonBlobFromStrIntStringFn>(); final GetConversationAttribute = getConversationAttributeC.asFunction<GetJsonBlobFromStrIntStringFn>();
@ -1007,13 +1007,13 @@ class CwtchFfi implements Cwtch {
try { try {
dynamic attributeResult = json.decode(jsonMessage); dynamic attributeResult = json.decode(jsonMessage);
if (attributeResult["Exists"]) { if (attributeResult["Exists"]) {
return attributeResult["Value"]; return Future.value(attributeResult["Value"]);
} }
} catch (e) { } catch (e) {
EnvironmentConfig.debugLog("error getting profile attribute: $e"); EnvironmentConfig.debugLog("error getting profile attribute: $e");
} }
return null; return Future.value(null);
} }
@override @override

View File

@ -390,21 +390,25 @@ class CwtchGomobile implements Cwtch {
} }
@override @override
String? GetProfileAttribute(String profile, String key) { Future<String?> GetProfileAttribute(String profile, String key) async {
dynamic attributeResult = cwtchPlatform.invokeMethod("GetProfileAttribute", {"ProfileOnion": profile, "key": key}); return await cwtchPlatform.invokeMethod("GetProfileAttribute", {"ProfileOnion": profile, "key": key}).then((dynamic json) {
if (attributeResult["Exists"]) { var value = jsonDecode(json);
return attributeResult["Value"]; if (value["Exists"]) {
} return value["Value"];
return null; }
return null;
});
} }
@override @override
String? GetConversationAttribute(String profile, int conversation, String key) { Future<String?> GetConversationAttribute(String profile, int conversation, String key) async {
dynamic attributeResult = cwtchPlatform.invokeMethod("GetProfileAttribute", {"ProfileOnion": profile, "conversation": conversation, "key": key}); return await cwtchPlatform.invokeMethod("GetConversationAttribute", {"ProfileOnion": profile, "conversation": conversation, "key": key}).then((dynamic json) {
if (attributeResult["Exists"]) { var value = jsonDecode(json);
return attributeResult["Value"]; if (value["Exists"]) {
} return value["Value"];
return null; }
return null;
});
} }
@override @override

View File

@ -87,7 +87,6 @@ class FlwtchState extends State<Flwtch> with WindowListener {
print("initState: running..."); print("initState: running...");
windowManager.addListener(this); windowManager.addListener(this);
print("initState: registering notification, shutdown handlers..."); print("initState: registering notification, shutdown handlers...");
profs = ProfileListState(); profs = ProfileListState();
notificationClickChannel.setMethodCallHandler(_externalNotificationClicked); notificationClickChannel.setMethodCallHandler(_externalNotificationClicked);
@ -307,8 +306,7 @@ class FlwtchState extends State<Flwtch> with WindowListener {
globalAppState.focus = false; globalAppState.focus = false;
} }
void onWindowClose() { void onWindowClose() {}
}
@override @override
void dispose() { void dispose() {

View File

@ -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; ConversationNotificationPolicy get notificationsPolicy => _notificationPolicy;
set notificationsPolicy(ConversationNotificationPolicy newVal) { set notificationsPolicy(ConversationNotificationPolicy newVal) {

View File

@ -1,24 +1,26 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
/// A "MessageDraft" structure that stores information about in-progress message drafts. /// A "MessageDraft" structure that stores information about in-progress message drafts.
/// MessageDraft stores text, quoted replies, and attached images. /// MessageDraft stores text, quoted replies, and attached images.
/// Only one draft is stored per conversation. /// Only one draft is stored per conversation.
class MessageDraft extends ChangeNotifier { class MessageDraft extends ChangeNotifier {
String? _messageText;
QuotedReference? _quotedReference; QuotedReference? _quotedReference;
TextEditingController ctrlCompose = TextEditingController();
static MessageDraft empty() { static MessageDraft empty() {
return MessageDraft(); return MessageDraft();
} }
bool isEmpty() { 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) { set messageText(String text) {
this._messageText = text; this.ctrlCompose.text = text;
notifyListeners(); notifyListeners();
} }
@ -35,6 +37,18 @@ class MessageDraft extends ChangeNotifier {
this._quotedReference = null; this._quotedReference = null;
notifyListeners(); 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. /// A QuotedReference encapsulates the state of replied-to message.

View File

@ -23,8 +23,7 @@ abstract class NotificationsManager {
// NullNotificationsManager ignores all notification requests // NullNotificationsManager ignores all notification requests
class NullNotificationsManager implements NotificationsManager { class NullNotificationsManager implements NotificationsManager {
@override @override
Future<void> notify( Future<void> notify(String message, String profile, int conversationId) async {}
String message, String profile, int conversationId) async {}
} }
// Windows Notification Manager uses https://pub.dev/packages/desktoasts to implement // Windows Notification Manager uses https://pub.dev/packages/desktoasts to implement
@ -34,7 +33,6 @@ class WindowsNotificationManager implements NotificationsManager {
bool initialized = false; bool initialized = false;
late Future<void> Function(String, int) notificationSelectConvo; late Future<void> Function(String, int) notificationSelectConvo;
WindowsNotificationManager(Future<void> Function(String, int) notificationSelectConvo) { WindowsNotificationManager(Future<void> Function(String, int) notificationSelectConvo) {
this.notificationSelectConvo = notificationSelectConvo; this.notificationSelectConvo = notificationSelectConvo;
scheduleMicrotask(() async { scheduleMicrotask(() async {
@ -83,28 +81,26 @@ class WindowsNotificationManager implements NotificationsManager {
* *
WinToast.instance().clear(); WinToast.instance().clear();
await WinToast.instance().showToast( await WinToast.instance().showToast(
toast: Toast( toast: Toast(duration: ToastDuration.short, children: [
duration: ToastDuration.short, ToastChildAudio(source: ToastAudioSource.im),
children: [ ToastChildVisual(
ToastChildAudio(source: ToastAudioSource.im), binding: ToastVisualBinding(children: [
ToastChildVisual( ToastVisualBindingChildText(
binding: ToastVisualBinding(children: [ text: message,
ToastVisualBindingChildText( id: 1,
text: message, ),
id: 1, ])),
), ToastChildActions(children: [
])), ToastAction(
ToastChildActions(children: [ content: "Open",
ToastAction( arguments: jsonEncode(NotificationPayload(profile, conversationId)),
content: "Open", ),
arguments: jsonEncode(NotificationPayload(profile, conversationId)), ToastAction(
), content: "Close",
ToastAction( arguments: "close",
content: "Close", ),
arguments: "close", ]),
), ]));
]),
]));
active = false; active = false;
}*/ }*/
} }
@ -142,8 +138,7 @@ class NixNotificationManager implements NotificationsManager {
Future<String> detectLinuxAssetsPath() async { Future<String> detectLinuxAssetsPath() async {
var devStat = FileStat.stat("assets"); var devStat = FileStat.stat("assets");
var localStat = FileStat.stat("data/flutter_assets"); var localStat = FileStat.stat("data/flutter_assets");
var homeStat = FileStat.stat((Platform.environment["HOME"] ?? "") + var homeStat = FileStat.stat((Platform.environment["HOME"] ?? "") + "/.local/share/cwtch/data/flutter_assets");
"/.local/share/cwtch/data/flutter_assets");
var rootStat = FileStat.stat("/usr/share/cwtch/data/flutter_assets"); var rootStat = FileStat.stat("/usr/share/cwtch/data/flutter_assets");
if ((await devStat).type == FileSystemEntityType.directory) { if ((await devStat).type == FileSystemEntityType.directory) {
@ -151,16 +146,14 @@ class NixNotificationManager implements NotificationsManager {
} else if ((await localStat).type == FileSystemEntityType.directory) { } else if ((await localStat).type == FileSystemEntityType.directory) {
return path.join(Directory.current.path, "data/flutter_assets/"); return path.join(Directory.current.path, "data/flutter_assets/");
} else if ((await homeStat).type == FileSystemEntityType.directory) { } else if ((await homeStat).type == FileSystemEntityType.directory) {
return (Platform.environment["HOME"] ?? "") + return (Platform.environment["HOME"] ?? "") + "/.local/share/cwtch/data/flutter_assets/";
"/.local/share/cwtch/data/flutter_assets/";
} else if ((await rootStat).type == FileSystemEntityType.directory) { } else if ((await rootStat).type == FileSystemEntityType.directory) {
return "/usr/share/cwtch/data/flutter_assets/"; return "/usr/share/cwtch/data/flutter_assets/";
} }
return ""; return "";
} }
NixNotificationManager( NixNotificationManager(Future<void> Function(String, int) notificationSelectConvo) {
Future<void> Function(String, int) notificationSelectConvo) {
this.notificationSelectConvo = notificationSelectConvo; this.notificationSelectConvo = notificationSelectConvo;
flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
@ -171,26 +164,14 @@ class NixNotificationManager implements NotificationsManager {
linuxAssetsPath = ""; linuxAssetsPath = "";
} }
var linuxIcon = var linuxIcon = FilePathLinuxIcon(path.join(linuxAssetsPath, 'assets/knott.png'));
FilePathLinuxIcon(path.join(linuxAssetsPath, 'assets/knott.png'));
final LinuxInitializationSettings initializationSettingsLinux = final LinuxInitializationSettings initializationSettingsLinux = LinuxInitializationSettings(defaultActionName: 'Open notification', defaultIcon: linuxIcon, defaultSuppressSound: true);
LinuxInitializationSettings(
defaultActionName: 'Open notification',
defaultIcon: linuxIcon,
defaultSuppressSound: true);
final InitializationSettings initializationSettings = final InitializationSettings initializationSettings =
InitializationSettings( InitializationSettings(android: null, iOS: null, macOS: DarwinInitializationSettings(defaultPresentSound: false), linux: initializationSettingsLinux);
android: null,
iOS: null,
macOS: DarwinInitializationSettings(defaultPresentSound: false),
linux: initializationSettingsLinux);
flutterLocalNotificationsPlugin flutterLocalNotificationsPlugin.resolvePlatformSpecificImplementation<MacOSFlutterLocalNotificationsPlugin>()?.requestPermissions(
.resolvePlatformSpecificImplementation<
MacOSFlutterLocalNotificationsPlugin>()
?.requestPermissions(
alert: true, alert: true,
badge: false, badge: false,
sound: false, sound: false,
@ -202,8 +183,7 @@ class NixNotificationManager implements NotificationsManager {
}); });
} }
Future<void> notify( Future<void> notify(String message, String profile, int conversationId) async {
String message, String profile, int conversationId) async {
if (!globalAppState.focus) { if (!globalAppState.focus) {
// Warning: Only use title field on Linux, body field will render links as clickable // Warning: Only use title field on Linux, body field will render links as clickable
await flutterLocalNotificationsPlugin.show( await flutterLocalNotificationsPlugin.show(
@ -211,11 +191,7 @@ class NixNotificationManager implements NotificationsManager {
message, message,
'', '',
NotificationDetails( NotificationDetails(
linux: LinuxNotificationDetails( linux: LinuxNotificationDetails(suppressSound: true, category: LinuxNotificationCategory.imReceived, icon: FilePathLinuxIcon(path.join(linuxAssetsPath, 'assets/knott.png')))),
suppressSound: true,
category: LinuxNotificationCategory.imReceived,
icon: FilePathLinuxIcon(
path.join(linuxAssetsPath, 'assets/knott.png')))),
payload: jsonEncode(NotificationPayload(profile, conversationId))); payload: jsonEncode(NotificationPayload(profile, conversationId)));
} }
} }
@ -230,9 +206,7 @@ class NixNotificationManager implements NotificationsManager {
} }
} }
NotificationsManager newDesktopNotificationsManager( NotificationsManager newDesktopNotificationsManager(Future<void> Function(String profileOnion, int convoId) notificationSelectConvo) {
Future<void> Function(String profileOnion, int convoId)
notificationSelectConvo) {
// We don't want notifications in Dev Mode // We don't want notifications in Dev Mode
if (EnvironmentConfig.TEST_MODE) { if (EnvironmentConfig.TEST_MODE) {
return NullNotificationsManager(); return NullNotificationsManager();
@ -242,22 +216,19 @@ NotificationsManager newDesktopNotificationsManager(
try { try {
return NixNotificationManager(notificationSelectConvo); return NixNotificationManager(notificationSelectConvo);
} catch (e) { } catch (e) {
EnvironmentConfig.debugLog( EnvironmentConfig.debugLog("Failed to create LinuxNotificationManager. Switching off notifications.");
"Failed to create LinuxNotificationManager. Switching off notifications.");
} }
} else if (Platform.isMacOS) { } else if (Platform.isMacOS) {
try { try {
return NixNotificationManager(notificationSelectConvo); return NixNotificationManager(notificationSelectConvo);
} catch (e) { } catch (e) {
EnvironmentConfig.debugLog( EnvironmentConfig.debugLog("Failed to create NixNotificationManager. Switching off notifications.");
"Failed to create NixNotificationManager. Switching off notifications.");
} }
} else if (Platform.isWindows) { } else if (Platform.isWindows) {
try { try {
return WindowsNotificationManager(notificationSelectConvo); return WindowsNotificationManager(notificationSelectConvo);
} catch (e) { } catch (e) {
EnvironmentConfig.debugLog( EnvironmentConfig.debugLog("Failed to create Windows desktoasts notification manager");
"Failed to create Windows desktoasts notification manager");
} }
} }

View File

@ -192,6 +192,11 @@ class Settings extends ChangeNotifier {
double get fontScaling => _fontScaling; 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 /// Switch the Locale of the App
switchLocale(Locale newLocale) { switchLocale(Locale newLocale) {
locale = newLocale; locale = newLocale;

View File

@ -19,6 +19,12 @@ import 'neon2.dart';
const mode_light = "light"; const mode_light = "light";
const mode_dark = "dark"; 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 = { final themes = {
cwtch_theme: {mode_light: CwtchLight(), mode_dark: CwtchDark()}, cwtch_theme: {mode_light: CwtchLight(), mode_dark: CwtchDark()},
ghost_theme: {mode_light: GhostLight(), mode_dark: GhostDark()}, ghost_theme: {mode_light: GhostLight(), mode_dark: GhostDark()},

View File

@ -44,7 +44,6 @@ class MessageView extends StatefulWidget {
} }
class _MessageViewState extends State<MessageView> { class _MessageViewState extends State<MessageView> {
final ctrlrCompose = TextEditingController();
final focusNode = FocusNode(); final focusNode = FocusNode();
int selectedContact = -1; int selectedContact = -1;
ItemPositionsListener scrollListener = ItemPositionsListener.create(); ItemPositionsListener scrollListener = ItemPositionsListener.create();
@ -68,7 +67,6 @@ class _MessageViewState extends State<MessageView> {
showDown = false; showDown = false;
} }
}); });
ctrlrCompose.text = Provider.of<ContactInfoState>(context, listen: false).messageDraft.messageText ?? "";
super.initState(); super.initState();
} }
@ -88,7 +86,6 @@ class _MessageViewState extends State<MessageView> {
@override @override
void dispose() { void dispose() {
focusNode.dispose(); focusNode.dispose();
ctrlrCompose.dispose();
super.dispose(); super.dispose();
} }
@ -321,22 +318,22 @@ class _MessageViewState extends State<MessageView> {
void _sendMessage([String? ignoredParam]) { void _sendMessage([String? ignoredParam]) {
// Do this after we trim to preserve enter-behaviour... // Do this after we trim to preserve enter-behaviour...
bool isOffline = Provider.of<ContactInfoState>(context, listen: false).isOnline() == false; bool cannotSend = Provider.of<ContactInfoState>(context, listen: false).canSend() == false;
bool performingAntiSpam = Provider.of<ContactInfoState>(context, listen: false).antispamTickets == 0; bool performingAntiSpam = Provider.of<ContactInfoState>(context, listen: false).antispamTickets == 0;
bool isGroup = Provider.of<ContactInfoState>(context, listen: false).isGroup; bool isGroup = Provider.of<ContactInfoState>(context, listen: false).isGroup;
if (isOffline || (isGroup && performingAntiSpam)) { if (cannotSend || (isGroup && performingAntiSpam)) {
return; return;
} }
// Trim message // Trim message
final messageWithoutNewLine = ctrlrCompose.value.text.trimRight(); var messageText = Provider.of<ContactInfoState>(context, listen: false).messageDraft.messageText ?? "";
ctrlrCompose.value = TextEditingValue(text: messageWithoutNewLine, selection: TextSelection.fromPosition(TextPosition(offset: messageWithoutNewLine.length))); final messageWithoutNewLine = messageText.trimRight();
// peers and groups currently have different length constraints (servers can store less)... // 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; var lengthOk = (isGroup && actualMessageLength < GroupMessageLengthMax) || actualMessageLength <= P2PMessageLengthMax;
if (ctrlrCompose.value.text.isNotEmpty && lengthOk) { if (messageWithoutNewLine.isNotEmpty && lengthOk) {
if (Provider.of<AppState>(context, listen: false).selectedConversation != null && Provider.of<ContactInfoState>(context, listen: false).messageDraft.getQuotedMessage() != null) { if (Provider.of<AppState>(context, listen: false).selectedConversation != null && Provider.of<ContactInfoState>(context, listen: false).messageDraft.getQuotedMessage() != null) {
var conversationId = Provider.of<AppState>(context, listen: false).selectedConversation!; var conversationId = Provider.of<AppState>(context, listen: false).selectedConversation!;
MessageCache? cache = Provider.of<ProfileInfoState>(context, listen: false).contactList.getContact(conversationId)?.messageCache; MessageCache? cache = Provider.of<ProfileInfoState>(context, listen: false).contactList.getContact(conversationId)?.messageCache;
@ -347,7 +344,7 @@ class _MessageViewState extends State<MessageView> {
var bytes1 = utf8.encode(data!.metadata.senderHandle + data.wrapper); var bytes1 = utf8.encode(data!.metadata.senderHandle + data.wrapper);
var digest1 = sha256.convert(bytes1); var digest1 = sha256.convert(bytes1);
var contentHash = base64Encode(digest1.bytes); 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); ChatMessage cm = new ChatMessage(o: QuotedMessageOverlay, d: quotedMessage);
Provider.of<FlwtchState>(context, listen: false) Provider.of<FlwtchState>(context, listen: false)
.cwtch .cwtch
@ -359,7 +356,7 @@ class _MessageViewState extends State<MessageView> {
Provider.of<ContactInfoState>(context, listen: false).messageDraft.clearQuotedReference(); Provider.of<ContactInfoState>(context, listen: false).messageDraft.clearQuotedReference();
}); });
} else { } else {
ChatMessage cm = new ChatMessage(o: TextMessageOverlay, d: ctrlrCompose.value.text); ChatMessage cm = new ChatMessage(o: TextMessageOverlay, d: messageWithoutNewLine);
Provider.of<FlwtchState>(context, listen: false) Provider.of<FlwtchState>(context, listen: false)
.cwtch .cwtch
.SendMessage(Provider.of<ContactInfoState>(context, listen: false).profileOnion, Provider.of<ContactInfoState>(context, listen: false).identifier, jsonEncode(cm)) .SendMessage(Provider.of<ContactInfoState>(context, listen: false).profileOnion, Provider.of<ContactInfoState>(context, listen: false).identifier, jsonEncode(cm))
@ -391,8 +388,7 @@ class _MessageViewState extends State<MessageView> {
// At this point we have decided to send the text to the backend, failure is still possible // 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. // but it will show as an error-ed message, as such the draft can be purged.
Provider.of<ContactInfoState>(context, listen: false).messageDraft = MessageDraft.empty(); Provider.of<ContactInfoState>(context, listen: false).messageDraft.clearDraft();
ctrlrCompose.clear();
var profileOnion = Provider.of<ContactInfoState>(context, listen: false).profileOnion; var profileOnion = Provider.of<ContactInfoState>(context, listen: false).profileOnion;
var identifier = Provider.of<ContactInfoState>(context, listen: false).identifier; var identifier = Provider.of<ContactInfoState>(context, listen: false).identifier;
@ -424,7 +420,7 @@ class _MessageViewState extends State<MessageView> {
var wdgMessage = Padding( var wdgMessage = Padding(
padding: EdgeInsets.all(8), padding: EdgeInsets.all(8),
child: SelectableLinkify( child: SelectableLinkify(
text: ctrlrCompose.text + '\n', text: Provider.of<ContactInfoState>(context).messageDraft.messageText + '\n',
options: LinkifyOptions(messageFormatting: true, parseLinks: showClickableLinks, looseUrl: true, defaultToHttps: true), options: LinkifyOptions(messageFormatting: true, parseLinks: showClickableLinks, looseUrl: true, defaultToHttps: true),
linkifiers: [UrlLinkifier()], linkifiers: [UrlLinkifier()],
onOpen: showClickableLinks ? null : null, onOpen: showClickableLinks ? null : null,
@ -468,7 +464,7 @@ class _MessageViewState extends State<MessageView> {
margin: EdgeInsets.all(2), margin: EdgeInsets.all(2),
// 164 minimum height + 16px for every line of text so the entire message is displayed when previewed. // 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<ContactInfoState>(context).messageDraft.messageText.split("\n").length - 1) * 16),
child: Column( child: Column(
children: [ children: [
Row(mainAxisAlignment: MainAxisAlignment.start, mainAxisSize: MainAxisSize.max, children: [preview]), Row(mainAxisAlignment: MainAxisAlignment.start, mainAxisSize: MainAxisSize.max, children: [preview]),
@ -483,11 +479,11 @@ class _MessageViewState extends State<MessageView> {
} }
Widget _buildComposeBox(BuildContext context) { Widget _buildComposeBox(BuildContext context) {
bool isOffline = Provider.of<ContactInfoState>(context).isOnline() == false; bool cannotSend = Provider.of<ContactInfoState>(context).canSend() == false;
bool isGroup = Provider.of<ContactInfoState>(context).isGroup; bool isGroup = Provider.of<ContactInfoState>(context).isGroup;
var showToolbar = Provider.of<Settings>(context).isExperimentEnabled(FormattingExperiment); var showToolbar = Provider.of<Settings>(context).isExperimentEnabled(FormattingExperiment);
var charLength = ctrlrCompose.value.text.characters.length; var charLength = Provider.of<ContactInfoState>(context).messageDraft.messageText.characters.length;
var expectedLength = ctrlrCompose.value.text.length; var expectedLength = Provider.of<ContactInfoState>(context).messageDraft.messageText.length;
var numberOfBytesMoreThanChar = (expectedLength - charLength); var numberOfBytesMoreThanChar = (expectedLength - charLength);
var bold = IconButton( var bold = IconButton(
@ -495,12 +491,14 @@ class _MessageViewState extends State<MessageView> {
tooltip: AppLocalizations.of(context)!.tooltipBoldText, tooltip: AppLocalizations.of(context)!.tooltipBoldText,
onPressed: () { onPressed: () {
setState(() { setState(() {
var ctrlrCompose = Provider.of<ContactInfoState>(context, listen: false).messageDraft.ctrlCompose;
var selected = ctrlrCompose.selection.textInside(ctrlrCompose.text); var selected = ctrlrCompose.selection.textInside(ctrlrCompose.text);
var selection = ctrlrCompose.selection; var selection = ctrlrCompose.selection;
var start = ctrlrCompose.selection.start; var start = ctrlrCompose.selection.start;
var end = ctrlrCompose.selection.end; var end = ctrlrCompose.selection.end;
ctrlrCompose.text = ctrlrCompose.text.replaceRange(start, end, "**" + selected + "**"); ctrlrCompose.text = ctrlrCompose.text.replaceRange(start, end, "**" + selected + "**");
ctrlrCompose.selection = selection.copyWith(baseOffset: selection.start + 2, extentOffset: selection.start + 2); ctrlrCompose.selection = selection.copyWith(baseOffset: selection.start + 2, extentOffset: selection.start + 2);
Provider.of<ContactInfoState>(context, listen: false).messageDraft.ctrlCompose = ctrlrCompose;
}); });
}); });
@ -509,12 +507,14 @@ class _MessageViewState extends State<MessageView> {
tooltip: AppLocalizations.of(context)!.tooltipItalicize, tooltip: AppLocalizations.of(context)!.tooltipItalicize,
onPressed: () { onPressed: () {
setState(() { setState(() {
var ctrlrCompose = Provider.of<ContactInfoState>(context, listen: false).messageDraft.ctrlCompose;
var selected = ctrlrCompose.selection.textInside(ctrlrCompose.text); var selected = ctrlrCompose.selection.textInside(ctrlrCompose.text);
var selection = ctrlrCompose.selection; var selection = ctrlrCompose.selection;
var start = ctrlrCompose.selection.start; var start = ctrlrCompose.selection.start;
var end = ctrlrCompose.selection.end; var end = ctrlrCompose.selection.end;
ctrlrCompose.text = ctrlrCompose.text.replaceRange(start, end, "*" + selected + "*"); ctrlrCompose.text = ctrlrCompose.text.replaceRange(start, end, "*" + selected + "*");
ctrlrCompose.selection = selection.copyWith(baseOffset: selection.start + 1, extentOffset: selection.start + 1); ctrlrCompose.selection = selection.copyWith(baseOffset: selection.start + 1, extentOffset: selection.start + 1);
Provider.of<ContactInfoState>(context, listen: false).messageDraft.ctrlCompose = ctrlrCompose;
}); });
}); });
@ -523,12 +523,14 @@ class _MessageViewState extends State<MessageView> {
tooltip: AppLocalizations.of(context)!.tooltipCode, tooltip: AppLocalizations.of(context)!.tooltipCode,
onPressed: () { onPressed: () {
setState(() { setState(() {
var ctrlrCompose = Provider.of<ContactInfoState>(context, listen: false).messageDraft.ctrlCompose;
var selected = ctrlrCompose.selection.textInside(ctrlrCompose.text); var selected = ctrlrCompose.selection.textInside(ctrlrCompose.text);
var selection = ctrlrCompose.selection; var selection = ctrlrCompose.selection;
var start = ctrlrCompose.selection.start; var start = ctrlrCompose.selection.start;
var end = ctrlrCompose.selection.end; var end = ctrlrCompose.selection.end;
ctrlrCompose.text = ctrlrCompose.text.replaceRange(start, end, "`" + selected + "`"); ctrlrCompose.text = ctrlrCompose.text.replaceRange(start, end, "`" + selected + "`");
ctrlrCompose.selection = selection.copyWith(baseOffset: selection.start + 1, extentOffset: selection.start + 1); ctrlrCompose.selection = selection.copyWith(baseOffset: selection.start + 1, extentOffset: selection.start + 1);
Provider.of<ContactInfoState>(context, listen: false).messageDraft.ctrlCompose = ctrlrCompose;
}); });
}); });
@ -537,12 +539,14 @@ class _MessageViewState extends State<MessageView> {
tooltip: AppLocalizations.of(context)!.tooltipSuperscript, tooltip: AppLocalizations.of(context)!.tooltipSuperscript,
onPressed: () { onPressed: () {
setState(() { setState(() {
var ctrlrCompose = Provider.of<ContactInfoState>(context, listen: false).messageDraft.ctrlCompose;
var selected = ctrlrCompose.selection.textInside(ctrlrCompose.text); var selected = ctrlrCompose.selection.textInside(ctrlrCompose.text);
var selection = ctrlrCompose.selection; var selection = ctrlrCompose.selection;
var start = ctrlrCompose.selection.start; var start = ctrlrCompose.selection.start;
var end = ctrlrCompose.selection.end; var end = ctrlrCompose.selection.end;
ctrlrCompose.text = ctrlrCompose.text.replaceRange(start, end, "^" + selected + "^"); ctrlrCompose.text = ctrlrCompose.text.replaceRange(start, end, "^" + selected + "^");
ctrlrCompose.selection = selection.copyWith(baseOffset: selection.start + 1, extentOffset: selection.start + 1); ctrlrCompose.selection = selection.copyWith(baseOffset: selection.start + 1, extentOffset: selection.start + 1);
Provider.of<ContactInfoState>(context, listen: false).messageDraft.ctrlCompose = ctrlrCompose;
}); });
}); });
@ -551,12 +555,14 @@ class _MessageViewState extends State<MessageView> {
tooltip: AppLocalizations.of(context)!.tooltipSubscript, tooltip: AppLocalizations.of(context)!.tooltipSubscript,
onPressed: () { onPressed: () {
setState(() { setState(() {
var ctrlrCompose = Provider.of<ContactInfoState>(context, listen: false).messageDraft.ctrlCompose;
var selected = ctrlrCompose.selection.textInside(ctrlrCompose.text); var selected = ctrlrCompose.selection.textInside(ctrlrCompose.text);
var selection = ctrlrCompose.selection; var selection = ctrlrCompose.selection;
var start = ctrlrCompose.selection.start; var start = ctrlrCompose.selection.start;
var end = ctrlrCompose.selection.end; var end = ctrlrCompose.selection.end;
ctrlrCompose.text = ctrlrCompose.text.replaceRange(start, end, "_" + selected + "_"); ctrlrCompose.text = ctrlrCompose.text.replaceRange(start, end, "_" + selected + "_");
ctrlrCompose.selection = selection.copyWith(baseOffset: selection.start + 1, extentOffset: selection.start + 1); ctrlrCompose.selection = selection.copyWith(baseOffset: selection.start + 1, extentOffset: selection.start + 1);
Provider.of<ContactInfoState>(context, listen: false).messageDraft.ctrlCompose = ctrlrCompose;
}); });
}); });
@ -565,12 +571,14 @@ class _MessageViewState extends State<MessageView> {
tooltip: AppLocalizations.of(context)!.tooltipStrikethrough, tooltip: AppLocalizations.of(context)!.tooltipStrikethrough,
onPressed: () { onPressed: () {
setState(() { setState(() {
var ctrlrCompose = Provider.of<ContactInfoState>(context, listen: false).messageDraft.ctrlCompose;
var selected = ctrlrCompose.selection.textInside(ctrlrCompose.text); var selected = ctrlrCompose.selection.textInside(ctrlrCompose.text);
var selection = ctrlrCompose.selection; var selection = ctrlrCompose.selection;
var start = ctrlrCompose.selection.start; var start = ctrlrCompose.selection.start;
var end = ctrlrCompose.selection.end; var end = ctrlrCompose.selection.end;
ctrlrCompose.text = ctrlrCompose.text.replaceRange(start, end, "~~" + selected + "~~"); ctrlrCompose.text = ctrlrCompose.text.replaceRange(start, end, "~~" + selected + "~~");
ctrlrCompose.selection = selection.copyWith(baseOffset: selection.start + 2, extentOffset: selection.start + 2); ctrlrCompose.selection = selection.copyWith(baseOffset: selection.start + 2, extentOffset: selection.start + 2);
Provider.of<ContactInfoState>(context, listen: false).messageDraft.ctrlCompose = ctrlrCompose;
}); });
}); });
@ -600,7 +608,7 @@ class _MessageViewState extends State<MessageView> {
padding: EdgeInsets.all(8), padding: EdgeInsets.all(8),
child: TextFormField( child: TextFormField(
key: Key('txtCompose'), key: Key('txtCompose'),
controller: ctrlrCompose, controller: Provider.of<ContactInfoState>(context).messageDraft.ctrlCompose,
focusNode: focusNode, focusNode: focusNode,
autofocus: !Platform.isAndroid, autofocus: !Platform.isAndroid,
textInputAction: TextInputAction.newline, textInputAction: TextInputAction.newline,
@ -615,18 +623,17 @@ class _MessageViewState extends State<MessageView> {
style: TextStyle( style: TextStyle(
fontFamily: "Inter", fontFamily: "Inter",
fontSize: 12.0 * Provider.of<Settings>(context).fontScaling, fontSize: 12.0 * Provider.of<Settings>(context).fontScaling,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w300,
), ),
enabled: true, // always allow editing... enabled: true, // always allow editing...
onChanged: (String x) { onChanged: (String x) {
Provider.of<ContactInfoState>(context, listen: false).messageDraft.messageText = x;
setState(() { setState(() {
// we need to force a rerender here to update the max length count // we need to force a rerender here to update the max length count
}); });
}, },
decoration: InputDecoration( decoration: InputDecoration(
hintText: isOffline ? "" : AppLocalizations.of(context)!.placeholderEnterMessage, hintText: AppLocalizations.of(context)!.placeholderEnterMessage,
hintStyle: TextStyle(fontFamily: "Inter", fontSize: 10.0 * Provider.of<Settings>(context).fontScaling, color: Provider.of<Settings>(context).theme.sendHintTextColor), hintStyle: TextStyle(fontFamily: "Inter", fontSize: 10.0 * Provider.of<Settings>(context).fontScaling, color: Provider.of<Settings>(context).theme.sendHintTextColor),
enabledBorder: InputBorder.none, enabledBorder: InputBorder.none,
focusedBorder: InputBorder.none, focusedBorder: InputBorder.none,
@ -635,13 +642,13 @@ class _MessageViewState extends State<MessageView> {
key: Key("btnSend"), key: Key("btnSend"),
style: ElevatedButton.styleFrom(padding: EdgeInsets.all(0.0), shape: new RoundedRectangleBorder(borderRadius: new BorderRadius.circular(45.0))), style: ElevatedButton.styleFrom(padding: EdgeInsets.all(0.0), shape: new RoundedRectangleBorder(borderRadius: new BorderRadius.circular(45.0))),
child: Tooltip( child: Tooltip(
message: isOffline message: cannotSend
? (isGroup ? AppLocalizations.of(context)!.serverNotSynced : AppLocalizations.of(context)!.peerOfflineMessage) ? (isGroup ? AppLocalizations.of(context)!.serverNotSynced : AppLocalizations.of(context)!.peerOfflineMessage)
: (isGroup && Provider.of<ContactInfoState>(context, listen: false).antispamTickets == 0) : (isGroup && Provider.of<ContactInfoState>(context, listen: false).antispamTickets == 0)
? AppLocalizations.of(context)!.acquiringTicketsFromServer ? AppLocalizations.of(context)!.acquiringTicketsFromServer
: AppLocalizations.of(context)!.sendMessage, : AppLocalizations.of(context)!.sendMessage,
child: Icon(CwtchIcons.send_24px, size: 24, color: Provider.of<Settings>(context).theme.defaultButtonTextColor)), child: Icon(CwtchIcons.send_24px, size: 24, color: Provider.of<Settings>(context).theme.defaultButtonTextColor)),
onPressed: isOffline || (isGroup && Provider.of<ContactInfoState>(context, listen: false).antispamTickets == 0) ? null : _sendMessage, onPressed: cannotSend || (isGroup && Provider.of<ContactInfoState>(context, listen: false).antispamTickets == 0) ? null : _sendMessage,
))), ))),
))); )));
@ -727,7 +734,8 @@ class _MessageViewState extends State<MessageView> {
if (event is RawKeyUpEvent) { if (event is RawKeyUpEvent) {
if ((data.logicalKey == LogicalKeyboardKey.enter && !event.isShiftPressed) || data.logicalKey == LogicalKeyboardKey.numpadEnter && !event.isShiftPressed) { 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 // 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<ContactInfoState>(context, listen: false).messageDraft.ctrlCompose.selection.baseOffset !=
Provider.of<ContactInfoState>(context, listen: false).messageDraft.ctrlCompose.text.length) {
return; return;
} }
_sendMessage(); _sendMessage();
@ -735,8 +743,6 @@ class _MessageViewState extends State<MessageView> {
} }
} }
void placeHolder() => {};
// explicitly passing BuildContext ctx here is important, change at risk to own health // explicitly passing BuildContext ctx here is important, change at risk to own health
// otherwise some Providers will become inaccessible to subwidgets...? // otherwise some Providers will become inaccessible to subwidgets...?
// https://stackoverflow.com/a/63818697 // https://stackoverflow.com/a/63818697

View File

@ -1,3 +1,4 @@
import 'package:cwtch/themes/opaque.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import '../settings.dart'; import '../settings.dart';
@ -18,7 +19,7 @@ class _CwtchLabelState extends State<CwtchLabel> {
return Consumer<Settings>(builder: (context, theme, child) { return Consumer<Settings>(builder: (context, theme, child) {
return Text( return Text(
widget.label, widget.label,
style: TextStyle(fontSize: 20, color: theme.current().mainTextColor), style: Provider.of<Settings>(context).scaleFonts(defaultFormLabelTextStyle),
); );
}); });
} }

View File

@ -7,7 +7,9 @@ import 'package:cwtch/models/contact.dart';
import 'package:cwtch/models/filedownloadprogress.dart'; import 'package:cwtch/models/filedownloadprogress.dart';
import 'package:cwtch/models/message.dart'; import 'package:cwtch/models/message.dart';
import 'package:cwtch/models/profile.dart'; import 'package:cwtch/models/profile.dart';
import 'package:cwtch/themes/opaque.dart';
import 'package:cwtch/widgets/malformedbubble.dart'; import 'package:cwtch/widgets/malformedbubble.dart';
import 'package:cwtch/widgets/messageBubbleWidgetHelpers.dart';
import 'package:file_picker/file_picker.dart'; import 'package:file_picker/file_picker.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -147,18 +149,10 @@ class FileBubbleState extends State<FileBubble> {
var wdgSender = Visibility( var wdgSender = Visibility(
visible: widget.interactive, visible: widget.interactive,
child: Container( child: Container(
height: 14 * Provider.of<Settings>(context).fontScaling, height: 14 * Provider.of<Settings>(context).fontScaling, clipBehavior: Clip.hardEdge, decoration: BoxDecoration(), child: compileSenderWidget(context, fromMe, senderDisplayStr)));
clipBehavior: Clip.hardEdge,
decoration: BoxDecoration(),
child: SelectableText(senderDisplayStr + '\u202F',
style: TextStyle(
fontSize: 9.0 * Provider.of<Settings>(context).fontScaling,
fontWeight: FontWeight.bold,
fontFamily: "Inter",
color: fromMe ? Provider.of<Settings>(context).theme.messageFromMeTextColor : Provider.of<Settings>(context).theme.messageFromOtherTextColor))));
var isPreview = false; var isPreview = false;
var wdgMessage = !showFileSharing var wdgMessage = !showFileSharing
? Text(AppLocalizations.of(context)!.messageEnableFileSharing) ? Text(AppLocalizations.of(context)!.messageEnableFileSharing, style: Provider.of<Settings>(context).scaleFonts(defaultTextStyle))
: fromMe : fromMe
? senderFileChrome(AppLocalizations.of(context)!.messageFileSent, widget.nameSuggestion, widget.rootHash, widget.fileSize) ? senderFileChrome(AppLocalizations.of(context)!.messageFileSent, widget.nameSuggestion, widget.rootHash, widget.fileSize)
: (fileChrome(AppLocalizations.of(context)!.messageFileOffered + ":", 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<FileBubble> {
}, },
))); )));
} else { } 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<Settings>(context).scaleFonts(defaultTextStyle)));
} }
} else if (downloadActive) { } else if (downloadActive) {
if (!downloadGotManifest) { 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<Settings>(context).scaleFonts(defaultTextStyle)));
} else { } else {
wdgDecorations = Visibility( wdgDecorations = Visibility(
visible: widget.interactive, visible: widget.interactive,
@ -199,19 +195,19 @@ class FileBubbleState extends State<FileBubble> {
// in this case, the download was done in a previous application launch, // in this case, the download was done in a previous application launch,
// so we probably have to request an info lookup // so we probably have to request an info lookup
if (!downloadInterrupted) { if (!downloadInterrupted) {
wdgDecorations = Text(AppLocalizations.of(context)!.fileCheckingStatus + '...' + '\u202F'); wdgDecorations = Text(AppLocalizations.of(context)!.fileCheckingStatus + '...' + '\u202F', style: Provider.of<Settings>(context).scaleFonts(defaultTextStyle));
// We should have already requested this... // We should have already requested this...
} else { } else {
var path = Provider.of<ProfileInfoState>(context).downloadFinalPath(widget.fileKey()) ?? ""; var path = Provider.of<ProfileInfoState>(context).downloadFinalPath(widget.fileKey()) ?? "";
wdgDecorations = Visibility( wdgDecorations = Visibility(
visible: widget.interactive, visible: widget.interactive,
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
Text(AppLocalizations.of(context)!.fileInterrupted + ': ' + path + '\u202F'), Text(AppLocalizations.of(context)!.fileInterrupted + ': ' + path + '\u202F', style: Provider.of<Settings>(context).scaleFonts(defaultTextStyle)),
ElevatedButton(onPressed: _btnResume, child: Text(AppLocalizations.of(context)!.verfiyResumeButton)) ElevatedButton(onPressed: _btnResume, child: Text(AppLocalizations.of(context)!.verfiyResumeButton, style: Provider.of<Settings>(context).scaleFonts(defaultTextButtonStyle)))
])); ]));
} }
} else if (!senderIsContact) { } else if (!senderIsContact) {
wdgDecorations = Text(AppLocalizations.of(context)!.msgAddToAccept); wdgDecorations = Text(AppLocalizations.of(context)!.msgAddToAccept, style: Provider.of<Settings>(context).scaleFonts(defaultTextStyle));
} else if (!widget.isAuto || Provider.of<MessageMetadata>(context).attributes["file-missing"] == "false") { } else if (!widget.isAuto || Provider.of<MessageMetadata>(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 //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.. // fetch it again, so we need to offer the user the ability to restart..
@ -220,7 +216,10 @@ class FileBubbleState extends State<FileBubble> {
child: Center( child: Center(
widthFactor: 1, widthFactor: 1,
child: Wrap(children: [ 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<Settings>(context).scaleFonts(defaultTextButtonStyle)), onPressed: _btnAccept)),
]))); ])));
} else { } else {
wdgDecorations = Container(); wdgDecorations = Container();
@ -278,7 +277,7 @@ class FileBubbleState extends State<FileBubble> {
} }
} else { } else {
try { try {
selectedFileName = await FilePicker.platform.saveFile( selectedFileName = await FilePicker.platform.saveFile(
fileName: widget.nameSuggestion, fileName: widget.nameSuggestion,
lockParentWindow: true, lockParentWindow: true,
); );
@ -308,52 +307,35 @@ class FileBubbleState extends State<FileBubble> {
// Construct an file chrome for the sender // Construct an file chrome for the sender
Widget senderFileChrome(String chrome, String fileName, String rootHash, int fileSize) { Widget senderFileChrome(String chrome, String fileName, String rootHash, int fileSize) {
var settings = Provider.of<Settings>(context);
return ListTile( return ListTile(
visualDensity: VisualDensity.compact, visualDensity: VisualDensity.compact,
title: Wrap(direction: Axis.horizontal, alignment: WrapAlignment.start, children: [ title: Wrap(direction: Axis.horizontal, alignment: WrapAlignment.start, children: [
SelectableText( SelectableText(
chrome + '\u202F', chrome + '\u202F',
style: TextStyle( style: settings.scaleFonts(defaultMessageTextStyle.copyWith(color: Provider.of<Settings>(context).theme.messageFromMeTextColor)),
color: Provider.of<Settings>(context).theme.messageFromMeTextColor,
fontWeight: FontWeight.normal,
fontFamily: "Inter",
fontSize: 12 * Provider.of<Settings>(context).fontScaling,
),
textAlign: TextAlign.left, textAlign: TextAlign.left,
maxLines: 2, maxLines: 2,
textWidthBasis: TextWidthBasis.longestLine, textWidthBasis: TextWidthBasis.longestLine,
), ),
SelectableText( SelectableText(
fileName + '\u202F', fileName + '\u202F',
style: TextStyle( style:
color: Provider.of<Settings>(context).theme.messageFromMeTextColor, settings.scaleFonts(defaultMessageTextStyle.copyWith(overflow: TextOverflow.ellipsis, fontWeight: FontWeight.bold, color: Provider.of<Settings>(context).theme.messageFromMeTextColor)),
fontWeight: FontWeight.bold,
overflow: TextOverflow.ellipsis,
fontFamily: "Inter",
fontSize: 12 * Provider.of<Settings>(context).fontScaling,
),
textAlign: TextAlign.left, textAlign: TextAlign.left,
textWidthBasis: TextWidthBasis.parent, textWidthBasis: TextWidthBasis.parent,
maxLines: 2, maxLines: 2,
), ),
SelectableText( SelectableText(
prettyBytes(fileSize) + '\u202F' + '\n', prettyBytes(fileSize) + '\u202F' + '\n',
style: TextStyle( style: settings.scaleFonts(defaultSmallTextStyle.copyWith(color: Provider.of<Settings>(context).theme.messageFromMeTextColor)),
color: Provider.of<Settings>(context).theme.messageFromMeTextColor,
fontSize: 10 * Provider.of<Settings>(context).fontScaling,
fontFamily: "Inter",
),
textAlign: TextAlign.left, textAlign: TextAlign.left,
maxLines: 2, maxLines: 2,
) )
]), ]),
subtitle: SelectableText( subtitle: SelectableText(
'sha512: ' + rootHash + '\u202F', 'sha512: ' + rootHash + '\u202F',
style: TextStyle( style: settings.scaleFonts(defaultSmallTextStyle.copyWith(fontFamily: "RobotoMono", color: Provider.of<Settings>(context).theme.messageFromMeTextColor)),
color: Provider.of<Settings>(context).theme.messageFromMeTextColor,
fontSize: 10 * Provider.of<Settings>(context).fontScaling,
fontFamily: "RobotoMono",
),
textAlign: TextAlign.left, textAlign: TextAlign.left,
maxLines: 4, maxLines: 4,
textWidthBasis: TextWidthBasis.parent, textWidthBasis: TextWidthBasis.parent,
@ -363,50 +345,35 @@ class FileBubbleState extends State<FileBubble> {
// Construct an file chrome // Construct an file chrome
Widget fileChrome(String chrome, String fileName, String rootHash, int fileSize, String speed) { Widget fileChrome(String chrome, String fileName, String rootHash, int fileSize, String speed) {
var settings = Provider.of<Settings>(context);
return ListTile( return ListTile(
visualDensity: VisualDensity.compact, visualDensity: VisualDensity.compact,
title: Wrap(direction: Axis.horizontal, alignment: WrapAlignment.start, children: [ title: Wrap(direction: Axis.horizontal, alignment: WrapAlignment.start, children: [
SelectableText( SelectableText(
chrome + '\u202F', chrome + '\u202F',
style: TextStyle( style: settings.scaleFonts(defaultMessageTextStyle.copyWith(color: Provider.of<Settings>(context).theme.messageFromOtherTextColor)),
color: Provider.of<Settings>(context).theme.messageFromOtherTextColor,
fontSize: 12 * Provider.of<Settings>(context).fontScaling,
fontFamily: "Inter",
),
textAlign: TextAlign.left, textAlign: TextAlign.left,
maxLines: 2, maxLines: 2,
textWidthBasis: TextWidthBasis.longestLine, textWidthBasis: TextWidthBasis.longestLine,
), ),
SelectableText( SelectableText(
fileName + '\u202F', fileName + '\u202F',
style: TextStyle( style: settings
color: Provider.of<Settings>(context).theme.messageFromOtherTextColor, .scaleFonts(defaultMessageTextStyle.copyWith(overflow: TextOverflow.ellipsis, fontWeight: FontWeight.bold, color: Provider.of<Settings>(context).theme.messageFromOtherTextColor)),
fontWeight: FontWeight.bold,
overflow: TextOverflow.ellipsis,
fontFamily: "Inter",
),
textAlign: TextAlign.left, textAlign: TextAlign.left,
textWidthBasis: TextWidthBasis.parent, textWidthBasis: TextWidthBasis.parent,
maxLines: 2, maxLines: 2,
), ),
SelectableText( SelectableText(
AppLocalizations.of(context)!.labelFilesize + ': ' + prettyBytes(fileSize) + '\u202F' + '\n', AppLocalizations.of(context)!.labelFilesize + ': ' + prettyBytes(fileSize) + '\u202F' + '\n',
style: TextStyle( style: settings.scaleFonts(defaultSmallTextStyle.copyWith(color: Provider.of<Settings>(context).theme.messageFromOtherTextColor)),
color: Provider.of<Settings>(context).theme.messageFromOtherTextColor,
fontFamily: "Inter",
fontSize: 10 * Provider.of<Settings>(context).fontScaling,
),
textAlign: TextAlign.left, textAlign: TextAlign.left,
maxLines: 2, maxLines: 2,
) )
]), ]),
subtitle: SelectableText( subtitle: SelectableText(
'sha512: ' + rootHash + '\u202F', 'sha512: ' + rootHash + '\u202F',
style: TextStyle( style: settings.scaleFonts(defaultSmallTextStyle.copyWith(fontFamily: "RobotoMono", color: Provider.of<Settings>(context).theme.messageFromOtherTextColor)),
color: Provider.of<Settings>(context).theme.messageFromMeTextColor,
fontSize: 10 * Provider.of<Settings>(context).fontScaling,
fontFamily: "RobotoMono",
),
textAlign: TextAlign.left, textAlign: TextAlign.left,
maxLines: 4, maxLines: 4,
textWidthBasis: TextWidthBasis.parent, textWidthBasis: TextWidthBasis.parent,
@ -416,11 +383,7 @@ class FileBubbleState extends State<FileBubble> {
visible: speed != "0 B/s", visible: speed != "0 B/s",
child: SelectableText( child: SelectableText(
speed + '\u202F', speed + '\u202F',
style: TextStyle( style: settings.scaleFonts(defaultSmallTextStyle.copyWith(color: Provider.of<Settings>(context).theme.messageFromOtherTextColor)),
color: Provider.of<Settings>(context).theme.messageFromMeTextColor,
fontFamily: "Inter",
fontSize: 10 * Provider.of<Settings>(context).fontScaling,
),
textAlign: TextAlign.left, textAlign: TextAlign.left,
maxLines: 1, maxLines: 1,
textWidthBasis: TextWidthBasis.longestLine, textWidthBasis: TextWidthBasis.longestLine,

View File

@ -5,6 +5,7 @@ import 'package:cwtch/cwtch_icons_icons.dart';
import 'package:cwtch/models/contact.dart'; import 'package:cwtch/models/contact.dart';
import 'package:cwtch/models/message.dart'; import 'package:cwtch/models/message.dart';
import 'package:cwtch/models/profile.dart'; import 'package:cwtch/models/profile.dart';
import 'package:cwtch/themes/opaque.dart';
import 'package:cwtch/widgets/malformedbubble.dart'; import 'package:cwtch/widgets/malformedbubble.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.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 'package:flutter_gen/gen_l10n/app_localizations.dart';
import '../settings.dart'; import '../settings.dart';
import 'messageBubbleWidgetHelpers.dart';
import 'messagebubbledecorations.dart'; import 'messagebubbledecorations.dart';
// Like MessageBubble but for displaying chat overlay 100/101 invitations // Like MessageBubble but for displaying chat overlay 100/101 invitations
@ -54,15 +56,7 @@ class InvitationBubbleState extends State<InvitationBubble> {
} }
} }
var wdgSender = Center( var wdgSender = compileSenderWidget(context, fromMe, senderDisplayStr);
widthFactor: 1,
child: SelectableText(senderDisplayStr + '\u202F',
style: TextStyle(
fontSize: 9.0 * Provider.of<Settings>(context).fontScaling,
fontWeight: FontWeight.bold,
fontFamily: "Inter",
color: fromMe ? Provider.of<Settings>(context).theme.messageFromMeTextColor : Provider.of<Settings>(context).theme.messageFromOtherTextColor)));
// 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 // 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. // some kind of malfeasance.
var selfInvite = widget.inviteNick == Provider.of<ProfileInfoState>(context).onion; var selfInvite = widget.inviteNick == Provider.of<ProfileInfoState>(context).onion;
@ -71,7 +65,7 @@ class InvitationBubbleState extends State<InvitationBubble> {
} }
var wdgMessage = isGroup && !showGroupInvite var wdgMessage = isGroup && !showGroupInvite
? Text(AppLocalizations.of(context)!.groupInviteSettingsWarning) ? Text(AppLocalizations.of(context)!.groupInviteSettingsWarning, style: Provider.of<Settings>(context).scaleFonts(defaultTextStyle))
: fromMe : fromMe
? senderInviteChrome( ? senderInviteChrome(
AppLocalizations.of(context)!.sendAnInvitation, isGroup ? Provider.of<ProfileInfoState>(context).contactList.findContact(widget.inviteTarget)!.nickname : widget.inviteTarget) AppLocalizations.of(context)!.sendAnInvitation, isGroup ? Provider.of<ProfileInfoState>(context).contactList.findContact(widget.inviteTarget)!.nickname : widget.inviteTarget)
@ -83,15 +77,21 @@ class InvitationBubbleState extends State<InvitationBubble> {
} else if (fromMe) { } else if (fromMe) {
wdgDecorations = MessageBubbleDecoration(ackd: Provider.of<MessageMetadata>(context).ackd, errored: Provider.of<MessageMetadata>(context).error, fromMe: fromMe, messageDate: messageDate); wdgDecorations = MessageBubbleDecoration(ackd: Provider.of<MessageMetadata>(context).ackd, errored: Provider.of<MessageMetadata>(context).error, fromMe: fromMe, messageDate: messageDate);
} else if (isAccepted) { } else if (isAccepted) {
wdgDecorations = Text(AppLocalizations.of(context)!.accepted + '\u202F'); wdgDecorations = Text(AppLocalizations.of(context)!.accepted + '\u202F', style: Provider.of<Settings>(context).scaleFonts(defaultTextStyle));
} else if (this.rejected) { } else if (this.rejected) {
wdgDecorations = Text(AppLocalizations.of(context)!.rejected + '\u202F'); wdgDecorations = Text(AppLocalizations.of(context)!.rejected + '\u202F', style: Provider.of<Settings>(context).scaleFonts(defaultTextStyle));
} else { } else {
wdgDecorations = Center( wdgDecorations = Center(
widthFactor: 1, widthFactor: 1,
child: Wrap(children: [ child: Wrap(children: [
Padding(padding: EdgeInsets.all(5), child: ElevatedButton(child: Text(AppLocalizations.of(context)!.rejectGroupBtn + '\u202F'), onPressed: _btnReject)), Padding(
Padding(padding: EdgeInsets.all(5), child: ElevatedButton(child: Text(AppLocalizations.of(context)!.acceptGroupBtn + '\u202F'), onPressed: _btnAccept)), padding: EdgeInsets.all(5),
child: ElevatedButton(
child: Text(AppLocalizations.of(context)!.rejectGroupBtn + '\u202F', style: Provider.of<Settings>(context).scaleFonts(defaultTextButtonStyle)), onPressed: _btnReject)),
Padding(
padding: EdgeInsets.all(5),
child: ElevatedButton(
child: Text(AppLocalizations.of(context)!.acceptGroupBtn + '\u202F', style: Provider.of<Settings>(context).scaleFonts(defaultTextButtonStyle)), onPressed: _btnAccept)),
])); ]));
} }
@ -149,21 +149,19 @@ class InvitationBubbleState extends State<InvitationBubble> {
// Construct an invite chrome for the sender // Construct an invite chrome for the sender
Widget senderInviteChrome(String chrome, String targetName) { Widget senderInviteChrome(String chrome, String targetName) {
var settings = Provider.of<Settings>(context);
return Wrap(children: [ return Wrap(children: [
SelectableText( SelectableText(
chrome + '\u202F', chrome + '\u202F',
style: TextStyle( style: settings.scaleFonts(defaultMessageTextStyle.copyWith(color: Provider.of<Settings>(context).theme.messageFromMeTextColor)),
color: Provider.of<Settings>(context).theme.messageFromMeTextColor,
),
textAlign: TextAlign.left, textAlign: TextAlign.left,
maxLines: 2, maxLines: 2,
textWidthBasis: TextWidthBasis.longestLine, textWidthBasis: TextWidthBasis.longestLine,
), ),
SelectableText( SelectableText(
targetName + '\u202F', targetName + '\u202F',
style: TextStyle( style: settings.scaleFonts(defaultMessageTextStyle.copyWith(color: Provider.of<Settings>(context).theme.messageFromMeTextColor)),
color: Provider.of<Settings>(context).theme.messageFromMeTextColor,
),
textAlign: TextAlign.left, textAlign: TextAlign.left,
maxLines: 2, maxLines: 2,
textWidthBasis: TextWidthBasis.longestLine, textWidthBasis: TextWidthBasis.longestLine,
@ -173,19 +171,19 @@ class InvitationBubbleState extends State<InvitationBubble> {
// Construct an invite chrome // Construct an invite chrome
Widget inviteChrome(String chrome, String targetName, String targetId) { Widget inviteChrome(String chrome, String targetName, String targetId) {
var settings = Provider.of<Settings>(context);
return Wrap(children: [ return Wrap(children: [
SelectableText( SelectableText(
chrome + '\u202F', chrome + '\u202F',
style: TextStyle( style: settings.scaleFonts(defaultMessageTextStyle.copyWith(color: Provider.of<Settings>(context).theme.messageFromOtherTextColor)),
color: Provider.of<Settings>(context).theme.messageFromOtherTextColor,
),
textAlign: TextAlign.left, textAlign: TextAlign.left,
textWidthBasis: TextWidthBasis.longestLine, textWidthBasis: TextWidthBasis.longestLine,
maxLines: 2, maxLines: 2,
), ),
SelectableText( SelectableText(
targetName + '\u202F', targetName + '\u202F',
style: TextStyle(color: Provider.of<Settings>(context).theme.messageFromOtherTextColor), style: settings.scaleFonts(defaultMessageTextStyle.copyWith(color: Provider.of<Settings>(context).theme.messageFromOtherTextColor)),
textAlign: TextAlign.left, textAlign: TextAlign.left,
maxLines: 2, maxLines: 2,
textWidthBasis: TextWidthBasis.longestLine, textWidthBasis: TextWidthBasis.longestLine,

View File

@ -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<Settings>(context).fontScaling,
clipBehavior: Clip.hardEdge,
decoration: BoxDecoration(),
child: SelectableText(senderDisplayStr,
maxLines: 1,
style: TextStyle(
fontSize: 9.0 * Provider.of<Settings>(context).fontScaling,
fontWeight: FontWeight.bold,
fontFamily: "Inter",
overflow: TextOverflow.clip,
color: fromMe ? Provider.of<Settings>(context).theme.messageFromMeTextColor : Provider.of<Settings>(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<Settings>(context)
.scaleFonts(defaultMessageTextStyle.copyWith(color: fromMe ? Provider.of<Settings>(context).theme.messageFromMeTextColor : Provider.of<Settings>(context).theme.messageFromOtherTextColor)),
linkStyle: Provider.of<Settings>(context)
.scaleFonts(defaultMessageTextStyle.copyWith(color: fromMe ? Provider.of<Settings>(context).theme.messageFromMeTextColor : Provider.of<Settings>(context).theme.messageFromOtherTextColor)),
codeStyle: Provider.of<Settings>(context).scaleFonts(defaultMessageTextStyle.copyWith(
fontFamily: "RobotoMono",
color: fromMe ? Provider.of<Settings>(context).theme.messageFromOtherTextColor : Provider.of<Settings>(context).theme.messageFromMeTextColor,
backgroundColor: fromMe ? Provider.of<Settings>(context).theme.messageFromOtherBackgroundColor : Provider.of<Settings>(context).theme.messageFromMeBackgroundColor)),
textAlign: TextAlign.left,
textWidthBasis: TextWidthBasis.longestLine,
);
}

View File

@ -3,6 +3,7 @@ import 'dart:io';
import 'package:cwtch/controllers/open_link_modal.dart'; import 'package:cwtch/controllers/open_link_modal.dart';
import 'package:cwtch/models/contact.dart'; import 'package:cwtch/models/contact.dart';
import 'package:cwtch/models/message.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/third_party/linkify/flutter_linkify.dart';
import 'package:cwtch/models/profile.dart'; import 'package:cwtch/models/profile.dart';
import 'package:cwtch/widgets/malformedbubble.dart'; import 'package:cwtch/widgets/malformedbubble.dart';
@ -10,6 +11,7 @@ import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import '../settings.dart'; import '../settings.dart';
import 'messageBubbleWidgetHelpers.dart';
import 'messagebubbledecorations.dart'; import 'messagebubbledecorations.dart';
class MessageBubble extends StatefulWidget { class MessageBubble extends StatefulWidget {
@ -42,56 +44,9 @@ class MessageBubbleState extends State<MessageBubble> {
senderDisplayStr = Provider.of<MessageMetadata>(context).senderHandle; senderDisplayStr = Provider.of<MessageMetadata>(context).senderHandle;
} }
} }
var wdgSender = Container( var wdgSender = compileSenderWidget(context, fromMe, senderDisplayStr);
height: 14 * Provider.of<Settings>(context).fontScaling, var wdgMessage = compileMessageContentWidget(context, fromMe, widget.content, _focus, formatMessages, showClickableLinks);
clipBehavior: Clip.hardEdge,
decoration: BoxDecoration(),
child: SelectableText(senderDisplayStr,
maxLines: 1,
style: TextStyle(
fontSize: 9.0 * Provider.of<Settings>(context).fontScaling,
fontWeight: FontWeight.bold,
fontFamily: "Inter",
overflow: TextOverflow.clip,
color: fromMe ? Provider.of<Settings>(context).theme.messageFromMeTextColor : Provider.of<Settings>(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<Settings>(context).fontScaling,
fontWeight: FontWeight.normal,
fontFamily: "Inter",
color: fromMe ? Provider.of<Settings>(context).theme.messageFromMeTextColor : Provider.of<Settings>(context).theme.messageFromOtherTextColor,
),
linkStyle: TextStyle(
fontSize: 12.0 * Provider.of<Settings>(context).fontScaling,
fontWeight: FontWeight.normal,
fontFamily: "Inter",
color: fromMe ? Provider.of<Settings>(context).theme.messageFromMeTextColor : Provider.of<Settings>(context).theme.messageFromOtherTextColor),
codeStyle: TextStyle(
fontSize: 12.0 * Provider.of<Settings>(context).fontScaling,
fontWeight: FontWeight.normal,
fontFamily: "Inter",
// note: these colors are flipped
color: fromMe ? Provider.of<Settings>(context).theme.messageFromOtherTextColor : Provider.of<Settings>(context).theme.messageFromMeTextColor,
backgroundColor: fromMe ? Provider.of<Settings>(context).theme.messageFromOtherBackgroundColor : Provider.of<Settings>(context).theme.messageFromMeBackgroundColor),
textAlign: TextAlign.left,
textWidthBasis: TextWidthBasis.longestLine,
);
var wdgDecorations = MessageBubbleDecoration(ackd: Provider.of<MessageMetadata>(context).ackd, errored: Provider.of<MessageMetadata>(context).error, fromMe: fromMe, messageDate: messageDate); var wdgDecorations = MessageBubbleDecoration(ackd: Provider.of<MessageMetadata>(context).ackd, errored: Provider.of<MessageMetadata>(context).error, fromMe: fromMe, messageDate: messageDate);
var error = Provider.of<MessageMetadata>(context).error; var error = Provider.of<MessageMetadata>(context).error;
return LayoutBuilder(builder: (context, constraints) { return LayoutBuilder(builder: (context, constraints) {

View File

@ -7,6 +7,7 @@ import 'package:cwtch/models/appstate.dart';
import 'package:cwtch/models/contact.dart'; import 'package:cwtch/models/contact.dart';
import 'package:cwtch/models/message.dart'; import 'package:cwtch/models/message.dart';
import 'package:cwtch/models/profile.dart'; import 'package:cwtch/models/profile.dart';
import 'package:cwtch/themes/opaque.dart';
import 'package:cwtch/third_party/base32/base32.dart'; import 'package:cwtch/third_party/base32/base32.dart';
import 'package:cwtch/views/contactsview.dart'; import 'package:cwtch/views/contactsview.dart';
import 'package:cwtch/widgets/staticmessagebubble.dart'; import 'package:cwtch/widgets/staticmessagebubble.dart';
@ -307,7 +308,7 @@ class MessageRowState extends State<MessageRow> with SingleTickerProviderStateMi
bottomRight: Radius.circular(8), 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<Settings>(context).scaleFonts(defaultTextButtonStyle))));
} }
void _runAnimation(Offset pixelsPerSecond, Size size) { void _runAnimation(Offset pixelsPerSecond, Size size) {

View File

@ -10,6 +10,7 @@ import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import '../settings.dart'; import '../settings.dart';
import 'messageBubbleWidgetHelpers.dart';
import 'messagebubbledecorations.dart'; import 'messagebubbledecorations.dart';
class QuotedMessageBubble extends StatefulWidget { class QuotedMessageBubble extends StatefulWidget {
@ -43,53 +44,11 @@ class QuotedMessageBubbleState extends State<QuotedMessageBubble> {
} }
} }
var wdgSender = Container( var wdgSender = compileSenderWidget(context, fromMe, senderDisplayStr);
height: 14 * Provider.of<Settings>(context).fontScaling,
clipBehavior: Clip.hardEdge,
decoration: BoxDecoration(),
child: SelectableText(senderDisplayStr,
style: TextStyle(
fontSize: 9.0 * Provider.of<Settings>(context).fontScaling,
fontWeight: FontWeight.bold,
fontFamily: "Inter",
color: fromMe ? Provider.of<Settings>(context).theme.messageFromMeTextColor : Provider.of<Settings>(context).theme.messageFromOtherTextColor)));
var showClickableLinks = Provider.of<Settings>(context).isExperimentEnabled(ClickableLinksExperiment); var showClickableLinks = Provider.of<Settings>(context).isExperimentEnabled(ClickableLinksExperiment);
var formatMessages = Provider.of<Settings>(context).isExperimentEnabled(FormattingExperiment); var formatMessages = Provider.of<Settings>(context).isExperimentEnabled(FormattingExperiment);
var wdgMessage = compileMessageContentWidget(context, fromMe, widget.body, _focus, formatMessages, showClickableLinks);
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<Settings>(context).fontScaling,
fontWeight: FontWeight.normal,
fontFamily: "Inter",
color: fromMe ? Provider.of<Settings>(context).theme.messageFromMeTextColor : Provider.of<Settings>(context).theme.messageFromOtherTextColor,
),
linkStyle: TextStyle(
fontSize: 12.0 * Provider.of<Settings>(context).fontScaling,
fontWeight: FontWeight.normal,
fontFamily: "Inter",
color: fromMe ? Provider.of<Settings>(context).theme.messageFromMeTextColor : Provider.of<Settings>(context).theme.messageFromOtherTextColor),
codeStyle: TextStyle(
fontSize: 12.0 * Provider.of<Settings>(context).fontScaling,
fontWeight: FontWeight.normal,
fontFamily: "RobotoMono",
// note: these colors are flipped
color: fromMe ? Provider.of<Settings>(context).theme.messageFromOtherTextColor : Provider.of<Settings>(context).theme.messageFromMeTextColor,
backgroundColor: fromMe ? Provider.of<Settings>(context).theme.messageFromOtherBackgroundColor : Provider.of<Settings>(context).theme.messageFromMeBackgroundColor),
textAlign: TextAlign.left,
textWidthBasis: TextWidthBasis.longestLine,
);
var wdgQuote = FutureBuilder( var wdgQuote = FutureBuilder(
future: widget.quotedMessage, future: widget.quotedMessage,
@ -118,7 +77,7 @@ class QuotedMessageBubbleState extends State<QuotedMessageBubble> {
var wdgReplyingTo = SelectableText( var wdgReplyingTo = SelectableText(
AppLocalizations.of(context)!.replyingTo.replaceAll("%1", qMessageSender), AppLocalizations.of(context)!.replyingTo.replaceAll("%1", qMessageSender),
style: TextStyle(fontSize: 10, color: qTextColor.withOpacity(0.8)), style: Provider.of<Settings>(context).scaleFonts(TextStyle(fontSize: 10, color: qTextColor.withOpacity(0.8))),
); );
// Swap the background color for quoted tweets.. // Swap the background color for quoted tweets..
return MouseRegion( return MouseRegion(

View File

@ -6,6 +6,7 @@ import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import '../settings.dart'; import '../settings.dart';
import 'messageBubbleWidgetHelpers.dart';
import 'messagebubbledecorations.dart'; import 'messagebubbledecorations.dart';
class StaticMessageBubble extends StatefulWidget { class StaticMessageBubble extends StatefulWidget {
@ -40,12 +41,7 @@ class StaticMessageBubbleState extends State<StaticMessageBubble> {
senderDisplayStr = widget.profile.nickname; senderDisplayStr = widget.profile.nickname;
} }
var wdgSender = SelectableText(senderDisplayStr, var wdgSender = compileSenderWidget(context, fromMe, senderDisplayStr);
style: TextStyle(
fontSize: 9.0 * Provider.of<Settings>(context).fontScaling,
fontWeight: FontWeight.bold,
fontFamily: "Inter",
color: fromMe ? widget.settings.theme.messageFromMeTextColor : widget.settings.theme.messageFromOtherTextColor));
var wdgDecorations = MessageBubbleDecoration(ackd: widget.metadata.ackd, errored: widget.metadata.error, fromMe: fromMe, messageDate: messageDate); var wdgDecorations = MessageBubbleDecoration(ackd: widget.metadata.ackd, errored: widget.metadata.error, fromMe: fromMe, messageDate: messageDate);