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" -> {
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()

View File

@ -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<void>(
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: <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>[
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<Settings>(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<Settings>(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<Settings>(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<Settings>(bcontext).scaleFonts(defaultTextButtonStyle)));
ScaffoldMessenger.of(bcontext).showSnackBar(snackBar);
}
},

View File

@ -97,11 +97,11 @@ abstract class Cwtch {
Future<dynamic> 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<String?> 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<String?> 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

View File

@ -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") {

View File

@ -967,7 +967,7 @@ class CwtchFfi implements Cwtch {
}
@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");
// ignore: non_constant_identifier_names
final GetProfileAttribute = getProfileAttributeC.asFunction<GetJsonBlobFromStrStrFn>();
@ -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<String?> GetConversationAttribute(String profile, int conversation, String key) {
var getConversationAttributeC = library.lookup<NativeFunction<get_json_blob_from_str_int_string_function>>("c_GetConversationAttribute");
// ignore: non_constant_identifier_names
final GetConversationAttribute = getConversationAttributeC.asFunction<GetJsonBlobFromStrIntStringFn>();
@ -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

View File

@ -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<String?> 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<String?> 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

View File

@ -87,7 +87,6 @@ class FlwtchState extends State<Flwtch> 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<Flwtch> with WindowListener {
globalAppState.focus = false;
}
void onWindowClose() {
}
void onWindowClose() {}
@override
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;
set notificationsPolicy(ConversationNotificationPolicy newVal) {

View File

@ -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.

View File

@ -23,8 +23,7 @@ abstract class NotificationsManager {
// NullNotificationsManager ignores all notification requests
class NullNotificationsManager implements NotificationsManager {
@override
Future<void> notify(
String message, String profile, int conversationId) async {}
Future<void> 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<void> Function(String, int) notificationSelectConvo;
WindowsNotificationManager(Future<void> 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<String> 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<void> Function(String, int) notificationSelectConvo) {
NixNotificationManager(Future<void> 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<MacOSFlutterLocalNotificationsPlugin>()?.requestPermissions(
alert: true,
badge: false,
sound: false,
@ -202,8 +183,7 @@ class NixNotificationManager implements NotificationsManager {
});
}
Future<void> notify(
String message, String profile, int conversationId) async {
Future<void> 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<void> Function(String profileOnion, int convoId)
notificationSelectConvo) {
NotificationsManager newDesktopNotificationsManager(Future<void> 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");
}
}

View File

@ -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;

View File

@ -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()},

View File

@ -44,7 +44,6 @@ class MessageView extends StatefulWidget {
}
class _MessageViewState extends State<MessageView> {
final ctrlrCompose = TextEditingController();
final focusNode = FocusNode();
int selectedContact = -1;
ItemPositionsListener scrollListener = ItemPositionsListener.create();
@ -68,7 +67,6 @@ class _MessageViewState extends State<MessageView> {
showDown = false;
}
});
ctrlrCompose.text = Provider.of<ContactInfoState>(context, listen: false).messageDraft.messageText ?? "";
super.initState();
}
@ -88,7 +86,6 @@ class _MessageViewState extends State<MessageView> {
@override
void dispose() {
focusNode.dispose();
ctrlrCompose.dispose();
super.dispose();
}
@ -321,22 +318,22 @@ class _MessageViewState extends State<MessageView> {
void _sendMessage([String? ignoredParam]) {
// 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 isGroup = Provider.of<ContactInfoState>(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<ContactInfoState>(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<AppState>(context, listen: false).selectedConversation != null && Provider.of<ContactInfoState>(context, listen: false).messageDraft.getQuotedMessage() != null) {
var conversationId = Provider.of<AppState>(context, listen: false).selectedConversation!;
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 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<FlwtchState>(context, listen: false)
.cwtch
@ -359,7 +356,7 @@ class _MessageViewState extends State<MessageView> {
Provider.of<ContactInfoState>(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<FlwtchState>(context, listen: false)
.cwtch
.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
// 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();
ctrlrCompose.clear();
Provider.of<ContactInfoState>(context, listen: false).messageDraft.clearDraft();
var profileOnion = Provider.of<ContactInfoState>(context, listen: false).profileOnion;
var identifier = Provider.of<ContactInfoState>(context, listen: false).identifier;
@ -424,7 +420,7 @@ class _MessageViewState extends State<MessageView> {
var wdgMessage = Padding(
padding: EdgeInsets.all(8),
child: SelectableLinkify(
text: ctrlrCompose.text + '\n',
text: Provider.of<ContactInfoState>(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<MessageView> {
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<ContactInfoState>(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<MessageView> {
}
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;
var showToolbar = Provider.of<Settings>(context).isExperimentEnabled(FormattingExperiment);
var charLength = ctrlrCompose.value.text.characters.length;
var expectedLength = ctrlrCompose.value.text.length;
var charLength = Provider.of<ContactInfoState>(context).messageDraft.messageText.characters.length;
var expectedLength = Provider.of<ContactInfoState>(context).messageDraft.messageText.length;
var numberOfBytesMoreThanChar = (expectedLength - charLength);
var bold = IconButton(
@ -495,12 +491,14 @@ class _MessageViewState extends State<MessageView> {
tooltip: AppLocalizations.of(context)!.tooltipBoldText,
onPressed: () {
setState(() {
var ctrlrCompose = Provider.of<ContactInfoState>(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<ContactInfoState>(context, listen: false).messageDraft.ctrlCompose = ctrlrCompose;
});
});
@ -509,12 +507,14 @@ class _MessageViewState extends State<MessageView> {
tooltip: AppLocalizations.of(context)!.tooltipItalicize,
onPressed: () {
setState(() {
var ctrlrCompose = Provider.of<ContactInfoState>(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<ContactInfoState>(context, listen: false).messageDraft.ctrlCompose = ctrlrCompose;
});
});
@ -523,12 +523,14 @@ class _MessageViewState extends State<MessageView> {
tooltip: AppLocalizations.of(context)!.tooltipCode,
onPressed: () {
setState(() {
var ctrlrCompose = Provider.of<ContactInfoState>(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<ContactInfoState>(context, listen: false).messageDraft.ctrlCompose = ctrlrCompose;
});
});
@ -537,12 +539,14 @@ class _MessageViewState extends State<MessageView> {
tooltip: AppLocalizations.of(context)!.tooltipSuperscript,
onPressed: () {
setState(() {
var ctrlrCompose = Provider.of<ContactInfoState>(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<ContactInfoState>(context, listen: false).messageDraft.ctrlCompose = ctrlrCompose;
});
});
@ -551,12 +555,14 @@ class _MessageViewState extends State<MessageView> {
tooltip: AppLocalizations.of(context)!.tooltipSubscript,
onPressed: () {
setState(() {
var ctrlrCompose = Provider.of<ContactInfoState>(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<ContactInfoState>(context, listen: false).messageDraft.ctrlCompose = ctrlrCompose;
});
});
@ -565,12 +571,14 @@ class _MessageViewState extends State<MessageView> {
tooltip: AppLocalizations.of(context)!.tooltipStrikethrough,
onPressed: () {
setState(() {
var ctrlrCompose = Provider.of<ContactInfoState>(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<ContactInfoState>(context, listen: false).messageDraft.ctrlCompose = ctrlrCompose;
});
});
@ -600,7 +608,7 @@ class _MessageViewState extends State<MessageView> {
padding: EdgeInsets.all(8),
child: TextFormField(
key: Key('txtCompose'),
controller: ctrlrCompose,
controller: Provider.of<ContactInfoState>(context).messageDraft.ctrlCompose,
focusNode: focusNode,
autofocus: !Platform.isAndroid,
textInputAction: TextInputAction.newline,
@ -615,18 +623,17 @@ class _MessageViewState extends State<MessageView> {
style: TextStyle(
fontFamily: "Inter",
fontSize: 12.0 * Provider.of<Settings>(context).fontScaling,
fontWeight: FontWeight.w500,
fontWeight: FontWeight.w300,
),
enabled: true, // always allow editing...
onChanged: (String x) {
Provider.of<ContactInfoState>(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<Settings>(context).fontScaling, color: Provider.of<Settings>(context).theme.sendHintTextColor),
enabledBorder: InputBorder.none,
focusedBorder: InputBorder.none,
@ -635,13 +642,13 @@ class _MessageViewState extends State<MessageView> {
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<ContactInfoState>(context, listen: false).antispamTickets == 0)
? AppLocalizations.of(context)!.acquiringTicketsFromServer
: AppLocalizations.of(context)!.sendMessage,
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 ((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<ContactInfoState>(context, listen: false).messageDraft.ctrlCompose.selection.baseOffset !=
Provider.of<ContactInfoState>(context, listen: false).messageDraft.ctrlCompose.text.length) {
return;
}
_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
// otherwise some Providers will become inaccessible to subwidgets...?
// https://stackoverflow.com/a/63818697

View File

@ -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<CwtchLabel> {
return Consumer<Settings>(builder: (context, theme, child) {
return Text(
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/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<FileBubble> {
var wdgSender = Visibility(
visible: widget.interactive,
child: Container(
height: 14 * Provider.of<Settings>(context).fontScaling,
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))));
height: 14 * Provider.of<Settings>(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<Settings>(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<FileBubble> {
},
)));
} 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) {
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 {
wdgDecorations = Visibility(
visible: widget.interactive,
@ -199,19 +195,19 @@ class FileBubbleState extends State<FileBubble> {
// 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<Settings>(context).scaleFonts(defaultTextStyle));
// We should have already requested this...
} else {
var path = Provider.of<ProfileInfoState>(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<Settings>(context).scaleFonts(defaultTextStyle)),
ElevatedButton(onPressed: _btnResume, child: Text(AppLocalizations.of(context)!.verfiyResumeButton, style: Provider.of<Settings>(context).scaleFonts(defaultTextButtonStyle)))
]));
}
} 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") {
//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<FileBubble> {
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<Settings>(context).scaleFonts(defaultTextButtonStyle)), onPressed: _btnAccept)),
])));
} else {
wdgDecorations = Container();
@ -278,7 +277,7 @@ class FileBubbleState extends State<FileBubble> {
}
} 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<FileBubble> {
// Construct an file chrome for the sender
Widget senderFileChrome(String chrome, String fileName, String rootHash, int fileSize) {
var settings = Provider.of<Settings>(context);
return ListTile(
visualDensity: VisualDensity.compact,
title: Wrap(direction: Axis.horizontal, alignment: WrapAlignment.start, children: [
SelectableText(
chrome + '\u202F',
style: TextStyle(
color: Provider.of<Settings>(context).theme.messageFromMeTextColor,
fontWeight: FontWeight.normal,
fontFamily: "Inter",
fontSize: 12 * Provider.of<Settings>(context).fontScaling,
),
style: settings.scaleFonts(defaultMessageTextStyle.copyWith(color: Provider.of<Settings>(context).theme.messageFromMeTextColor)),
textAlign: TextAlign.left,
maxLines: 2,
textWidthBasis: TextWidthBasis.longestLine,
),
SelectableText(
fileName + '\u202F',
style: TextStyle(
color: Provider.of<Settings>(context).theme.messageFromMeTextColor,
fontWeight: FontWeight.bold,
overflow: TextOverflow.ellipsis,
fontFamily: "Inter",
fontSize: 12 * Provider.of<Settings>(context).fontScaling,
),
style:
settings.scaleFonts(defaultMessageTextStyle.copyWith(overflow: TextOverflow.ellipsis, fontWeight: FontWeight.bold, color: Provider.of<Settings>(context).theme.messageFromMeTextColor)),
textAlign: TextAlign.left,
textWidthBasis: TextWidthBasis.parent,
maxLines: 2,
),
SelectableText(
prettyBytes(fileSize) + '\u202F' + '\n',
style: TextStyle(
color: Provider.of<Settings>(context).theme.messageFromMeTextColor,
fontSize: 10 * Provider.of<Settings>(context).fontScaling,
fontFamily: "Inter",
),
style: settings.scaleFonts(defaultSmallTextStyle.copyWith(color: Provider.of<Settings>(context).theme.messageFromMeTextColor)),
textAlign: TextAlign.left,
maxLines: 2,
)
]),
subtitle: SelectableText(
'sha512: ' + rootHash + '\u202F',
style: TextStyle(
color: Provider.of<Settings>(context).theme.messageFromMeTextColor,
fontSize: 10 * Provider.of<Settings>(context).fontScaling,
fontFamily: "RobotoMono",
),
style: settings.scaleFonts(defaultSmallTextStyle.copyWith(fontFamily: "RobotoMono", color: Provider.of<Settings>(context).theme.messageFromMeTextColor)),
textAlign: TextAlign.left,
maxLines: 4,
textWidthBasis: TextWidthBasis.parent,
@ -363,50 +345,35 @@ class FileBubbleState extends State<FileBubble> {
// Construct an file chrome
Widget fileChrome(String chrome, String fileName, String rootHash, int fileSize, String speed) {
var settings = Provider.of<Settings>(context);
return ListTile(
visualDensity: VisualDensity.compact,
title: Wrap(direction: Axis.horizontal, alignment: WrapAlignment.start, children: [
SelectableText(
chrome + '\u202F',
style: TextStyle(
color: Provider.of<Settings>(context).theme.messageFromOtherTextColor,
fontSize: 12 * Provider.of<Settings>(context).fontScaling,
fontFamily: "Inter",
),
style: settings.scaleFonts(defaultMessageTextStyle.copyWith(color: Provider.of<Settings>(context).theme.messageFromOtherTextColor)),
textAlign: TextAlign.left,
maxLines: 2,
textWidthBasis: TextWidthBasis.longestLine,
),
SelectableText(
fileName + '\u202F',
style: TextStyle(
color: Provider.of<Settings>(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<Settings>(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<Settings>(context).theme.messageFromOtherTextColor,
fontFamily: "Inter",
fontSize: 10 * Provider.of<Settings>(context).fontScaling,
),
style: settings.scaleFonts(defaultSmallTextStyle.copyWith(color: Provider.of<Settings>(context).theme.messageFromOtherTextColor)),
textAlign: TextAlign.left,
maxLines: 2,
)
]),
subtitle: SelectableText(
'sha512: ' + rootHash + '\u202F',
style: TextStyle(
color: Provider.of<Settings>(context).theme.messageFromMeTextColor,
fontSize: 10 * Provider.of<Settings>(context).fontScaling,
fontFamily: "RobotoMono",
),
style: settings.scaleFonts(defaultSmallTextStyle.copyWith(fontFamily: "RobotoMono", color: Provider.of<Settings>(context).theme.messageFromOtherTextColor)),
textAlign: TextAlign.left,
maxLines: 4,
textWidthBasis: TextWidthBasis.parent,
@ -416,11 +383,7 @@ class FileBubbleState extends State<FileBubble> {
visible: speed != "0 B/s",
child: SelectableText(
speed + '\u202F',
style: TextStyle(
color: Provider.of<Settings>(context).theme.messageFromMeTextColor,
fontFamily: "Inter",
fontSize: 10 * Provider.of<Settings>(context).fontScaling,
),
style: settings.scaleFonts(defaultSmallTextStyle.copyWith(color: Provider.of<Settings>(context).theme.messageFromOtherTextColor)),
textAlign: TextAlign.left,
maxLines: 1,
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/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<InvitationBubble> {
}
}
var wdgSender = Center(
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)));
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<ProfileInfoState>(context).onion;
@ -71,7 +65,7 @@ class InvitationBubbleState extends State<InvitationBubble> {
}
var wdgMessage = isGroup && !showGroupInvite
? Text(AppLocalizations.of(context)!.groupInviteSettingsWarning)
? Text(AppLocalizations.of(context)!.groupInviteSettingsWarning, style: Provider.of<Settings>(context).scaleFonts(defaultTextStyle))
: fromMe
? senderInviteChrome(
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) {
wdgDecorations = MessageBubbleDecoration(ackd: Provider.of<MessageMetadata>(context).ackd, errored: Provider.of<MessageMetadata>(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<Settings>(context).scaleFonts(defaultTextStyle));
} 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 {
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<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
Widget senderInviteChrome(String chrome, String targetName) {
var settings = Provider.of<Settings>(context);
return Wrap(children: [
SelectableText(
chrome + '\u202F',
style: TextStyle(
color: Provider.of<Settings>(context).theme.messageFromMeTextColor,
),
style: settings.scaleFonts(defaultMessageTextStyle.copyWith(color: Provider.of<Settings>(context).theme.messageFromMeTextColor)),
textAlign: TextAlign.left,
maxLines: 2,
textWidthBasis: TextWidthBasis.longestLine,
),
SelectableText(
targetName + '\u202F',
style: TextStyle(
color: Provider.of<Settings>(context).theme.messageFromMeTextColor,
),
style: settings.scaleFonts(defaultMessageTextStyle.copyWith(color: Provider.of<Settings>(context).theme.messageFromMeTextColor)),
textAlign: TextAlign.left,
maxLines: 2,
textWidthBasis: TextWidthBasis.longestLine,
@ -173,19 +171,19 @@ class InvitationBubbleState extends State<InvitationBubble> {
// Construct an invite chrome
Widget inviteChrome(String chrome, String targetName, String targetId) {
var settings = Provider.of<Settings>(context);
return Wrap(children: [
SelectableText(
chrome + '\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,
textWidthBasis: TextWidthBasis.longestLine,
maxLines: 2,
),
SelectableText(
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,
maxLines: 2,
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/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<MessageBubble> {
senderDisplayStr = Provider.of<MessageMetadata>(context).senderHandle;
}
}
var wdgSender = 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,
)));
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 wdgSender = compileSenderWidget(context, fromMe, senderDisplayStr);
var wdgMessage = compileMessageContentWidget(context, fromMe, widget.content, _focus, formatMessages, showClickableLinks);
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;
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/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<MessageRow> 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<Settings>(context).scaleFonts(defaultTextButtonStyle))));
}
void _runAnimation(Offset pixelsPerSecond, Size size) {

View File

@ -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<QuotedMessageBubble> {
}
}
var wdgSender = Container(
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 wdgSender = compileSenderWidget(context, fromMe, senderDisplayStr);
var showClickableLinks = Provider.of<Settings>(context).isExperimentEnabled(ClickableLinksExperiment);
var formatMessages = Provider.of<Settings>(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<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 wdgMessage = compileMessageContentWidget(context, fromMe, widget.body, _focus, formatMessages, showClickableLinks);
var wdgQuote = FutureBuilder(
future: widget.quotedMessage,
@ -118,7 +77,7 @@ class QuotedMessageBubbleState extends State<QuotedMessageBubble> {
var wdgReplyingTo = SelectableText(
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..
return MouseRegion(

View File

@ -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<StaticMessageBubble> {
senderDisplayStr = widget.profile.nickname;
}
var wdgSender = SelectableText(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 wdgSender = compileSenderWidget(context, fromMe, senderDisplayStr);
var wdgDecorations = MessageBubbleDecoration(ackd: widget.metadata.ackd, errored: widget.metadata.error, fromMe: fromMe, messageDate: messageDate);