dart-analyzer #822

Closed
sarah wants to merge 2 commits from dart-analyzer into trunk
98 changed files with 1611 additions and 1316 deletions

1
.gitignore vendored
View File

@ -65,7 +65,6 @@ integration_test/gherkin_suite_test.g.dart
integration_test/gherkin_suite_test.dart integration_test/gherkin_suite_test.dart
integration_test/gherkin/ integration_test/gherkin/
integration_test/CustomSteps.md integration_test/CustomSteps.md
analysis_options.yaml
integration_test/env/default/tor integration_test/env/default/tor
integration_test/env/temp* integration_test/env/temp*
linux/Tor linux/Tor

31
analysis_options.yaml Normal file
View File

@ -0,0 +1,31 @@
# This file configures the analyzer, which statically analyzes Dart code to
# check for errors, warnings, and lints.
#
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
# invoked from the command line by running `flutter analyze`.
# The following line activates a set of recommended lints for Flutter apps,
# packages, and plugins designed to encourage good coding practices.
include: package:flutter_lints/flutter.yaml
analyzer:
exclude: [integration_test/gherkin_suit**.dart]
linter:
# The lint rules applied to this project can be customized in the
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
# included above or to enable additional rules. A list of all available lints
# and their documentation is published at
# https://dart-lang.github.io/linter/lints/index.html.
#
# Instead of disabling a lint rule for the entire project in the
# section below, it can also be suppressed for a single line of code
# or a specific dart file by using the `// ignore: name_of_lint` and
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
# producing the lint.
rules:
# avoid_print: false # Uncomment to disable the `avoid_print` rule
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options

View File

@ -1,13 +1,11 @@
//import 'package:flutter_gherkin/flutter_gherkin_integration_test.dart'; // notice new import name //import 'package:flutter_gherkin/flutter_gherkin_integration_test.dart'; // notice new import name
import 'package:flutter_gherkin/flutter_gherkin.dart'; import 'package:flutter_gherkin/flutter_gherkin.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:gherkin/gherkin.dart'; import 'package:gherkin/gherkin.dart';
import 'dart:io'; import 'dart:io';
// The application under test. // The application under test.
import 'package:cwtch/main.dart' as app; import 'package:cwtch/main.dart' as app;
import 'package:glob/glob.dart';
import 'gherkin_suite_test.dart'; import 'gherkin_suite_test.dart';
import 'hooks/env.dart'; import 'hooks/env.dart';
@ -17,7 +15,8 @@ import 'steps/form_elements.dart';
import 'steps/overrides.dart'; import 'steps/overrides.dart';
import 'steps/text.dart'; import 'steps/text.dart';
import 'steps/utils.dart'; import 'steps/utils.dart';
import 'package:flutter_test/flutter_test.dart'; // NEEDED DO NOT REMOVE EVEN IF DART ANALYZER SAYS SO
import 'package:glob/glob.dart';
part 'gherkin_suite_test.g.dart'; part 'gherkin_suite_test.g.dart';
const REPLACED_BY_SCRIPT = <String>['integration_test/features/**.feature']; const REPLACED_BY_SCRIPT = <String>['integration_test/features/**.feature'];

View File

@ -1,17 +1,8 @@
import 'package:cwtch/main.dart';
import 'package:cwtch/widgets/messagebubble.dart';
import 'package:cwtch/widgets/profilerow.dart';
import 'package:cwtch/widgets/quotedmessage.dart'; import 'package:cwtch/widgets/quotedmessage.dart';
import 'package:cwtch/widgets/tor_icon.dart';
import 'package:cwtch/views/profilemgrview.dart';
import 'package:flutter_gherkin/flutter_gherkin.dart'; import 'package:flutter_gherkin/flutter_gherkin.dart';
import 'package:flutter_gherkin/src/flutter/parameters/existence_parameter.dart';
import 'package:flutter_gherkin/src/flutter/parameters/swipe_direction_parameter.dart';
import 'package:gherkin/gherkin.dart'; import 'package:gherkin/gherkin.dart';
import 'package:flutter/material.dart';
import 'overrides.dart';
StepDefinitionGeneric ExpectReply() { StepDefinitionGeneric ExpectReply() {
return given3<String, String, int, FlutterWorld>( return given3<String, String, int, FlutterWorld>(

View File

@ -1,10 +1,7 @@
import 'dart:convert';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_driver/flutter_driver.dart';
import 'package:flutter_gherkin/flutter_gherkin.dart'; import 'package:flutter_gherkin/flutter_gherkin.dart';
import 'package:gherkin/gherkin.dart'; import 'package:gherkin/gherkin.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
enum SwitchState { checked, unchecked } enum SwitchState { checked, unchecked }
@ -17,6 +14,7 @@ class SwitchStateParameter extends CustomParameter<SwitchState> {
case "unchecked": case "unchecked":
return SwitchState.unchecked; return SwitchState.unchecked;
} }
return null;
}); });
} }

View File

@ -149,7 +149,7 @@ StepDefinitionGeneric ExpectWidgetWithTextWithin() {
); );
} }
}() }()
.timeout(Duration(seconds: 120)); .timeout(const Duration(seconds: 120));
}, },
configuration: StepDefinitionConfiguration()..timeout = const Duration(days: 1), configuration: StepDefinitionConfiguration()..timeout = const Duration(days: 1),
); );
@ -173,7 +173,7 @@ StepDefinitionGeneric WaitUntilTextExists() {
)); ));
} }
}() }()
.timeout(Duration(seconds: 120)); .timeout(const Duration(seconds: 120));
}, },
configuration: StepDefinitionConfiguration()..timeout = const Duration(days: 1), configuration: StepDefinitionConfiguration()..timeout = const Duration(days: 1),
); );
@ -195,7 +195,7 @@ StepDefinitionGeneric WaitUntilTooltipExists() {
context.world.appDriver.findBy(ofType, FindType.tooltip), context.world.appDriver.findBy(ofType, FindType.tooltip),
); );
}, },
timeout: Duration(seconds: 120), timeout: const Duration(seconds: 120),
); );
}, },
configuration: StepDefinitionConfiguration()..timeout = const Duration(days: 1), configuration: StepDefinitionConfiguration()..timeout = const Duration(days: 1),
@ -213,7 +213,7 @@ mixin _SwipeHelper on When4WithWorld<SwipeDirection, int, String, String, Flutte
await world.appDriver.scroll( await world.appDriver.scroll(
finder, finder,
dx: offset.toDouble(), dx: offset.toDouble(),
duration: Duration(milliseconds: 500), duration: const Duration(milliseconds: 500),
timeout: timeout, timeout: timeout,
); );
} else { } else {
@ -222,7 +222,7 @@ mixin _SwipeHelper on When4WithWorld<SwipeDirection, int, String, String, Flutte
await world.appDriver.scroll( await world.appDriver.scroll(
finder, finder,
dy: offset.toDouble(), dy: offset.toDouble(),
duration: Duration(milliseconds: 500), duration: const Duration(milliseconds: 500),
timeout: timeout, timeout: timeout,
); );
} }
@ -237,7 +237,7 @@ class SwipeOnType extends When4WithWorld<SwipeDirection, int, String, String, Fl
String typeOf, String typeOf,
String text, String text,
) async { ) async {
final finder = this.world.appDriver.findByDescendant(this.world.appDriver.findBy(widgetTypeByName(typeOf), FindType.type), this.world.appDriver.findBy(text, FindType.text)); final finder = world.appDriver.findByDescendant(world.appDriver.findBy(widgetTypeByName(typeOf), FindType.type), world.appDriver.findBy(text, FindType.text));
await swipeOnFinder(finder, direction, swipeAmount); await swipeOnFinder(finder, direction, swipeAmount);
} }
@ -259,8 +259,6 @@ Type widgetTypeByName(String input1) {
return ElevatedButton; return ElevatedButton;
case "IconButton": case "IconButton":
return IconButton; return IconButton;
case "ProfileRow":
return ProfileRow;
default: default:
throw ("Unknown type $input1. add to integration_test/features/overrides.dart"); throw ("Unknown type $input1. add to integration_test/features/overrides.dart");
} }

View File

@ -25,7 +25,7 @@ StepDefinitionGeneric TorVersionPresent() {
(context) async { (context) async {
String versionString = ""; String versionString = "";
final file = File('fetch-tor.sh'); final file = File('fetch-tor.sh');
Stream<String> lines = file.openRead().transform(utf8.decoder).transform(LineSplitter()); Stream<String> lines = file.openRead().transform(utf8.decoder).transform(const LineSplitter());
try { try {
await for (var line in lines) { await for (var line in lines) {
if (line.startsWith("wget https://git.openprivacy.ca/openprivacy/buildfiles/raw/branch/master/tor/tor-")) { if (line.startsWith("wget https://git.openprivacy.ca/openprivacy/buildfiles/raw/branch/master/tor/tor-")) {

View File

@ -10,7 +10,7 @@ StepDefinitionGeneric TakeScreenshot() {
final bytes = await context.world.appDriver.screenshot(); final bytes = await context.world.appDriver.screenshot();
final screenshotData = base64Encode(bytes); final screenshotData = base64Encode(bytes);
print("EMBEDDING SCREENSHOT...."); print("EMBEDDING SCREENSHOT....");
print("$screenshotData"); print(screenshotData);
context.world.attach(screenshotData, 'image/png', 'And I take a screenshot'); context.world.attach(screenshotData, 'image/png', 'And I take a screenshot');
} catch (e, st) { } catch (e, st) {
print("FAILED TO EMBED??? $e $st"); print("FAILED TO EMBED??? $e $st");

View File

@ -1,6 +1,5 @@
import 'package:cwtch/themes/opaque.dart'; 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/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart';
@ -13,11 +12,11 @@ void modalOpenLink(BuildContext ctx, LinkableElement link) {
showModalBottomSheet<void>( showModalBottomSheet<void>(
context: ctx, context: ctx,
builder: (BuildContext bcontext) { builder: (BuildContext bcontext) {
return Container( return SizedBox(
height: 200, height: 200,
child: Center( child: Center(
child: Padding( child: Padding(
padding: EdgeInsets.all(30.0), padding: const EdgeInsets.all(30.0),
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
@ -28,12 +27,12 @@ void modalOpenLink(BuildContext ctx, LinkableElement link) {
), ),
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: const EdgeInsets.symmetric(vertical: 20, horizontal: 10),
child: ElevatedButton( child: ElevatedButton(
child: Text(AppLocalizations.of(bcontext)!.clickableLinksCopy, child: Text(AppLocalizations.of(bcontext)!.clickableLinksCopy,
style: Provider.of<Settings>(bcontext).scaleFonts(defaultTextButtonStyle), semanticsLabel: 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(ClipboardData(text: link.url));
final snackBar = SnackBar( final snackBar = SnackBar(
content: Text( content: Text(
@ -48,7 +47,7 @@ void modalOpenLink(BuildContext ctx, LinkableElement link) {
), ),
), ),
Container( Container(
margin: EdgeInsets.symmetric(vertical: 20, horizontal: 10), margin: const EdgeInsets.symmetric(vertical: 20, horizontal: 10),
child: ElevatedButton( child: ElevatedButton(
child: Text(AppLocalizations.of(bcontext)!.clickableLinkOpen, child: Text(AppLocalizations.of(bcontext)!.clickableLinkOpen,
style: Provider.of<Settings>(bcontext).scaleFonts(defaultTextButtonStyle), semanticsLabel: AppLocalizations.of(bcontext)!.clickableLinkOpen), style: Provider.of<Settings>(bcontext).scaleFonts(defaultTextButtonStyle), semanticsLabel: AppLocalizations.of(bcontext)!.clickableLinkOpen),

View File

@ -3,15 +3,10 @@ import 'package:cwtch/cwtch/cwtch.dart';
import 'package:cwtch/main.dart'; import 'package:cwtch/main.dart';
import 'package:cwtch/models/appstate.dart'; 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/profilelist.dart'; import 'package:cwtch/models/profilelist.dart';
import 'package:cwtch/models/profileservers.dart';
import 'package:cwtch/models/remoteserver.dart'; import 'package:cwtch/models/remoteserver.dart';
import 'package:cwtch/models/servers.dart'; import 'package:cwtch/models/servers.dart';
import 'package:cwtch/notification_manager.dart'; import 'package:cwtch/notification_manager.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:provider/provider.dart';
import 'package:cwtch/torstatus.dart'; import 'package:cwtch/torstatus.dart';
@ -111,7 +106,7 @@ class CwtchNotifier {
defaultImagePath: data["defaultPicture"], defaultImagePath: data["defaultPicture"],
blocked: data["blocked"] == "true", blocked: data["blocked"] == "true",
accepted: data["accepted"] == "true", accepted: data["accepted"] == "true",
savePeerHistory: data["saveConversationHistory"] == null ? "DeleteHistoryConfirmed" : data["saveConversationHistory"], savePeerHistory: data["saveConversationHistory"] ?? "DeleteHistoryConfirmed",
numMessages: int.parse(data["numMessages"]), numMessages: int.parse(data["numMessages"]),
numUnread: int.parse(data["unread"]), numUnread: int.parse(data["unread"]),
isGroup: false, // by definition isGroup: false, // by definition
@ -254,7 +249,7 @@ class CwtchNotifier {
var notification = data["notification"]; var notification = data["notification"];
// Only bother to do anything if we know about the group and the provided index is greater than our current total... // Only bother to do anything if we know about the group and the provided index is greater than our current total...
if (currentTotal != null && idx >= currentTotal) { if (idx >= currentTotal) {
// TODO: There are 2 timestamps associated with a new group message - time sent and time received. // TODO: There are 2 timestamps associated with a new group message - time sent and time received.
// Sent refers to the time a profile alleges they sent a message // Sent refers to the time a profile alleges they sent a message
// Received refers to the time we actually saw the message from the server // Received refers to the time we actually saw the message from the server
@ -311,7 +306,7 @@ class CwtchNotifier {
// local.conversation.filekey.path // local.conversation.filekey.path
List<String> keyparts = data["Key"].toString().split("."); List<String> keyparts = data["Key"].toString().split(".");
if (keyparts.length == 5) { if (keyparts.length == 5) {
String filekey = keyparts[2] + "." + keyparts[3]; String filekey = "${keyparts[2]}.${keyparts[3]}";
profileCN.getProfile(data["ProfileOnion"])?.downloadSetPathForSender(filekey, data["Data"]); profileCN.getProfile(data["ProfileOnion"])?.downloadSetPathForSender(filekey, data["Data"]);
} }
} }
@ -343,10 +338,10 @@ class CwtchNotifier {
try { try {
List<dynamic> associatedGroups = jsonDecode(data["Data"]); List<dynamic> associatedGroups = jsonDecode(data["Data"]);
int count = int.parse(data["ServerTokenCount"]); int count = int.parse(data["ServerTokenCount"]);
associatedGroups.forEach((identifier) { for (var identifier in associatedGroups) {
profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(int.parse(identifier.toString()))!.antispamTickets = count; profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(int.parse(identifier.toString()))!.antispamTickets = count;
}); }
EnvironmentConfig.debugLog("update server token count for ${associatedGroups}, $count"); EnvironmentConfig.debugLog("update server token count for $associatedGroups, $count");
} catch (e) { } catch (e) {
// No tokens in data... // No tokens in data...
} }
@ -354,7 +349,7 @@ class CwtchNotifier {
case "NewGroup": case "NewGroup":
String invite = data["GroupInvite"].toString(); String invite = data["GroupInvite"].toString();
if (invite.startsWith("torv3")) { if (invite.startsWith("torv3")) {
String inviteJson = new String.fromCharCodes(base64Decode(invite.substring(5))); String inviteJson = String.fromCharCodes(base64Decode(invite.substring(5)));
dynamic groupInvite = jsonDecode(inviteJson); dynamic groupInvite = jsonDecode(inviteJson);
// Retrieve Server Status from Cache... // Retrieve Server Status from Cache...
@ -396,7 +391,7 @@ class CwtchNotifier {
break; break;
case "UpdatedConversationAttribute": case "UpdatedConversationAttribute":
if (data["Path"] == "profile.name") { if (data["Path"] == "profile.name") {
if (data["Data"].toString().trim().length > 0) { if (data["Data"].toString().trim().isNotEmpty) {
// Update locally on the UI... // Update locally on the UI...
if (profileCN.getProfile(data["ProfileOnion"])?.contactList.findContact(data["RemotePeer"]) != null) { if (profileCN.getProfile(data["ProfileOnion"])?.contactList.findContact(data["RemotePeer"]) != null) {
profileCN.getProfile(data["ProfileOnion"])?.contactList.findContact(data["RemotePeer"])!.nickname = data["Data"]; profileCN.getProfile(data["ProfileOnion"])?.contactList.findContact(data["RemotePeer"])!.nickname = data["Data"];

View File

@ -3,7 +3,6 @@ import 'dart:convert';
import 'dart:ffi'; import 'dart:ffi';
import 'dart:io'; import 'dart:io';
import 'dart:isolate'; import 'dart:isolate';
import 'dart:io' show Platform;
import 'package:cwtch/cwtch/cwtchNotifier.dart'; import 'package:cwtch/cwtch/cwtchNotifier.dart';
import 'package:path/path.dart' as path; import 'package:path/path.dart' as path;
@ -13,7 +12,6 @@ import 'package:cwtch/cwtch/cwtch.dart';
import '../config.dart'; import '../config.dart';
import "package:path/path.dart" show dirname, join; import "package:path/path.dart" show dirname, join;
import 'dart:io' show Platform;
///////////////////// /////////////////////
/// Cwtch API /// /// Cwtch API ///
@ -132,7 +130,7 @@ class CwtchFfi implements Cwtch {
late DynamicLibrary library; late DynamicLibrary library;
late CwtchNotifier cwtchNotifier; late CwtchNotifier cwtchNotifier;
late Isolate cwtchIsolate; late Isolate cwtchIsolate;
ReceivePort _receivePort = ReceivePort(); final ReceivePort _receivePort = ReceivePort();
bool _isL10nInit = false; bool _isL10nInit = false;
String _assetsDir = path.join(Directory.current.path, "data", "flutter_assets"); String _assetsDir = path.join(Directory.current.path, "data", "flutter_assets");
String _cwtchDir = ""; String _cwtchDir = "";
@ -162,10 +160,11 @@ class CwtchFfi implements Cwtch {
} }
library = DynamicLibrary.open(libraryPath); library = DynamicLibrary.open(libraryPath);
cwtchNotifier = _cwtchNotifier; cwtchNotifier = _cwtchNotifier;
cwtchNotifier.setMessageSeenCallback((String profile, int conversation, DateTime time) => {this.SetConversationAttribute(profile, conversation, LastMessageSeenTimeKey, time.toIso8601String())}); cwtchNotifier.setMessageSeenCallback((String profile, int conversation, DateTime time) => {SetConversationAttribute(profile, conversation, LastMessageSeenTimeKey, time.toIso8601String())});
} }
// ignore: non_constant_identifier_names // ignore: non_constant_identifier_names
@override
Future<void> Start() async { Future<void> Start() async {
String home = ""; String home = "";
String bundledTor = ""; String bundledTor = "";
@ -264,15 +263,18 @@ class CwtchFfi implements Cwtch {
}); });
} }
@override
String getAssetsDir() { String getAssetsDir() {
return _assetsDir; return _assetsDir;
} }
@override
Future<String> getCwtchDir() async { Future<String> getCwtchDir() async {
return _cwtchDir; return _cwtchDir;
} }
// ignore: non_constant_identifier_names // ignore: non_constant_identifier_names
@override
Future<void> ReconnectCwtchForeground() async { Future<void> ReconnectCwtchForeground() async {
var reconnectCwtch = library.lookup<NativeFunction<Void Function()>>("c_ReconnectCwtchForeground"); var reconnectCwtch = library.lookup<NativeFunction<Void Function()>>("c_ReconnectCwtchForeground");
// ignore: non_constant_identifier_names // ignore: non_constant_identifier_names
@ -310,13 +312,13 @@ class CwtchFfi implements Cwtch {
final Free = free.asFunction<FreeFn>(); final Free = free.asFunction<FreeFn>();
// ignore: non_constant_identifier_names // ignore: non_constant_identifier_names
final GetAppBusEvent = () { GetAppBusEvent() {
// ignore: non_constant_identifier_names // ignore: non_constant_identifier_names
Pointer<Utf8> result = GetAppbusEvent(); Pointer<Utf8> result = GetAppbusEvent();
String event = result.toDartString(); String event = result.toDartString();
Free(result); Free(result);
return event; return event;
}; }
while (true) { while (true) {
final event = GetAppBusEvent(); final event = GetAppBusEvent();
@ -330,6 +332,7 @@ class CwtchFfi implements Cwtch {
} }
// ignore: non_constant_identifier_names // ignore: non_constant_identifier_names
@override
void CreateProfile(String nick, String pass, bool autostart) { void CreateProfile(String nick, String pass, bool autostart) {
var createProfileC = library.lookup<NativeFunction<void_from_string_string_byte_function>>("c_CreateProfile"); var createProfileC = library.lookup<NativeFunction<void_from_string_string_byte_function>>("c_CreateProfile");
// ignore: non_constant_identifier_names // ignore: non_constant_identifier_names
@ -342,6 +345,7 @@ class CwtchFfi implements Cwtch {
} }
// ignore: non_constant_identifier_names // ignore: non_constant_identifier_names
@override
void ActivatePeerEngine(String profile) { void ActivatePeerEngine(String profile) {
var activatePeerEngineC = library.lookup<NativeFunction<string_to_void_function>>("c_ActivatePeerEngine"); var activatePeerEngineC = library.lookup<NativeFunction<string_to_void_function>>("c_ActivatePeerEngine");
final ActivatePeerEngine = activatePeerEngineC.asFunction<StringFn>(); final ActivatePeerEngine = activatePeerEngineC.asFunction<StringFn>();
@ -351,6 +355,7 @@ class CwtchFfi implements Cwtch {
} }
// ignore: non_constant_identifier_names // ignore: non_constant_identifier_names
@override
void DeactivatePeerEngine(String profile) { void DeactivatePeerEngine(String profile) {
var deactivatePeerEngineC = library.lookup<NativeFunction<string_to_void_function>>("c_DeactivatePeerEngine"); var deactivatePeerEngineC = library.lookup<NativeFunction<string_to_void_function>>("c_DeactivatePeerEngine");
final DeactivatePeerEngine = deactivatePeerEngineC.asFunction<StringFn>(); final DeactivatePeerEngine = deactivatePeerEngineC.asFunction<StringFn>();
@ -360,6 +365,7 @@ class CwtchFfi implements Cwtch {
} }
// ignore: non_constant_identifier_names // ignore: non_constant_identifier_names
@override
void LoadProfiles(String pass) { void LoadProfiles(String pass) {
var loadProfileC = library.lookup<NativeFunction<string_to_void_function>>("c_LoadProfiles"); var loadProfileC = library.lookup<NativeFunction<string_to_void_function>>("c_LoadProfiles");
// ignore: non_constant_identifier_names // ignore: non_constant_identifier_names
@ -370,6 +376,7 @@ class CwtchFfi implements Cwtch {
} }
// ignore: non_constant_identifier_names // ignore: non_constant_identifier_names
@override
Future<String> GetMessage(String profile, int handle, int index) async { Future<String> GetMessage(String profile, int handle, int index) async {
var getMessageC = library.lookup<NativeFunction<get_json_blob_from_str_int_int_function>>("c_GetMessage"); var getMessageC = library.lookup<NativeFunction<get_json_blob_from_str_int_int_function>>("c_GetMessage");
// ignore: non_constant_identifier_names // ignore: non_constant_identifier_names
@ -383,6 +390,7 @@ class CwtchFfi implements Cwtch {
} }
// ignore: non_constant_identifier_names // ignore: non_constant_identifier_names
@override
Future<dynamic> GetMessages(String profile, int handle, int index, int count) async { Future<dynamic> GetMessages(String profile, int handle, int index, int count) async {
var getMessagesC = library.lookup<NativeFunction<get_json_blob_from_str_int_int_int_function>>("c_GetMessages"); var getMessagesC = library.lookup<NativeFunction<get_json_blob_from_str_int_int_int_function>>("c_GetMessages");
// ignore: non_constant_identifier_names // ignore: non_constant_identifier_names
@ -499,6 +507,7 @@ class CwtchFfi implements Cwtch {
} }
// ignore: non_constant_identifier_names // ignore: non_constant_identifier_names
@override
void ExportPreviewedFile(String sourceFile, String suggestion) { void ExportPreviewedFile(String sourceFile, String suggestion) {
// android only - do nothing // android only - do nothing
} }

View File

@ -22,8 +22,8 @@ Future startCwtch() async {
*/ */
class CwtchGomobile implements Cwtch { class CwtchGomobile implements Cwtch {
static const appInfoPlatform = const MethodChannel('test.flutter.dev/applicationInfo'); static const appInfoPlatform = MethodChannel('test.flutter.dev/applicationInfo');
static const cwtchPlatform = const MethodChannel('cwtch'); static const cwtchPlatform = MethodChannel('cwtch');
final appbusEventChannelName = 'test.flutter.dev/eventBus'; final appbusEventChannelName = 'test.flutter.dev/eventBus';
@ -54,30 +54,33 @@ class CwtchGomobile implements Cwtch {
CwtchGomobile(CwtchNotifier _cwtchNotifier) { CwtchGomobile(CwtchNotifier _cwtchNotifier) {
print("gomobile.dart: CwtchGomobile()"); print("gomobile.dart: CwtchGomobile()");
cwtchNotifier = _cwtchNotifier; cwtchNotifier = _cwtchNotifier;
cwtchNotifier.setMessageSeenCallback((String profile, int conversation, DateTime time) => {this.SetConversationAttribute(profile, conversation, LastMessageSeenTimeKey, time.toIso8601String())}); cwtchNotifier.setMessageSeenCallback((String profile, int conversation, DateTime time) => {SetConversationAttribute(profile, conversation, LastMessageSeenTimeKey, time.toIso8601String())});
androidHomeDirectory = getApplicationDocumentsDirectory(); androidHomeDirectory = getApplicationDocumentsDirectory();
androidLibraryDir = appInfoPlatform.invokeMethod('getNativeLibDir'); androidLibraryDir = appInfoPlatform.invokeMethod('getNativeLibDir');
// Method channel to receive libcwtch-go events via Kotlin and dispatch them to _handleAppbusEvent (sends to cwtchNotifier) // Method channel to receive libcwtch-go events via Kotlin and dispatch them to _handleAppbusEvent (sends to cwtchNotifier)
final appbusEventChannel = MethodChannel(appbusEventChannelName); final appbusEventChannel = MethodChannel(appbusEventChannelName);
appbusEventChannel.setMethodCallHandler(this._handleAppbusEvent); appbusEventChannel.setMethodCallHandler(_handleAppbusEvent);
} }
@override
String getAssetsDir() { String getAssetsDir() {
// TODO // TODO
return ""; return "";
} }
// Requires Start() to have been run to initialize // Requires Start() to have been run to initialize
@override
Future<String> getCwtchDir() async { Future<String> getCwtchDir() async {
return _cwtchDir; return _cwtchDir;
} }
// ignore: non_constant_identifier_names // ignore: non_constant_identifier_names
@override
Future<void> Start() async { Future<void> Start() async {
print("gomobile.dart: Start()..."); print("gomobile.dart: Start()...");
androidHomeDirectoryStr = (await androidHomeDirectory).path; androidHomeDirectoryStr = (await androidHomeDirectory).path;
_cwtchDir = path.join(await androidHomeDirectoryStr, ".cwtch"); _cwtchDir = path.join(androidHomeDirectoryStr, ".cwtch");
if (EnvironmentConfig.BUILD_VER == dev_version) { if (EnvironmentConfig.BUILD_VER == dev_version) {
_cwtchDir = path.join(_cwtchDir, "dev"); _cwtchDir = path.join(_cwtchDir, "dev");
} }
@ -100,41 +103,49 @@ class CwtchGomobile implements Cwtch {
} }
// ignore: non_constant_identifier_names // ignore: non_constant_identifier_names
@override
void CreateProfile(String nick, String pass, bool autostart) { void CreateProfile(String nick, String pass, bool autostart) {
cwtchPlatform.invokeMethod("CreateProfile", {"nick": nick, "pass": pass, "autostart": autostart}); cwtchPlatform.invokeMethod("CreateProfile", {"nick": nick, "pass": pass, "autostart": autostart});
} }
// ignore: non_constant_identifier_names // ignore: non_constant_identifier_names
@override
void ActivatePeerEngine(String profile) { void ActivatePeerEngine(String profile) {
cwtchPlatform.invokeMethod("ActivatePeerEngine", {"profile": profile}); cwtchPlatform.invokeMethod("ActivatePeerEngine", {"profile": profile});
} }
// ignore: non_constant_identifier_names // ignore: non_constant_identifier_names
@override
void DeactivatePeerEngine(String profile) { void DeactivatePeerEngine(String profile) {
cwtchPlatform.invokeMethod("DeactivatePeerEngine", {"profile": profile}); cwtchPlatform.invokeMethod("DeactivatePeerEngine", {"profile": profile});
} }
// ignore: non_constant_identifier_names // ignore: non_constant_identifier_names
@override
void LoadProfiles(String pass) { void LoadProfiles(String pass) {
cwtchPlatform.invokeMethod("LoadProfiles", {"pass": pass}); cwtchPlatform.invokeMethod("LoadProfiles", {"pass": pass});
} }
// ignore: non_constant_identifier_names // ignore: non_constant_identifier_names
@override
void DeleteProfile(String onion, String pass) { void DeleteProfile(String onion, String pass) {
cwtchPlatform.invokeMethod("DeleteProfile", {"ProfileOnion": onion, "pass": pass}); cwtchPlatform.invokeMethod("DeleteProfile", {"ProfileOnion": onion, "pass": pass});
} }
// ignore: non_constant_identifier_names // ignore: non_constant_identifier_names
@override
Future<dynamic> GetMessage(String profile, int conversation, int index) { Future<dynamic> GetMessage(String profile, int conversation, int index) {
return cwtchPlatform.invokeMethod("GetMessage", {"ProfileOnion": profile, "conversation": conversation, "index": index}); return cwtchPlatform.invokeMethod("GetMessage", {"ProfileOnion": profile, "conversation": conversation, "index": index});
} }
// ignore: non_constant_identifier_names // ignore: non_constant_identifier_names
@override
Future<dynamic> GetMessageByID(String profile, int conversation, int id) { Future<dynamic> GetMessageByID(String profile, int conversation, int id) {
return cwtchPlatform.invokeMethod("GetMessageByID", {"ProfileOnion": profile, "conversation": conversation, "id": id}); return cwtchPlatform.invokeMethod("GetMessageByID", {"ProfileOnion": profile, "conversation": conversation, "id": id});
} }
// ignore: non_constant_identifier_names // ignore: non_constant_identifier_names
@override
Future<dynamic> GetMessages(String profile, int conversation, int index, int count) { Future<dynamic> GetMessages(String profile, int conversation, int index, int count) {
return cwtchPlatform.invokeMethod("GetMessages", {"ProfileOnion": profile, "conversation": conversation, "index": index, "count": count}); return cwtchPlatform.invokeMethod("GetMessages", {"ProfileOnion": profile, "conversation": conversation, "index": index, "count": count});
} }
@ -197,12 +208,14 @@ class CwtchGomobile implements Cwtch {
} }
// ignore: non_constant_identifier_names // ignore: non_constant_identifier_names
@override
void CreateDownloadableFile(String profileOnion, int conversation, String filenameSuggestion, String filekey, String manifestpath) { void CreateDownloadableFile(String profileOnion, int conversation, String filenameSuggestion, String filekey, String manifestpath) {
cwtchPlatform cwtchPlatform
.invokeMethod("CreateDownloadableFile", {"ProfileOnion": profileOnion, "conversation": conversation, "manifestpath": manifestpath, "filename": filenameSuggestion, "filekey": filekey}); .invokeMethod("CreateDownloadableFile", {"ProfileOnion": profileOnion, "conversation": conversation, "manifestpath": manifestpath, "filename": filenameSuggestion, "filekey": filekey});
} }
// ignore: non_constant_identifier_names // ignore: non_constant_identifier_names
@override
void ExportPreviewedFile(String sourceFile, String suggestion) { void ExportPreviewedFile(String sourceFile, String suggestion) {
cwtchPlatform.invokeMethod("ExportPreviewedFile", { cwtchPlatform.invokeMethod("ExportPreviewedFile", {
"Path": sourceFile, "Path": sourceFile,
@ -335,7 +348,7 @@ class CwtchGomobile implements Cwtch {
@override @override
String? defaultDownloadPath() { String? defaultDownloadPath() {
return this.androidHomeDirectoryStr; return androidHomeDirectoryStr;
} }
@override @override

View File

@ -490,7 +490,7 @@ class MaterialLocalizationLu extends MaterialLocalizations {
@override @override
String aboutListTileTitle(String applicationName) { String aboutListTileTitle(String applicationName) {
return aboutListTileTitleRaw.replaceFirst("$applicationName", applicationName); return aboutListTileTitleRaw.replaceFirst(applicationName, applicationName);
} }
@override @override

View File

@ -2,7 +2,7 @@ import 'package:flutter/foundation.dart';
Stream<LicenseEntry> licenses() async* { Stream<LicenseEntry> licenses() async* {
/// Open Privacy Code /// Open Privacy Code
yield LicenseEntryWithLineBreaks(["cwtch", "tapir", "connectivity", "log"], '''MIT License yield const LicenseEntryWithLineBreaks(["cwtch", "tapir", "connectivity", "log"], '''MIT License
Copyright (c) 2018-2023 Open Privacy Research Society Copyright (c) 2018-2023 Open Privacy Research Society
@ -13,7 +13,7 @@ Stream<LicenseEntry> licenses() async* {
THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.'''); THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.''');
/// Ristretto Code /// Ristretto Code
yield LicenseEntryWithLineBreaks(["ristretto255"], '''Copyright (c) 2009 The Go Authors. All rights reserved. yield const LicenseEntryWithLineBreaks(["ristretto255"], '''Copyright (c) 2009 The Go Authors. All rights reserved.
Copyright (c) 2017 George Tankersley. All rights reserved. Copyright (c) 2017 George Tankersley. All rights reserved.
Copyright (c) 2019 Henry de Valence. All rights reserved. Copyright (c) 2019 Henry de Valence. All rights reserved.
@ -44,7 +44,7 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.'''); OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''');
/// Package pretty provides pretty-printing for Go values. (via Cwtch) /// Package pretty provides pretty-printing for Go values. (via Cwtch)
yield LicenseEntryWithLineBreaks(["pretty"], '''Copyright 2012 Keith Rarick yield const LicenseEntryWithLineBreaks(["pretty"], '''Copyright 2012 Keith Rarick
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
@ -64,7 +64,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.'''); THE SOFTWARE.''');
yield LicenseEntryWithLineBreaks(["pidusage"], '''MIT License yield const LicenseEntryWithLineBreaks(["pidusage"], '''MIT License
Copyright (c) 2017 David Copyright (c) 2017 David
@ -87,7 +87,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.'''); SOFTWARE.''');
/// Go Standard Lib /// Go Standard Lib
yield LicenseEntryWithLineBreaks(["crypto, net"], '''Copyright (c) 2009 The Go Authors. All rights reserved. yield const LicenseEntryWithLineBreaks(["crypto, net"], '''Copyright (c) 2009 The Go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are modification, are permitted provided that the following conditions are
@ -115,9 +115,9 @@ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.'''); OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''');
yield LicenseEntryWithLineBreaks(["flaticons"], "Icons made by Freepik (https://www.freepik.com) from Flaticon (www.flaticon.com)"); yield const LicenseEntryWithLineBreaks(["flaticons"], "Icons made by Freepik (https://www.freepik.com) from Flaticon (www.flaticon.com)");
yield LicenseEntryWithLineBreaks(["flutter_linkify", "linkify"], '''MIT License yield const LicenseEntryWithLineBreaks(["flutter_linkify", "linkify"], '''MIT License
Copyright (c) 2019/2020 Charles-William Crete Copyright (c) 2019/2020 Charles-William Crete
@ -139,7 +139,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.'''); SOFTWARE.''');
yield LicenseEntryWithLineBreaks(["connectivity_plus", "connectivity_plus_platform_interface"], ''' yield const LicenseEntryWithLineBreaks(["connectivity_plus", "connectivity_plus_platform_interface"], '''
Copyright 2017 The Chromium Authors. All rights reserved. Copyright 2017 The Chromium Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without
@ -168,14 +168,14 @@ SOFTWARE.''');
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.'''); OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.''');
yield LicenseEntryWithLineBreaks(["connectivity_plus"], ''' yield const LicenseEntryWithLineBreaks(["connectivity_plus"], '''
* Copyright (c) 2013-2020, The PurpleI2P Project * Copyright (c) 2013-2020, The PurpleI2P Project
* *
* This file is part of Purple i2pd project and licensed under BSD3 * This file is part of Purple i2pd project and licensed under BSD3
* *
* See full license text in LICENSE file at top of project tree'''); * See full license text in LICENSE file at top of project tree''');
yield LicenseEntryWithLineBreaks(["nm"], '''Mozilla Public License Version 2.0 yield const LicenseEntryWithLineBreaks(["nm"], '''Mozilla Public License Version 2.0
================================== ==================================
1. Definitions 1. Definitions
@ -549,7 +549,7 @@ Exhibit B - "Incompatible With Secondary Licenses" Notice
This Source Code Form is "Incompatible With Secondary Licenses", as This Source Code Form is "Incompatible With Secondary Licenses", as
defined by the Mozilla Public License, v. 2.0.'''); defined by the Mozilla Public License, v. 2.0.''');
yield LicenseEntryWithLineBreaks(["Inter Fonts"], ''' yield const LicenseEntryWithLineBreaks(["Inter Fonts"], '''
Copyright 2020 The Inter Project Authors (https://github.com/rsms/inter) Copyright 2020 The Inter Project Authors (https://github.com/rsms/inter)
This Font Software is licensed under the SIL Open Font License, Version 1.1. This Font Software is licensed under the SIL Open Font License, Version 1.1.
@ -645,7 +645,7 @@ FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE. OTHER DEALINGS IN THE FONT SOFTWARE.
'''); ''');
yield LicenseEntryWithLineBreaks(["Noto Color Emoji Fonts"], ''' yield const LicenseEntryWithLineBreaks(["Noto Color Emoji Fonts"], '''
Copyright 2021 Google Inc. All Rights Reserved. Copyright 2021 Google Inc. All Rights Reserved.
This Font Software is licensed under the SIL Open Font License, Version 1.1. This Font Software is licensed under the SIL Open Font License, Version 1.1.
@ -741,7 +741,7 @@ FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE. OTHER DEALINGS IN THE FONT SOFTWARE.
'''); ''');
yield LicenseEntryWithLineBreaks(["Roboto fonts"], ''' yield const LicenseEntryWithLineBreaks(["Roboto fonts"], '''
Apache License Apache License
Version 2.0, January 2004 Version 2.0, January 2004

View File

@ -1,6 +1,5 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:isolate';
import 'package:cwtch/config.dart'; import 'package:cwtch/config.dart';
import 'package:cwtch/notification_manager.dart'; import 'package:cwtch/notification_manager.dart';
import 'package:cwtch/themes/cwtch.dart'; import 'package:cwtch/themes/cwtch.dart';
@ -33,9 +32,7 @@ import 'themes/opaque.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:intl/intl.dart' as intl; var globalSettings = Settings(const Locale("en", ''), CwtchDark());
var globalSettings = Settings(Locale("en", ''), CwtchDark());
var globalErrorHandler = ErrorHandler(); var globalErrorHandler = ErrorHandler();
var globalTorStatus = TorStatus(); var globalTorStatus = TorStatus();
var globalAppState = AppState(); var globalAppState = AppState();
@ -54,6 +51,8 @@ Future<void> main() async {
class Flwtch extends StatefulWidget { class Flwtch extends StatefulWidget {
final Key flwtch = GlobalKey(); final Key flwtch = GlobalKey();
Flwtch({super.key});
@override @override
FlwtchState createState() => FlwtchState(); FlwtchState createState() => FlwtchState();
} }
@ -63,9 +62,9 @@ enum ConnectivityState { assumed_online, confirmed_offline, confirmed_online }
class FlwtchState extends State<Flwtch> with WindowListener { class FlwtchState extends State<Flwtch> with WindowListener {
late Cwtch cwtch; late Cwtch cwtch;
late ProfileListState profs; late ProfileListState profs;
final MethodChannel notificationClickChannel = MethodChannel('im.cwtch.flwtch/notificationClickHandler'); final MethodChannel notificationClickChannel = const MethodChannel('im.cwtch.flwtch/notificationClickHandler');
final MethodChannel shutdownMethodChannel = MethodChannel('im.cwtch.flwtch/shutdownClickHandler'); final MethodChannel shutdownMethodChannel = const MethodChannel('im.cwtch.flwtch/shutdownClickHandler');
final MethodChannel shutdownLinuxMethodChannel = MethodChannel('im.cwtch.linux.shutdown'); final MethodChannel shutdownLinuxMethodChannel = const MethodChannel('im.cwtch.linux.shutdown');
late StreamSubscription? connectivityStream; late StreamSubscription? connectivityStream;
ConnectivityState connectivityState = ConnectivityState.assumed_online; ConnectivityState connectivityState = ConnectivityState.assumed_online;
@ -80,7 +79,7 @@ class FlwtchState extends State<Flwtch> with WindowListener {
@override @override
initState() { initState() {
print("initState() started, setting up handlers"); print("initState() started, setting up handlers");
globalSettings = Settings(Locale("en", ''), CwtchDark()); globalSettings = Settings(const Locale("en", ''), CwtchDark());
globalErrorHandler = ErrorHandler(); globalErrorHandler = ErrorHandler();
globalTorStatus = TorStatus(); globalTorStatus = TorStatus();
globalAppState = AppState(); globalAppState = AppState();
@ -96,15 +95,13 @@ class FlwtchState extends State<Flwtch> with WindowListener {
shutdownLinuxMethodChannel.setMethodCallHandler(shutdownDirect); shutdownLinuxMethodChannel.setMethodCallHandler(shutdownDirect);
print("initState: creating cwtchnotifier, ffi"); print("initState: creating cwtchnotifier, ffi");
if (Platform.isAndroid) { if (Platform.isAndroid) {
var cwtchNotifier = new CwtchNotifier(profs, globalSettings, globalErrorHandler, globalTorStatus, NullNotificationsManager(), globalAppState, globalServersList, this); var cwtchNotifier = CwtchNotifier(profs, globalSettings, globalErrorHandler, globalTorStatus, NullNotificationsManager(), globalAppState, globalServersList, this);
cwtch = CwtchGomobile(cwtchNotifier); cwtch = CwtchGomobile(cwtchNotifier);
} else if (Platform.isLinux) { } else if (Platform.isLinux) {
var cwtchNotifier = var cwtchNotifier = CwtchNotifier(profs, globalSettings, globalErrorHandler, globalTorStatus, newDesktopNotificationsManager(_notificationSelectConvo), globalAppState, globalServersList, this);
new CwtchNotifier(profs, globalSettings, globalErrorHandler, globalTorStatus, newDesktopNotificationsManager(_notificationSelectConvo), globalAppState, globalServersList, this);
cwtch = CwtchFfi(cwtchNotifier); cwtch = CwtchFfi(cwtchNotifier);
} else { } else {
var cwtchNotifier = var cwtchNotifier = CwtchNotifier(profs, globalSettings, globalErrorHandler, globalTorStatus, newDesktopNotificationsManager(_notificationSelectConvo), globalAppState, globalServersList, this);
new CwtchNotifier(profs, globalSettings, globalErrorHandler, globalTorStatus, newDesktopNotificationsManager(_notificationSelectConvo), globalAppState, globalServersList, this);
cwtch = CwtchFfi(cwtchNotifier); cwtch = CwtchFfi(cwtchNotifier);
} }
print("initState: invoking cwtch.Start()"); print("initState: invoking cwtch.Start()");
@ -128,7 +125,7 @@ class FlwtchState extends State<Flwtch> with WindowListener {
// gracefully fails and NOPs, as it's not a required functionality // gracefully fails and NOPs, as it's not a required functionality
startConnectivityListener() async { startConnectivityListener() async {
try { try {
connectivityStream = await Connectivity().onConnectivityChanged.listen((ConnectivityResult result) { connectivityStream = Connectivity().onConnectivityChanged.listen((ConnectivityResult result) {
// Got a new connectivity status! // Got a new connectivity status!
if (result == ConnectivityResult.none) { if (result == ConnectivityResult.none) {
connectivityState = ConnectivityState.confirmed_offline; connectivityState = ConnectivityState.confirmed_offline;
@ -174,11 +171,11 @@ class FlwtchState extends State<Flwtch> with WindowListener {
builder: (context, widget) { builder: (context, widget) {
// in test mode...rebuild everything every second...if cwtch isn't loaded... // in test mode...rebuild everything every second...if cwtch isn't loaded...
if (EnvironmentConfig.TEST_MODE && cwtch.IsLoaded() == false) { if (EnvironmentConfig.TEST_MODE && cwtch.IsLoaded() == false) {
Timer t = new Timer.periodic(Duration(seconds: 1), (Timer t) => setState(() {})); Timer t = Timer.periodic(const Duration(seconds: 1), (Timer t) => setState(() {}));
} }
return Consumer2<Settings, AppState>( return Consumer2<Settings, AppState>(
builder: (context, settings, appState, child) => MaterialApp( builder: (context, settings, appState, child) => MaterialApp(
key: Key('app'), key: const Key('app'),
navigatorKey: navKey, navigatorKey: navKey,
locale: settings.locale, locale: settings.locale,
showPerformanceOverlay: settings.profileMode, showPerformanceOverlay: settings.profileMode,
@ -193,7 +190,7 @@ class FlwtchState extends State<Flwtch> with WindowListener {
title: 'Cwtch', title: 'Cwtch',
showSemanticsDebugger: settings.useSemanticDebugger, showSemanticsDebugger: settings.useSemanticDebugger,
theme: mkThemeData(settings), theme: mkThemeData(settings),
home: (!appState.cwtchInit || appState.modalState != ModalState.none) || !cwtch.IsLoaded() ? SplashView() : ProfileMgrView(), home: (!appState.cwtchInit || appState.modalState != ModalState.none) || !cwtch.IsLoaded() ? const SplashView() : const ProfileMgrView(),
), ),
); );
}, },
@ -256,7 +253,6 @@ class FlwtchState extends State<Flwtch> with WindowListener {
exit(0); exit(0);
} }
} }
;
} }
// Invoked via notificationClickChannel by MyBroadcastReceiver in MainActivity.kt // Invoked via notificationClickChannel by MyBroadcastReceiver in MainActivity.kt
@ -286,7 +282,7 @@ class FlwtchState extends State<Flwtch> with WindowListener {
Navigator.of(navKey.currentContext!).push( Navigator.of(navKey.currentContext!).push(
PageRouteBuilder( PageRouteBuilder(
settings: RouteSettings(name: "conversations"), settings: const RouteSettings(name: "conversations"),
pageBuilder: (c, a1, a2) { pageBuilder: (c, a1, a2) {
return OrientationBuilder(builder: (orientationBuilderContext, orientation) { return OrientationBuilder(builder: (orientationBuilderContext, orientation) {
return MultiProvider( return MultiProvider(
@ -294,12 +290,12 @@ class FlwtchState extends State<Flwtch> with WindowListener {
builder: (innercontext, widget) { builder: (innercontext, widget) {
var appState = Provider.of<AppState>(navKey.currentContext!); var appState = Provider.of<AppState>(navKey.currentContext!);
var settings = Provider.of<Settings>(navKey.currentContext!); var settings = Provider.of<Settings>(navKey.currentContext!);
return settings.uiColumns(appState.isLandscape(innercontext)).length > 1 ? DoubleColumnView() : MessageView(); return settings.uiColumns(appState.isLandscape(innercontext)).length > 1 ? const DoubleColumnView() : const MessageView();
}); });
}); });
}, },
transitionsBuilder: (c, anim, a2, child) => FadeTransition(opacity: anim, child: child), transitionsBuilder: (c, anim, a2, child) => FadeTransition(opacity: anim, child: child),
transitionDuration: Duration(milliseconds: 200), transitionDuration: const Duration(milliseconds: 200),
), ),
); );
// On Gnome follows up a clicked notification with a "Cwtch is ready" notification that takes you to the app. AFAICT just because Gnome is bad // On Gnome follows up a clicked notification with a "Cwtch is ready" notification that takes you to the app. AFAICT just because Gnome is bad
@ -320,6 +316,7 @@ class FlwtchState extends State<Flwtch> with WindowListener {
globalAppState.focus = false; globalAppState.focus = false;
} }
@override
void onWindowClose() {} void onWindowClose() {}
@override @override

View File

@ -8,16 +8,14 @@ import 'package:glob/list_local_fs.dart';
import 'config.dart'; import 'config.dart';
import 'licenses.dart'; import 'licenses.dart';
import 'main.dart'; import 'main.dart';
import 'themes/opaque.dart';
import 'dart:convert'; import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'dart:typed_data';
import "package:flutter_driver/driver_extension.dart"; import "package:flutter_driver/driver_extension.dart";
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:glob/glob.dart'; import 'package:glob/glob.dart';
var globalSettings = Settings(Locale("en", ''), CwtchDark()); var globalSettings = Settings(const Locale("en", ''), CwtchDark());
var globalErrorHandler = ErrorHandler(); var globalErrorHandler = ErrorHandler();
Future<void> main() async { Future<void> main() async {

View File

@ -1,5 +1,4 @@
import 'dart:async'; import 'dart:async';
import 'dart:io';
import 'package:cwtch/config.dart'; import 'package:cwtch/config.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
@ -19,7 +18,7 @@ class AppState extends ChangeNotifier {
bool _disableFilePicker = false; bool _disableFilePicker = false;
bool _focus = true; bool _focus = true;
StreamController<bool> _profilesUnreadNotifyControler = StreamController<bool>(); final StreamController<bool> _profilesUnreadNotifyControler = StreamController<bool>();
late Stream<bool> profilesUnreadNotify; late Stream<bool> profilesUnreadNotify;
AppState() { AppState() {
@ -33,50 +32,50 @@ class AppState extends ChangeNotifier {
void SetAppError(String error) { void SetAppError(String error) {
appError = error; appError = error;
EnvironmentConfig.debugLog("App Error: ${appError}"); EnvironmentConfig.debugLog("App Error: $appError");
notifyListeners(); notifyListeners();
} }
void SetModalState(ModalState newState) { void SetModalState(ModalState newState) {
modalState = newState; modalState = newState;
EnvironmentConfig.debugLog("Modal State: ${newState}"); EnvironmentConfig.debugLog("Modal State: $newState");
notifyListeners(); notifyListeners();
} }
String? get selectedProfile => _selectedProfile; String? get selectedProfile => _selectedProfile;
set selectedProfile(String? newVal) { set selectedProfile(String? newVal) {
this._selectedConversation = null; _selectedConversation = null;
this._selectedProfile = newVal; _selectedProfile = newVal;
notifyListeners(); notifyListeners();
} }
int? get selectedConversation => _selectedConversation; int? get selectedConversation => _selectedConversation;
set selectedConversation(int? newVal) { set selectedConversation(int? newVal) {
this._selectedConversation = newVal; _selectedConversation = newVal;
notifyListeners(); notifyListeners();
} }
int? get selectedSearchMessage => _selectedSearchMessage; int? get selectedSearchMessage => _selectedSearchMessage;
set selectedSearchMessage(int? newVal) { set selectedSearchMessage(int? newVal) {
this._selectedSearchMessage = newVal; _selectedSearchMessage = newVal;
notifyListeners(); notifyListeners();
} }
bool get disableFilePicker => _disableFilePicker; bool get disableFilePicker => _disableFilePicker;
set disableFilePicker(bool newVal) { set disableFilePicker(bool newVal) {
this._disableFilePicker = newVal; _disableFilePicker = newVal;
notifyListeners(); notifyListeners();
} }
bool get unreadMessagesBelow => _unreadMessagesBelow; bool get unreadMessagesBelow => _unreadMessagesBelow;
set unreadMessagesBelow(bool newVal) { set unreadMessagesBelow(bool newVal) {
this._unreadMessagesBelow = newVal; _unreadMessagesBelow = newVal;
notifyListeners(); notifyListeners();
} }
int get initialScrollIndex => _initialScrollIndex; int get initialScrollIndex => _initialScrollIndex;
set initialScrollIndex(int newVal) { set initialScrollIndex(int newVal) {
this._initialScrollIndex = newVal; _initialScrollIndex = newVal;
notifyListeners(); notifyListeners();
} }

View File

@ -1,5 +1,3 @@
import 'dart:ffi';
import 'package:cwtch/main.dart'; import 'package:cwtch/main.dart';
import 'package:cwtch/models/message_draft.dart'; import 'package:cwtch/models/message_draft.dart';
import 'package:cwtch/models/profile.dart'; import 'package:cwtch/models/profile.dart';
@ -13,7 +11,6 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
import 'message.dart';
import 'messagecache.dart'; import 'messagecache.dart';
enum ConversationNotificationPolicy { enum ConversationNotificationPolicy {
@ -57,7 +54,7 @@ class ContactInfoState extends ChangeNotifier {
late Map<String, GlobalKey<MessageRowState>> keys; late Map<String, GlobalKey<MessageRowState>> keys;
int _newMarkerMsgIndex = -1; int _newMarkerMsgIndex = -1;
late MessageCache messageCache; late MessageCache messageCache;
ItemScrollController messageScrollController = new ItemScrollController(); ItemScrollController messageScrollController = ItemScrollController();
// todo: a nicer way to model contacts, groups and other "entities" // todo: a nicer way to model contacts, groups and other "entities"
late bool _isGroup; late bool _isGroup;
@ -98,55 +95,55 @@ class ContactInfoState extends ChangeNotifier {
notificationPolicy = "ConversationNotificationPolicy.Default", notificationPolicy = "ConversationNotificationPolicy.Default",
pinned = false, pinned = false,
}) { }) {
this._nickname = nickname; _nickname = nickname;
this._localNickname = localNickname; _localNickname = localNickname;
this._isGroup = isGroup; _isGroup = isGroup;
this._accepted = accepted; _accepted = accepted;
this._blocked = blocked; _blocked = blocked;
this._status = status; _status = status;
this._imagePath = imagePath; _imagePath = imagePath;
this._defaultImagePath = defaultImagePath; _defaultImagePath = defaultImagePath;
this._totalMessages = numMessages; _totalMessages = numMessages;
this._unreadMessages = numUnread; _unreadMessages = numUnread;
this._savePeerHistory = savePeerHistory; _savePeerHistory = savePeerHistory;
this._lastMessageReceivedTime = lastMessageTime == null ? DateTime.fromMillisecondsSinceEpoch(0) : lastMessageTime; _lastMessageReceivedTime = lastMessageTime ?? DateTime.fromMillisecondsSinceEpoch(0);
this._lastMessageSentTime = _lastMessageReceivedTime; _lastMessageSentTime = _lastMessageReceivedTime;
this._server = server; _server = server;
this._archived = archived; _archived = archived;
this._notificationPolicy = notificationPolicyFromString(notificationPolicy); _notificationPolicy = notificationPolicyFromString(notificationPolicy);
this.messageCache = new MessageCache(_totalMessages); messageCache = MessageCache(_totalMessages);
this._pinned = pinned; _pinned = pinned;
keys = Map<String, GlobalKey<MessageRowState>>(); keys = <String, GlobalKey<MessageRowState>>{};
} }
String get nickname { String get nickname {
if (this._localNickname != "") { if (_localNickname != "") {
return this._localNickname; return _localNickname;
} }
return this._nickname; return _nickname;
} }
String get savePeerHistory => this._savePeerHistory; String get savePeerHistory => _savePeerHistory;
String? get acnCircuit => this._acnCircuit; String? get acnCircuit => _acnCircuit;
MessageDraft get messageDraft => this._messageDraft; MessageDraft get messageDraft => _messageDraft;
DateTime get lastRetryTime => this._lastRetryTime; DateTime get lastRetryTime => _lastRetryTime;
set lastRetryTime(DateTime lastRetryTime) { set lastRetryTime(DateTime lastRetryTime) {
this._lastRetryTime = lastRetryTime; _lastRetryTime = lastRetryTime;
notifyListeners(); notifyListeners();
} }
set antispamTickets(int antispamTickets) { set antispamTickets(int antispamTickets) {
this._antispamTickets = antispamTickets; _antispamTickets = antispamTickets;
notifyListeners(); notifyListeners();
} }
int get antispamTickets => this._antispamTickets; int get antispamTickets => _antispamTickets;
set acnCircuit(String? acnCircuit) { set acnCircuit(String? acnCircuit) {
this._acnCircuit = acnCircuit; _acnCircuit = acnCircuit;
notifyListeners(); notifyListeners();
} }
@ -154,58 +151,58 @@ class ContactInfoState extends ChangeNotifier {
// be moved to the very bottom of the active conversations list until // be moved to the very bottom of the active conversations list until
// new messages appear // new messages appear
set isArchived(bool archived) { set isArchived(bool archived) {
this._archived = archived; _archived = archived;
notifyListeners(); notifyListeners();
} }
bool get isArchived => this._archived; bool get isArchived => _archived;
set savePeerHistory(String newVal) { set savePeerHistory(String newVal) {
this._savePeerHistory = newVal; _savePeerHistory = newVal;
notifyListeners(); notifyListeners();
} }
set nickname(String newVal) { set nickname(String newVal) {
this._nickname = newVal; _nickname = newVal;
notifyListeners(); notifyListeners();
} }
set localNickname(String newVal) { set localNickname(String newVal) {
this._localNickname = newVal; _localNickname = newVal;
notifyListeners(); notifyListeners();
} }
bool get isGroup => this._isGroup; bool get isGroup => _isGroup;
set isGroup(bool newVal) { set isGroup(bool newVal) {
this._isGroup = newVal; _isGroup = newVal;
notifyListeners(); notifyListeners();
} }
bool get isBlocked => this._blocked; bool get isBlocked => _blocked;
bool get isInvitation => !this._blocked && !this._accepted; bool get isInvitation => !_blocked && !_accepted;
set accepted(bool newVal) { set accepted(bool newVal) {
this._accepted = newVal; _accepted = newVal;
notifyListeners(); notifyListeners();
} }
set blocked(bool newVal) { set blocked(bool newVal) {
this._blocked = newVal; _blocked = newVal;
notifyListeners(); notifyListeners();
} }
String get status => this._status; String get status => _status;
set status(String newVal) { set status(String newVal) {
this._status = newVal; _status = newVal;
this.contactEvents.add(ContactEvent("Update Peer Status Received: $newVal")); contactEvents.add(ContactEvent("Update Peer Status Received: $newVal"));
notifyListeners(); notifyListeners();
} }
set messageDraft(MessageDraft newVal) { set messageDraft(MessageDraft newVal) {
this._messageDraft = newVal; _messageDraft = newVal;
notifyListeners(); notifyListeners();
} }
@ -214,89 +211,89 @@ class ContactInfoState extends ChangeNotifier {
} }
void selected() { void selected() {
this._newMarkerMsgIndex = this._unreadMessages - 1; _newMarkerMsgIndex = _unreadMessages - 1;
this._unreadMessages = 0; _unreadMessages = 0;
} }
void unselected() { void unselected() {
this._newMarkerMsgIndex = -1; _newMarkerMsgIndex = -1;
} }
int get unreadMessages => this._unreadMessages; int get unreadMessages => _unreadMessages;
set unreadMessages(int newVal) { set unreadMessages(int newVal) {
this._unreadMessages = newVal; _unreadMessages = newVal;
notifyListeners(); notifyListeners();
} }
int get newMarkerMsgIndex { int get newMarkerMsgIndex {
return this._newMarkerMsgIndex; return _newMarkerMsgIndex;
} }
int get totalMessages => this._totalMessages; int get totalMessages => _totalMessages;
set totalMessages(int newVal) { set totalMessages(int newVal) {
this._totalMessages = newVal; _totalMessages = newVal;
this.messageCache.storageMessageCount = newVal; messageCache.storageMessageCount = newVal;
notifyListeners(); notifyListeners();
} }
String get imagePath { String get imagePath {
// don't show custom images for blocked contacts.. // don't show custom images for blocked contacts..
if (!this.isBlocked) { if (!isBlocked) {
return this._imagePath; return _imagePath;
} }
return this.defaultImagePath; return defaultImagePath;
} }
set imagePath(String newVal) { set imagePath(String newVal) {
this._imagePath = newVal; _imagePath = newVal;
notifyListeners(); notifyListeners();
} }
String get defaultImagePath => this._defaultImagePath; String get defaultImagePath => _defaultImagePath;
set defaultImagePath(String newVal) { set defaultImagePath(String newVal) {
this._defaultImagePath = newVal; _defaultImagePath = newVal;
notifyListeners(); notifyListeners();
} }
// This is last message received time (local) and to be used for sorting only // This is last message received time (local) and to be used for sorting only
// for instance, group sync, we want to pop to the top, so we set to time.Now() for new messages // for instance, group sync, we want to pop to the top, so we set to time.Now() for new messages
// but it should not be used for display // but it should not be used for display
DateTime get lastMessageReceivedTime => this._lastMessageReceivedTime; DateTime get lastMessageReceivedTime => _lastMessageReceivedTime;
set lastMessageReceivedTime(DateTime newVal) { set lastMessageReceivedTime(DateTime newVal) {
this._lastMessageReceivedTime = newVal; _lastMessageReceivedTime = newVal;
notifyListeners(); notifyListeners();
} }
// This is last message sent time and is based on message reports of sent times // This is last message sent time and is based on message reports of sent times
// this can be used to display in the contact list a last time a message was received // this can be used to display in the contact list a last time a message was received
DateTime get lastMessageSentTime => this._lastMessageSentTime; DateTime get lastMessageSentTime => _lastMessageSentTime;
set lastMessageSentTime(DateTime newVal) { set lastMessageSentTime(DateTime newVal) {
this._lastMessageSentTime = newVal; _lastMessageSentTime = newVal;
notifyListeners(); notifyListeners();
} }
// we only allow callers to fetch the server // we only allow callers to fetch the server
String? get server => this._server; String? get server => _server;
bool isOnline() { bool isOnline() {
if (this.isGroup == true) { if (isGroup == true) {
// We now have an out of sync warning so we will mark these as online... // We now have an out of sync warning so we will mark these as online...
return this.status == "Authenticated" || this.status == "Synced"; return status == "Authenticated" || status == "Synced";
} else { } else {
return this.status == "Authenticated"; return status == "Authenticated";
} }
} }
bool canSend() { bool canSend() {
if (this.isGroup == true) { if (isGroup == true) {
// We now have an out of sync warning so we will mark these as online... // We now have an out of sync warning so we will mark these as online...
return this.status == "Synced" && this.antispamTickets > 0; return status == "Synced" && antispamTickets > 0;
} else { } else {
return this.isOnline(); return isOnline();
} }
} }
@ -308,7 +305,7 @@ class ContactInfoState extends ChangeNotifier {
} }
GlobalKey<MessageRowState> getMessageKey(int conversation, int message) { GlobalKey<MessageRowState> getMessageKey(int conversation, int message) {
String index = "c: " + conversation.toString() + " m:" + message.toString(); String index = "c: $conversation m:$message";
if (keys[index] == null) { if (keys[index] == null) {
keys[index] = GlobalKey<MessageRowState>(); keys[index] = GlobalKey<MessageRowState>();
} }
@ -317,7 +314,7 @@ class ContactInfoState extends ChangeNotifier {
} }
GlobalKey<MessageRowState>? getMessageKeyOrFail(int conversation, int message) { GlobalKey<MessageRowState>? getMessageKeyOrFail(int conversation, int message) {
String index = "c: " + conversation.toString() + " m:" + message.toString(); String index = "c: $conversation m:$message";
if (keys[index] == null) { if (keys[index] == null) {
return null; return null;
@ -338,10 +335,10 @@ class ContactInfoState extends ChangeNotifier {
_newMarkerMsgIndex++; _newMarkerMsgIndex++;
} }
this._lastMessageReceivedTime = timestamp; _lastMessageReceivedTime = timestamp;
this._lastMessageSentTime = timestamp; _lastMessageSentTime = timestamp;
this.messageCache.addNew(profileOnion, identifier, messageID, timestamp, senderHandle, senderImage, isAuto, data, contenthash); messageCache.addNew(profileOnion, identifier, messageID, timestamp, senderHandle, senderImage, isAuto, data, contenthash);
this.totalMessages += 1; totalMessages += 1;
// We only ever see messages from authenticated peers. // We only ever see messages from authenticated peers.
// If the contact is marked as offline then override this - can happen when the contact is removed from the front // If the contact is marked as offline then override this - can happen when the contact is removed from the front
@ -353,12 +350,12 @@ class ContactInfoState extends ChangeNotifier {
} }
void ackCache(int messageID) { void ackCache(int messageID) {
this.messageCache.ackCache(messageID); messageCache.ackCache(messageID);
notifyListeners(); notifyListeners();
} }
void errCache(int messageID) { void errCache(int messageID) {
this.messageCache.errCache(messageID); messageCache.errCache(messageID);
notifyListeners(); notifyListeners();
} }
@ -408,14 +405,14 @@ class ContactInfoState extends ChangeNotifier {
} }
void updateTranslationEvent(int messageID, String translation) { void updateTranslationEvent(int messageID, String translation) {
this.messageCache.updateTranslationEvent(messageID, translation); messageCache.updateTranslationEvent(messageID, translation);
notifyListeners(); notifyListeners();
} }
// Contact Attributes. Can be set in Profile Edit View... // Contact Attributes. Can be set in Profile Edit View...
List<String?> attributes = [null, null, null]; List<String?> attributes = [null, null, null];
void setAttribute(int i, String? value) { void setAttribute(int i, String? value) {
this.attributes[i] = value; attributes[i] = value;
notifyListeners(); notifyListeners();
} }
@ -438,11 +435,11 @@ class ContactInfoState extends ChangeNotifier {
} }
Color getBorderColor(OpaqueThemeType theme) { Color getBorderColor(OpaqueThemeType theme) {
if (this.isBlocked) { if (isBlocked) {
return theme.portraitBlockedBorderColor; return theme.portraitBlockedBorderColor;
} }
if (this.isOnline()) { if (isOnline()) {
switch (this.availabilityStatus) { switch (availabilityStatus) {
case ProfileStatusMenu.available: case ProfileStatusMenu.available:
return theme.portraitOnlineBorderColor; return theme.portraitOnlineBorderColor;
case ProfileStatusMenu.away: case ProfileStatusMenu.away:
@ -458,26 +455,26 @@ class ContactInfoState extends ChangeNotifier {
} }
String augmentedNickname(BuildContext context) { String augmentedNickname(BuildContext context) {
var nick = redactedNick(context, this.onion, this.nickname); var nick = redactedNick(context, onion, nickname);
return nick + (this.availabilityStatus == ProfileStatusMenu.available ? "" : " (" + this.statusString(context) + ")"); return nick + (availabilityStatus == ProfileStatusMenu.available ? "" : " (${statusString(context)})");
} }
// Never use this for message lookup - can be a non-indexed value // Never use this for message lookup - can be a non-indexed value
// e.g. -1 // e.g. -1
int get hoveredIndex => _hoveredIndex; int get hoveredIndex => _hoveredIndex;
set hoveredIndex(int newVal) { set hoveredIndex(int newVal) {
this._hoveredIndex = newVal; _hoveredIndex = newVal;
notifyListeners(); notifyListeners();
} }
int get pendingScroll => _pendingScroll; int get pendingScroll => _pendingScroll;
set pendingScroll(int newVal) { set pendingScroll(int newVal) {
this._pendingScroll = newVal; _pendingScroll = newVal;
notifyListeners(); notifyListeners();
} }
String statusString(BuildContext context) { String statusString(BuildContext context) {
switch (this.availabilityStatus) { switch (availabilityStatus) {
case ProfileStatusMenu.available: case ProfileStatusMenu.available:
return AppLocalizations.of(context)!.availabilityStatusAvailable; return AppLocalizations.of(context)!.availabilityStatusAvailable;
case ProfileStatusMenu.away: case ProfileStatusMenu.away:
@ -494,6 +491,6 @@ class ContactEvent {
String summary; String summary;
late DateTime timestamp; late DateTime timestamp;
ContactEvent(this.summary) { ContactEvent(this.summary) {
this.timestamp = DateTime.now(); timestamp = DateTime.now();
} }
} }

View File

@ -6,7 +6,7 @@ import 'profileservers.dart';
class ContactListState extends ChangeNotifier { class ContactListState extends ChangeNotifier {
ProfileServerListState? servers; ProfileServerListState? servers;
List<ContactInfoState> _contacts = []; final List<ContactInfoState> _contacts = [];
String _filter = ""; String _filter = "";
int get num => _contacts.length; int get num => _contacts.length;
int get numFiltered => isFiltered ? filteredList().length : num; int get numFiltered => isFiltered ? filteredList().length : num;
@ -29,11 +29,11 @@ class ContactListState extends ChangeNotifier {
void addAll(Iterable<ContactInfoState> newContacts) { void addAll(Iterable<ContactInfoState> newContacts) {
_contacts.addAll(newContacts); _contacts.addAll(newContacts);
servers?.clearGroups(); servers?.clearGroups();
_contacts.forEach((contact) { for (var contact in _contacts) {
if (contact.isGroup) { if (contact.isGroup) {
servers?.addGroup(contact); servers?.addGroup(contact);
} }
}); }
resort(); resort();
notifyListeners(); notifyListeners();
} }
@ -54,7 +54,7 @@ class ContactListState extends ChangeNotifier {
if (otherGroups != null && otherGroups.isNotEmpty) { if (otherGroups != null && otherGroups.isNotEmpty) {
EnvironmentConfig.debugLog("sharing antispam tickets to new group. FIXME: in Cwtch 1.14"); EnvironmentConfig.debugLog("sharing antispam tickets to new group. FIXME: in Cwtch 1.14");
var antispamTickets = otherGroups[0].antispamTickets; var antispamTickets = otherGroups[0].antispamTickets;
_contacts.last!.antispamTickets = antispamTickets; _contacts.last.antispamTickets = antispamTickets;
} }
servers?.addGroup(newContact); servers?.addGroup(newContact);
} }

View File

@ -7,7 +7,7 @@ class FileDownloadProgress {
// we keep track of both an explicit interrupt flag (for when a request fails or is explicitly cancelled) // we keep track of both an explicit interrupt flag (for when a request fails or is explicitly cancelled)
set interrupted(isInterrupted) { set interrupted(isInterrupted) {
this._interrupted = isInterrupted; _interrupted = isInterrupted;
} }
// but we have a fuzzy get which depends on lastUpdate, if the file isn't complete, but the last update was more // but we have a fuzzy get which depends on lastUpdate, if the file isn't complete, but the last update was more
@ -33,12 +33,12 @@ class FileDownloadProgress {
String prettyBytes(int bytes) { String prettyBytes(int bytes) {
if (bytes > 1000000000) { if (bytes > 1000000000) {
return (1.0 * bytes / 1000000000).toStringAsFixed(1) + " GB"; return "${(1.0 * bytes / 1000000000).toStringAsFixed(1)} GB";
} else if (bytes > 1000000) { } else if (bytes > 1000000) {
return (1.0 * bytes / 1000000).toStringAsFixed(1) + " MB"; return "${(1.0 * bytes / 1000000).toStringAsFixed(1)} MB";
} else if (bytes > 1000) { } else if (bytes > 1000) {
return (1.0 * bytes / 1000).toStringAsFixed(1) + " kB"; return "${(1.0 * bytes / 1000).toStringAsFixed(1)} kB";
} else { } else {
return bytes.toString() + " B"; return "$bytes B";
} }
} }

View File

@ -76,6 +76,7 @@ class ByIndex implements CacheHandler {
return msg; return msg;
} }
@override
Future<MessageInfo?> get(Cwtch cwtch, String profileOnion, int conversationIdentifier, MessageCache cache) async { Future<MessageInfo?> get(Cwtch cwtch, String profileOnion, int conversationIdentifier, MessageCache cache) async {
// if in cache, get. But if the cache has unsynced or not in cache, we'll have to do a fetch // if in cache, get. But if the cache has unsynced or not in cache, we'll have to do a fetch
if (index < cache.cacheByIndex.length) { if (index < cache.cacheByIndex.length) {
@ -132,7 +133,7 @@ class ByIndex implements CacheHandler {
cache.addIndexed(messageInfo, start + i); cache.addIndexed(messageInfo, start + i);
} }
} catch (e, stacktrace) { } catch (e, stacktrace) {
EnvironmentConfig.debugLog("Error: Getting indexed messages $start to ${start + amount} failed parsing: " + e.toString() + " " + stacktrace.toString()); EnvironmentConfig.debugLog("Error: Getting indexed messages $start to ${start + amount} failed parsing: $e $stacktrace");
} finally { } finally {
if (i != amount) { if (i != amount) {
cache.malformIndexes(start + i, start + amount); cache.malformIndexes(start + i, start + amount);
@ -165,6 +166,7 @@ class ById implements CacheHandler {
return Future.value(messageInfo); return Future.value(messageInfo);
} }
@override
Future<MessageInfo?> get(Cwtch cwtch, String profileOnion, int conversationIdentifier, MessageCache cache) async { Future<MessageInfo?> get(Cwtch cwtch, String profileOnion, int conversationIdentifier, MessageCache cache) async {
var messageInfo = await lookup(cache); var messageInfo = await lookup(cache);
if (messageInfo != null) { if (messageInfo != null) {
@ -193,6 +195,7 @@ class ByContentHash implements CacheHandler {
return Future.value(messageInfo); return Future.value(messageInfo);
} }
@override
Future<MessageInfo?> get(Cwtch cwtch, String profileOnion, int conversationIdentifier, MessageCache cache) async { Future<MessageInfo?> get(Cwtch cwtch, String profileOnion, int conversationIdentifier, MessageCache cache) async {
var messageInfo = await lookup(cache); var messageInfo = await lookup(cache);
if (messageInfo != null) { if (messageInfo != null) {
@ -274,7 +277,7 @@ MessageInfo? messageJsonToInfo(String profileOnion, int conversationIdentifier,
return messageWrapperToInfo(profileOnion, conversationIdentifier, messageWrapper); return messageWrapperToInfo(profileOnion, conversationIdentifier, messageWrapper);
} catch (e, stacktrace) { } catch (e, stacktrace) {
EnvironmentConfig.debugLog("message handler exception on parse message and cache: " + e.toString() + " " + stacktrace.toString()); EnvironmentConfig.debugLog("message handler exception on parse message and cache: $e $stacktrace");
return null; return null;
} }
} }
@ -291,7 +294,7 @@ MessageInfo messageWrapperToInfo(String profileOnion, int conversationIdentifier
var signature = messageWrapper['Signature']; var signature = messageWrapper['Signature'];
var contenthash = messageWrapper['ContentHash']; var contenthash = messageWrapper['ContentHash'];
var metadata = MessageMetadata(profileOnion, conversationIdentifier, messageID, timestamp, senderHandle, senderImage, signature, attributes, ackd, error, false, contenthash); var metadata = MessageMetadata(profileOnion, conversationIdentifier, messageID, timestamp, senderHandle, senderImage, signature, attributes, ackd, error, false, contenthash);
var messageInfo = new MessageInfo(metadata, messageWrapper['Message']); var messageInfo = MessageInfo(metadata, messageWrapper['Message']);
return messageInfo; return messageInfo;
} }
@ -313,9 +316,9 @@ class MessageMetadata extends ChangeNotifier {
final String? signature; final String? signature;
final String contenthash; final String contenthash;
dynamic get attributes => this._attributes; dynamic get attributes => _attributes;
bool get ackd => this._ackd; bool get ackd => _ackd;
String translation = ""; String translation = "";
void updateTranslationEvent(String translation) { void updateTranslationEvent(String translation) {
@ -324,14 +327,14 @@ class MessageMetadata extends ChangeNotifier {
} }
set ackd(bool newVal) { set ackd(bool newVal) {
this._ackd = newVal; _ackd = newVal;
notifyListeners(); notifyListeners();
} }
bool get error => this._error; bool get error => _error;
set error(bool newVal) { set error(bool newVal) {
this._error = newVal; _error = newVal;
notifyListeners(); notifyListeners();
} }

View File

@ -1,5 +1,4 @@
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.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.
@ -14,43 +13,43 @@ class MessageDraft extends ChangeNotifier {
} }
bool isEmpty() { bool isEmpty() {
return (this._quotedReference == null) || (this.messageText.isEmpty); return (_quotedReference == null) || (messageText.isEmpty);
} }
String get messageText => ctrlCompose.text; String get messageText => ctrlCompose.text;
set messageText(String text) { set messageText(String text) {
this.ctrlCompose.text = text; ctrlCompose.text = text;
notifyListeners(); notifyListeners();
} }
set quotedReference(int index) { set quotedReference(int index) {
this._quotedReference = QuotedReference(index); _quotedReference = QuotedReference(index);
notifyListeners(); notifyListeners();
} }
void attachInvite(int handle) { void attachInvite(int handle) {
this._inviteHandle = handle; _inviteHandle = handle;
notifyListeners(); notifyListeners();
} }
int? getInviteHandle() { int? getInviteHandle() {
return this._inviteHandle; return _inviteHandle;
} }
QuotedReference? getQuotedMessage() { QuotedReference? getQuotedMessage() {
return this._quotedReference; return _quotedReference;
} }
void clearQuotedReference() { void clearQuotedReference() {
this._quotedReference = null; _quotedReference = null;
notifyListeners(); notifyListeners();
} }
void clearDraft() { void clearDraft() {
this._quotedReference = null; _quotedReference = null;
this.ctrlCompose.clear(); ctrlCompose.clear();
this._inviteHandle = null; _inviteHandle = null;
notifyListeners(); notifyListeners();
} }
@ -61,7 +60,7 @@ class MessageDraft extends ChangeNotifier {
} }
void clearInvite() { void clearInvite() {
this._inviteHandle = null; _inviteHandle = null;
} }
} }

View File

@ -26,10 +26,7 @@ class LocalIndexMessage {
late int? messageId; late int? messageId;
LocalIndexMessage(int? messageId, {cacheOnly = false, isLoading = false}) { LocalIndexMessage(this.messageId, {this.cacheOnly = false, this.isLoading = false}) {
this.messageId = messageId;
this.cacheOnly = cacheOnly;
this.isLoading = isLoading;
loader = Completer<void>(); loader = Completer<void>();
loaded = loader.future; loaded = loader.future;
if (!isLoading) { if (!isLoading) {
@ -46,7 +43,7 @@ class LocalIndexMessage {
} }
void failLoad() { void failLoad() {
this.messageId = null; messageId = null;
if (!loader.isCompleted) { if (!loader.isCompleted) {
isLoading = false; isLoading = false;
loader.complete(true); loader.complete(true);
@ -92,17 +89,17 @@ class MessageCache extends ChangeNotifier {
cache = {}; cache = {};
cacheByIndex = List.empty(growable: true); cacheByIndex = List.empty(growable: true);
cacheByHash = {}; cacheByHash = {};
this._storageMessageCount = storageMessageCount; _storageMessageCount = storageMessageCount;
} }
int get storageMessageCount => _storageMessageCount; int get storageMessageCount => _storageMessageCount;
set storageMessageCount(int newval) { set storageMessageCount(int newval) {
this._storageMessageCount = newval; _storageMessageCount = newval;
} }
// On android reconnect, if backend supplied message count > UI message count, add the difference to the front of the index // On android reconnect, if backend supplied message count > UI message count, add the difference to the front of the index
void addFrontIndexGap(int count) { void addFrontIndexGap(int count) {
this._indexUnsynced = count; _indexUnsynced = count;
} }
int get indexUnsynced => _indexUnsynced; int get indexUnsynced => _indexUnsynced;
@ -127,10 +124,10 @@ class MessageCache extends ChangeNotifier {
MessageInfo? getByContentHash(String contenthash) => cache[cacheByHash[contenthash]]; MessageInfo? getByContentHash(String contenthash) => cache[cacheByHash[contenthash]];
void addNew(String profileOnion, int conversation, int messageID, DateTime timestamp, String senderHandle, String senderImage, bool isAuto, String data, String contenthash) { void addNew(String profileOnion, int conversation, int messageID, DateTime timestamp, String senderHandle, String senderImage, bool isAuto, String data, String contenthash) {
this.cache[messageID] = MessageInfo(MessageMetadata(profileOnion, conversation, messageID, timestamp, senderHandle, senderImage, "", {}, false, false, isAuto, contenthash), data); cache[messageID] = MessageInfo(MessageMetadata(profileOnion, conversation, messageID, timestamp, senderHandle, senderImage, "", {}, false, false, isAuto, contenthash), data);
this.cacheByIndex.insert(0, LocalIndexMessage(messageID)); cacheByIndex.insert(0, LocalIndexMessage(messageID));
if (contenthash != null && contenthash != "") { if (contenthash != "") {
this.cacheByHash[contenthash] = messageID; cacheByHash[contenthash] = messageID;
} }
} }
@ -139,35 +136,35 @@ class MessageCache extends ChangeNotifier {
// this prevents successive ui message build requests from triggering multiple GetMesssage requests to the backend, as the first one locks a block of messages and the rest wait on that // this prevents successive ui message build requests from triggering multiple GetMesssage requests to the backend, as the first one locks a block of messages and the rest wait on that
void lockIndexes(int start, int end) { void lockIndexes(int start, int end) {
for (var i = start; i < end; i++) { for (var i = start; i < end; i++) {
this.cacheByIndex.insert(i, LocalIndexMessage(null, isLoading: true)); cacheByIndex.insert(i, LocalIndexMessage(null, isLoading: true));
// if there are unsynced messages on the index cache it means there are messages at the front, and by the logic in message/ByIndex/get() we will be loading those // if there are unsynced messages on the index cache it means there are messages at the front, and by the logic in message/ByIndex/get() we will be loading those
// there for we can decrement the count as this will be one of them // there for we can decrement the count as this will be one of them
if (this._indexUnsynced > 0) { if (_indexUnsynced > 0) {
this._indexUnsynced--; _indexUnsynced--;
} }
} }
} }
void malformIndexes(int start, int end) { void malformIndexes(int start, int end) {
for (var i = start; i < end; i++) { for (var i = start; i < end; i++) {
this.cacheByIndex[i].failLoad(); cacheByIndex[i].failLoad();
} }
} }
void addIndexed(MessageInfo messageInfo, int index) { void addIndexed(MessageInfo messageInfo, int index) {
this.cache[messageInfo.metadata.messageID] = messageInfo; cache[messageInfo.metadata.messageID] = messageInfo;
if (index < this.cacheByIndex.length) { if (index < cacheByIndex.length) {
this.cacheByIndex[index].finishLoad(messageInfo.metadata.messageID); cacheByIndex[index].finishLoad(messageInfo.metadata.messageID);
} else { } else {
this.cacheByIndex.insert(index, LocalIndexMessage(messageInfo.metadata.messageID)); cacheByIndex.insert(index, LocalIndexMessage(messageInfo.metadata.messageID));
} }
this.cacheByHash[messageInfo.metadata.contenthash] = messageInfo.metadata.messageID; cacheByHash[messageInfo.metadata.contenthash] = messageInfo.metadata.messageID;
} }
void addUnindexed(MessageInfo messageInfo) { void addUnindexed(MessageInfo messageInfo) {
this.cache[messageInfo.metadata.messageID] = messageInfo; cache[messageInfo.metadata.messageID] = messageInfo;
if (messageInfo.metadata.contenthash != "") { if (messageInfo.metadata.contenthash != "") {
this.cacheByHash[messageInfo.metadata.contenthash] = messageInfo.metadata.messageID; cacheByHash[messageInfo.metadata.contenthash] = messageInfo.metadata.messageID;
} }
} }

View File

@ -20,30 +20,30 @@ class FileMessage extends Message {
@override @override
Widget getWidget(BuildContext context, Key key, int index) { Widget getWidget(BuildContext context, Key key, int index) {
return ChangeNotifierProvider.value( return ChangeNotifierProvider.value(
value: this.metadata, value: metadata,
builder: (bcontext, child) { builder: (bcontext, child) {
try { try {
dynamic shareObj = jsonDecode(this.content); dynamic shareObj = jsonDecode(content);
if (shareObj == null) { if (shareObj == null) {
return MessageRow(MalformedBubble(), index); return MessageRow(const MalformedBubble(), index);
} }
String nameSuggestion = shareObj['f'] as String; String nameSuggestion = shareObj['f'] as String;
String rootHash = shareObj['h'] as String; String rootHash = shareObj['h'] as String;
String nonce = shareObj['n'] as String; String nonce = shareObj['n'] as String;
int fileSize = shareObj['s'] as int; int fileSize = shareObj['s'] as int;
String fileKey = rootHash + "." + nonce; String fileKey = "$rootHash.$nonce";
if (!Provider.of<ProfileInfoState>(context, listen: false).downloadKnown(fileKey)) { if (!Provider.of<ProfileInfoState>(context, listen: false).downloadKnown(fileKey)) {
Provider.of<FlwtchState>(context, listen: false).cwtch.CheckDownloadStatus(Provider.of<ProfileInfoState>(context, listen: false).onion, fileKey); Provider.of<FlwtchState>(context, listen: false).cwtch.CheckDownloadStatus(Provider.of<ProfileInfoState>(context, listen: false).onion, fileKey);
} }
if (!validHash(rootHash, nonce)) { if (!validHash(rootHash, nonce)) {
return MessageRow(MalformedBubble(), index); return MessageRow(const MalformedBubble(), index);
} }
return MessageRow(FileBubble(nameSuggestion, rootHash, nonce, fileSize, isAuto: metadata.isAuto), index, key: key); return MessageRow(FileBubble(nameSuggestion, rootHash, nonce, fileSize, isAuto: metadata.isAuto), index, key: key);
} catch (e) { } catch (e) {
return MessageRow(MalformedBubble(), index); return MessageRow(const MalformedBubble(), index);
} }
}); });
} }
@ -51,24 +51,24 @@ class FileMessage extends Message {
@override @override
Widget getPreviewWidget(BuildContext context, {BoxConstraints? constraints}) { Widget getPreviewWidget(BuildContext context, {BoxConstraints? constraints}) {
return ChangeNotifierProvider.value( return ChangeNotifierProvider.value(
value: this.metadata, value: metadata,
builder: (bcontext, child) { builder: (bcontext, child) {
dynamic shareObj = jsonDecode(this.content); dynamic shareObj = jsonDecode(content);
if (shareObj == null) { if (shareObj == null) {
return MalformedBubble(); return const MalformedBubble();
} }
String nameSuggestion = shareObj['f'] as String; String nameSuggestion = shareObj['f'] as String;
String rootHash = shareObj['h'] as String; String rootHash = shareObj['h'] as String;
String nonce = shareObj['n'] as String; String nonce = shareObj['n'] as String;
int fileSize = shareObj['s'] as int; int fileSize = shareObj['s'] as int;
if (!validHash(rootHash, nonce)) { if (!validHash(rootHash, nonce)) {
return MalformedBubble(); return const MalformedBubble();
} }
return Container( return Container(
padding: EdgeInsets.all(1.0), padding: const EdgeInsets.all(1.0),
decoration: BoxDecoration(), decoration: const BoxDecoration(),
clipBehavior: Clip.antiAliasWithSaveLayer, clipBehavior: Clip.antiAliasWithSaveLayer,
constraints: BoxConstraints(minHeight: 50, maxHeight: 50, minWidth: 50, maxWidth: 300), constraints: const BoxConstraints(minHeight: 50, maxHeight: 50, minWidth: 50, maxWidth: 300),
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: FileBubble( child: FileBubble(
nameSuggestion, nameSuggestion,
@ -84,7 +84,7 @@ class FileMessage extends Message {
@override @override
MessageMetadata getMetadata() { MessageMetadata getMetadata() {
return this.metadata; return metadata;
} }
bool validHash(String hash, String nonce) { bool validHash(String hash, String nonce) {

View File

@ -19,28 +19,28 @@ class InviteMessage extends Message {
@override @override
Widget getWidget(BuildContext context, Key key, int index) { Widget getWidget(BuildContext context, Key key, int index) {
return ChangeNotifierProvider.value( return ChangeNotifierProvider.value(
value: this.metadata, value: metadata,
builder: (bcontext, child) { builder: (bcontext, child) {
String inviteTarget; String inviteTarget;
String inviteNick; String inviteNick;
String invite = this.content; String invite = content;
if (this.content.length == TorV3ContactHandleLength) { if (content.length == TorV3ContactHandleLength) {
inviteTarget = this.content; inviteTarget = content;
var targetContact = Provider.of<ProfileInfoState>(context).contactList.findContact(inviteTarget); var targetContact = Provider.of<ProfileInfoState>(context).contactList.findContact(inviteTarget);
inviteNick = targetContact == null ? this.content : targetContact.nickname; inviteNick = targetContact == null ? content : targetContact.nickname;
} else { } else {
var parts = this.content.toString().split("||"); var parts = content.toString().split("||");
if (parts.length == 2) { if (parts.length == 2) {
try { try {
var jsonObj = jsonDecode(utf8.fuse(base64).decode(parts[1].substring(5))); var jsonObj = jsonDecode(utf8.fuse(base64).decode(parts[1].substring(5)));
inviteTarget = jsonObj['GroupID']; inviteTarget = jsonObj['GroupID'];
inviteNick = jsonObj['GroupName']; inviteNick = jsonObj['GroupName'];
} catch (e) { } catch (e) {
return MessageRow(MalformedBubble(), index); return MessageRow(const MalformedBubble(), index);
} }
} else { } else {
return MessageRow(MalformedBubble(), index); return MessageRow(const MalformedBubble(), index);
} }
} }
return MessageRow(InvitationBubble(overlay, inviteTarget, inviteNick, invite), index, key: key); return MessageRow(InvitationBubble(overlay, inviteTarget, inviteNick, invite), index, key: key);
@ -50,27 +50,27 @@ class InviteMessage extends Message {
@override @override
Widget getPreviewWidget(BuildContext context, {BoxConstraints? constraints}) { Widget getPreviewWidget(BuildContext context, {BoxConstraints? constraints}) {
return ChangeNotifierProvider.value( return ChangeNotifierProvider.value(
value: this.metadata, value: metadata,
builder: (bcontext, child) { builder: (bcontext, child) {
String inviteTarget; String inviteTarget;
String inviteNick; String inviteNick;
String invite = this.content; String invite = content;
if (this.content.length == TorV3ContactHandleLength) { if (content.length == TorV3ContactHandleLength) {
inviteTarget = this.content; inviteTarget = content;
var targetContact = Provider.of<ProfileInfoState>(context).contactList.findContact(inviteTarget); var targetContact = Provider.of<ProfileInfoState>(context).contactList.findContact(inviteTarget);
inviteNick = targetContact == null ? this.content : targetContact.nickname; inviteNick = targetContact == null ? content : targetContact.nickname;
} else { } else {
var parts = this.content.toString().split("||"); var parts = content.toString().split("||");
if (parts.length == 2) { if (parts.length == 2) {
try { try {
var jsonObj = jsonDecode(utf8.fuse(base64).decode(parts[1].substring(5))); var jsonObj = jsonDecode(utf8.fuse(base64).decode(parts[1].substring(5)));
inviteTarget = jsonObj['GroupID']; inviteTarget = jsonObj['GroupID'];
inviteNick = jsonObj['GroupName']; inviteNick = jsonObj['GroupName'];
} catch (e) { } catch (e) {
return MalformedBubble(); return const MalformedBubble();
} }
} else { } else {
return MalformedBubble(); return const MalformedBubble();
} }
} }
return InvitationBubble(overlay, inviteTarget, inviteNick, invite); return InvitationBubble(overlay, inviteTarget, inviteNick, invite);
@ -79,6 +79,6 @@ class InviteMessage extends Message {
@override @override
MessageMetadata getMetadata() { MessageMetadata getMetadata() {
return this.metadata; return metadata;
} }
} }

View File

@ -11,23 +11,23 @@ class MalformedMessage extends Message {
@override @override
Widget getWidget(BuildContext context, Key key, int index) { Widget getWidget(BuildContext context, Key key, int index) {
return ChangeNotifierProvider.value( return ChangeNotifierProvider.value(
value: this.metadata, value: metadata,
builder: (context, child) { builder: (context, child) {
return MessageRow(MalformedBubble(), index, key: key); return MessageRow(const MalformedBubble(), index, key: key);
}); });
} }
@override @override
Widget getPreviewWidget(BuildContext context, {BoxConstraints? constraints}) { Widget getPreviewWidget(BuildContext context, {BoxConstraints? constraints}) {
return ChangeNotifierProvider.value( return ChangeNotifierProvider.value(
value: this.metadata, value: metadata,
builder: (bcontext, child) { builder: (bcontext, child) {
return MalformedBubble(); return const MalformedBubble();
}); });
} }
@override @override
MessageMetadata getMetadata() { MessageMetadata getMetadata() {
return this.metadata; return metadata;
} }
} }

View File

@ -1,6 +1,5 @@
import 'dart:convert'; import 'dart:convert';
import 'package:cwtch/config.dart';
import 'package:cwtch/models/message.dart'; import 'package:cwtch/models/message.dart';
import 'package:cwtch/models/messages/malformedmessage.dart'; import 'package:cwtch/models/messages/malformedmessage.dart';
import 'package:cwtch/widgets/malformedbubble.dart'; import 'package:cwtch/widgets/malformedbubble.dart';
@ -11,7 +10,6 @@ import 'package:flutter/widgets.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import '../../settings.dart'; import '../../settings.dart';
import '../../third_party/linkify/flutter_linkify.dart';
class QuotedMessageStructure { class QuotedMessageStructure {
final String quotedHash; final String quotedHash;
@ -32,7 +30,7 @@ class QuotedMessage extends Message {
@override @override
Widget getPreviewWidget(BuildContext context, {BoxConstraints? constraints}) { Widget getPreviewWidget(BuildContext context, {BoxConstraints? constraints}) {
return ChangeNotifierProvider.value( return ChangeNotifierProvider.value(
value: this.metadata, value: metadata,
builder: (bcontext, child) { builder: (bcontext, child) {
try { try {
dynamic message = jsonDecode( dynamic message = jsonDecode(
@ -42,33 +40,33 @@ class QuotedMessage extends Message {
var formatMessages = Provider.of<Settings>(bcontext).isExperimentEnabled(FormattingExperiment); var formatMessages = Provider.of<Settings>(bcontext).isExperimentEnabled(FormattingExperiment);
return compileMessageContentWidget(context, constraints ?? BoxConstraints.loose(MediaQuery.sizeOf(context)), false, content, FocusNode(), formatMessages, false); return compileMessageContentWidget(context, constraints ?? BoxConstraints.loose(MediaQuery.sizeOf(context)), false, content, FocusNode(), formatMessages, false);
} catch (e) { } catch (e) {
return MalformedBubble(); return const MalformedBubble();
} }
}); });
} }
@override @override
MessageMetadata getMetadata() { MessageMetadata getMetadata() {
return this.metadata; return metadata;
} }
@override @override
Widget getWidget(BuildContext context, Key key, int index) { Widget getWidget(BuildContext context, Key key, int index) {
try { try {
dynamic message = jsonDecode(this.content); dynamic message = jsonDecode(content);
if (message["body"] == null || message["quotedHash"] == null) { if (message["body"] == null || message["quotedHash"] == null) {
return MalformedMessage(this.metadata).getWidget(context, key, index); return MalformedMessage(metadata).getWidget(context, key, index);
} }
return ChangeNotifierProvider.value( return ChangeNotifierProvider.value(
value: this.metadata, value: metadata,
builder: (bcontext, child) { builder: (bcontext, child) {
return MessageRow(QuotedMessageBubble(message["body"], messageHandler(bcontext, metadata.profileOnion, metadata.conversationIdentifier, ByContentHash(message["quotedHash"]))), index, return MessageRow(QuotedMessageBubble(message["body"], messageHandler(bcontext, metadata.profileOnion, metadata.conversationIdentifier, ByContentHash(message["quotedHash"]))), index,
key: key); key: key);
}); });
} catch (e) { } catch (e) {
return MalformedMessage(this.metadata).getWidget(context, key, index); return MalformedMessage(metadata).getWidget(context, key, index);
} }
} }
} }

View File

@ -1,17 +1,11 @@
import 'dart:math';
import 'package:cwtch/models/message.dart'; import 'package:cwtch/models/message.dart';
import 'package:cwtch/models/messages/malformedmessage.dart';
import 'package:cwtch/widgets/malformedbubble.dart';
import 'package:cwtch/widgets/messagebubble.dart'; import 'package:cwtch/widgets/messagebubble.dart';
import 'package:cwtch/widgets/messageloadingbubble.dart';
import 'package:cwtch/widgets/messagerow.dart'; import 'package:cwtch/widgets/messagerow.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import '../../settings.dart'; import '../../settings.dart';
import '../../third_party/linkify/flutter_linkify.dart';
import '../../widgets/messageBubbleWidgetHelpers.dart'; import '../../widgets/messageBubbleWidgetHelpers.dart';
class TextMessage extends Message { class TextMessage extends Message {
@ -23,26 +17,25 @@ class TextMessage extends Message {
@override @override
Widget getPreviewWidget(BuildContext context, {BoxConstraints? constraints}) { Widget getPreviewWidget(BuildContext context, {BoxConstraints? constraints}) {
return ChangeNotifierProvider.value( return ChangeNotifierProvider.value(
value: this.metadata, value: metadata,
builder: (bcontext, child) { builder: (bcontext, child) {
var formatMessages = Provider.of<Settings>(bcontext).isExperimentEnabled(FormattingExperiment); var formatMessages = Provider.of<Settings>(bcontext).isExperimentEnabled(FormattingExperiment);
return compileMessageContentWidget(context, constraints ?? BoxConstraints.loose(MediaQuery.sizeOf(context)), false, content, FocusNode(), formatMessages, false); return compileMessageContentWidget(context, constraints ?? BoxConstraints.loose(MediaQuery.sizeOf(context)), false, content, FocusNode(), formatMessages, false);
;
}); });
} }
@override @override
MessageMetadata getMetadata() { MessageMetadata getMetadata() {
return this.metadata; return metadata;
} }
@override @override
Widget getWidget(BuildContext context, Key key, int index) { Widget getWidget(BuildContext context, Key key, int index) {
return ChangeNotifierProvider.value( return ChangeNotifierProvider.value(
value: this.metadata, value: metadata,
builder: (bcontext, child) { builder: (bcontext, child) {
return MessageRow( return MessageRow(
MessageBubble(this.content), MessageBubble(content),
index, index,
key: key, key: key,
); );

View File

@ -14,22 +14,20 @@ import '../views/contactsview.dart';
import 'contact.dart'; import 'contact.dart';
import 'contactlist.dart'; import 'contactlist.dart';
import 'filedownloadprogress.dart'; import 'filedownloadprogress.dart';
import 'message.dart';
import 'messagecache.dart';
import 'profileservers.dart'; import 'profileservers.dart';
class ProfileInfoState extends ChangeNotifier { class ProfileInfoState extends ChangeNotifier {
ProfileServerListState _servers = ProfileServerListState(); final ProfileServerListState _servers = ProfileServerListState();
ContactListState _contacts = ContactListState(); final ContactListState _contacts = ContactListState();
final String onion; final String onion;
String _nickname = ""; String _nickname = "";
String _imagePath = ""; String _imagePath = "";
String _defaultImagePath = ""; String _defaultImagePath = "";
int _unreadMessages = 0; int _unreadMessages = 0;
bool _online = false; bool _online = false;
Map<String, FileDownloadProgress> _downloads = Map<String, FileDownloadProgress>(); final Map<String, FileDownloadProgress> _downloads = <String, FileDownloadProgress>{};
Map<String, int> _downloadTriggers = Map<String, int>(); final Map<String, int> _downloadTriggers = <String, int>{};
ItemScrollController contactListScrollController = new ItemScrollController(); ItemScrollController contactListScrollController = ItemScrollController();
// assume profiles are encrypted...this will be set to false // assume profiles are encrypted...this will be set to false
// in the constructor if the profile is encrypted with the defacto password. // in the constructor if the profile is encrypted with the defacto password.
bool _encrypted = true; bool _encrypted = true;
@ -53,30 +51,30 @@ class ProfileInfoState extends ChangeNotifier {
appearOffline = false, appearOffline = false,
String, String,
}) { }) {
this._nickname = nickname; _nickname = nickname;
this._imagePath = imagePath; _imagePath = imagePath;
this._defaultImagePath = defaultImagePath; _defaultImagePath = defaultImagePath;
this._unreadMessages = unreadMessages; _unreadMessages = unreadMessages;
this._online = online; _online = online;
this._enabled = _enabled; _enabled = _enabled;
this._autostart = autostart; _autostart = autostart;
if (autostart) { if (autostart) {
this._enabled = true; _enabled = true;
} }
this._appearOffline = appearOffline; _appearOffline = appearOffline;
this._appearOfflineAtStartup = appearOffline; _appearOfflineAtStartup = appearOffline;
this._encrypted = encrypted; _encrypted = encrypted;
_contacts.connectServers(this._servers); _contacts.connectServers(_servers);
if (contactsJson != null && contactsJson != "" && contactsJson != "null") { if (contactsJson != null && contactsJson != "" && contactsJson != "null") {
this.replaceServers(serversJson); replaceServers(serversJson);
List<dynamic> contacts = jsonDecode(contactsJson); List<dynamic> contacts = jsonDecode(contactsJson);
this._contacts.addAll(contacts.map((contact) { _contacts.addAll(contacts.map((contact) {
this._unreadMessages += contact["numUnread"] as int; _unreadMessages += contact["numUnread"] as int;
return ContactInfoState(this.onion, contact["identifier"], contact["onion"], return ContactInfoState(onion, contact["identifier"], contact["onion"],
nickname: contact["name"], nickname: contact["name"],
localNickname: contact["attributes"]?["local.profile.name"] ?? "", // contact may not have a local name localNickname: contact["attributes"]?["local.profile.name"] ?? "", // contact may not have a local name
status: contact["status"], status: contact["status"],
@ -96,8 +94,8 @@ class ProfileInfoState extends ChangeNotifier {
})); }));
// dummy set to invoke sort-on-load // dummy set to invoke sort-on-load
if (this._contacts.num > 0) { if (_contacts.num > 0) {
this._contacts.updateLastMessageReceivedTime(this._contacts.contacts.first.identifier, this._contacts.contacts.first.lastMessageReceivedTime); _contacts.updateLastMessageReceivedTime(_contacts.contacts.first.identifier, _contacts.contacts.first.lastMessageReceivedTime);
} }
} }
} }
@ -108,7 +106,7 @@ class ProfileInfoState extends ChangeNotifier {
void newSearch(String activeSearchID) { void newSearch(String activeSearchID) {
this.activeSearchID = activeSearchID; this.activeSearchID = activeSearchID;
this.activeSearchResults.clear(); activeSearchResults.clear();
notifyListeners(); notifyListeners();
} }
@ -123,18 +121,18 @@ class ProfileInfoState extends ChangeNotifier {
void replaceServers(String serversJson) { void replaceServers(String serversJson) {
if (serversJson != "" && serversJson != "null") { if (serversJson != "" && serversJson != "null") {
List<dynamic> servers = jsonDecode(serversJson); List<dynamic> servers = jsonDecode(serversJson);
this._servers.replace(servers.map((server) { _servers.replace(servers.map((server) {
// TODO Keys... // TODO Keys...
var preSyncStartTime = DateTime.tryParse(server["syncProgress"]["startTime"]); var preSyncStartTime = DateTime.tryParse(server["syncProgress"]["startTime"]);
var lastMessageTime = DateTime.tryParse(server["syncProgress"]["lastMessageTime"]); var lastMessageTime = DateTime.tryParse(server["syncProgress"]["lastMessageTime"]);
return RemoteServerInfoState(server["onion"], server["identifier"], server["description"], server["status"], lastPreSyncMessageTime: preSyncStartTime, mostRecentMessageTime: lastMessageTime); return RemoteServerInfoState(server["onion"], server["identifier"], server["description"], server["status"], lastPreSyncMessageTime: preSyncStartTime, mostRecentMessageTime: lastMessageTime);
})); }));
this._contacts.contacts.forEach((contact) { for (var contact in _contacts.contacts) {
if (contact.isGroup) { if (contact.isGroup) {
_servers.addGroup(contact); _servers.addGroup(contact);
} }
}); }
notifyListeners(); notifyListeners();
} }
@ -142,110 +140,105 @@ class ProfileInfoState extends ChangeNotifier {
// //
void updateServerStatusCache(String server, String status) { void updateServerStatusCache(String server, String status) {
this._servers.updateServerState(server, status); _servers.updateServerState(server, status);
notifyListeners(); notifyListeners();
} }
// Getters and Setters for Online Status // Getters and Setters for Online Status
bool get isOnline => this._online; bool get isOnline => _online;
set isOnline(bool newValue) { set isOnline(bool newValue) {
this._online = newValue; _online = newValue;
notifyListeners(); notifyListeners();
} }
// Check encrypted status for profile info screen // Check encrypted status for profile info screen
bool get isEncrypted => this._encrypted; bool get isEncrypted => _encrypted;
set isEncrypted(bool newValue) { set isEncrypted(bool newValue) {
this._encrypted = newValue; _encrypted = newValue;
notifyListeners(); notifyListeners();
} }
String get nickname => this._nickname; String get nickname => _nickname;
set nickname(String newValue) { set nickname(String newValue) {
this._nickname = newValue; _nickname = newValue;
notifyListeners(); notifyListeners();
} }
String get imagePath => this._imagePath; String get imagePath => _imagePath;
set imagePath(String newVal) { set imagePath(String newVal) {
this._imagePath = newVal; _imagePath = newVal;
notifyListeners(); notifyListeners();
} }
bool get enabled => this._enabled; bool get enabled => _enabled;
set enabled(bool newVal) { set enabled(bool newVal) {
this._enabled = newVal; _enabled = newVal;
notifyListeners(); notifyListeners();
} }
bool get autostart => this._autostart; bool get autostart => _autostart;
set autostart(bool newVal) { set autostart(bool newVal) {
this._autostart = newVal; _autostart = newVal;
notifyListeners(); notifyListeners();
} }
bool get appearOfflineAtStartup => this._appearOfflineAtStartup; bool get appearOfflineAtStartup => _appearOfflineAtStartup;
set appearOfflineAtStartup(bool newVal) { set appearOfflineAtStartup(bool newVal) {
this._appearOfflineAtStartup = newVal; _appearOfflineAtStartup = newVal;
notifyListeners(); notifyListeners();
} }
bool get appearOffline => this._appearOffline; bool get appearOffline => _appearOffline;
set appearOffline(bool newVal) { set appearOffline(bool newVal) {
this._appearOffline = newVal; _appearOffline = newVal;
notifyListeners(); notifyListeners();
} }
String get defaultImagePath => this._defaultImagePath; String get defaultImagePath => _defaultImagePath;
set defaultImagePath(String newVal) { set defaultImagePath(String newVal) {
this._defaultImagePath = newVal; _defaultImagePath = newVal;
notifyListeners(); notifyListeners();
} }
int get unreadMessages => this._unreadMessages; int get unreadMessages => _unreadMessages;
set unreadMessages(int newVal) { set unreadMessages(int newVal) {
this._unreadMessages = newVal; _unreadMessages = newVal;
notifyListeners(); notifyListeners();
} }
void recountUnread() { void recountUnread() {
this._unreadMessages = _contacts.contacts.fold(0, (i, c) => i + c.unreadMessages); _unreadMessages = _contacts.contacts.fold(0, (i, c) => i + c.unreadMessages);
} }
// Remove a contact from a list. Currently only used when rejecting a group invitation. // Remove a contact from a list. Currently only used when rejecting a group invitation.
// Eventually will also be used for other removals. // Eventually will also be used for other removals.
void removeContact(String handle) { void removeContact(String handle) {
this.contactList.removeContactByHandle(handle); contactList.removeContactByHandle(handle);
notifyListeners(); notifyListeners();
} }
ContactListState get contactList => this._contacts; ContactListState get contactList => _contacts;
ProfileServerListState get serverList => this._servers; ProfileServerListState get serverList => _servers;
@override
void dispose() {
super.dispose();
}
void updateFrom(String onion, String name, String picture, String contactsJson, String serverJson, bool online) { void updateFrom(String onion, String name, String picture, String contactsJson, String serverJson, bool online) {
this._nickname = name; _nickname = name;
this._imagePath = picture; _imagePath = picture;
this._online = online; _online = online;
this._unreadMessages = 0; _unreadMessages = 0;
this.replaceServers(serverJson); replaceServers(serverJson);
if (contactsJson != null && contactsJson != "" && contactsJson != "null") { if (contactsJson != "" && contactsJson != "null") {
List<dynamic> contacts = jsonDecode(contactsJson); List<dynamic> contacts = jsonDecode(contactsJson);
contacts.forEach((contact) { for (var contact in contacts) {
var profileContact = this._contacts.getContact(contact["identifier"]); var profileContact = _contacts.getContact(contact["identifier"]);
this._unreadMessages += contact["numUnread"] as int; _unreadMessages += contact["numUnread"] as int;
if (profileContact != null) { if (profileContact != null) {
profileContact.status = contact["status"]; profileContact.status = contact["status"];
@ -271,28 +264,28 @@ class ProfileInfoState extends ChangeNotifier {
profileContact.unreadMessages = contact["numUnread"]; profileContact.unreadMessages = contact["numUnread"];
profileContact.lastMessageReceivedTime = DateTime.fromMillisecondsSinceEpoch(1000 * int.parse(contact["lastMsgTime"])); profileContact.lastMessageReceivedTime = DateTime.fromMillisecondsSinceEpoch(1000 * int.parse(contact["lastMsgTime"]));
} else { } else {
this._contacts.add(ContactInfoState( _contacts.add(ContactInfoState(
this.onion, this.onion,
contact["identifier"], contact["identifier"],
contact["onion"], contact["onion"],
nickname: contact["name"], nickname: contact["name"],
defaultImagePath: contact["defaultPicture"], defaultImagePath: contact["defaultPicture"],
status: contact["status"], status: contact["status"],
imagePath: contact["picture"], imagePath: contact["picture"],
accepted: contact["accepted"], accepted: contact["accepted"],
blocked: contact["blocked"], blocked: contact["blocked"],
savePeerHistory: contact["saveConversationHistory"], savePeerHistory: contact["saveConversationHistory"],
numMessages: contact["numMessages"], numMessages: contact["numMessages"],
numUnread: contact["numUnread"], numUnread: contact["numUnread"],
isGroup: contact["isGroup"], isGroup: contact["isGroup"],
server: contact["groupServer"], server: contact["groupServer"],
lastMessageTime: DateTime.fromMillisecondsSinceEpoch(1000 * int.parse(contact["lastMsgTime"])), lastMessageTime: DateTime.fromMillisecondsSinceEpoch(1000 * int.parse(contact["lastMsgTime"])),
notificationPolicy: contact["notificationPolicy"] ?? "ConversationNotificationPolicy.Default", notificationPolicy: contact["notificationPolicy"] ?? "ConversationNotificationPolicy.Default",
)); ));
} }
}); }
} }
this._contacts.resort(); _contacts.resort();
} }
void newMessage( void newMessage(
@ -306,33 +299,33 @@ class ProfileInfoState extends ChangeNotifier {
} }
void downloadInit(String fileKey, int numChunks) { void downloadInit(String fileKey, int numChunks) {
this._downloads[fileKey] = FileDownloadProgress(numChunks, DateTime.now()); _downloads[fileKey] = FileDownloadProgress(numChunks, DateTime.now());
notifyListeners(); notifyListeners();
} }
void downloadUpdate(String fileKey, int progress, int numChunks) { void downloadUpdate(String fileKey, int progress, int numChunks) {
if (!downloadActive(fileKey)) { if (!downloadActive(fileKey)) {
this._downloads[fileKey] = FileDownloadProgress(numChunks, DateTime.now()); _downloads[fileKey] = FileDownloadProgress(numChunks, DateTime.now());
if (progress < 0) { if (progress < 0) {
this._downloads[fileKey]!.interrupted = true; _downloads[fileKey]!.interrupted = true;
} }
} else { } else {
if (this._downloads[fileKey]!.interrupted) { if (_downloads[fileKey]!.interrupted) {
this._downloads[fileKey]!.interrupted = false; _downloads[fileKey]!.interrupted = false;
} }
this._downloads[fileKey]!.chunksDownloaded = progress; _downloads[fileKey]!.chunksDownloaded = progress;
this._downloads[fileKey]!.chunksTotal = numChunks; _downloads[fileKey]!.chunksTotal = numChunks;
this._downloads[fileKey]!.markUpdate(); _downloads[fileKey]!.markUpdate();
} }
notifyListeners(); notifyListeners();
} }
void downloadMarkManifest(String fileKey) { void downloadMarkManifest(String fileKey) {
if (!downloadActive(fileKey)) { if (!downloadActive(fileKey)) {
this._downloads[fileKey] = FileDownloadProgress(1, DateTime.now()); _downloads[fileKey] = FileDownloadProgress(1, DateTime.now());
} }
this._downloads[fileKey]!.gotManifest = true; _downloads[fileKey]!.gotManifest = true;
this._downloads[fileKey]!.markUpdate(); _downloads[fileKey]!.markUpdate();
notifyListeners(); notifyListeners();
} }
@ -341,46 +334,46 @@ class ProfileInfoState extends ChangeNotifier {
// happens as a result of a CheckDownloadStatus call, // happens as a result of a CheckDownloadStatus call,
// invoked from a historical (timeline) download message // invoked from a historical (timeline) download message
// so setting numChunks correctly shouldn't matter // so setting numChunks correctly shouldn't matter
this.downloadInit(fileKey, 1); downloadInit(fileKey, 1);
} }
// Update the contact with a custom profile image if we are // Update the contact with a custom profile image if we are
// waiting for one... // waiting for one...
if (this._downloadTriggers.containsKey(fileKey)) { if (_downloadTriggers.containsKey(fileKey)) {
int identifier = this._downloadTriggers[fileKey]!; int identifier = _downloadTriggers[fileKey]!;
this.contactList.getContact(identifier)!.imagePath = finalPath; contactList.getContact(identifier)!.imagePath = finalPath;
notifyListeners(); notifyListeners();
} }
// only update if different // only update if different
if (!this._downloads[fileKey]!.complete) { if (!_downloads[fileKey]!.complete) {
this._downloads[fileKey]!.timeEnd = DateTime.now(); _downloads[fileKey]!.timeEnd = DateTime.now();
this._downloads[fileKey]!.downloadedTo = finalPath; _downloads[fileKey]!.downloadedTo = finalPath;
this._downloads[fileKey]!.complete = true; _downloads[fileKey]!.complete = true;
this._downloads[fileKey]!.markUpdate(); _downloads[fileKey]!.markUpdate();
notifyListeners(); notifyListeners();
} }
} }
bool downloadKnown(String fileKey) { bool downloadKnown(String fileKey) {
return this._downloads.containsKey(fileKey); return _downloads.containsKey(fileKey);
} }
bool downloadActive(String fileKey) { bool downloadActive(String fileKey) {
return this._downloads.containsKey(fileKey) && !this._downloads[fileKey]!.interrupted; return _downloads.containsKey(fileKey) && !_downloads[fileKey]!.interrupted;
} }
bool downloadGotManifest(String fileKey) { bool downloadGotManifest(String fileKey) {
return this._downloads.containsKey(fileKey) && this._downloads[fileKey]!.gotManifest; return _downloads.containsKey(fileKey) && _downloads[fileKey]!.gotManifest;
} }
bool downloadComplete(String fileKey) { bool downloadComplete(String fileKey) {
return this._downloads.containsKey(fileKey) && this._downloads[fileKey]!.complete; return _downloads.containsKey(fileKey) && _downloads[fileKey]!.complete;
} }
bool downloadInterrupted(String fileKey) { bool downloadInterrupted(String fileKey) {
if (this._downloads.containsKey(fileKey)) { if (_downloads.containsKey(fileKey)) {
if (this._downloads[fileKey]!.interrupted) { if (_downloads[fileKey]!.interrupted) {
return true; return true;
} }
} }
@ -388,22 +381,22 @@ class ProfileInfoState extends ChangeNotifier {
} }
void downloadMarkResumed(String fileKey) { void downloadMarkResumed(String fileKey) {
if (this._downloads.containsKey(fileKey)) { if (_downloads.containsKey(fileKey)) {
this._downloads[fileKey]!.interrupted = false; _downloads[fileKey]!.interrupted = false;
this._downloads[fileKey]!.requested = DateTime.now(); _downloads[fileKey]!.requested = DateTime.now();
this._downloads[fileKey]!.markUpdate(); _downloads[fileKey]!.markUpdate();
notifyListeners(); notifyListeners();
} }
} }
double downloadProgress(String fileKey) { double downloadProgress(String fileKey) {
return this._downloads.containsKey(fileKey) ? this._downloads[fileKey]!.progress() : 0.0; return _downloads.containsKey(fileKey) ? _downloads[fileKey]!.progress() : 0.0;
} }
// used for loading interrupted download info; use downloadMarkFinished for successful downloads // used for loading interrupted download info; use downloadMarkFinished for successful downloads
void downloadSetPath(String fileKey, String path) { void downloadSetPath(String fileKey, String path) {
if (this._downloads.containsKey(fileKey)) { if (_downloads.containsKey(fileKey)) {
this._downloads[fileKey]!.downloadedTo = path; _downloads[fileKey]!.downloadedTo = path;
notifyListeners(); notifyListeners();
} }
} }
@ -413,28 +406,28 @@ class ProfileInfoState extends ChangeNotifier {
// we may trigger this event for auto-downloaded receivers too, // we may trigger this event for auto-downloaded receivers too,
// as such we don't assume anything else about the file...other than that // as such we don't assume anything else about the file...other than that
// it exists. // it exists.
if (!this._downloads.containsKey(fileKey)) { if (!_downloads.containsKey(fileKey)) {
// this will be overwritten by download update if the file is being downloaded // this will be overwritten by download update if the file is being downloaded
this._downloads[fileKey] = FileDownloadProgress(1, DateTime.now()); _downloads[fileKey] = FileDownloadProgress(1, DateTime.now());
} }
this._downloads[fileKey]!.downloadedTo = path; _downloads[fileKey]!.downloadedTo = path;
notifyListeners(); notifyListeners();
} }
String? downloadFinalPath(String fileKey) { String? downloadFinalPath(String fileKey) {
return this._downloads.containsKey(fileKey) ? this._downloads[fileKey]!.downloadedTo : null; return _downloads.containsKey(fileKey) ? _downloads[fileKey]!.downloadedTo : null;
} }
String downloadSpeed(String fileKey) { String downloadSpeed(String fileKey) {
if (!downloadActive(fileKey) || this._downloads[fileKey]!.chunksDownloaded == 0) { if (!downloadActive(fileKey) || _downloads[fileKey]!.chunksDownloaded == 0) {
return "0 B/s"; return "0 B/s";
} }
var bytes = this._downloads[fileKey]!.chunksDownloaded * 4096; var bytes = _downloads[fileKey]!.chunksDownloaded * 4096;
var seconds = (this._downloads[fileKey]!.timeEnd ?? DateTime.now()).difference(this._downloads[fileKey]!.timeStart!).inSeconds; var seconds = (_downloads[fileKey]!.timeEnd ?? DateTime.now()).difference(_downloads[fileKey]!.timeStart!).inSeconds;
if (seconds == 0) { if (seconds == 0) {
return "0 B/s"; return "0 B/s";
} }
return prettyBytes((bytes / seconds).round()) + "/s"; return "${prettyBytes((bytes / seconds).round())}/s";
} }
void waitForDownloadComplete(int identifier, String fileKey) { void waitForDownloadComplete(int identifier, String fileKey) {
@ -447,14 +440,14 @@ class ProfileInfoState extends ChangeNotifier {
} }
void downloadReset(String fileKey) { void downloadReset(String fileKey) {
this._downloads.remove(fileKey); _downloads.remove(fileKey);
notifyListeners(); notifyListeners();
} }
// Profile Attributes. Can be set in Profile Edit View... // Profile Attributes. Can be set in Profile Edit View...
List<String?> attributes = [null, null, null]; List<String?> attributes = [null, null, null];
void setAttribute(int i, String? value) { void setAttribute(int i, String? value) {
this.attributes[i] = value; attributes[i] = value;
notifyListeners(); notifyListeners();
} }
@ -477,7 +470,7 @@ class ProfileInfoState extends ChangeNotifier {
} }
Color getBorderColor(OpaqueThemeType theme) { Color getBorderColor(OpaqueThemeType theme) {
switch (this.availabilityStatus) { switch (availabilityStatus) {
case ProfileStatusMenu.available: case ProfileStatusMenu.available:
return theme.portraitOnlineBorderColor; return theme.portraitOnlineBorderColor;
case ProfileStatusMenu.away: case ProfileStatusMenu.away:
@ -494,13 +487,13 @@ class ProfileInfoState extends ChangeNotifier {
// FIXME: Cwtch should be sending these events prior to shutting down the engine... // FIXME: Cwtch should be sending these events prior to shutting down the engine...
void deactivatePeerEngine(BuildContext context) { void deactivatePeerEngine(BuildContext context) {
Provider.of<FlwtchState>(context, listen: false).cwtch.DeactivatePeerEngine(onion); Provider.of<FlwtchState>(context, listen: false).cwtch.DeactivatePeerEngine(onion);
this.contactList.contacts.forEach((element) { for (var element in contactList.contacts) {
element.status = "Disconnected"; element.status = "Disconnected";
// reset retry time to allow for instant reconnection... // reset retry time to allow for instant reconnection...
element.lastRetryTime = element.loaded; element.lastRetryTime = element.loaded;
}); }
this.serverList.servers.forEach((element) { for (var element in serverList.servers) {
element.status = "Disconnected"; element.status = "Disconnected";
}); }
} }
} }

View File

@ -1,13 +1,10 @@
import 'dart:ui';
import 'package:cwtch/config.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'profile.dart'; import 'profile.dart';
class ProfileListState extends ChangeNotifier { class ProfileListState extends ChangeNotifier {
List<ProfileInfoState> _profiles = []; final List<ProfileInfoState> _profiles = [];
int get num => _profiles.length; int get num => _profiles.length;
void add(String onion, String name, String picture, String defaultPicture, String contactsJson, String serverJson, bool online, bool autostart, bool encrypted, bool appearOffline) { void add(String onion, String name, String picture, String defaultPicture, String contactsJson, String serverJson, bool online, bool autostart, bool encrypted, bool appearOffline) {

View File

@ -4,7 +4,7 @@ import 'package:flutter/material.dart';
import 'contact.dart'; import 'contact.dart';
class ProfileServerListState extends ChangeNotifier { class ProfileServerListState extends ChangeNotifier {
List<RemoteServerInfoState> _servers = []; final List<RemoteServerInfoState> _servers = [];
void replace(Iterable<RemoteServerInfoState> newServers) { void replace(Iterable<RemoteServerInfoState> newServers) {
_servers.clear(); _servers.clear();

View File

@ -1,5 +1,3 @@
import 'dart:io';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.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';
@ -10,7 +8,7 @@ String redactedNick(BuildContext context, String handle, String nick) {
var settings = Provider.of<Settings>(context, listen: false); var settings = Provider.of<Settings>(context, listen: false);
if (settings.streamerMode) { if (settings.streamerMode) {
if (handle == nick) { if (handle == nick) {
return handle.substring(0, 7) + "..."; return "${handle.substring(0, 7)}...";
} }
} }
return nick; return nick;

View File

@ -1,4 +1,3 @@
import 'package:cwtch/config.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'contact.dart'; import 'contact.dart';
@ -11,17 +10,17 @@ class RemoteServerInfoState extends ChangeNotifier {
List<ContactInfoState> _groups = []; List<ContactInfoState> _groups = [];
double syncProgress = 0; double syncProgress = 0;
DateTime lastPreSyncMessagTime = new DateTime(2020); DateTime lastPreSyncMessagTime = DateTime(2020);
RemoteServerInfoState(this.onion, this.identifier, this.description, this._status, {lastPreSyncMessageTime, mostRecentMessageTime}) { RemoteServerInfoState(this.onion, this.identifier, this.description, this._status, {lastPreSyncMessageTime, mostRecentMessageTime}) {
if (_status == "Authenticated" || _status == "Synced") { if (_status == "Authenticated" || _status == "Synced") {
this.lastPreSyncMessagTime = lastPreSyncMessageTime; lastPreSyncMessagTime = lastPreSyncMessageTime;
updateSyncProgressFor(mostRecentMessageTime); updateSyncProgressFor(mostRecentMessageTime);
} }
} }
void updateDescription(String newDescription) { void updateDescription(String newDescription) {
this.description = newDescription; description = newDescription;
notifyListeners(); notifyListeners();
} }
@ -39,11 +38,11 @@ class RemoteServerInfoState extends ChangeNotifier {
_status = newStatus; _status = newStatus;
if (status == "Authenticated") { if (status == "Authenticated") {
// syncing, set lastPreSyncMessageTime // syncing, set lastPreSyncMessageTime
_groups.forEach((g) { for (var g in _groups) {
if (g.lastMessageReceivedTime.isAfter(lastPreSyncMessagTime)) { if (g.lastMessageReceivedTime.isAfter(lastPreSyncMessagTime)) {
lastPreSyncMessagTime = g.lastMessageReceivedTime; lastPreSyncMessagTime = g.lastMessageReceivedTime;
} }
}); }
} }
notifyListeners(); notifyListeners();
} }
@ -57,7 +56,7 @@ class RemoteServerInfoState extends ChangeNotifier {
// ! is Negative cus all the duration's we're calculating incidently are negative // ! is Negative cus all the duration's we're calculating incidently are negative
// this message is from before we think we should be syncing with the server // this message is from before we think we should be syncing with the server
// Can be because of a new server or a full resync, either way, use this (oldest message) as our lastPreSyncMessageTime // Can be because of a new server or a full resync, either way, use this (oldest message) as our lastPreSyncMessageTime
this.lastPreSyncMessagTime = point; lastPreSyncMessagTime = point;
pointFromStart = lastPreSyncMessagTime.toUtc().difference(point.toUtc()); pointFromStart = lastPreSyncMessagTime.toUtc().difference(point.toUtc());
} }
syncProgress = pointFromStart.inSeconds / range.inSeconds; syncProgress = pointFromStart.inSeconds / range.inSeconds;

View File

@ -1,7 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class ServerListState extends ChangeNotifier { class ServerListState extends ChangeNotifier {
List<ServerInfoState> _servers = []; final List<ServerInfoState> _servers = [];
void replace(Iterable<ServerInfoState> newServers) { void replace(Iterable<ServerInfoState> newServers) {
_servers.clear(); _servers.clear();

View File

@ -3,13 +3,8 @@ import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'package:cwtch/main.dart'; import 'package:cwtch/main.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:win_toast/win_toast.dart'; import 'package:win_toast/win_toast.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:flutter_local_notifications_linux/flutter_local_notifications_linux.dart';
import 'package:flutter_local_notifications_linux/src/model/hint.dart';
import 'package:flutter_local_notifications_linux/src/model/icon.dart';
import 'package:path/path.dart' as path; import 'package:path/path.dart' as path;
@ -33,8 +28,7 @@ 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(this.notificationSelectConvo) {
this.notificationSelectConvo = notificationSelectConvo;
scheduleMicrotask(() async { scheduleMicrotask(() async {
initialized = await WinToast.instance().initialize(appName: 'cwtch', productName: 'Cwtch', companyName: 'Open Privacy Research Society'); initialized = await WinToast.instance().initialize(appName: 'cwtch', productName: 'Cwtch', companyName: 'Open Privacy Research Society');
/* /*
@ -61,6 +55,7 @@ class WindowsNotificationManager implements NotificationsManager {
}); });
} }
@override
Future<void> notify(String message, String profile, int conversationId) async { Future<void> notify(String message, String profile, int conversationId) async {
if (initialized && !globalAppState.focus) { if (initialized && !globalAppState.focus) {
if (!active) { if (!active) {
@ -137,7 +132,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"] ?? "") + "/.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"); var rootStat = FileStat.stat("/usr/share/cwtch/data/flutter_assets");
if ((await devStat).type == FileSystemEntityType.directory) { if ((await devStat).type == FileSystemEntityType.directory) {
@ -145,15 +140,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"] ?? "") + "/.local/share/cwtch/data/flutter_assets/"; return "${Platform.environment["HOME"] ?? ""}/.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(Future<void> Function(String, int) notificationSelectConvo) { NixNotificationManager(this.notificationSelectConvo) {
this.notificationSelectConvo = notificationSelectConvo;
flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
scheduleMicrotask(() async { scheduleMicrotask(() async {
@ -168,7 +162,7 @@ class NixNotificationManager implements NotificationsManager {
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 = final InitializationSettings initializationSettings =
InitializationSettings(android: null, iOS: null, macOS: DarwinInitializationSettings(defaultPresentSound: false), linux: initializationSettingsLinux); InitializationSettings(android: null, iOS: null, macOS: const DarwinInitializationSettings(defaultPresentSound: false), linux: initializationSettingsLinux);
flutterLocalNotificationsPlugin.resolvePlatformSpecificImplementation<MacOSFlutterLocalNotificationsPlugin>()?.requestPermissions( flutterLocalNotificationsPlugin.resolvePlatformSpecificImplementation<MacOSFlutterLocalNotificationsPlugin>()?.requestPermissions(
alert: true, alert: true,
@ -182,6 +176,7 @@ class NixNotificationManager implements NotificationsManager {
}); });
} }
@override
Future<void> notify(String message, String profile, int conversationId) async { Future<void> notify(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

View File

@ -1,8 +1,6 @@
import 'dart:collection'; import 'dart:collection';
import 'dart:ui';
import 'dart:core'; import 'dart:core';
import 'package:cwtch/themes/cwtch.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:package_info_plus/package_info_plus.dart'; import 'package:package_info_plus/package_info_plus.dart';
@ -64,7 +62,7 @@ class Settings extends ChangeNotifier {
String _customTorConfig = ""; String _customTorConfig = "";
int _socksPort = -1; int _socksPort = -1;
int _controlPort = -1; int _controlPort = -1;
String _customTorAuth = ""; final String _customTorAuth = "";
bool _useTorCache = false; bool _useTorCache = false;
String _torCacheDir = ""; String _torCacheDir = "";
bool _useSemanticDebugger = false; bool _useSemanticDebugger = false;
@ -77,12 +75,12 @@ class Settings extends ChangeNotifier {
bool get profileMode => _profileMode; bool get profileMode => _profileMode;
set profileMode(bool newval) { set profileMode(bool newval) {
this._profileMode = newval; _profileMode = newval;
notifyListeners(); notifyListeners();
} }
set useSemanticDebugger(bool newval) { set useSemanticDebugger(bool newval) {
this._useSemanticDebugger = newval; _useSemanticDebugger = newval;
notifyListeners(); notifyListeners();
} }
@ -107,20 +105,20 @@ class Settings extends ChangeNotifier {
/// isExperimentEnabled can be used to safely check whether a particular /// isExperimentEnabled can be used to safely check whether a particular
/// experiment is enabled /// experiment is enabled
bool isExperimentEnabled(String experiment) { bool isExperimentEnabled(String experiment) {
if (this.experimentsEnabled) { if (experimentsEnabled) {
if (this.experiments.containsKey(experiment)) { if (experiments.containsKey(experiment)) {
// We now know it cannot be null... // We now know it cannot be null...
return this.experiments[experiment]! == true; return experiments[experiment]! == true;
} }
} }
// allow message formatting to be turned off even when experiments are // allow message formatting to be turned off even when experiments are
// disabled... // disabled...
if (experiment == FormattingExperiment) { if (experiment == FormattingExperiment) {
if (this.experiments.containsKey(FormattingExperiment)) { if (experiments.containsKey(FormattingExperiment)) {
// If message formatting has not explicitly been turned off, then // If message formatting has not explicitly been turned off, then
// turn it on by default (even when experiments are disabled) // turn it on by default (even when experiments are disabled)
return this.experiments[experiment]! == true; return experiments[experiment]! == true;
} else { } else {
return true; // enable by default return true; // enable by default
} }
@ -133,7 +131,7 @@ class Settings extends ChangeNotifier {
/// be sent to the function and new settings will be instantiated based on the contents. /// be sent to the function and new settings will be instantiated based on the contents.
handleUpdate(dynamic settings) { handleUpdate(dynamic settings) {
// Set Theme and notify listeners // Set Theme and notify listeners
this.setTheme(settings["Theme"], settings["ThemeMode"] ?? mode_dark); setTheme(settings["Theme"], settings["ThemeMode"] ?? mode_dark);
_themeImages = settings["ThemeImages"] ?? false; _themeImages = settings["ThemeImages"] ?? false;
// Set Locale and notify listeners // Set Locale and notify listeners
@ -141,9 +139,7 @@ class Settings extends ChangeNotifier {
// Decide whether to enable Experiments // Decide whether to enable Experiments
var fontScale = settings["FontScaling"]; var fontScale = settings["FontScaling"];
if (fontScale == null) { fontScale ??= 1.0;
fontScale = 1.0;
}
_fontScaling = double.parse(fontScale.toString()).clamp(0.5, 2.0); _fontScaling = double.parse(fontScale.toString()).clamp(0.5, 2.0);
blockUnknownConnections = settings["BlockUnknownConnections"] ?? false; blockUnknownConnections = settings["BlockUnknownConnections"] ?? false;
@ -154,7 +150,7 @@ class Settings extends ChangeNotifier {
preserveHistoryByDefault = settings["DefaultSaveHistory"] ?? false; preserveHistoryByDefault = settings["DefaultSaveHistory"] ?? false;
// Set the internal experiments map. Casting from the Map<dynamic, dynamic> that we get from JSON // Set the internal experiments map. Casting from the Map<dynamic, dynamic> that we get from JSON
experiments = new HashMap<String, bool>.from(settings["Experiments"]); experiments = HashMap<String, bool>.from(settings["Experiments"]);
// single pane vs dual pane preferences // single pane vs dual pane preferences
_uiColumnModePortrait = uiColumnModeFromString(settings["UIColumnModePortrait"]); _uiColumnModePortrait = uiColumnModeFromString(settings["UIColumnModePortrait"]);
@ -192,15 +188,15 @@ class Settings extends ChangeNotifier {
switchLocaleByCode(String languageCode) { switchLocaleByCode(String languageCode) {
var code = languageCode.split("_"); var code = languageCode.split("_");
if (code.length == 1) { if (code.length == 1) {
this.switchLocale(Locale(languageCode)); switchLocale(Locale(languageCode));
} else { } else {
this.switchLocale(Locale(code[0], code[1])); switchLocale(Locale(code[0], code[1]));
} }
} }
/// Handle Font Scaling /// Handle Font Scaling
set fontScaling(double newFontScaling) { set fontScaling(double newFontScaling) {
this._fontScaling = newFontScaling; _fontScaling = newFontScaling;
notifyListeners(); notifyListeners();
} }
@ -208,7 +204,7 @@ class Settings extends ChangeNotifier {
// a convenience function to scale fonts dynamically... // a convenience function to scale fonts dynamically...
TextStyle scaleFonts(TextStyle input) { TextStyle scaleFonts(TextStyle input) {
return input.copyWith(fontSize: (input.fontSize ?? 12) * this.fontScaling); return input.copyWith(fontSize: (input.fontSize ?? 12) * fontScaling);
} }
/// Switch the Locale of the App /// Switch the Locale of the App
@ -280,28 +276,28 @@ class Settings extends ChangeNotifier {
DualpaneMode get uiColumnModePortrait => _uiColumnModePortrait; DualpaneMode get uiColumnModePortrait => _uiColumnModePortrait;
set uiColumnModePortrait(DualpaneMode newval) { set uiColumnModePortrait(DualpaneMode newval) {
this._uiColumnModePortrait = newval; _uiColumnModePortrait = newval;
notifyListeners(); notifyListeners();
} }
DualpaneMode get uiColumnModeLandscape => _uiColumnModeLandscape; DualpaneMode get uiColumnModeLandscape => _uiColumnModeLandscape;
set uiColumnModeLandscape(DualpaneMode newval) { set uiColumnModeLandscape(DualpaneMode newval) {
this._uiColumnModeLandscape = newval; _uiColumnModeLandscape = newval;
notifyListeners(); notifyListeners();
} }
NotificationPolicy get notificationPolicy => _notificationPolicy; NotificationPolicy get notificationPolicy => _notificationPolicy;
set notificationPolicy(NotificationPolicy newpol) { set notificationPolicy(NotificationPolicy newpol) {
this._notificationPolicy = newpol; _notificationPolicy = newpol;
notifyListeners(); notifyListeners();
} }
NotificationContent get notificationContent => _notificationContent; NotificationContent get notificationContent => _notificationContent;
set notificationContent(NotificationContent newcon) { set notificationContent(NotificationContent newcon) {
this._notificationContent = newcon; _notificationContent = newcon;
notifyListeners(); notifyListeners();
} }
@ -320,15 +316,16 @@ class Settings extends ChangeNotifier {
} }
static List<DualpaneMode> uiColumnModeOptions(bool isLandscape) { static List<DualpaneMode> uiColumnModeOptions(bool isLandscape) {
if (isLandscape) if (isLandscape) {
return [ return [
DualpaneMode.CopyPortrait, DualpaneMode.CopyPortrait,
DualpaneMode.Single, DualpaneMode.Single,
DualpaneMode.Dual1to2, DualpaneMode.Dual1to2,
DualpaneMode.Dual1to4, DualpaneMode.Dual1to4,
]; ];
else } else {
return [DualpaneMode.Single, DualpaneMode.Dual1to2, DualpaneMode.Dual1to4]; return [DualpaneMode.Single, DualpaneMode.Dual1to2, DualpaneMode.Dual1to4];
}
} }
static DualpaneMode uiColumnModeFromString(String m) { static DualpaneMode uiColumnModeFromString(String m) {
@ -477,7 +474,7 @@ class Settings extends ChangeNotifier {
/// event bus. /// event bus.
dynamic asJson() { dynamic asJson() {
return { return {
"Locale": this.locale.toString(), "Locale": locale.toString(),
"Theme": theme.theme, "Theme": theme.theme,
"ThemeMode": theme.mode, "ThemeMode": theme.mode,
"ThemeImages": _themeImages, "ThemeImages": _themeImages,
@ -486,7 +483,7 @@ class Settings extends ChangeNotifier {
"NotificationPolicy": _notificationPolicy.toString(), "NotificationPolicy": _notificationPolicy.toString(),
"NotificationContent": _notificationContent.toString(), "NotificationContent": _notificationContent.toString(),
"StreamerMode": streamerMode, "StreamerMode": streamerMode,
"ExperimentsEnabled": this.experimentsEnabled, "ExperimentsEnabled": experimentsEnabled,
"Experiments": experiments, "Experiments": experiments,
"StateRootPane": 0, "StateRootPane": 0,
"FirstTime": false, "FirstTime": false,

View File

@ -11,23 +11,23 @@ import 'opaque.dart';
const cwtch_theme = "cwtch"; const cwtch_theme = "cwtch";
final Color darkGreyPurple = Color(0xFF281831); const Color darkGreyPurple = Color(0xFF281831);
final Color deepPurple = Color(0xFF422850); const Color deepPurple = Color(0xFF422850);
final Color mauvePurple = Color(0xFF8E64A5); const Color mauvePurple = Color(0xFF8E64A5);
final Color whiteishPurple = Color(0xFFE3DFE4); const Color whiteishPurple = Color(0xFFE3DFE4);
final Color lightGrey = Color(0xFF9E9E9E); const Color lightGrey = Color(0xFF9E9E9E);
final Color softGreen = Color(0xFFA0FFB0); const Color softGreen = Color(0xFFA0FFB0);
final Color softRed = Color(0xFFFFA0B0); const Color softRed = Color(0xFFFFA0B0);
final Color whitePurple = Color(0xFFFFFDFF); const Color whitePurple = Color(0xFFFFFDFF);
final Color softPurple = Color(0xFFFDF3FC); const Color softPurple = Color(0xFFFDF3FC);
final Color purple = Color(0xFFDFB9DE); const Color purple = Color(0xFFDFB9DE);
final Color brightPurple = Color(0xFFD1B0E0); // not in new: portrait badge color const Color brightPurple = Color(0xFFD1B0E0); // not in new: portrait badge color
final Color darkPurple = Color(0xFF350052); const Color darkPurple = Color(0xFF350052);
final Color greyPurple = Color(0xFF775F84); // not in new: portrait borders const Color greyPurple = Color(0xFF775F84); // not in new: portrait borders
final Color pink = Color(0xFFE85DA1); // not in new: active button color const Color pink = Color(0xFFE85DA1); // not in new: active button color
final Color hotPink = Color(0xFFD20070); // Color(0xFFD01972); const Color hotPink = Color(0xFFD20070); // Color(0xFFD01972);
final Color softGrey = Color(0xFFB3B6B3); // not in new theme: blocked const Color softGrey = Color(0xFFB3B6B3); // not in new theme: blocked
OpaqueThemeType GetCwtchTheme(String mode) { OpaqueThemeType GetCwtchTheme(String mode) {
if (mode == mode_dark) { if (mode == mode_dark) {
@ -38,110 +38,192 @@ OpaqueThemeType GetCwtchTheme(String mode) {
} }
class CwtchDark extends OpaqueThemeType { class CwtchDark extends OpaqueThemeType {
static final Color background = darkGreyPurple; static const Color background = darkGreyPurple;
static final Color header = darkGreyPurple; static const Color header = darkGreyPurple;
static final Color userBubble = mauvePurple; static const Color userBubble = mauvePurple;
static final Color peerBubble = deepPurple; static const Color peerBubble = deepPurple;
static final Color font = whiteishPurple; static const Color font = whiteishPurple;
static final Color settings = whiteishPurple; static const Color settings = whiteishPurple;
static final Color accent = hotPink; static const Color accent = hotPink;
@override
get theme => cwtch_theme; get theme => cwtch_theme;
@override
get mode => mode_dark; get mode => mode_dark;
@override
get backgroundHilightElementColor => deepPurple; get backgroundHilightElementColor => deepPurple;
@override
get backgroundMainColor => background; // darkGreyPurple; get backgroundMainColor => background; // darkGreyPurple;
@override
get backgroundPaneColor => header; //darkGreyPurple; get backgroundPaneColor => header; //darkGreyPurple;
@override
get defaultButtonColor => accent; //hotPink; get defaultButtonColor => accent; //hotPink;
@override
get defaultButtonDisabledColor => lightGrey; get defaultButtonDisabledColor => lightGrey;
get defaultButtonDisabledTextColor => darkGreyPurple; get defaultButtonDisabledTextColor => darkGreyPurple;
@override
get defaultButtonTextColor => whiteishPurple; get defaultButtonTextColor => whiteishPurple;
@override
get dropShadowColor => mauvePurple; get dropShadowColor => mauvePurple;
@override
get hilightElementColor => purple; get hilightElementColor => purple;
@override
get mainTextColor => font; //whiteishPurple; get mainTextColor => font; //whiteishPurple;
@override
get messageFromMeBackgroundColor => userBubble; // mauvePurple; get messageFromMeBackgroundColor => userBubble; // mauvePurple;
@override
get messageFromMeTextColor => font; //whiteishPurple; get messageFromMeTextColor => font; //whiteishPurple;
@override
get messageFromOtherBackgroundColor => peerBubble; //deepPurple; get messageFromOtherBackgroundColor => peerBubble; //deepPurple;
@override
get messageFromOtherTextColor => font; //whiteishPurple; get messageFromOtherTextColor => font; //whiteishPurple;
@override
get messageSelectionColor => accent; get messageSelectionColor => accent;
@override
get portraitBackgroundColor => deepPurple; get portraitBackgroundColor => deepPurple;
@override
get portraitBlockedBorderColor => lightGrey; get portraitBlockedBorderColor => lightGrey;
@override
get portraitBlockedTextColor => lightGrey; get portraitBlockedTextColor => lightGrey;
@override
get portraitContactBadgeColor => hotPink; get portraitContactBadgeColor => hotPink;
@override
get portraitContactBadgeTextColor => whiteishPurple; get portraitContactBadgeTextColor => whiteishPurple;
@override
get portraitOfflineBorderColor => purple; get portraitOfflineBorderColor => purple;
@override
get portraitOnlineBorderColor => whiteishPurple; get portraitOnlineBorderColor => whiteishPurple;
get portraitOnlineAwayColor => Color(0xFFFFF59D); @override
get portraitOnlineBusyColor => Color(0xFFEF9A9A); get portraitOnlineAwayColor => const Color(0xFFFFF59D);
@override
get portraitOnlineBusyColor => const Color(0xFFEF9A9A);
@override
get portraitProfileBadgeColor => hotPink; get portraitProfileBadgeColor => hotPink;
@override
get portraitProfileBadgeTextColor => whiteishPurple; get portraitProfileBadgeTextColor => whiteishPurple;
@override
get scrollbarDefaultColor => purple; get scrollbarDefaultColor => purple;
@override
get sendHintTextColor => mauvePurple; get sendHintTextColor => mauvePurple;
@override
get chatReactionIconColor => mauvePurple; get chatReactionIconColor => mauvePurple;
@override
get textfieldBackgroundColor => deepPurple; get textfieldBackgroundColor => deepPurple;
@override
get textfieldBorderColor => deepPurple; get textfieldBorderColor => deepPurple;
@override
get textfieldErrorColor => hotPink; get textfieldErrorColor => hotPink;
@override
get textfieldHintColor => mainTextColor; get textfieldHintColor => mainTextColor;
@override
get textfieldSelectionColor => accent; get textfieldSelectionColor => accent;
@override
get toolbarIconColor => settings; //whiteishPurple; get toolbarIconColor => settings; //whiteishPurple;
@override
get topbarColor => header; //darkGreyPurple; get topbarColor => header; //darkGreyPurple;
@override
get menuBackgroundColor => accent; get menuBackgroundColor => accent;
get menuTextColor => darkGreyPurple; get menuTextColor => darkGreyPurple;
@override
get snackbarBackgroundColor => accent; get snackbarBackgroundColor => accent;
@override
get snackbarTextColor => whitePurple; get snackbarTextColor => whitePurple;
@override
get chatImageColor => purple; get chatImageColor => purple;
} }
class CwtchLight extends OpaqueThemeType { class CwtchLight extends OpaqueThemeType {
static final Color background = whitePurple; static const Color background = whitePurple;
static final Color header = softPurple; static const Color header = softPurple;
static final Color userBubble = purple; static const Color userBubble = purple;
static final Color peerBubble = softPurple; static const Color peerBubble = softPurple;
static final Color font = darkPurple; static const Color font = darkPurple;
static final Color settings = darkPurple; static const Color settings = darkPurple;
static final Color accent = hotPink; static const Color accent = hotPink;
@override
get theme => cwtch_theme; get theme => cwtch_theme;
@override
get mode => mode_light; get mode => mode_light;
@override
get backgroundHilightElementColor => softPurple; get backgroundHilightElementColor => softPurple;
@override
get backgroundMainColor => background; //whitePurple; get backgroundMainColor => background; //whitePurple;
@override
get backgroundPaneColor => background; //whitePurple; get backgroundPaneColor => background; //whitePurple;
@override
get defaultButtonColor => accent; // hotPink; get defaultButtonColor => accent; // hotPink;
@override
get defaultButtonDisabledColor => softGrey; get defaultButtonDisabledColor => softGrey;
@override
get defaultButtonTextColor => whitePurple; // ? get defaultButtonTextColor => whitePurple; // ?
@override
get dropShadowColor => purple; get dropShadowColor => purple;
@override
get hilightElementColor => purple; get hilightElementColor => purple;
@override
get mainTextColor => settings; get mainTextColor => settings;
@override
get messageFromMeBackgroundColor => userBubble; //brightPurple; get messageFromMeBackgroundColor => userBubble; //brightPurple;
@override
get messageFromMeTextColor => font; //mainTextColor; get messageFromMeTextColor => font; //mainTextColor;
@override
get messageFromOtherBackgroundColor => peerBubble; //purple; get messageFromOtherBackgroundColor => peerBubble; //purple;
@override
get messageFromOtherTextColor => font; //darkPurple; get messageFromOtherTextColor => font; //darkPurple;
@override
get messageSelectionColor => accent; get messageSelectionColor => accent;
@override
get portraitBackgroundColor => softPurple; get portraitBackgroundColor => softPurple;
@override
get portraitBlockedBorderColor => softGrey; get portraitBlockedBorderColor => softGrey;
@override
get portraitBlockedTextColor => softGrey; get portraitBlockedTextColor => softGrey;
@override
get portraitContactBadgeColor => accent; get portraitContactBadgeColor => accent;
@override
get portraitContactBadgeTextColor => whitePurple; get portraitContactBadgeTextColor => whitePurple;
@override
get portraitOfflineBorderColor => greyPurple; get portraitOfflineBorderColor => greyPurple;
@override
get portraitOnlineBorderColor => greyPurple; get portraitOnlineBorderColor => greyPurple;
get portraitOnlineAwayColor => Color(0xFFFFF59D); @override
get portraitOnlineBusyColor => Color(0xFFEF9A9A); get portraitOnlineAwayColor => const Color(0xFFFFF59D);
@override
get portraitOnlineBusyColor => const Color(0xFFEF9A9A);
@override
get portraitProfileBadgeColor => accent; get portraitProfileBadgeColor => accent;
@override
get portraitProfileBadgeTextColor => whitePurple; get portraitProfileBadgeTextColor => whitePurple;
@override
get scrollbarDefaultColor => accent; get scrollbarDefaultColor => accent;
@override
get sendHintTextColor => purple; get sendHintTextColor => purple;
@override
get chatReactionIconColor => purple; get chatReactionIconColor => purple;
@override
get textfieldBackgroundColor => purple; get textfieldBackgroundColor => purple;
@override
get textfieldBorderColor => purple; get textfieldBorderColor => purple;
@override
get textfieldErrorColor => hotPink; get textfieldErrorColor => hotPink;
@override
get textfieldHintColor => font; get textfieldHintColor => font;
@override
get textfieldSelectionColor => accent; get textfieldSelectionColor => accent;
@override
get toolbarIconColor => settings; //darkPurple; get toolbarIconColor => settings; //darkPurple;
@override
get topbarColor => header; //softPurple; get topbarColor => header; //softPurple;
@override
get menuBackgroundColor => accent; get menuBackgroundColor => accent;
get menuTextColor => whitePurple; get menuTextColor => whitePurple;
@override
get snackbarBackgroundColor => accent; get snackbarBackgroundColor => accent;
@override
get snackbarTextColor => whitePurple; get snackbarTextColor => whitePurple;
@override
get chatImageColor => purple; get chatImageColor => purple;
} }

View File

@ -1,5 +1,4 @@
import 'dart:io'; import 'dart:io';
import 'dart:ui';
import 'dart:core'; import 'dart:core';
import 'package:cwtch/themes/cwtch.dart'; import 'package:cwtch/themes/cwtch.dart';
@ -14,14 +13,14 @@ const custom_themes_subdir = "themes";
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); const TextStyle defaultSmallTextStyle = TextStyle(fontFamily: "Inter", fontWeight: FontWeight.normal, fontSize: 10);
final TextStyle defaultMessageTextStyle = TextStyle(fontFamily: "Inter", fontWeight: FontWeight.w400, fontSize: 13, fontFamilyFallback: [Platform.isWindows ? 'Segoe UI Emoji' : "Noto Color Emoji"]); final TextStyle defaultMessageTextStyle = TextStyle(fontFamily: "Inter", fontWeight: FontWeight.w400, fontSize: 13, fontFamilyFallback: [Platform.isWindows ? 'Segoe UI Emoji' : "Noto Color Emoji"]);
final TextStyle defaultFormLabelTextStyle = TextStyle(fontFamily: "Inter", fontWeight: FontWeight.bold, fontSize: 20); const TextStyle defaultFormLabelTextStyle = TextStyle(fontFamily: "Inter", fontWeight: FontWeight.bold, fontSize: 20);
final TextStyle defaultTextStyle = TextStyle(fontFamily: "Inter", fontWeight: FontWeight.w500, fontSize: 12); const TextStyle defaultTextStyle = TextStyle(fontFamily: "Inter", fontWeight: FontWeight.w500, fontSize: 12);
final TextStyle defaultTextButtonStyle = defaultTextStyle.copyWith(fontWeight: FontWeight.bold); final TextStyle defaultTextButtonStyle = defaultTextStyle.copyWith(fontWeight: FontWeight.bold);
final TextStyle defaultDropDownMenuItemTextStyle = TextStyle(fontFamily: "Inter", fontWeight: FontWeight.bold, fontSize: 16); const TextStyle defaultDropDownMenuItemTextStyle = TextStyle(fontFamily: "Inter", fontWeight: FontWeight.bold, fontSize: 16);
Map<String, Map<String, OpaqueThemeType>> themes = Map(); Map<String, Map<String, OpaqueThemeType>> themes = {};
LoadThemes(String cwtchDir) async { LoadThemes(String cwtchDir) async {
themes = await loadBuiltinThemes(); themes = await loadBuiltinThemes();
@ -68,7 +67,7 @@ Color darken(Color color, [double amount = 0.15]) {
} }
abstract class OpaqueThemeType { abstract class OpaqueThemeType {
static final Color red = Color(0xFFFF0000); static const Color red = Color(0xFFFF0000);
get theme => "dummy"; get theme => "dummy";
get mode => mode_light; get mode => mode_light;
@ -111,8 +110,8 @@ abstract class OpaqueThemeType {
get portraitProfileBadgeColor => red; get portraitProfileBadgeColor => red;
get portraitProfileBadgeTextColor => red; get portraitProfileBadgeTextColor => red;
get portraitOnlineAwayColor => Color(0xFFFFF59D); get portraitOnlineAwayColor => const Color(0xFFFFF59D);
get portraitOnlineBusyColor => Color(0xFFEF9A9A); get portraitOnlineBusyColor => const Color(0xFFEF9A9A);
// dropshaddpow // dropshaddpow
// todo: probably should not be reply icon color in messagerow // todo: probably should not be reply icon color in messagerow
@ -137,7 +136,7 @@ abstract class OpaqueThemeType {
get chatImage => null; get chatImage => null;
ImageProvider loadImage(String key, {BuildContext? context}) { ImageProvider loadImage(String key, {BuildContext? context}) {
return AssetImage(""); return const AssetImage("");
} }
// Sizes // Sizes
@ -172,13 +171,11 @@ ThemeData mkThemeData(Settings opaque) {
return ThemeData( return ThemeData(
hoverColor: opaque.current().backgroundHilightElementColor.withOpacity(0.5), hoverColor: opaque.current().backgroundHilightElementColor.withOpacity(0.5),
visualDensity: VisualDensity.adaptivePlatformDensity, visualDensity: VisualDensity.adaptivePlatformDensity,
primarySwatch: getMaterialColor(opaque.current().topbarColor),
primaryIconTheme: IconThemeData( primaryIconTheme: IconThemeData(
color: opaque.current().mainTextColor, color: opaque.current().mainTextColor,
), ),
primaryColor: opaque.current().mainTextColor, primaryColor: opaque.current().mainTextColor,
canvasColor: opaque.current().backgroundMainColor, canvasColor: opaque.current().backgroundMainColor,
backgroundColor: opaque.current().backgroundMainColor,
highlightColor: opaque.current().hilightElementColor, highlightColor: opaque.current().hilightElementColor,
iconTheme: IconThemeData( iconTheme: IconThemeData(
color: opaque.current().toolbarIconColor, color: opaque.current().toolbarIconColor,
@ -207,7 +204,7 @@ ThemeData mkThemeData(Settings opaque) {
backgroundColor: MaterialStateProperty.all(opaque.current().defaultButtonColor), backgroundColor: MaterialStateProperty.all(opaque.current().defaultButtonColor),
foregroundColor: MaterialStateProperty.all(opaque.current().defaultButtonTextColor), foregroundColor: MaterialStateProperty.all(opaque.current().defaultButtonTextColor),
overlayColor: MaterialStateProperty.all(opaque.current().defaultButtonActiveColor), overlayColor: MaterialStateProperty.all(opaque.current().defaultButtonActiveColor),
padding: MaterialStateProperty.all(EdgeInsets.all(20))), padding: MaterialStateProperty.all(const EdgeInsets.all(20))),
), ),
hintColor: opaque.current().textfieldHintColor, hintColor: opaque.current().textfieldHintColor,
elevatedButtonTheme: ElevatedButtonThemeData( elevatedButtonTheme: ElevatedButtonThemeData(
@ -221,7 +218,7 @@ ThemeData mkThemeData(Settings opaque) {
: null), : null),
enableFeedback: true, enableFeedback: true,
textStyle: MaterialStateProperty.all(opaque.scaleFonts(defaultTextButtonStyle)), textStyle: MaterialStateProperty.all(opaque.scaleFonts(defaultTextButtonStyle)),
padding: MaterialStateProperty.all(EdgeInsets.all(20)), padding: MaterialStateProperty.all(const EdgeInsets.all(20)),
shape: MaterialStateProperty.all(RoundedRectangleBorder( shape: MaterialStateProperty.all(RoundedRectangleBorder(
borderRadius: BorderRadius.circular(6.0), borderRadius: BorderRadius.circular(6.0),
)), )),

View File

@ -1,13 +1,10 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'dart:ui';
import 'package:cwtch/config.dart';
import 'package:cwtch/themes/cwtch.dart'; import 'package:cwtch/themes/cwtch.dart';
import 'package:cwtch/themes/opaque.dart'; import 'package:cwtch/themes/opaque.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:glob/list_local_fs.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:yaml/yaml.dart'; import 'package:yaml/yaml.dart';
import 'package:path/path.dart' as path; import 'package:path/path.dart' as path;
@ -18,7 +15,7 @@ Future<Map<String, Map<String, OpaqueThemeType>>> loadBuiltinThemes() async {
final manifestJson = await rootBundle.loadString('AssetManifest.json'); final manifestJson = await rootBundle.loadString('AssetManifest.json');
final themesList = json.decode(manifestJson).keys.where((String key) => key.startsWith('assets/themes')); final themesList = json.decode(manifestJson).keys.where((String key) => key.startsWith('assets/themes'));
Map<String, Map<String, OpaqueThemeType>> themes = Map(); Map<String, Map<String, OpaqueThemeType>> themes = {};
for (String themefile in themesList) { for (String themefile in themesList) {
if (themefile.substring(themefile.length - 4) != ".yml") { if (themefile.substring(themefile.length - 4) != ".yml") {
@ -41,7 +38,7 @@ Future<Map<String, Map<String, OpaqueThemeType>>> loadBuiltinThemes() async {
} }
Future<Map<String, Map<String, OpaqueThemeType>>> loadCustomThemes(String themesDirPath) async { Future<Map<String, Map<String, OpaqueThemeType>>> loadCustomThemes(String themesDirPath) async {
Map<String, Map<String, OpaqueThemeType>> themes = Map(); Map<String, Map<String, OpaqueThemeType>> themes = {};
Directory themesDir = Directory(themesDirPath); Directory themesDir = Directory(themesDirPath);
if (!themesDir.existsSync()) { if (!themesDir.existsSync()) {
@ -97,7 +94,7 @@ Future<Map<String, OpaqueThemeType>?> loadFileYamlTheme(String themefile, String
} }
Future<Map<String, OpaqueThemeType>?> loadYamlTheme(YamlMap yml, [String? assetsDir]) async { Future<Map<String, OpaqueThemeType>?> loadYamlTheme(YamlMap yml, [String? assetsDir]) async {
Map<String, OpaqueThemeType> subthemes = Map(); Map<String, OpaqueThemeType> subthemes = {};
if ((yml["themes"] as YamlMap).containsKey(mode_dark)) { if ((yml["themes"] as YamlMap).containsKey(mode_dark)) {
subthemes[mode_dark] = YmlTheme(yml, yml["themes"]["name"], mode_dark, assetsDir); subthemes[mode_dark] = YmlTheme(yml, yml["themes"]["name"], mode_dark, assetsDir);
} }
@ -109,16 +106,14 @@ Future<Map<String, OpaqueThemeType>?> loadYamlTheme(YamlMap yml, [String? assets
class YmlTheme extends OpaqueThemeType { class YmlTheme extends OpaqueThemeType {
late YamlMap yml; late YamlMap yml;
@override
late String mode; late String mode;
@override
late String theme; late String theme;
late String? assetsDir; late String? assetsDir;
late OpaqueThemeType fallbackTheme; late OpaqueThemeType fallbackTheme;
YmlTheme(YamlMap yml, theme, mode, assetsDir) { YmlTheme(this.yml, this.theme, this.mode, this.assetsDir) {
this.yml = yml;
this.theme = theme;
this.mode = mode;
this.assetsDir = assetsDir;
if (mode == mode_dark) { if (mode == mode_dark) {
fallbackTheme = CwtchDark(); fallbackTheme = CwtchDark();
} else { } else {
@ -128,16 +123,16 @@ class YmlTheme extends OpaqueThemeType {
Color? getColor(String name) { Color? getColor(String name) {
var val = yml["themes"][mode]["theme"][name]; var val = yml["themes"][mode]["theme"][name];
if (!(val is int)) { if ((val is! int)) {
val = yml["themes"][mode]["theme"][val] ?? val; val = yml["themes"][mode]["theme"][val] ?? val;
} }
if (!(val is int)) { if ((val is! int)) {
val = yml["themes"][mode]?["colors"][val] ?? val; val = yml["themes"][mode]?["colors"][val] ?? val;
} }
if (!(val is int)) { if ((val is! int)) {
val = yml["colors"]?[val]; val = yml["colors"]?[val];
} }
if (!(val is int)) { if ((val is! int)) {
return null; return null;
} }
@ -160,51 +155,93 @@ class YmlTheme extends OpaqueThemeType {
return null; return null;
} }
@override
get backgroundMainColor => getColor("backgroundMainColor") ?? fallbackTheme.backgroundMainColor; get backgroundMainColor => getColor("backgroundMainColor") ?? fallbackTheme.backgroundMainColor;
@override
get backgroundPaneColor => getColor("backgroundPaneColor") ?? fallbackTheme.backgroundPaneColor; get backgroundPaneColor => getColor("backgroundPaneColor") ?? fallbackTheme.backgroundPaneColor;
@override
get topbarColor => getColor("topbarColor") ?? fallbackTheme.topbarColor; get topbarColor => getColor("topbarColor") ?? fallbackTheme.topbarColor;
@override
get mainTextColor => getColor("mainTextColor") ?? fallbackTheme.mainTextColor; get mainTextColor => getColor("mainTextColor") ?? fallbackTheme.mainTextColor;
@override
get hilightElementColor => getColor("hilightElementColor") ?? fallbackTheme.hilightElementColor; get hilightElementColor => getColor("hilightElementColor") ?? fallbackTheme.hilightElementColor;
@override
get backgroundHilightElementColor => getColor("backgroundHilightElementColor") ?? fallbackTheme.backgroundHilightElementColor; get backgroundHilightElementColor => getColor("backgroundHilightElementColor") ?? fallbackTheme.backgroundHilightElementColor;
@override
get sendHintTextColor => getColor("sendHintTextColor") ?? fallbackTheme.sendHintTextColor; get sendHintTextColor => getColor("sendHintTextColor") ?? fallbackTheme.sendHintTextColor;
@override
get defaultButtonColor => getColor("defaultButtonColor") ?? fallbackTheme.defaultButtonColor; get defaultButtonColor => getColor("defaultButtonColor") ?? fallbackTheme.defaultButtonColor;
@override
get defaultButtonActiveColor => /*mode == mode_light ? darken(defaultButtonColor) :*/ lighten(getColor("defaultButtonColor") ?? fallbackTheme.defaultButtonColor); get defaultButtonActiveColor => /*mode == mode_light ? darken(defaultButtonColor) :*/ lighten(getColor("defaultButtonColor") ?? fallbackTheme.defaultButtonColor);
@override
get defaultButtonTextColor => getColor("defaultButtonTextColor") ?? fallbackTheme.defaultButtonTextColor; get defaultButtonTextColor => getColor("defaultButtonTextColor") ?? fallbackTheme.defaultButtonTextColor;
@override
get defaultButtonDisabledColor => getColor("defaultButtonDisabledColor") ?? fallbackTheme.defaultButtonDisabledColor; get defaultButtonDisabledColor => getColor("defaultButtonDisabledColor") ?? fallbackTheme.defaultButtonDisabledColor;
@override
get textfieldBackgroundColor => getColor("textfieldBackgroundColor") ?? fallbackTheme.textfieldBackgroundColor; get textfieldBackgroundColor => getColor("textfieldBackgroundColor") ?? fallbackTheme.textfieldBackgroundColor;
@override
get textfieldBorderColor => getColor("textfieldBorderColor") ?? fallbackTheme.textfieldBorderColor; get textfieldBorderColor => getColor("textfieldBorderColor") ?? fallbackTheme.textfieldBorderColor;
@override
get textfieldHintColor => getColor("textfieldHintColor") ?? fallbackTheme.textfieldHintColor; get textfieldHintColor => getColor("textfieldHintColor") ?? fallbackTheme.textfieldHintColor;
@override
get textfieldErrorColor => getColor("textfieldErrorColor") ?? fallbackTheme.textfieldErrorColor; get textfieldErrorColor => getColor("textfieldErrorColor") ?? fallbackTheme.textfieldErrorColor;
@override
get textfieldSelectionColor => getColor("textfieldSelectionColor") ?? fallbackTheme.textfieldSelectionColor; get textfieldSelectionColor => getColor("textfieldSelectionColor") ?? fallbackTheme.textfieldSelectionColor;
@override
get scrollbarDefaultColor => getColor("scrollbarDefaultColor") ?? fallbackTheme.scrollbarDefaultColor; get scrollbarDefaultColor => getColor("scrollbarDefaultColor") ?? fallbackTheme.scrollbarDefaultColor;
@override
get portraitBackgroundColor => getColor("portraitBackgroundColor") ?? fallbackTheme.portraitBackgroundColor; get portraitBackgroundColor => getColor("portraitBackgroundColor") ?? fallbackTheme.portraitBackgroundColor;
@override
get portraitOnlineBorderColor => getColor("portraitOnlineBorderColor") ?? fallbackTheme.portraitOnlineBorderColor; get portraitOnlineBorderColor => getColor("portraitOnlineBorderColor") ?? fallbackTheme.portraitOnlineBorderColor;
@override
get portraitOfflineBorderColor => getColor("portraitOfflineBorderColor") ?? fallbackTheme.portraitOfflineBorderColor; get portraitOfflineBorderColor => getColor("portraitOfflineBorderColor") ?? fallbackTheme.portraitOfflineBorderColor;
@override
get portraitBlockedBorderColor => getColor("portraitBlockedBorderColor") ?? fallbackTheme.portraitBlockedBorderColor; get portraitBlockedBorderColor => getColor("portraitBlockedBorderColor") ?? fallbackTheme.portraitBlockedBorderColor;
@override
get portraitBlockedTextColor => getColor("portraitBlockedTextColor") ?? fallbackTheme.portraitBlockedTextColor; get portraitBlockedTextColor => getColor("portraitBlockedTextColor") ?? fallbackTheme.portraitBlockedTextColor;
@override
get portraitContactBadgeColor => getColor("portraitContactBadgeColor") ?? fallbackTheme.portraitContactBadgeColor; get portraitContactBadgeColor => getColor("portraitContactBadgeColor") ?? fallbackTheme.portraitContactBadgeColor;
@override
get portraitContactBadgeTextColor => getColor("portraitContactBadgeTextColor") ?? fallbackTheme.portraitContactBadgeTextColor; get portraitContactBadgeTextColor => getColor("portraitContactBadgeTextColor") ?? fallbackTheme.portraitContactBadgeTextColor;
@override
get portraitProfileBadgeColor => getColor("portraitProfileBadgeColor") ?? fallbackTheme.portraitProfileBadgeColor; get portraitProfileBadgeColor => getColor("portraitProfileBadgeColor") ?? fallbackTheme.portraitProfileBadgeColor;
@override
get portraitProfileBadgeTextColor => getColor("portraitProfileBadgeTextColor") ?? fallbackTheme.portraitProfileBadgeTextColor; get portraitProfileBadgeTextColor => getColor("portraitProfileBadgeTextColor") ?? fallbackTheme.portraitProfileBadgeTextColor;
@override
get portraitOnlineAwayColor => getColor("portraitOnlineAwayColor") ?? fallbackTheme.portraitOnlineAwayColor; get portraitOnlineAwayColor => getColor("portraitOnlineAwayColor") ?? fallbackTheme.portraitOnlineAwayColor;
@override
get portraitOnlineBusyColor => getColor("portraitOnlineBusyColor") ?? fallbackTheme.portraitOnlineBusyColor; get portraitOnlineBusyColor => getColor("portraitOnlineBusyColor") ?? fallbackTheme.portraitOnlineBusyColor;
@override
get dropShadowColor => getColor("dropShadowColor") ?? fallbackTheme.dropShadowColor; get dropShadowColor => getColor("dropShadowColor") ?? fallbackTheme.dropShadowColor;
@override
get toolbarIconColor => getColor("toolbarIconColor") ?? fallbackTheme.toolbarIconColor; get toolbarIconColor => getColor("toolbarIconColor") ?? fallbackTheme.toolbarIconColor;
@override
get chatReactionIconColor => getColor("chatReactionIconColor") ?? fallbackTheme.chatReactionIconColor; get chatReactionIconColor => getColor("chatReactionIconColor") ?? fallbackTheme.chatReactionIconColor;
@override
get messageFromMeBackgroundColor => getColor("messageFromMeBackgroundColor") ?? fallbackTheme.messageFromMeBackgroundColor; get messageFromMeBackgroundColor => getColor("messageFromMeBackgroundColor") ?? fallbackTheme.messageFromMeBackgroundColor;
@override
get messageFromMeTextColor => getColor("messageFromMeTextColor") ?? fallbackTheme.messageFromMeTextColor; get messageFromMeTextColor => getColor("messageFromMeTextColor") ?? fallbackTheme.messageFromMeTextColor;
@override
get messageFromOtherBackgroundColor => getColor("messageFromOtherBackgroundColor") ?? fallbackTheme.messageFromOtherBackgroundColor; get messageFromOtherBackgroundColor => getColor("messageFromOtherBackgroundColor") ?? fallbackTheme.messageFromOtherBackgroundColor;
@override
get messageFromOtherTextColor => getColor("messageFromOtherTextColor") ?? fallbackTheme.messageFromOtherTextColor; get messageFromOtherTextColor => getColor("messageFromOtherTextColor") ?? fallbackTheme.messageFromOtherTextColor;
@override
get messageSelectionColor => getColor("messageSelectionColor") ?? fallbackTheme.messageSelectionColor; get messageSelectionColor => getColor("messageSelectionColor") ?? fallbackTheme.messageSelectionColor;
@override
get menuBackgroundColor => getColor("menuBackgroundColor") ?? fallbackTheme.menuBackgroundColor; get menuBackgroundColor => getColor("menuBackgroundColor") ?? fallbackTheme.menuBackgroundColor;
@override
get snackbarBackgroundColor => getColor("snackbarBackgroundColor") ?? fallbackTheme.snackbarBackgroundColor; get snackbarBackgroundColor => getColor("snackbarBackgroundColor") ?? fallbackTheme.snackbarBackgroundColor;
@override
get snackbarTextColor => getColor("snackbarTextColor") ?? fallbackTheme.snackbarTextColor; get snackbarTextColor => getColor("snackbarTextColor") ?? fallbackTheme.snackbarTextColor;
// Images // Images
@override
get chatImageColor => getColor("chatImageColor") ?? fallbackTheme.chatImageColor; get chatImageColor => getColor("chatImageColor") ?? fallbackTheme.chatImageColor;
@override
get chatImage => getImage("chatImage") ?? fallbackTheme.chatImage; get chatImage => getImage("chatImage") ?? fallbackTheme.chatImage;
@override
ImageProvider loadImage(String key, {BuildContext? context}) { ImageProvider loadImage(String key, {BuildContext? context}) {
File f = File(key); File f = File(key);
if (f.existsSync()) { if (f.existsSync()) {

View File

@ -109,7 +109,7 @@ class base32 {
base32 = _pad(base32, encoding: encoding); base32 = _pad(base32, encoding: encoding);
if (!_isValid(base32, encoding: encoding)) { if (!_isValid(base32, encoding: encoding)) {
throw FormatException('Invalid Base32 characters'); throw const FormatException('Invalid Base32 characters');
} }
if (encoding == Encoding.crockford) { if (encoding == Encoding.crockford) {

21
lib/third_party/base85/LICENSE vendored Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 litaoh
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,32 @@
import 'dart:convert';
import 'dart:typed_data';
import 'base_85_decoder.dart';
import 'base_85_encoder.dart';
enum AlgoType { ascii85 }
//This is not any official base85 alphabet, because we need one where the json encoder won't have an issue with
// the characters being encoded. For dart that means no angle brackets, and no double quotes
const String ascii85 = '{}&()*+,-./0123456789:;=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[~]^_`abcdefghijklmnopqrstuvwxyz';
class Base85Codec extends Codec<Uint8List, String> {
final String alphabet;
Converter<Uint8List, String>? _encoder;
Converter<String, Uint8List>? _decoder;
AlgoType algo;
Base85Codec([this.alphabet = ascii85, this.algo = AlgoType.ascii85]);
@override
Converter<Uint8List, String> get encoder {
_encoder ??= Base85Encoder(alphabet, algo);
return _encoder!;
}
@override
Converter<String, Uint8List> get decoder {
_decoder ??= Base85Decoder(alphabet, algo);
return _decoder!;
}
}

View File

@ -0,0 +1,101 @@
import 'dart:convert';
import 'dart:math';
import 'dart:typed_data';
import 'base_85_codec.dart';
/// Characters to allow (and ignore) in an encoded buffer
const List<int> _IGNORE_CHARS = <int>[0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x20];
/// num max value
var NUM_MAX_VALUE = pow(2, 32) - 1;
/// quad 85
var QUAD85 = pow(85, 4);
/// trio 85
var TRIO85 = pow(85, 3);
/// duo 85
var DUO85 = pow(85, 2);
/// sign 85
var SING85 = 85;
class Base85Decoder extends Converter<String, Uint8List> {
String alphabet;
AlgoType algo;
late Uint8List _baseMap;
Base85Decoder(this.alphabet, this.algo) {
_baseMap = Uint8List(256);
_baseMap.fillRange(0, _baseMap.length, 255);
for (var i = 0; i < alphabet.length; i++) {
var xc = alphabet.codeUnitAt(i);
if (_baseMap[xc] != 255) {
throw FormatException('${alphabet[i]} is ambiguous');
}
_baseMap[xc] = i;
}
}
@override
Uint8List convert(String input) {
if (input.isEmpty) {
return Uint8List(0);
}
var bytes = Uint8List.fromList(input.codeUnits);
var dataLength = bytes.length;
var padding = (dataLength % 5 == 0) ? 0 : 5 - dataLength % 5;
var bufferStart = 0;
var bufferEnd = bufferStart + dataLength;
var result = Uint8List(4 * ((bufferEnd - bufferStart) / 5).ceil());
nextValidByte(index) {
if (index < bufferEnd) {
while (_IGNORE_CHARS.contains(bytes[index])) {
padding = (padding + 1) % 5;
index++; // skip newline character
}
}
return index;
}
var writeIndex = 0;
for (var i = bufferStart; i < bufferEnd;) {
var num = 0;
var starti = i;
i = nextValidByte(i);
num = (_baseMap[bytes[i]]) * (QUAD85 as int);
i = nextValidByte(i + 1);
num += (i >= bufferEnd ? 84 : _baseMap[bytes[i]]) * (TRIO85 as int);
i = nextValidByte(i + 1);
num += (i >= bufferEnd ? 84 : _baseMap[bytes[i]]) * (DUO85 as int);
i = nextValidByte(i + 1);
num += (i >= bufferEnd ? 84 : _baseMap[bytes[i]]) * SING85;
i = nextValidByte(i + 1);
num += (i >= bufferEnd ? 84 : _baseMap[bytes[i]]);
i = nextValidByte(i + 1);
if (num > NUM_MAX_VALUE || num < 0) {
throw const FormatException('Bogus data');
}
result[writeIndex] = (num >> 24);
result[writeIndex + 1] = (num >> 16);
result[writeIndex + 2] = (num >> 8);
result[writeIndex + 3] = (num & 0xff);
writeIndex += 4;
}
return result.sublist(0, writeIndex - padding);
}
}

View File

@ -0,0 +1,46 @@
import 'dart:convert';
import 'dart:typed_data';
import 'base_85_codec.dart';
class Base85Encoder extends Converter<Uint8List, String> {
final String alphabet;
final AlgoType algo;
Base85Encoder(this.alphabet, this.algo);
@override
/// Encodes the specified data. If algo is [AlgoType.ascii85],
/// the encoded data will be prepended with <~ and appended with ~>.
/// The [bytes] to encode, may be a [Uint8List]
/// return A String with the encoded [bytes].
String convert(Uint8List bytes) {
var padding = (bytes.length % 4 == 0) ? 0 : 4 - bytes.length % 4;
var result = '';
for (var i = 0; i < bytes.length; i += 4) {
/// 32-bit number of the current 4 bytes (padded with 0 as necessary)
var num = ((bytes[i] << 24).toUnsigned(32)) +
(((i + 1 >= bytes.length ? 0 : bytes[i + 1]) << 16).toUnsigned(32)) +
(((i + 2 >= bytes.length ? 0 : bytes[i + 2]) << 8).toUnsigned(32)) +
(((i + 3 >= bytes.length ? 0 : bytes[i + 3]) << 0).toUnsigned(32));
/// Create 5 characters from '!' to 'u' alphabet
var block = <String>[];
for (var j = 0; j < 5; ++j) {
block.insert(0, alphabet[num % 85]);
num = num ~/ 85;
}
var r = block.join('');
if (r == '!!!!!' && algo == AlgoType.ascii85) {
r = 'z';
}
/// And append them to the result
result += r;
}
return result.substring(0, result.length - padding);
}
}

View File

@ -24,11 +24,8 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE. // SOFTWARE.
import 'dart:ui';
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'linkify.dart'; import 'linkify.dart';
@ -132,7 +129,7 @@ class SelectableLinkify extends StatelessWidget {
final BoxConstraints constraints; final BoxConstraints constraints;
const SelectableLinkify({ const SelectableLinkify({
Key? key, super.key,
required this.text, required this.text,
this.linkifiers = defaultLinkifiers, this.linkifiers = defaultLinkifiers,
this.onOpen, this.onOpen,
@ -165,7 +162,7 @@ class SelectableLinkify extends StatelessWidget {
this.selectionControls, this.selectionControls,
this.onSelectionChanged, this.onSelectionChanged,
required this.constraints, required this.constraints,
}) : super(key: key); });
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -304,7 +301,7 @@ TextSpan buildTextSpan(
semanticsLabel: element.text); semanticsLabel: element.text);
} else if (element is CodeElement) { } else if (element is CodeElement) {
return TextSpan( return TextSpan(
text: element.text.replaceAll("\`", ""), text: element.text.replaceAll("`", ""),
// monospace fonts at the same size as regular text makes them appear // monospace fonts at the same size as regular text makes them appear
// slightly larger, so we compensate by making them slightly smaller... // slightly larger, so we compensate by making them slightly smaller...
style: codeStyle?.copyWith(fontFamily: "RobotoMono", fontSize: codeStyle.fontSize! - 1.5), style: codeStyle?.copyWith(fontFamily: "RobotoMono", fontSize: codeStyle.fontSize! - 1.5),

View File

@ -38,27 +38,27 @@ abstract class LinkifyElement {
} }
class BoldElement extends LinkifyElement { class BoldElement extends LinkifyElement {
BoldElement(String text) : super(text); BoldElement(super.text);
} }
class ItalicElement extends LinkifyElement { class ItalicElement extends LinkifyElement {
ItalicElement(String text) : super(text); ItalicElement(super.text);
} }
class SuperElement extends LinkifyElement { class SuperElement extends LinkifyElement {
SuperElement(String text) : super(text); SuperElement(super.text);
} }
class SubElement extends LinkifyElement { class SubElement extends LinkifyElement {
SubElement(String text) : super(text); SubElement(super.text);
} }
class StrikeElement extends LinkifyElement { class StrikeElement extends LinkifyElement {
StrikeElement(String text) : super(text); StrikeElement(super.text);
} }
class CodeElement extends LinkifyElement { class CodeElement extends LinkifyElement {
CodeElement(String text) : super(text); CodeElement(super.text);
} }
class LinkableElement extends LinkifyElement { class LinkableElement extends LinkifyElement {
@ -75,7 +75,7 @@ class LinkableElement extends LinkifyElement {
/// Represents an element containing text /// Represents an element containing text
class TextElement extends LinkifyElement { class TextElement extends LinkifyElement {
TextElement(String text) : super(text); TextElement(super.text);
@override @override
String toString() { String toString() {
@ -136,9 +136,9 @@ List<LinkifyElement> linkify(
return list; return list;
} }
linkifiers.forEach((linkifier) { for (var linkifier in linkifiers) {
list = linkifier.parse(list, options); list = linkifier.parse(list, options);
}); }
return list; return list;
} }

View File

@ -148,7 +148,7 @@ class UrlLinkifier extends Linkifier {
List<LinkifyElement> parse(elements, options) { List<LinkifyElement> parse(elements, options) {
var list = <LinkifyElement>[]; var list = <LinkifyElement>[];
elements.forEach((element) { for (var element in elements) {
if (element is TextElement) { if (element is TextElement) {
if (options.parseLinks == false && options.messageFormatting == false) { if (options.parseLinks == false && options.messageFormatting == false) {
list.add(element); list.add(element);
@ -188,7 +188,7 @@ class UrlLinkifier extends Linkifier {
// If protocol has not been specified then append a protocol // If protocol has not been specified then append a protocol
// to the start of the URL so that it can be opened... // to the start of the URL so that it can be opened...
if (!url.startsWith("https://") && !url.startsWith("http://")) { if (!url.startsWith("https://") && !url.startsWith("http://")) {
url = "https://" + url; url = "https://$url";
} }
list.add(UrlElement(url, originalUrl)); list.add(UrlElement(url, originalUrl));
@ -211,7 +211,7 @@ class UrlLinkifier extends Linkifier {
EnvironmentConfig.debugLog("'unreachable' code path in formatting has been triggered. this is very likely a bug - please report $options"); EnvironmentConfig.debugLog("'unreachable' code path in formatting has been triggered. this is very likely a bug - please report $options");
} }
} }
}); }
return list; return list;
} }

View File

@ -252,7 +252,7 @@ class _NetworkManagerObject extends DBusRemoteObject {
return (value as DBusArray).children.map((value) => convertData(value)).toList(); return (value as DBusArray).children.map((value) => convertData(value)).toList();
} }
_NetworkManagerObject(DBusClient client, DBusObjectPath path, Map<String, Map<String, DBusValue>> interfacesAndProperties) : super(client, name: 'org.freedesktop.NetworkManager', path: path) { _NetworkManagerObject(super.client, DBusObjectPath path, Map<String, Map<String, DBusValue>> interfacesAndProperties) : super(name: 'org.freedesktop.NetworkManager', path: path) {
updateInterfaces(interfacesAndProperties); updateInterfaces(interfacesAndProperties);
} }
} }
@ -284,7 +284,7 @@ class NetworkManagerClient {
} }
/// Stream of property names as their values change. /// Stream of property names as their values change.
Stream<List<String>> get propertiesChanged => _manager?.interfaces[_managerInterfaceName]?.propertiesChangedStreamController.stream ?? Stream<List<String>>.empty(); Stream<List<String>> get propertiesChanged => _manager?.interfaces[_managerInterfaceName]?.propertiesChangedStreamController.stream ?? const Stream<List<String>>.empty();
/// Connects to the NetworkManager D-Bus objects. /// Connects to the NetworkManager D-Bus objects.
/// Must be called before accessing methods and properties. /// Must be called before accessing methods and properties.

View File

@ -9,24 +9,24 @@ class TorStatus extends ChangeNotifier {
TorStatus({this.connected = false, this.progress = 0, this.status = "", this.version = ""}); TorStatus({this.connected = false, this.progress = 0, this.status = "", this.version = ""});
/// Called by the event bus. /// Called by the event bus.
handleUpdate(int new_progress, String new_status) { handleUpdate(int newProgress, String newStatus) {
if (progress == 100) { if (progress == 100) {
connected = true; connected = true;
} else { } else {
connected = false; connected = false;
} }
progress = new_progress; progress = newProgress;
status = new_status; status = newStatus;
if (new_progress != 100) { if (newProgress != 100) {
status = "$new_progress% - $new_status"; status = "$newProgress% - $newStatus";
} }
notifyListeners(); notifyListeners();
} }
updateVersion(String new_version) { updateVersion(String newVersion) {
version = new_version; version = newVersion;
notifyListeners(); notifyListeners();
} }
} }

View File

@ -1,12 +1,9 @@
import 'dart:convert';
import 'package:cwtch/cwtch_icons_icons.dart'; import 'package:cwtch/cwtch_icons_icons.dart';
import 'package:cwtch/models/profile.dart'; import 'package:cwtch/models/profile.dart';
import 'package:cwtch/models/remoteserver.dart'; import 'package:cwtch/models/remoteserver.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:cwtch/errorHandler.dart'; import 'package:cwtch/errorHandler.dart';
import 'package:cwtch/models/profileservers.dart';
import 'package:cwtch/settings.dart'; import 'package:cwtch/settings.dart';
import 'package:cwtch/widgets/buttontextfield.dart'; import 'package:cwtch/widgets/buttontextfield.dart';
import 'package:cwtch/widgets/cwtchlabel.dart'; import 'package:cwtch/widgets/cwtchlabel.dart';
@ -23,7 +20,7 @@ import '../main.dart';
class AddContactView extends StatefulWidget { class AddContactView extends StatefulWidget {
final newGroup; final newGroup;
const AddContactView({Key? key, this.newGroup}) : super(key: key); const AddContactView({super.key, this.newGroup});
@override @override
_AddContactViewState createState() => _AddContactViewState(); _AddContactViewState createState() => _AddContactViewState();
@ -80,7 +77,7 @@ class _AddContactViewState extends State<AddContactView> {
} }
void _copyOnion() { void _copyOnion() {
Clipboard.setData(new ClipboardData(text: Provider.of<ProfileInfoState>(context, listen: false).onion)); Clipboard.setData(ClipboardData(text: Provider.of<ProfileInfoState>(context, listen: false).onion));
final snackBar = SnackBar(content: Text(AppLocalizations.of(context)!.copiedToClipboardNotification)); final snackBar = SnackBar(content: Text(AppLocalizations.of(context)!.copiedToClipboardNotification));
ScaffoldMessenger.of(context).showSnackBar(snackBar); ScaffoldMessenger.of(context).showSnackBar(snackBar);
} }
@ -90,7 +87,7 @@ class _AddContactViewState extends State<AddContactView> {
return TabBar( return TabBar(
tabs: [ tabs: [
Tab( Tab(
icon: Icon(CwtchIcons.add_peer), icon: const Icon(CwtchIcons.add_peer),
text: AppLocalizations.of(context)!.addPeer, text: AppLocalizations.of(context)!.addPeer,
), ),
], ],
@ -102,11 +99,11 @@ class _AddContactViewState extends State<AddContactView> {
return TabBar( return TabBar(
tabs: [ tabs: [
Tab( Tab(
icon: Icon(CwtchIcons.add_peer), icon: const Icon(CwtchIcons.add_peer),
text: AppLocalizations.of(context)!.tooltipAddContact, text: AppLocalizations.of(context)!.tooltipAddContact,
), ),
//Tab(icon: Icon(Icons.backup), text: AppLocalizations.of(context)!.titleManageServers), //Tab(icon: Icon(Icons.backup), text: AppLocalizations.of(context)!.titleManageServers),
Tab(icon: Icon(CwtchIcons.add_group), text: AppLocalizations.of(context)!.createGroup), Tab(icon: const Icon(CwtchIcons.add_group), text: AppLocalizations.of(context)!.createGroup),
], ],
); );
} }
@ -121,35 +118,35 @@ class _AddContactViewState extends State<AddContactView> {
clipBehavior: Clip.antiAlias, clipBehavior: Clip.antiAlias,
controller: controller, controller: controller,
child: Container( child: Container(
margin: EdgeInsets.all(30), margin: const EdgeInsets.all(30),
padding: EdgeInsets.all(20), padding: const EdgeInsets.all(20),
child: Form( child: Form(
autovalidateMode: AutovalidateMode.always, autovalidateMode: AutovalidateMode.always,
key: _formKey, key: _formKey,
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
CwtchLabel(label: AppLocalizations.of(context)!.profileOnionLabel), CwtchLabel(label: AppLocalizations.of(context)!.profileOnionLabel),
SizedBox( const SizedBox(
height: 20, height: 20,
), ),
CwtchButtonTextField( CwtchButtonTextField(
controller: ctrlrOnion, controller: ctrlrOnion,
onPressed: _copyOnion, onPressed: _copyOnion,
readonly: true, readonly: true,
icon: Icon( icon: const Icon(
CwtchIcons.address_copy, CwtchIcons.address_copy,
size: 32, size: 32,
), ),
tooltip: AppLocalizations.of(context)!.copyBtn, tooltip: AppLocalizations.of(context)!.copyBtn,
), ),
SizedBox( const SizedBox(
height: 20, height: 20,
), ),
CwtchLabel(label: AppLocalizations.of(context)!.pasteAddressToAddContact), CwtchLabel(label: AppLocalizations.of(context)!.pasteAddressToAddContact),
SizedBox( const SizedBox(
height: 20, height: 20,
), ),
CwtchTextField( CwtchTextField(
key: Key("txtAddP2P"), key: const Key("txtAddP2P"),
controller: ctrlrContact, controller: ctrlrContact,
validator: (value) { validator: (value) {
if (value == "") { if (value == "") {
@ -196,8 +193,8 @@ class _AddContactViewState extends State<AddContactView> {
clipBehavior: Clip.antiAlias, clipBehavior: Clip.antiAlias,
controller: controller, controller: controller,
child: Container( child: Container(
margin: EdgeInsets.all(30), margin: const EdgeInsets.all(30),
padding: EdgeInsets.all(20), padding: const EdgeInsets.all(20),
child: Form( child: Form(
autovalidateMode: AutovalidateMode.always, autovalidateMode: AutovalidateMode.always,
key: _createGroupFormKey, key: _createGroupFormKey,
@ -206,7 +203,7 @@ class _AddContactViewState extends State<AddContactView> {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
CwtchLabel(label: AppLocalizations.of(context)!.server), CwtchLabel(label: AppLocalizations.of(context)!.server),
SizedBox( const SizedBox(
height: 20, height: 20,
), ),
DropdownButton( DropdownButton(
@ -226,20 +223,22 @@ class _AddContactViewState extends State<AddContactView> {
), ),
); );
}).toList()), }).toList()),
SizedBox( const SizedBox(
height: 20, height: 20,
), ),
CwtchLabel(label: AppLocalizations.of(bcontext)!.groupNameLabel), CwtchLabel(label: AppLocalizations.of(bcontext)!.groupNameLabel),
SizedBox( const SizedBox(
height: 20, height: 20,
), ),
CwtchTextField( CwtchTextField(
controller: ctrlrGroupName, controller: ctrlrGroupName,
hintText: AppLocalizations.of(bcontext)!.groupNameLabel, hintText: AppLocalizations.of(bcontext)!.groupNameLabel,
onChanged: (newValue) {}, onChanged: (newValue) {},
validator: (value) {}, validator: (value) {
return null;
},
), ),
SizedBox( const SizedBox(
height: 20, height: 20,
), ),
ElevatedButton( ElevatedButton(
@ -247,7 +246,7 @@ class _AddContactViewState extends State<AddContactView> {
var profileOnion = Provider.of<ProfileInfoState>(bcontext, listen: false).onion; var profileOnion = Provider.of<ProfileInfoState>(bcontext, listen: false).onion;
Provider.of<FlwtchState>(bcontext, listen: false).cwtch.CreateGroup(profileOnion, server, ctrlrGroupName.text); Provider.of<FlwtchState>(bcontext, listen: false).cwtch.CreateGroup(profileOnion, server, ctrlrGroupName.text);
Future.delayed(const Duration(milliseconds: 500), () { Future.delayed(const Duration(milliseconds: 500), () {
final snackBar = SnackBar(content: Text(AppLocalizations.of(context)!.successfullAddedContact + " " + ctrlrGroupName.text)); final snackBar = SnackBar(content: Text("${AppLocalizations.of(context)!.successfullAddedContact} ${ctrlrGroupName.text}"));
ScaffoldMessenger.of(bcontext).showSnackBar(snackBar); ScaffoldMessenger.of(bcontext).showSnackBar(snackBar);
Navigator.pop(bcontext); Navigator.pop(bcontext);
}); });

View File

@ -20,11 +20,10 @@ import '../constants.dart';
import '../cwtch_icons_icons.dart'; import '../cwtch_icons_icons.dart';
import '../errorHandler.dart'; import '../errorHandler.dart';
import '../main.dart'; import '../main.dart';
import '../themes/opaque.dart';
import '../settings.dart'; import '../settings.dart';
class AddEditProfileView extends StatefulWidget { class AddEditProfileView extends StatefulWidget {
const AddEditProfileView({Key? key}) : super(key: key); const AddEditProfileView({super.key});
@override @override
_AddEditProfileViewState createState() => _AddEditProfileViewState(); _AddEditProfileViewState createState() => _AddEditProfileViewState();
@ -93,8 +92,8 @@ class _AddEditProfileViewState extends State<AddEditProfileView> {
child: Form( child: Form(
key: _formKey, key: _formKey,
child: Container( child: Container(
margin: EdgeInsets.all(30), margin: const EdgeInsets.all(30),
padding: EdgeInsets.all(20), padding: const EdgeInsets.all(20),
child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.center, children: [ child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.center, children: [
Visibility( Visibility(
visible: Provider.of<ProfileInfoState>(context).onion.isNotEmpty, visible: Provider.of<ProfileInfoState>(context).onion.isNotEmpty,
@ -121,7 +120,7 @@ class _AddEditProfileViewState extends State<AddEditProfileView> {
}, () { }, () {
final snackBar = SnackBar( final snackBar = SnackBar(
content: Text(AppLocalizations.of(context)!.msgFileTooBig), content: Text(AppLocalizations.of(context)!.msgFileTooBig),
duration: Duration(seconds: 4), duration: const Duration(seconds: 4),
); );
ScaffoldMessenger.of(context).showSnackBar(snackBar); ScaffoldMessenger.of(context).showSnackBar(snackBar);
}, () {}); }, () {});
@ -145,7 +144,7 @@ class _AddEditProfileViewState extends State<AddEditProfileView> {
child: Column( child: Column(
children: [ children: [
Padding( Padding(
padding: EdgeInsets.all(5.0), padding: const EdgeInsets.all(5.0),
child: CwtchTextField( child: CwtchTextField(
controller: ctrlrAttribute1, controller: ctrlrAttribute1,
multiLine: false, multiLine: false,
@ -154,9 +153,9 @@ class _AddEditProfileViewState extends State<AddEditProfileView> {
Provider.of<FlwtchState>(context, listen: false).cwtch.SetProfileAttribute(onion, "profile.profile-attribute-1", profileAttribute1); Provider.of<FlwtchState>(context, listen: false).cwtch.SetProfileAttribute(onion, "profile.profile-attribute-1", profileAttribute1);
Provider.of<ProfileInfoState>(context, listen: false).attributes[0] = profileAttribute1; Provider.of<ProfileInfoState>(context, listen: false).attributes[0] = profileAttribute1;
}, },
hintText: Provider.of<ProfileInfoState>(context).attributes[0] ?? AppLocalizations.of(context)!.profileInfoHint!)), hintText: Provider.of<ProfileInfoState>(context).attributes[0] ?? AppLocalizations.of(context)!.profileInfoHint)),
Padding( Padding(
padding: EdgeInsets.all(5.0), padding: const EdgeInsets.all(5.0),
child: CwtchTextField( child: CwtchTextField(
controller: ctrlrAttribute2, controller: ctrlrAttribute2,
multiLine: false, multiLine: false,
@ -165,9 +164,9 @@ class _AddEditProfileViewState extends State<AddEditProfileView> {
Provider.of<FlwtchState>(context, listen: false).cwtch.SetProfileAttribute(onion, "profile.profile-attribute-2", profileAttribute2); Provider.of<FlwtchState>(context, listen: false).cwtch.SetProfileAttribute(onion, "profile.profile-attribute-2", profileAttribute2);
Provider.of<ProfileInfoState>(context, listen: false).attributes[1] = profileAttribute2; Provider.of<ProfileInfoState>(context, listen: false).attributes[1] = profileAttribute2;
}, },
hintText: Provider.of<ProfileInfoState>(context).attributes[1] ?? AppLocalizations.of(context)!.profileInfoHint2!)), hintText: Provider.of<ProfileInfoState>(context).attributes[1] ?? AppLocalizations.of(context)!.profileInfoHint2)),
Padding( Padding(
padding: EdgeInsets.all(5.0), padding: const EdgeInsets.all(5.0),
child: CwtchTextField( child: CwtchTextField(
controller: ctrlrAttribute3, controller: ctrlrAttribute3,
multiLine: false, multiLine: false,
@ -176,21 +175,21 @@ class _AddEditProfileViewState extends State<AddEditProfileView> {
Provider.of<FlwtchState>(context, listen: false).cwtch.SetProfileAttribute(onion, "profile.profile-attribute-3", profileAttribute3); Provider.of<FlwtchState>(context, listen: false).cwtch.SetProfileAttribute(onion, "profile.profile-attribute-3", profileAttribute3);
Provider.of<ProfileInfoState>(context, listen: false).attributes[2] = profileAttribute3; Provider.of<ProfileInfoState>(context, listen: false).attributes[2] = profileAttribute3;
}, },
hintText: Provider.of<ProfileInfoState>(context).attributes[2] ?? AppLocalizations.of(context)!.profileInfoHint3!)), hintText: Provider.of<ProfileInfoState>(context).attributes[2] ?? AppLocalizations.of(context)!.profileInfoHint3)),
], ],
)) ))
], ],
)), )),
Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
SizedBox( const SizedBox(
height: 20, height: 20,
), ),
CwtchLabel(label: AppLocalizations.of(context)!.displayNameLabel), CwtchLabel(label: AppLocalizations.of(context)!.displayNameLabel),
SizedBox( const SizedBox(
height: 20, height: 20,
), ),
CwtchTextField( CwtchTextField(
key: Key("displayNameFormElement"), key: const Key("displayNameFormElement"),
controller: ctrlrNick, controller: ctrlrNick,
autofocus: false, autofocus: false,
hintText: AppLocalizations.of(context)!.yourDisplayName, hintText: AppLocalizations.of(context)!.yourDisplayName,
@ -205,18 +204,18 @@ class _AddEditProfileViewState extends State<AddEditProfileView> {
Visibility( Visibility(
visible: Provider.of<ProfileInfoState>(context).onion.isNotEmpty, visible: Provider.of<ProfileInfoState>(context).onion.isNotEmpty,
child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [
SizedBox( const SizedBox(
height: 20, height: 20,
), ),
CwtchLabel(label: AppLocalizations.of(context)!.addressLabel), CwtchLabel(label: AppLocalizations.of(context)!.addressLabel),
SizedBox( const SizedBox(
height: 20, height: 20,
), ),
CwtchButtonTextField( CwtchButtonTextField(
controller: ctrlrOnion, controller: ctrlrOnion,
onPressed: _copyOnion, onPressed: _copyOnion,
readonly: true, readonly: true,
icon: Icon( icon: const Icon(
CwtchIcons.address_copy, CwtchIcons.address_copy,
size: 32, size: 32,
), ),
@ -258,7 +257,7 @@ class _AddEditProfileViewState extends State<AddEditProfileView> {
onChanged: (bool value) { onChanged: (bool value) {
Provider.of<ProfileInfoState>(context, listen: false).autostart = value; Provider.of<ProfileInfoState>(context, listen: false).autostart = value;
if (!Provider.of<ProfileInfoState>(context, listen: false).onion.isEmpty) { if (Provider.of<ProfileInfoState>(context, listen: false).onion.isNotEmpty) {
Provider.of<FlwtchState>(context, listen: false) Provider.of<FlwtchState>(context, listen: false)
.cwtch .cwtch
.SetProfileAttribute(Provider.of<ProfileInfoState>(context, listen: false).onion, "profile.autostart", value ? "true" : "false"); .SetProfileAttribute(Provider.of<ProfileInfoState>(context, listen: false).onion, "profile.autostart", value ? "true" : "false");
@ -280,7 +279,7 @@ class _AddEditProfileViewState extends State<AddEditProfileView> {
onChanged: (bool value) { onChanged: (bool value) {
Provider.of<ProfileInfoState>(context, listen: false).appearOfflineAtStartup = value; Provider.of<ProfileInfoState>(context, listen: false).appearOfflineAtStartup = value;
var onion = Provider.of<ProfileInfoState>(context, listen: false).onion; var onion = Provider.of<ProfileInfoState>(context, listen: false).onion;
if (!onion.isEmpty) { if (onion.isNotEmpty) {
Provider.of<FlwtchState>(context, listen: false).cwtch.SetProfileAttribute(onion, "profile.appear-offline", value ? "true" : "false"); Provider.of<FlwtchState>(context, listen: false).cwtch.SetProfileAttribute(onion, "profile.appear-offline", value ? "true" : "false");
} }
}, },
@ -291,14 +290,14 @@ class _AddEditProfileViewState extends State<AddEditProfileView> {
Visibility( Visibility(
visible: Provider.of<ProfileInfoState>(context).onion.isEmpty, visible: Provider.of<ProfileInfoState>(context).onion.isEmpty,
child: SizedBox( child: const SizedBox(
height: 20, height: 20,
)), )),
Visibility( Visibility(
visible: Provider.of<ProfileInfoState>(context).onion.isEmpty, visible: Provider.of<ProfileInfoState>(context).onion.isEmpty,
child: Column(mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ child: Column(mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[
Checkbox( Checkbox(
key: Key("passwordCheckBox"), key: const Key("passwordCheckBox"),
value: usePassword, value: usePassword,
fillColor: MaterialStateProperty.all(theme.current().defaultButtonColor), fillColor: MaterialStateProperty.all(theme.current().defaultButtonColor),
activeColor: theme.current().defaultButtonActiveColor, activeColor: theme.current().defaultButtonActiveColor,
@ -308,17 +307,17 @@ class _AddEditProfileViewState extends State<AddEditProfileView> {
AppLocalizations.of(context)!.radioUsePassword, AppLocalizations.of(context)!.radioUsePassword,
style: TextStyle(color: theme.current().mainTextColor), style: TextStyle(color: theme.current().mainTextColor),
), ),
SizedBox( const SizedBox(
height: 20, height: 20,
), ),
Padding( Padding(
padding: EdgeInsets.symmetric(horizontal: 24), padding: const EdgeInsets.symmetric(horizontal: 24),
child: Text( child: Text(
usePassword ? AppLocalizations.of(context)!.encryptedProfileDescription : AppLocalizations.of(context)!.plainProfileDescription, usePassword ? AppLocalizations.of(context)!.encryptedProfileDescription : AppLocalizations.of(context)!.plainProfileDescription,
textAlign: TextAlign.center, textAlign: TextAlign.center,
)) ))
])), ])),
SizedBox( const SizedBox(
height: 20, height: 20,
), ),
Visibility( Visibility(
@ -328,13 +327,13 @@ class _AddEditProfileViewState extends State<AddEditProfileView> {
visible: Provider.of<ProfileInfoState>(context, listen: false).onion.isNotEmpty && Provider.of<ProfileInfoState>(context).isEncrypted, visible: Provider.of<ProfileInfoState>(context, listen: false).onion.isNotEmpty && Provider.of<ProfileInfoState>(context).isEncrypted,
child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [
CwtchLabel(label: AppLocalizations.of(context)!.currentPasswordLabel), CwtchLabel(label: AppLocalizations.of(context)!.currentPasswordLabel),
SizedBox( const SizedBox(
height: 20, height: 20,
), ),
CwtchPasswordField( CwtchPasswordField(
key: Key("currentPasswordFormElement"), key: const Key("currentPasswordFormElement"),
controller: ctrlrOldPass, controller: ctrlrOldPass,
autoFillHints: [AutofillHints.newPassword], autoFillHints: const [AutofillHints.newPassword],
validator: (value) { validator: (value) {
// Password field can be empty when just updating the profile, not on creation // Password field can be empty when just updating the profile, not on creation
if (Provider.of<ProfileInfoState>(context, listen: false).isEncrypted && if (Provider.of<ProfileInfoState>(context, listen: false).isEncrypted &&
@ -349,16 +348,16 @@ class _AddEditProfileViewState extends State<AddEditProfileView> {
return null; return null;
}, },
), ),
SizedBox( const SizedBox(
height: 20, height: 20,
), ),
])), ])),
CwtchLabel(label: AppLocalizations.of(context)!.newPassword), CwtchLabel(label: AppLocalizations.of(context)!.newPassword),
SizedBox( const SizedBox(
height: 20, height: 20,
), ),
CwtchPasswordField( CwtchPasswordField(
key: Key("passwordFormElement"), key: const Key("passwordFormElement"),
controller: ctrlrPass, controller: ctrlrPass,
validator: (value) { validator: (value) {
// Password field can be empty when just updating the profile, not on creation // Password field can be empty when just updating the profile, not on creation
@ -371,15 +370,15 @@ class _AddEditProfileViewState extends State<AddEditProfileView> {
return null; return null;
}, },
), ),
SizedBox( const SizedBox(
height: 20, height: 20,
), ),
CwtchLabel(label: AppLocalizations.of(context)!.password2Label), CwtchLabel(label: AppLocalizations.of(context)!.password2Label),
SizedBox( const SizedBox(
height: 20, height: 20,
), ),
CwtchPasswordField( CwtchPasswordField(
key: Key("confirmPasswordFormElement"), key: const Key("confirmPasswordFormElement"),
controller: ctrlrPass2, controller: ctrlrPass2,
validator: (value) { validator: (value) {
// Password field can be empty when just updating the profile, not on creation // Password field can be empty when just updating the profile, not on creation
@ -393,22 +392,22 @@ class _AddEditProfileViewState extends State<AddEditProfileView> {
}), }),
]), ]),
), ),
SizedBox( const SizedBox(
height: 20, height: 20,
), ),
ElevatedButton( ElevatedButton(
onPressed: _createPressed, onPressed: _createPressed,
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
minimumSize: Size(400, 75), minimumSize: const Size(400, 75),
maximumSize: Size(800, 75), maximumSize: const Size(800, 75),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.horizontal(left: Radius.circular(180), right: Radius.circular(180))), shape: const RoundedRectangleBorder(borderRadius: BorderRadius.horizontal(left: Radius.circular(180), right: Radius.circular(180))),
), ),
child: Text( child: Text(
Provider.of<ProfileInfoState>(context).onion.isEmpty ? AppLocalizations.of(context)!.addNewProfileBtn : AppLocalizations.of(context)!.saveProfileBtn, Provider.of<ProfileInfoState>(context).onion.isEmpty ? AppLocalizations.of(context)!.addNewProfileBtn : AppLocalizations.of(context)!.saveProfileBtn,
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
), ),
SizedBox( const SizedBox(
height: 20, height: 20,
), ),
Visibility( Visibility(
@ -417,29 +416,29 @@ class _AddEditProfileViewState extends State<AddEditProfileView> {
message: AppLocalizations.of(context)!.exportProfileTooltip, message: AppLocalizations.of(context)!.exportProfileTooltip,
child: ElevatedButton.icon( child: ElevatedButton.icon(
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
minimumSize: Size(400, 75), minimumSize: const Size(400, 75),
maximumSize: Size(800, 75), maximumSize: const Size(800, 75),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.horizontal(left: Radius.circular(180), right: Radius.circular(180))), shape: const RoundedRectangleBorder(borderRadius: BorderRadius.horizontal(left: Radius.circular(180), right: Radius.circular(180))),
), ),
onPressed: () { onPressed: () {
if (Platform.isAndroid) { if (Platform.isAndroid) {
Provider.of<FlwtchState>(context, listen: false).cwtch.ExportProfile(ctrlrOnion.value.text, ctrlrOnion.value.text + ".tar.gz"); Provider.of<FlwtchState>(context, listen: false).cwtch.ExportProfile(ctrlrOnion.value.text, "${ctrlrOnion.value.text}.tar.gz");
final snackBar = SnackBar(content: Text(AppLocalizations.of(context)!.fileSavedTo + " " + ctrlrOnion.value.text + ".tar.gz")); final snackBar = SnackBar(content: Text("${AppLocalizations.of(context)!.fileSavedTo} ${ctrlrOnion.value.text}.tar.gz"));
ScaffoldMessenger.of(context).showSnackBar(snackBar); ScaffoldMessenger.of(context).showSnackBar(snackBar);
} else { } else {
showCreateFilePicker(context).then((name) { showCreateFilePicker(context).then((name) {
if (name != null) { if (name != null) {
Provider.of<FlwtchState>(context, listen: false).cwtch.ExportProfile(ctrlrOnion.value.text, name); Provider.of<FlwtchState>(context, listen: false).cwtch.ExportProfile(ctrlrOnion.value.text, name);
final snackBar = SnackBar(content: Text(AppLocalizations.of(context)!.fileSavedTo + " " + name)); final snackBar = SnackBar(content: Text("${AppLocalizations.of(context)!.fileSavedTo} $name"));
ScaffoldMessenger.of(context).showSnackBar(snackBar); ScaffoldMessenger.of(context).showSnackBar(snackBar);
} }
}); });
} }
}, },
icon: Icon(Icons.import_export), icon: const Icon(Icons.import_export),
label: Text(AppLocalizations.of(context)!.exportProfile), label: Text(AppLocalizations.of(context)!.exportProfile),
))), ))),
SizedBox( const SizedBox(
height: 20, height: 20,
), ),
Visibility( Visibility(
@ -448,17 +447,17 @@ class _AddEditProfileViewState extends State<AddEditProfileView> {
message: AppLocalizations.of(context)!.enterCurrentPasswordForDelete, message: AppLocalizations.of(context)!.enterCurrentPasswordForDelete,
child: ElevatedButton.icon( child: ElevatedButton.icon(
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
minimumSize: Size(400, 75), minimumSize: const Size(400, 75),
maximumSize: Size(800, 75), backgroundColor: Provider.of<Settings>(context).theme.backgroundMainColor,
maximumSize: const Size(800, 75),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
side: BorderSide(color: Provider.of<Settings>(context).theme.defaultButtonActiveColor, width: 2.0), side: BorderSide(color: Provider.of<Settings>(context).theme.defaultButtonActiveColor, width: 2.0),
borderRadius: BorderRadius.horizontal(left: Radius.circular(180), right: Radius.circular(180))), borderRadius: const BorderRadius.horizontal(left: Radius.circular(180), right: Radius.circular(180))),
primary: Provider.of<Settings>(context).theme.backgroundMainColor,
), ),
onPressed: () { onPressed: () {
showAlertDialog(context); showAlertDialog(context);
}, },
icon: Icon(Icons.delete_forever), icon: const Icon(Icons.delete_forever),
label: Text(AppLocalizations.of(context)!.deleteBtn, style: TextStyle(color: Provider.of<Settings>(context).theme.defaultButtonActiveColor)), label: Text(AppLocalizations.of(context)!.deleteBtn, style: TextStyle(color: Provider.of<Settings>(context).theme.defaultButtonActiveColor)),
))) )))
])))))); ]))))));
@ -467,7 +466,7 @@ class _AddEditProfileViewState extends State<AddEditProfileView> {
} }
void _copyOnion() { void _copyOnion() {
Clipboard.setData(new ClipboardData(text: Provider.of<ProfileInfoState>(context, listen: false).onion)); Clipboard.setData(ClipboardData(text: Provider.of<ProfileInfoState>(context, listen: false).onion));
final snackBar = SnackBar(content: Text(AppLocalizations.of(context)!.copiedToClipboardNotification)); final snackBar = SnackBar(content: Text(AppLocalizations.of(context)!.copiedToClipboardNotification));
ScaffoldMessenger.of(context).showSnackBar(snackBar); ScaffoldMessenger.of(context).showSnackBar(snackBar);
} }
@ -547,7 +546,7 @@ class _AddEditProfileViewState extends State<AddEditProfileView> {
const Duration(milliseconds: 500), const Duration(milliseconds: 500),
() { () {
if (globalErrorHandler.deleteProfileSuccess) { if (globalErrorHandler.deleteProfileSuccess) {
final snackBar = SnackBar(content: Text(AppLocalizations.of(context)!.deleteProfileSuccess + ":" + onion)); final snackBar = SnackBar(content: Text("${AppLocalizations.of(context)!.deleteProfileSuccess}:$onion"));
ScaffoldMessenger.of(context).showSnackBar(snackBar); ScaffoldMessenger.of(context).showSnackBar(snackBar);
Navigator.of(context).popUntil((route) => route.isFirst); // dismiss dialog Navigator.of(context).popUntil((route) => route.isFirst); // dismiss dialog
} else { } else {

View File

@ -1,11 +1,9 @@
import 'dart:convert';
import 'package:cwtch/cwtch/cwtch.dart'; import 'package:cwtch/cwtch/cwtch.dart';
import 'package:cwtch/cwtch_icons_icons.dart'; import 'package:cwtch/cwtch_icons_icons.dart';
import 'package:cwtch/models/servers.dart'; import 'package:cwtch/models/servers.dart';
import 'package:cwtch/widgets/cwtchlabel.dart'; import 'package:cwtch/widgets/cwtchlabel.dart';
import 'package:cwtch/widgets/passwordfield.dart'; import 'package:cwtch/widgets/passwordfield.dart';
import 'package:cwtch/widgets/textfield.dart'; import 'package:cwtch/widgets/textfield.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:cwtch/settings.dart'; import 'package:cwtch/settings.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@ -13,11 +11,10 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import '../errorHandler.dart'; import '../errorHandler.dart';
import '../main.dart'; import '../main.dart';
import '../config.dart';
/// Pane to add or edit a server /// Pane to add or edit a server
class AddEditServerView extends StatefulWidget { class AddEditServerView extends StatefulWidget {
const AddEditServerView(); const AddEditServerView({super.key});
@override @override
_AddEditServerViewState createState() => _AddEditServerViewState(); _AddEditServerViewState createState() => _AddEditServerViewState();
@ -83,8 +80,8 @@ class _AddEditServerViewState extends State<AddEditServerView> {
child: Form( child: Form(
key: _formKey, key: _formKey,
child: Container( child: Container(
margin: EdgeInsets.fromLTRB(30, 5, 30, 10), margin: const EdgeInsets.fromLTRB(30, 5, 30, 10),
padding: EdgeInsets.fromLTRB(20, 5, 20, 10), padding: const EdgeInsets.fromLTRB(20, 5, 20, 10),
child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.stretch, children: [
// Onion // Onion
Visibility( Visibility(
@ -96,12 +93,12 @@ class _AddEditServerViewState extends State<AddEditServerView> {
// Description // Description
Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [
SizedBox( const SizedBox(
height: 20, height: 20,
), ),
CwtchLabel(label: AppLocalizations.of(context)!.serverDescriptionLabel), CwtchLabel(label: AppLocalizations.of(context)!.serverDescriptionLabel),
Text(AppLocalizations.of(context)!.serverDescriptionDescription), Text(AppLocalizations.of(context)!.serverDescriptionDescription),
SizedBox( const SizedBox(
height: 20, height: 20,
), ),
CwtchTextField( CwtchTextField(
@ -111,7 +108,7 @@ class _AddEditServerViewState extends State<AddEditServerView> {
) )
]), ]),
SizedBox( const SizedBox(
height: 20, height: 20,
), ),
@ -143,7 +140,7 @@ class _AddEditServerViewState extends State<AddEditServerView> {
onChanged: (bool value) { onChanged: (bool value) {
serverInfoState.setAutostart(value); serverInfoState.setAutostart(value);
if (!serverInfoState.onion.isEmpty) { if (serverInfoState.onion.isNotEmpty) {
Provider.of<FlwtchState>(context, listen: false).cwtch.SetServerAttribute(serverInfoState.onion, "autostart", value ? "true" : "false"); Provider.of<FlwtchState>(context, listen: false).cwtch.SetServerAttribute(serverInfoState.onion, "autostart", value ? "true" : "false");
} }
}, },
@ -156,7 +153,7 @@ class _AddEditServerViewState extends State<AddEditServerView> {
Visibility( Visibility(
visible: serverInfoState.onion.isNotEmpty && serverInfoState.running, visible: serverInfoState.onion.isNotEmpty && serverInfoState.running,
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
SizedBox( const SizedBox(
height: 20, height: 20,
), ),
Text( Text(
@ -182,7 +179,7 @@ class _AddEditServerViewState extends State<AddEditServerView> {
Visibility( Visibility(
visible: serverInfoState.onion.isEmpty, visible: serverInfoState.onion.isEmpty,
child: Column(mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[ child: Column(mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[
SizedBox( const SizedBox(
height: 20, height: 20,
), ),
Checkbox( Checkbox(
@ -195,16 +192,16 @@ class _AddEditServerViewState extends State<AddEditServerView> {
AppLocalizations.of(context)!.radioUsePassword, AppLocalizations.of(context)!.radioUsePassword,
style: TextStyle(color: settings.current().mainTextColor), style: TextStyle(color: settings.current().mainTextColor),
), ),
SizedBox( const SizedBox(
height: 20, height: 20,
), ),
Padding( Padding(
padding: EdgeInsets.symmetric(horizontal: 24), padding: const EdgeInsets.symmetric(horizontal: 24),
child: Text( child: Text(
usePassword ? AppLocalizations.of(context)!.encryptedServerDescription : AppLocalizations.of(context)!.plainServerDescription, usePassword ? AppLocalizations.of(context)!.encryptedServerDescription : AppLocalizations.of(context)!.plainServerDescription,
textAlign: TextAlign.center, textAlign: TextAlign.center,
)), )),
SizedBox( const SizedBox(
height: 20, height: 20,
), ),
])), ])),
@ -214,12 +211,12 @@ class _AddEditServerViewState extends State<AddEditServerView> {
visible: serverInfoState.onion.isNotEmpty && serverInfoState.isEncrypted, visible: serverInfoState.onion.isNotEmpty && serverInfoState.isEncrypted,
child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: <Widget>[ child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: <Widget>[
CwtchLabel(label: AppLocalizations.of(context)!.currentPasswordLabel), CwtchLabel(label: AppLocalizations.of(context)!.currentPasswordLabel),
SizedBox( const SizedBox(
height: 20, height: 20,
), ),
CwtchPasswordField( CwtchPasswordField(
controller: ctrlrOldPass, controller: ctrlrOldPass,
autoFillHints: [AutofillHints.newPassword], autoFillHints: const [AutofillHints.newPassword],
validator: (value) { validator: (value) {
// Password field can be empty when just updating the profile, not on creation // Password field can be empty when just updating the profile, not on creation
if (serverInfoState.isEncrypted && serverInfoState.onion.isEmpty && value.isEmpty && usePassword) { if (serverInfoState.isEncrypted && serverInfoState.onion.isEmpty && value.isEmpty && usePassword) {
@ -231,7 +228,7 @@ class _AddEditServerViewState extends State<AddEditServerView> {
return null; return null;
}, },
), ),
SizedBox( const SizedBox(
height: 20, height: 20,
), ),
])), ])),
@ -242,7 +239,7 @@ class _AddEditServerViewState extends State<AddEditServerView> {
visible: serverInfoState.onion.isEmpty && usePassword, visible: serverInfoState.onion.isEmpty && usePassword,
child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [
CwtchLabel(label: AppLocalizations.of(context)!.newPassword), CwtchLabel(label: AppLocalizations.of(context)!.newPassword),
SizedBox( const SizedBox(
height: 20, height: 20,
), ),
CwtchPasswordField( CwtchPasswordField(
@ -258,11 +255,11 @@ class _AddEditServerViewState extends State<AddEditServerView> {
return null; return null;
}, },
), ),
SizedBox( const SizedBox(
height: 20, height: 20,
), ),
CwtchLabel(label: AppLocalizations.of(context)!.password2Label), CwtchLabel(label: AppLocalizations.of(context)!.password2Label),
SizedBox( const SizedBox(
height: 20, height: 20,
), ),
CwtchPasswordField( CwtchPasswordField(
@ -280,7 +277,7 @@ class _AddEditServerViewState extends State<AddEditServerView> {
]), ]),
), ),
SizedBox( const SizedBox(
height: 20, height: 20,
), ),
@ -301,7 +298,7 @@ class _AddEditServerViewState extends State<AddEditServerView> {
Visibility( Visibility(
visible: serverInfoState.onion.isNotEmpty, visible: serverInfoState.onion.isNotEmpty,
child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.end, children: [ child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.end, children: [
SizedBox( const SizedBox(
height: 20, height: 20,
), ),
Tooltip( Tooltip(
@ -310,7 +307,7 @@ class _AddEditServerViewState extends State<AddEditServerView> {
onPressed: () { onPressed: () {
showAlertDialog(context); showAlertDialog(context);
}, },
icon: Icon(Icons.delete_forever), icon: const Icon(Icons.delete_forever),
label: Text(AppLocalizations.of(context)!.deleteBtn), label: Text(AppLocalizations.of(context)!.deleteBtn),
)) ))
])) ]))
@ -364,7 +361,7 @@ class _AddEditServerViewState extends State<AddEditServerView> {
const Duration(milliseconds: 500), const Duration(milliseconds: 500),
() { () {
if (globalErrorHandler.deletedServerSuccess) { if (globalErrorHandler.deletedServerSuccess) {
final snackBar = SnackBar(content: Text(AppLocalizations.of(context)!.deleteServerSuccess + ":" + onion)); final snackBar = SnackBar(content: Text("${AppLocalizations.of(context)!.deleteServerSuccess}:$onion"));
ScaffoldMessenger.of(context).showSnackBar(snackBar); ScaffoldMessenger.of(context).showSnackBar(snackBar);
Navigator.of(context).popUntil((route) => route.settings.name == "servers"); // dismiss dialog Navigator.of(context).popUntil((route) => route.settings.name == "servers"); // dismiss dialog
} else { } else {

View File

@ -19,7 +19,6 @@ import 'package:provider/provider.dart';
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
import '../config.dart'; import '../config.dart';
import '../main.dart'; import '../main.dart';
import '../models/message.dart';
import '../models/redaction.dart'; import '../models/redaction.dart';
import '../settings.dart'; import '../settings.dart';
import '../themes/opaque.dart'; import '../themes/opaque.dart';
@ -35,7 +34,7 @@ enum ShareMenu { copyCode, qrcode }
enum ProfileStatusMenu { available, away, busy, appearOnline, appearOffline, allowUnknownContacts, blockUnknownContacts, enableProfile, disableProfile, editProfile } enum ProfileStatusMenu { available, away, busy, appearOnline, appearOffline, allowUnknownContacts, blockUnknownContacts, enableProfile, disableProfile, editProfile }
class ContactsView extends StatefulWidget { class ContactsView extends StatefulWidget {
const ContactsView({Key? key}) : super(key: key); const ContactsView({super.key});
@override @override
_ContactsViewState createState() => _ContactsViewState(); _ContactsViewState createState() => _ContactsViewState();
@ -43,18 +42,18 @@ class ContactsView extends StatefulWidget {
// selectConversation can be called from anywhere to set the active conversation // selectConversation can be called from anywhere to set the active conversation
void selectConversation(BuildContext context, int handle, int? messageIndex) { void selectConversation(BuildContext context, int handle, int? messageIndex) {
int? index = null; int? index;
if (messageIndex != null) { if (messageIndex != null) {
// this message is loaded // this message is loaded
Provider.of<AppState>(context, listen: false).selectedSearchMessage = messageIndex; Provider.of<AppState>(context, listen: false).selectedSearchMessage = messageIndex;
Provider.of<AppState>(context, listen: false).initialScrollIndex = messageIndex!; Provider.of<AppState>(context, listen: false).initialScrollIndex = messageIndex;
EnvironmentConfig.debugLog("Looked up index $messageIndex"); EnvironmentConfig.debugLog("Looked up index $messageIndex");
} }
if (handle == Provider.of<AppState>(context, listen: false).selectedConversation) { if (handle == Provider.of<AppState>(context, listen: false).selectedConversation) {
if (messageIndex != null) { if (messageIndex != null) {
Provider.of<ContactInfoState>(context, listen: false).messageScrollController.scrollTo(index: messageIndex, duration: Duration(milliseconds: 100)); Provider.of<ContactInfoState>(context, listen: false).messageScrollController.scrollTo(index: messageIndex, duration: const Duration(milliseconds: 100));
} }
return; return;
} }
@ -89,7 +88,7 @@ void _pushMessageView(BuildContext context, int handle) {
Navigator.of(context).push( Navigator.of(context).push(
PageRouteBuilder( PageRouteBuilder(
settings: RouteSettings(name: "messages"), settings: const RouteSettings(name: "messages"),
pageBuilder: (builderContext, a1, a2) { pageBuilder: (builderContext, a1, a2) {
var profile = Provider.of<FlwtchState>(builderContext).profs.getProfile(profileOnion)!; var profile = Provider.of<FlwtchState>(builderContext).profs.getProfile(profileOnion)!;
return MultiProvider( return MultiProvider(
@ -97,11 +96,11 @@ void _pushMessageView(BuildContext context, int handle) {
ChangeNotifierProvider.value(value: profile), ChangeNotifierProvider.value(value: profile),
ChangeNotifierProvider.value(value: profile.contactList.getContact(handle)!), ChangeNotifierProvider.value(value: profile.contactList.getContact(handle)!),
], ],
builder: (context, child) => MessageView(), builder: (context, child) => const MessageView(),
); );
}, },
transitionsBuilder: (c, anim, a2, child) => FadeTransition(opacity: anim, child: child), transitionsBuilder: (c, anim, a2, child) => FadeTransition(opacity: anim, child: child),
transitionDuration: Duration(milliseconds: 200), transitionDuration: const Duration(milliseconds: 200),
), ),
); );
} }
@ -114,7 +113,7 @@ class _ContactsViewState extends State<ContactsView> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
ctrlrFilter = new TextEditingController(text: Provider.of<ContactListState>(context, listen: false).filter); ctrlrFilter = TextEditingController(text: Provider.of<ContactListState>(context, listen: false).filter);
} }
@override @override
@ -132,7 +131,7 @@ class _ContactsViewState extends State<ContactsView> {
Align( Align(
alignment: Alignment.center, alignment: Alignment.center,
child: IconButton( child: IconButton(
icon: Icon(Icons.arrow_back), icon: const Icon(Icons.arrow_back),
tooltip: MaterialLocalizations.of(context).backButtonTooltip, tooltip: MaterialLocalizations.of(context).backButtonTooltip,
onPressed: () { onPressed: () {
Provider.of<ProfileInfoState>(context, listen: false).recountUnread(); Provider.of<ProfileInfoState>(context, listen: false).recountUnread();
@ -198,11 +197,11 @@ class _ContactsViewState extends State<ContactsView> {
providers: [ providers: [
ChangeNotifierProvider.value(value: Provider.of<ProfileInfoState>(context, listen: false)), ChangeNotifierProvider.value(value: Provider.of<ProfileInfoState>(context, listen: false)),
], ],
builder: (context, widget) => AddEditProfileView(key: Key('addprofile')), builder: (context, widget) => const AddEditProfileView(key: Key('addprofile')),
); );
}, },
transitionsBuilder: (c, anim, a2, child) => FadeTransition(opacity: anim, child: child), transitionsBuilder: (c, anim, a2, child) => FadeTransition(opacity: anim, child: child),
transitionDuration: Duration(milliseconds: 200), transitionDuration: const Duration(milliseconds: 200),
), ),
); );
break; break;
@ -238,12 +237,12 @@ class _ContactsViewState extends State<ContactsView> {
value: ProfileStatusMenu.available, value: ProfileStatusMenu.available,
enabled: enabled, enabled: enabled,
child: Row(children: [ child: Row(children: [
Icon( const Icon(
CwtchIcons.account_circle_24px, CwtchIcons.account_circle_24px,
color: Colors.white, color: Colors.white,
), ),
Expanded( Expanded(
child: Text(AppLocalizations.of(context)!.availabilityStatusAvailable!, child: Text(AppLocalizations.of(context)!.availabilityStatusAvailable,
textAlign: TextAlign.right, style: Provider.of<Settings>(context, listen: false).scaleFonts(defaultTextButtonStyle))) textAlign: TextAlign.right, style: Provider.of<Settings>(context, listen: false).scaleFonts(defaultTextButtonStyle)))
]), ]),
), ),
@ -251,12 +250,12 @@ class _ContactsViewState extends State<ContactsView> {
value: ProfileStatusMenu.away, value: ProfileStatusMenu.away,
enabled: enabled, enabled: enabled,
child: Row(children: [ child: Row(children: [
Icon( const Icon(
CwtchIcons.account_circle_24px, CwtchIcons.account_circle_24px,
color: Colors.yellowAccent, color: Colors.yellowAccent,
), ),
Expanded( Expanded(
child: Text(AppLocalizations.of(context)!.availabilityStatusAway!, child: Text(AppLocalizations.of(context)!.availabilityStatusAway,
textAlign: TextAlign.right, style: Provider.of<Settings>(context, listen: false).scaleFonts(defaultTextButtonStyle))) textAlign: TextAlign.right, style: Provider.of<Settings>(context, listen: false).scaleFonts(defaultTextButtonStyle)))
]), ]),
), ),
@ -264,23 +263,23 @@ class _ContactsViewState extends State<ContactsView> {
value: ProfileStatusMenu.busy, value: ProfileStatusMenu.busy,
enabled: enabled, enabled: enabled,
child: Row(children: [ child: Row(children: [
Icon( const Icon(
CwtchIcons.account_circle_24px, CwtchIcons.account_circle_24px,
color: Colors.redAccent, color: Colors.redAccent,
), ),
Expanded( Expanded(
child: Text(AppLocalizations.of(context)!.availabilityStatusBusy!, child: Text(AppLocalizations.of(context)!.availabilityStatusBusy,
textAlign: TextAlign.right, style: Provider.of<Settings>(context, listen: false).scaleFonts(defaultTextButtonStyle))) textAlign: TextAlign.right, style: Provider.of<Settings>(context, listen: false).scaleFonts(defaultTextButtonStyle)))
]), ]),
), ),
PopupMenuDivider(), const PopupMenuDivider(),
PopupMenuItem<ProfileStatusMenu>( PopupMenuItem<ProfileStatusMenu>(
value: ProfileStatusMenu.appearOffline, value: ProfileStatusMenu.appearOffline,
enabled: enabled && !appearOffline, enabled: enabled && !appearOffline,
child: Row(children: [ child: Row(children: [
Icon(CwtchIcons.disconnect_from_contact), const Icon(CwtchIcons.disconnect_from_contact),
Expanded( Expanded(
child: Text(AppLocalizations.of(context)!.profileAppearOffline!, child: Text(AppLocalizations.of(context)!.profileAppearOffline,
textAlign: TextAlign.right, style: Provider.of<Settings>(context, listen: false).scaleFonts(defaultTextButtonStyle))) textAlign: TextAlign.right, style: Provider.of<Settings>(context, listen: false).scaleFonts(defaultTextButtonStyle)))
]), ]),
), ),
@ -288,13 +287,13 @@ class _ContactsViewState extends State<ContactsView> {
value: ProfileStatusMenu.appearOnline, value: ProfileStatusMenu.appearOnline,
enabled: enabled && appearOffline, enabled: enabled && appearOffline,
child: Row(children: [ child: Row(children: [
Icon(CwtchIcons.disconnect_from_contact), const Icon(CwtchIcons.disconnect_from_contact),
Expanded( Expanded(
child: Text(AppLocalizations.of(context)!.profileAppearOnline!, child: Text(AppLocalizations.of(context)!.profileAppearOnline,
textAlign: TextAlign.right, style: Provider.of<Settings>(context, listen: false).scaleFonts(defaultTextButtonStyle))) textAlign: TextAlign.right, style: Provider.of<Settings>(context, listen: false).scaleFonts(defaultTextButtonStyle)))
]), ]),
), ),
PopupMenuDivider(), const PopupMenuDivider(),
PopupMenuItem<ProfileStatusMenu>( PopupMenuItem<ProfileStatusMenu>(
value: !settings.blockUnknownConnections ? ProfileStatusMenu.blockUnknownContacts : ProfileStatusMenu.allowUnknownContacts, value: !settings.blockUnknownConnections ? ProfileStatusMenu.blockUnknownContacts : ProfileStatusMenu.allowUnknownContacts,
child: Row(children: [ child: Row(children: [
@ -303,17 +302,17 @@ class _ContactsViewState extends State<ContactsView> {
color: settings.theme.mainTextColor, color: settings.theme.mainTextColor,
), ),
Expanded( Expanded(
child: Text((settings.blockUnknownConnections ? AppLocalizations.of(context)!.profileAllowUnknownContacts! : AppLocalizations.of(context)!.profileBlockUnknownContacts!), child: Text((settings.blockUnknownConnections ? AppLocalizations.of(context)!.profileAllowUnknownContacts : AppLocalizations.of(context)!.profileBlockUnknownContacts),
textAlign: TextAlign.right, style: Provider.of<Settings>(context, listen: false).scaleFonts(defaultTextButtonStyle))) textAlign: TextAlign.right, style: Provider.of<Settings>(context, listen: false).scaleFonts(defaultTextButtonStyle)))
]), ]),
), ),
PopupMenuDivider(), const PopupMenuDivider(),
PopupMenuItem<ProfileStatusMenu>( PopupMenuItem<ProfileStatusMenu>(
value: enabled ? ProfileStatusMenu.disableProfile : ProfileStatusMenu.enableProfile, value: enabled ? ProfileStatusMenu.disableProfile : ProfileStatusMenu.enableProfile,
child: Row(children: [ child: Row(children: [
Icon(CwtchIcons.favorite_24dp, color: settings.theme.mainTextColor), Icon(CwtchIcons.favorite_24dp, color: settings.theme.mainTextColor),
Expanded( Expanded(
child: Text((enabled ? AppLocalizations.of(context)!.profileDisableProfile! : AppLocalizations.of(context)!.profileEnableProfile!), child: Text((enabled ? AppLocalizations.of(context)!.profileDisableProfile : AppLocalizations.of(context)!.profileEnableProfile),
textAlign: TextAlign.right, style: Provider.of<Settings>(context, listen: false).scaleFonts(defaultTextButtonStyle))) textAlign: TextAlign.right, style: Provider.of<Settings>(context, listen: false).scaleFonts(defaultTextButtonStyle)))
]), ]),
), ),
@ -326,13 +325,12 @@ class _ContactsViewState extends State<ContactsView> {
color: settings.theme.mainTextColor, color: settings.theme.mainTextColor,
), ),
Expanded( Expanded(
child: child: Text(AppLocalizations.of(context)!.editProfile, textAlign: TextAlign.right, style: Provider.of<Settings>(context, listen: false).scaleFonts(defaultTextButtonStyle)))
Text(AppLocalizations.of(context)!.editProfile!, textAlign: TextAlign.right, style: Provider.of<Settings>(context, listen: false).scaleFonts(defaultTextButtonStyle)))
]), ]),
), ),
], ],
), ),
SizedBox( const SizedBox(
width: 10, width: 10,
), ),
Expanded( Expanded(
@ -363,26 +361,26 @@ class _ContactsViewState extends State<ContactsView> {
List<Widget> getActions(context) { List<Widget> getActions(context) {
var actions = List<Widget>.empty(growable: true); var actions = List<Widget>.empty(growable: true);
if (Provider.of<Settings>(context).blockUnknownConnections) { if (Provider.of<Settings>(context).blockUnknownConnections) {
actions.add(Tooltip(message: AppLocalizations.of(context)!.blockUnknownConnectionsEnabledDescription, child: Icon(CwtchIcons.block_unknown))); actions.add(Tooltip(message: AppLocalizations.of(context)!.blockUnknownConnectionsEnabledDescription, child: const Icon(CwtchIcons.block_unknown)));
} }
if (Provider.of<Settings>(context, listen: false).isExperimentEnabled(QRCodeExperiment)) { if (Provider.of<Settings>(context, listen: false).isExperimentEnabled(QRCodeExperiment)) {
actions.add(PopupMenuButton<ShareMenu>( actions.add(PopupMenuButton<ShareMenu>(
icon: Icon(CwtchIcons.address_copy), icon: const Icon(CwtchIcons.address_copy),
tooltip: AppLocalizations.of(context)!.shareProfileMenuTooltop, tooltip: AppLocalizations.of(context)!.shareProfileMenuTooltop,
splashRadius: Material.defaultSplashRadius / 2, splashRadius: Material.defaultSplashRadius / 2,
onSelected: (ShareMenu item) { onSelected: (ShareMenu item) {
switch (item) { switch (item) {
case ShareMenu.copyCode: case ShareMenu.copyCode:
{ {
Clipboard.setData(new ClipboardData(text: Provider.of<ProfileInfoState>(context, listen: false).onion)); Clipboard.setData(ClipboardData(text: Provider.of<ProfileInfoState>(context, listen: false).onion));
final snackBar = SnackBar(content: Text(AppLocalizations.of(context)!.copiedToClipboardNotification)); final snackBar = SnackBar(content: Text(AppLocalizations.of(context)!.copiedToClipboardNotification));
scaffoldKey.currentState?.showSnackBar(snackBar); scaffoldKey.currentState?.showSnackBar(snackBar);
} }
break; break;
case ShareMenu.qrcode: case ShareMenu.qrcode:
{ {
_showQRCode("cwtch:" + Provider.of<ProfileInfoState>(context, listen: false).onion); _showQRCode("cwtch:${Provider.of<ProfileInfoState>(context, listen: false).onion}");
} }
break; break;
} }
@ -400,11 +398,11 @@ class _ContactsViewState extends State<ContactsView> {
)); ));
} else { } else {
actions.add(IconButton( actions.add(IconButton(
icon: Icon(CwtchIcons.address_copy), icon: const Icon(CwtchIcons.address_copy),
tooltip: AppLocalizations.of(context)!.copyAddress, tooltip: AppLocalizations.of(context)!.copyAddress,
splashRadius: Material.defaultSplashRadius / 2, splashRadius: Material.defaultSplashRadius / 2,
onPressed: () { onPressed: () {
Clipboard.setData(new ClipboardData(text: Provider.of<ProfileInfoState>(context, listen: false).onion)); Clipboard.setData(ClipboardData(text: Provider.of<ProfileInfoState>(context, listen: false).onion));
final snackBar = SnackBar(content: Text(AppLocalizations.of(context)!.copiedToClipboardNotification)); final snackBar = SnackBar(content: Text(AppLocalizations.of(context)!.copiedToClipboardNotification));
scaffoldKey.currentState?.showSnackBar(snackBar); scaffoldKey.currentState?.showSnackBar(snackBar);
})); }));
@ -413,7 +411,7 @@ class _ContactsViewState extends State<ContactsView> {
// Manage known Servers // Manage known Servers
if (Provider.of<Settings>(context, listen: false).isExperimentEnabled(TapirGroupsExperiment) || Provider.of<Settings>(context, listen: false).isExperimentEnabled(ServerManagementExperiment)) { if (Provider.of<Settings>(context, listen: false).isExperimentEnabled(TapirGroupsExperiment) || Provider.of<Settings>(context, listen: false).isExperimentEnabled(ServerManagementExperiment)) {
actions.add(IconButton( actions.add(IconButton(
icon: Icon(CwtchIcons.dns_24px), icon: const Icon(CwtchIcons.dns_24px),
tooltip: AppLocalizations.of(context)!.manageKnownServersButton, tooltip: AppLocalizations.of(context)!.manageKnownServersButton,
splashRadius: Material.defaultSplashRadius / 2, splashRadius: Material.defaultSplashRadius / 2,
onPressed: () { onPressed: () {
@ -447,7 +445,7 @@ class _ContactsViewState extends State<ContactsView> {
Provider.of<ContactListState>(context, listen: false).filter = newVal; Provider.of<ContactListState>(context, listen: false).filter = newVal;
}, },
); );
return Column(children: [Padding(padding: EdgeInsets.all(8), child: txtfield), Expanded(child: _buildContactList())]); return Column(children: [Padding(padding: const EdgeInsets.all(8), child: txtfield), Expanded(child: _buildContactList())]);
} }
Widget _buildContactList() { Widget _buildContactList() {
@ -485,7 +483,7 @@ class _ContactsViewState extends State<ContactsView> {
itemCount: tilesSearchResult.length, itemCount: tilesSearchResult.length,
initialScrollIndex: initialScroll, initialScrollIndex: initialScroll,
shrinkWrap: true, shrinkWrap: true,
physics: BouncingScrollPhysics(), physics: const BouncingScrollPhysics(),
semanticChildCount: tilesSearchResult.length, semanticChildCount: tilesSearchResult.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
if (tilesSearchResult.length > index) { if (tilesSearchResult.length > index) {
@ -494,7 +492,7 @@ class _ContactsViewState extends State<ContactsView> {
return Container(); return Container();
}, },
separatorBuilder: (BuildContext context, int index) { separatorBuilder: (BuildContext context, int index) {
return Divider(height: 1); return const Divider(height: 1);
}, },
); );
@ -507,7 +505,7 @@ class _ContactsViewState extends State<ContactsView> {
Navigator.of(context).push( Navigator.of(context).push(
PageRouteBuilder( PageRouteBuilder(
settings: RouteSettings(name: "addcontact"), settings: const RouteSettings(name: "addcontact"),
pageBuilder: (builderContext, a1, a2) { pageBuilder: (builderContext, a1, a2) {
return MultiProvider( return MultiProvider(
providers: [ providers: [
@ -517,7 +515,7 @@ class _ContactsViewState extends State<ContactsView> {
); );
}, },
transitionsBuilder: (c, anim, a2, child) => FadeTransition(opacity: anim, child: child), transitionsBuilder: (c, anim, a2, child) => FadeTransition(opacity: anim, child: child),
transitionDuration: Duration(milliseconds: 200), transitionDuration: const Duration(milliseconds: 200),
), ),
); );
} }
@ -527,15 +525,15 @@ class _ContactsViewState extends State<ContactsView> {
Navigator.of(context).push( Navigator.of(context).push(
PageRouteBuilder( PageRouteBuilder(
settings: RouteSettings(name: "profileremoteservers"), settings: const RouteSettings(name: "profileremoteservers"),
pageBuilder: (bcontext, a1, a2) { pageBuilder: (bcontext, a1, a2) {
return MultiProvider( return MultiProvider(
providers: [ChangeNotifierProvider.value(value: profileInfoState), Provider.value(value: Provider.of<FlwtchState>(context))], providers: [ChangeNotifierProvider.value(value: profileInfoState), Provider.value(value: Provider.of<FlwtchState>(context))],
child: ProfileServersView(), child: const ProfileServersView(),
); );
}, },
transitionsBuilder: (c, anim, a2, child) => FadeTransition(opacity: anim, child: child), transitionsBuilder: (c, anim, a2, child) => FadeTransition(opacity: anim, child: child),
transitionDuration: Duration(milliseconds: 200), transitionDuration: const Duration(milliseconds: 200),
), ),
); );
} }
@ -550,17 +548,17 @@ class _ContactsViewState extends State<ContactsView> {
return Padding( return Padding(
padding: MediaQuery.of(context).viewInsets, padding: MediaQuery.of(context).viewInsets,
child: RepaintBoundary( child: RepaintBoundary(
child: Container( child: SizedBox(
height: Platform.isAndroid ? 250 : 200, // bespoke value courtesy of the [TextField] docs height: Platform.isAndroid ? 250 : 200, // bespoke value courtesy of the [TextField] docs
child: Center( child: Center(
child: Padding( child: Padding(
padding: EdgeInsets.all(2.0), padding: const EdgeInsets.all(2.0),
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.max, mainAxisSize: MainAxisSize.max,
children: <Widget>[ children: <Widget>[
SizedBox( const SizedBox(
height: 20, height: 20,
), ),
Expanded( Expanded(
@ -568,9 +566,9 @@ class _ContactsViewState extends State<ContactsView> {
message: AppLocalizations.of(context)!.tooltipAddContact, message: AppLocalizations.of(context)!.tooltipAddContact,
child: ElevatedButton( child: ElevatedButton(
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
minimumSize: Size.fromWidth(399), minimumSize: const Size.fromWidth(399),
maximumSize: Size.fromWidth(400), maximumSize: const Size.fromWidth(400),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.horizontal(left: Radius.circular(180), right: Radius.circular(180))), shape: const RoundedRectangleBorder(borderRadius: BorderRadius.horizontal(left: Radius.circular(180), right: Radius.circular(180))),
), ),
child: Text( child: Text(
AppLocalizations.of(context)!.addContact, AppLocalizations.of(context)!.addContact,
@ -582,7 +580,7 @@ class _ContactsViewState extends State<ContactsView> {
_pushAddContact(false); _pushAddContact(false);
}, },
))), ))),
SizedBox( const SizedBox(
height: 20, height: 20,
), ),
Expanded( Expanded(
@ -590,24 +588,24 @@ class _ContactsViewState extends State<ContactsView> {
message: groupsEnabled ? AppLocalizations.of(context)!.addServerTooltip : AppLocalizations.of(context)!.thisFeatureRequiresGroupExpermientsToBeEnabled, message: groupsEnabled ? AppLocalizations.of(context)!.addServerTooltip : AppLocalizations.of(context)!.thisFeatureRequiresGroupExpermientsToBeEnabled,
child: ElevatedButton( child: ElevatedButton(
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
minimumSize: Size.fromWidth(399), minimumSize: const Size.fromWidth(399),
maximumSize: Size.fromWidth(400), maximumSize: const Size.fromWidth(400),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.horizontal(left: Radius.circular(180), right: Radius.circular(180))), shape: const RoundedRectangleBorder(borderRadius: BorderRadius.horizontal(left: Radius.circular(180), right: Radius.circular(180))),
),
child: Text(
AppLocalizations.of(context)!.addServerTitle,
semanticsLabel: AppLocalizations.of(context)!.addServerTitle,
textAlign: TextAlign.center,
style: TextStyle(fontFamily: "Inter", fontSize: 10.0 * Provider.of<Settings>(context).fontScaling, fontWeight: FontWeight.bold),
), ),
onPressed: groupsEnabled onPressed: groupsEnabled
? () { ? () {
_pushAddContact(false); _pushAddContact(false);
} }
: null, : null,
child: Text(
AppLocalizations.of(context)!.addServerTitle,
semanticsLabel: AppLocalizations.of(context)!.addServerTitle,
textAlign: TextAlign.center,
style: TextStyle(fontFamily: "Inter", fontSize: 10.0 * Provider.of<Settings>(context).fontScaling, fontWeight: FontWeight.bold),
),
)), )),
), ),
SizedBox( const SizedBox(
height: 20, height: 20,
), ),
Expanded( Expanded(
@ -615,23 +613,23 @@ class _ContactsViewState extends State<ContactsView> {
message: groupsEnabled ? AppLocalizations.of(context)!.createGroupTitle : AppLocalizations.of(context)!.thisFeatureRequiresGroupExpermientsToBeEnabled, message: groupsEnabled ? AppLocalizations.of(context)!.createGroupTitle : AppLocalizations.of(context)!.thisFeatureRequiresGroupExpermientsToBeEnabled,
child: ElevatedButton( child: ElevatedButton(
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
minimumSize: Size.fromWidth(399), minimumSize: const Size.fromWidth(399),
maximumSize: Size.fromWidth(400), maximumSize: const Size.fromWidth(400),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.horizontal(left: Radius.circular(180), right: Radius.circular(180))), shape: const RoundedRectangleBorder(borderRadius: BorderRadius.horizontal(left: Radius.circular(180), right: Radius.circular(180))),
),
child: Text(
AppLocalizations.of(context)!.createGroupTitle,
semanticsLabel: AppLocalizations.of(context)!.createGroupTitle,
textAlign: TextAlign.center,
style: TextStyle(fontFamily: "Inter", fontSize: 10.0 * Provider.of<Settings>(context).fontScaling, fontWeight: FontWeight.bold),
), ),
onPressed: groupsEnabled onPressed: groupsEnabled
? () { ? () {
_pushAddContact(true); _pushAddContact(true);
} }
: null, : null,
child: Text(
AppLocalizations.of(context)!.createGroupTitle,
semanticsLabel: AppLocalizations.of(context)!.createGroupTitle,
textAlign: TextAlign.center,
style: TextStyle(fontFamily: "Inter", fontSize: 10.0 * Provider.of<Settings>(context).fontScaling, fontWeight: FontWeight.bold),
),
))), ))),
SizedBox( const SizedBox(
height: 20, height: 20,
), ),
], ],
@ -640,14 +638,14 @@ class _ContactsViewState extends State<ContactsView> {
}); });
} }
void _showQRCode(String profile_code) { void _showQRCode(String profileCode) {
showModalBottomSheet<dynamic>( showModalBottomSheet<dynamic>(
context: context, context: context,
builder: (BuildContext context) { builder: (BuildContext context) {
return Wrap(children: <Widget>[ return Wrap(children: <Widget>[
Center( Center(
child: QrImageView( child: QrImageView(
data: profile_code, data: profileCode,
version: QrVersions.auto, version: QrVersions.auto,
size: 400.0, size: 400.0,
backgroundColor: Provider.of<Settings>(context).theme.backgroundPaneColor, backgroundColor: Provider.of<Settings>(context).theme.backgroundPaneColor,

View File

@ -4,12 +4,13 @@ import 'package:cwtch/models/profile.dart';
import 'package:flutter/material.dart'; 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 '../main.dart';
import '../settings.dart'; import '../settings.dart';
import 'contactsview.dart'; import 'contactsview.dart';
import 'messageview.dart'; import 'messageview.dart';
class DoubleColumnView extends StatefulWidget { class DoubleColumnView extends StatefulWidget {
const DoubleColumnView({super.key});
@override @override
_DoubleColumnViewState createState() => _DoubleColumnViewState(); _DoubleColumnViewState createState() => _DoubleColumnViewState();
} }
@ -35,8 +36,8 @@ class _DoubleColumnViewState extends State<DoubleColumnView> {
? Container( ? Container(
color: Provider.of<Settings>(context).theme.backgroundMainColor, color: Provider.of<Settings>(context).theme.backgroundMainColor,
child: Card( child: Card(
margin: EdgeInsets.all(0.0), margin: const EdgeInsets.all(0.0),
shape: new RoundedRectangleBorder(side: new BorderSide(color: Provider.of<Settings>(context).theme.defaultButtonColor, width: 4.0), borderRadius: BorderRadius.circular(4.0)), shape: RoundedRectangleBorder(side: BorderSide(color: Provider.of<Settings>(context).theme.defaultButtonColor, width: 4.0), borderRadius: BorderRadius.circular(4.0)),
child: Center(child: Text(AppLocalizations.of(context)!.addContactFirst)))) child: Center(child: Text(AppLocalizations.of(context)!.addContactFirst))))
: //dev : //dev
MultiProvider(providers: [ MultiProvider(providers: [
@ -44,7 +45,7 @@ class _DoubleColumnViewState extends State<DoubleColumnView> {
// there is a potential timing issue here where selectConversation is changes as we move profiles, this will result // there is a potential timing issue here where selectConversation is changes as we move profiles, this will result
// in getContact being null, in that case we replace with an empty Contact Info State // in getContact being null, in that case we replace with an empty Contact Info State
ChangeNotifierProvider.value(value: Provider.of<ProfileInfoState>(context).contactList.getContact(selectedConversation) ?? ContactInfoState("", -1, "")), ChangeNotifierProvider.value(value: Provider.of<ProfileInfoState>(context).contactList.getContact(selectedConversation) ?? ContactInfoState("", -1, "")),
], child: Container(key: Key(selectedConversation.toString()), child: MessageView())), ], child: Container(key: Key(selectedConversation.toString()), child: const MessageView())),
), ),
], ],
); );

View File

@ -12,6 +12,8 @@ import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
import '../cwtch_icons_icons.dart'; import '../cwtch_icons_icons.dart';
class FileSharingView extends StatefulWidget { class FileSharingView extends StatefulWidget {
const FileSharingView({super.key});
@override @override
_FileSharingViewState createState() => _FileSharingViewState(); _FileSharingViewState createState() => _FileSharingViewState();
} }
@ -28,7 +30,7 @@ class _FileSharingViewState extends State<FileSharingView> {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text(handle + " » " + AppLocalizations.of(context)!.manageSharedFiles), title: Text("$handle » ${AppLocalizations.of(context)!.manageSharedFiles}"),
), ),
body: FutureBuilder( body: FutureBuilder(
future: Provider.of<FlwtchState>(context, listen: false).cwtch.GetSharedFiles(profileHandle, Provider.of<ContactInfoState>(context).identifier), future: Provider.of<FlwtchState>(context, listen: false).cwtch.GetSharedFiles(profileHandle, Provider.of<ContactInfoState>(context).identifier),
@ -44,7 +46,7 @@ class _FileSharingViewState extends State<FileSharingView> {
itemCount: sharedFiles.length, itemCount: sharedFiles.length,
shrinkWrap: true, shrinkWrap: true,
reverse: true, reverse: true,
physics: BouncingScrollPhysics(), physics: const BouncingScrollPhysics(),
semanticChildCount: sharedFiles.length, semanticChildCount: sharedFiles.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
String filekey = sharedFiles[index]["FileKey"]; String filekey = sharedFiles[index]["FileKey"];
@ -69,7 +71,7 @@ class _FileSharingViewState extends State<FileSharingView> {
}); });
}, },
separatorBuilder: (BuildContext context, int index) { separatorBuilder: (BuildContext context, int index) {
return Divider(height: 1); return const Divider(height: 1);
}, },
); );
return fileList; return fileList;

View File

@ -10,10 +10,11 @@ import '../config.dart';
import '../cwtch_icons_icons.dart'; import '../cwtch_icons_icons.dart';
import '../main.dart'; import '../main.dart';
import '../settings.dart'; import '../settings.dart';
import '../themes/opaque.dart';
import 'globalsettingsview.dart'; import 'globalsettingsview.dart';
class GlobalSettingsAboutView extends StatefulWidget { class GlobalSettingsAboutView extends StatefulWidget {
const GlobalSettingsAboutView({super.key});
@override @override
_GlobalSettingsAboutViewState createState() => _GlobalSettingsAboutViewState(); _GlobalSettingsAboutViewState createState() => _GlobalSettingsAboutViewState();
} }
@ -21,18 +22,19 @@ class GlobalSettingsAboutView extends StatefulWidget {
class _GlobalSettingsAboutViewState extends State<GlobalSettingsAboutView> { class _GlobalSettingsAboutViewState extends State<GlobalSettingsAboutView> {
ScrollController settingsListScrollController = ScrollController(); ScrollController settingsListScrollController = ScrollController();
@override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Consumer<Settings>(builder: (ccontext, settings, child) { return Consumer<Settings>(builder: (ccontext, settings, child) {
return LayoutBuilder(builder: (BuildContext context, BoxConstraints viewportConstraints) { return LayoutBuilder(builder: (BuildContext context, BoxConstraints viewportConstraints) {
var appIcon = Icon(Icons.info, color: settings.current().mainTextColor); var appIcon = Icon(Icons.info, color: settings.current().mainTextColor);
return Scrollbar( return Scrollbar(
key: Key("AboutSettingsView"), key: const Key("AboutSettingsView"),
trackVisibility: true, trackVisibility: true,
controller: settingsListScrollController, controller: settingsListScrollController,
child: SingleChildScrollView( child: SingleChildScrollView(
clipBehavior: Clip.antiAlias, clipBehavior: Clip.antiAlias,
controller: settingsListScrollController, controller: settingsListScrollController,
padding: EdgeInsets.symmetric(vertical: 0, horizontal: 20), padding: const EdgeInsets.symmetric(vertical: 0, horizontal: 20),
child: ConstrainedBox( child: ConstrainedBox(
constraints: BoxConstraints( constraints: BoxConstraints(
minHeight: viewportConstraints.maxHeight, minHeight: viewportConstraints.maxHeight,
@ -40,7 +42,7 @@ class _GlobalSettingsAboutViewState extends State<GlobalSettingsAboutView> {
child: Column(children: [ child: Column(children: [
AboutListTile( AboutListTile(
icon: appIcon, icon: appIcon,
applicationIcon: Padding(padding: EdgeInsets.all(5), child: Icon(CwtchIcons.cwtch_knott)), applicationIcon: const Padding(padding: EdgeInsets.all(5), child: Icon(CwtchIcons.cwtch_knott)),
applicationName: "Cwtch UI", applicationName: "Cwtch UI",
applicationLegalese: '\u{a9} 2021-2023 Open Privacy Research Society', applicationLegalese: '\u{a9} 2021-2023 Open Privacy Research Society',
aboutBoxChildren: <Widget>[ aboutBoxChildren: <Widget>[
@ -52,8 +54,8 @@ class _GlobalSettingsAboutViewState extends State<GlobalSettingsAboutView> {
]), ]),
SwitchListTile( SwitchListTile(
// TODO: Translate, Remove, OR Hide Prior to Release // TODO: Translate, Remove, OR Hide Prior to Release
title: Text("Show Performance Overlay"), title: const Text("Show Performance Overlay"),
subtitle: Text("Display an overlay graph of render time."), subtitle: const Text("Display an overlay graph of render time."),
value: settings.profileMode, value: settings.profileMode,
onChanged: (bool value) { onChanged: (bool value) {
setState(() { setState(() {
@ -71,8 +73,8 @@ class _GlobalSettingsAboutViewState extends State<GlobalSettingsAboutView> {
Visibility( Visibility(
visible: EnvironmentConfig.BUILD_VER == dev_version && !Platform.isAndroid, visible: EnvironmentConfig.BUILD_VER == dev_version && !Platform.isAndroid,
child: SwitchListTile( child: SwitchListTile(
title: Text("Show Semantic Debugger"), title: const Text("Show Semantic Debugger"),
subtitle: Text("Show Accessibility Debugging View"), subtitle: const Text("Show Accessibility Debugging View"),
value: settings.useSemanticDebugger, value: settings.useSemanticDebugger,
onChanged: (bool value) { onChanged: (bool value) {
if (value) { if (value) {
@ -93,10 +95,7 @@ class _GlobalSettingsAboutViewState extends State<GlobalSettingsAboutView> {
builder: (context, snapshot) { builder: (context, snapshot) {
if (snapshot.hasData) { if (snapshot.hasData) {
return Column( return Column(
children: [ children: [Text("libCwtch Debug Info: ${snapshot.data}"), Text("Message Cache Size (Mb): ${Provider.of<FlwtchState>(context).profs.cacheMemUsage() / (1024 * 1024)}")],
Text("libCwtch Debug Info: " + snapshot.data.toString()),
Text("Message Cache Size (Mb): " + (Provider.of<FlwtchState>(context).profs.cacheMemUsage() / (1024 * 1024)).toString())
],
); );
} else { } else {
return Container(); return Container();
@ -124,13 +123,13 @@ class _GlobalSettingsAboutViewState extends State<GlobalSettingsAboutView> {
var sortedKeys = platformChannelInfo.keys.toList(); var sortedKeys = platformChannelInfo.keys.toList();
sortedKeys.sort(); sortedKeys.sort();
var widgets = List<Widget>.empty(growable: true); var widgets = List<Widget>.empty(growable: true);
sortedKeys.forEach((element) { for (var element in sortedKeys) {
widgets.add(ListTile( widgets.add(ListTile(
leading: Icon(Icons.android, color: settings.current().mainTextColor), leading: Icon(Icons.android, color: settings.current().mainTextColor),
title: Text(element), title: Text(element),
subtitle: Text(platformChannelInfo[element]!), subtitle: Text(platformChannelInfo[element]!),
)); ));
}); }
return Column( return Column(
children: widgets, children: widgets,
); );
@ -142,6 +141,6 @@ class _GlobalSettingsAboutViewState extends State<GlobalSettingsAboutView> {
if (pinfo == null) { if (pinfo == null) {
return ""; return "";
} }
return pinfo.version + "." + pinfo.buildNumber; return "${pinfo.version}.${pinfo.buildNumber}";
} }
} }

View File

@ -14,10 +14,11 @@ import '../settings.dart';
import '../themes/cwtch.dart'; import '../themes/cwtch.dart';
import '../themes/opaque.dart'; import '../themes/opaque.dart';
import '../themes/yamltheme.dart'; import '../themes/yamltheme.dart';
import '../widgets/folderpicker.dart';
import 'globalsettingsview.dart'; import 'globalsettingsview.dart';
class GlobalSettingsAppearanceView extends StatefulWidget { class GlobalSettingsAppearanceView extends StatefulWidget {
const GlobalSettingsAppearanceView({super.key});
@override @override
_GlobalSettingsAppearanceViewState createState() => _GlobalSettingsAppearanceViewState(); _GlobalSettingsAppearanceViewState createState() => _GlobalSettingsAppearanceViewState();
} }
@ -25,17 +26,18 @@ class GlobalSettingsAppearanceView extends StatefulWidget {
class _GlobalSettingsAppearanceViewState extends State<GlobalSettingsAppearanceView> { class _GlobalSettingsAppearanceViewState extends State<GlobalSettingsAppearanceView> {
ScrollController settingsListScrollController = ScrollController(); ScrollController settingsListScrollController = ScrollController();
@override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Consumer<Settings>(builder: (ccontext, settings, child) { return Consumer<Settings>(builder: (ccontext, settings, child) {
return LayoutBuilder(builder: (BuildContext context, BoxConstraints viewportConstraints) { return LayoutBuilder(builder: (BuildContext context, BoxConstraints viewportConstraints) {
return Scrollbar( return Scrollbar(
key: Key("AppearanceSettingsView"), key: const Key("AppearanceSettingsView"),
trackVisibility: true, trackVisibility: true,
controller: settingsListScrollController, controller: settingsListScrollController,
child: SingleChildScrollView( child: SingleChildScrollView(
clipBehavior: Clip.antiAlias, clipBehavior: Clip.antiAlias,
controller: settingsListScrollController, controller: settingsListScrollController,
padding: EdgeInsets.symmetric(vertical: 0, horizontal: 20), padding: const EdgeInsets.symmetric(vertical: 0, horizontal: 20),
child: ConstrainedBox( child: ConstrainedBox(
constraints: BoxConstraints( constraints: BoxConstraints(
minHeight: viewportConstraints.maxHeight, minHeight: viewportConstraints.maxHeight,
@ -44,10 +46,10 @@ class _GlobalSettingsAppearanceViewState extends State<GlobalSettingsAppearanceV
ListTile( ListTile(
title: Text(AppLocalizations.of(context)!.settingLanguage), title: Text(AppLocalizations.of(context)!.settingLanguage),
leading: Icon(CwtchIcons.change_language, color: settings.current().mainTextColor), leading: Icon(CwtchIcons.change_language, color: settings.current().mainTextColor),
trailing: Container( trailing: SizedBox(
width: MediaQuery.of(context).size.width / 4, width: MediaQuery.of(context).size.width / 4,
child: DropdownButton( child: DropdownButton(
key: Key("languagelist"), key: const Key("languagelist"),
isExpanded: true, isExpanded: true,
value: Provider.of<Settings>(context).locale.toString(), value: Provider.of<Settings>(context).locale.toString(),
onChanged: (String? newValue) { onChanged: (String? newValue) {
@ -61,7 +63,7 @@ class _GlobalSettingsAppearanceViewState extends State<GlobalSettingsAppearanceV
return DropdownMenuItem<String>( return DropdownMenuItem<String>(
value: value.toString(), value: value.toString(),
child: Text( child: Text(
key: Key("dropdownLanguage" + value.languageCode), key: Key("dropdownLanguage${value.languageCode}"),
getLanguageFull(context, value.languageCode, value.countryCode), getLanguageFull(context, value.languageCode, value.countryCode),
style: settings.scaleFonts(defaultDropDownMenuItemTextStyle), style: settings.scaleFonts(defaultDropDownMenuItemTextStyle),
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
@ -87,10 +89,10 @@ class _GlobalSettingsAppearanceViewState extends State<GlobalSettingsAppearanceV
), ),
ListTile( ListTile(
title: Text(AppLocalizations.of(context)!.themeColorLabel), title: Text(AppLocalizations.of(context)!.themeColorLabel),
trailing: Container( trailing: SizedBox(
width: MediaQuery.of(context).size.width / 4, width: MediaQuery.of(context).size.width / 4,
child: DropdownButton<String>( child: DropdownButton<String>(
key: Key("DropdownTheme"), key: const Key("DropdownTheme"),
isExpanded: true, isExpanded: true,
value: Provider.of<Settings>(context).theme.theme, value: Provider.of<Settings>(context).theme.theme,
onChanged: (String? newValue) { onChanged: (String? newValue) {
@ -117,7 +119,7 @@ class _GlobalSettingsAppearanceViewState extends State<GlobalSettingsAppearanceV
//AppLocalizations.of( //AppLocalizations.of(
//context)! //context)!
//.fileSharingSettingsDownloadFolderDescription, //.fileSharingSettingsDownloadFolderDescription,
trailing: Container( trailing: SizedBox(
width: MediaQuery.of(context).size.width / 4, width: MediaQuery.of(context).size.width / 4,
child: ElevatedButton.icon( child: ElevatedButton.icon(
label: Text(AppLocalizations.of(context)!.settingsImportThemeButton), label: Text(AppLocalizations.of(context)!.settingsImportThemeButton),
@ -137,7 +139,7 @@ class _GlobalSettingsAppearanceViewState extends State<GlobalSettingsAppearanceV
} }
}, },
//onChanged: widget.onSave, //onChanged: widget.onSave,
icon: Icon(Icons.folder), icon: const Icon(Icons.folder),
//tooltip: widget.tooltip, //tooltip: widget.tooltip,
)))), )))),
SwitchListTile( SwitchListTile(
@ -155,7 +157,7 @@ class _GlobalSettingsAppearanceViewState extends State<GlobalSettingsAppearanceV
ListTile( ListTile(
title: Text(AppLocalizations.of(context)!.settingUIColumnPortrait), title: Text(AppLocalizations.of(context)!.settingUIColumnPortrait),
leading: Icon(Icons.table_chart, color: settings.current().mainTextColor), leading: Icon(Icons.table_chart, color: settings.current().mainTextColor),
trailing: Container( trailing: SizedBox(
width: MediaQuery.of(context).size.width / 4, width: MediaQuery.of(context).size.width / 4,
child: DropdownButton( child: DropdownButton(
isExpanded: true, isExpanded: true,
@ -177,9 +179,9 @@ class _GlobalSettingsAppearanceViewState extends State<GlobalSettingsAppearanceV
softWrap: true, softWrap: true,
), ),
leading: Icon(Icons.stay_primary_landscape, color: settings.current().mainTextColor), leading: Icon(Icons.stay_primary_landscape, color: settings.current().mainTextColor),
trailing: Container( trailing: SizedBox(
width: MediaQuery.of(context).size.width / 4, width: MediaQuery.of(context).size.width / 4,
child: Container( child: SizedBox(
width: MediaQuery.of(context).size.width / 4, width: MediaQuery.of(context).size.width / 4,
child: DropdownButton( child: DropdownButton(
isExpanded: true, isExpanded: true,
@ -197,7 +199,7 @@ class _GlobalSettingsAppearanceViewState extends State<GlobalSettingsAppearanceV
ListTile( ListTile(
title: Text(AppLocalizations.of(context)!.defaultScalingText), title: Text(AppLocalizations.of(context)!.defaultScalingText),
subtitle: Text(AppLocalizations.of(context)!.fontScalingDescription), subtitle: Text(AppLocalizations.of(context)!.fontScalingDescription),
trailing: Container( trailing: SizedBox(
width: MediaQuery.of(context).size.width / 4, width: MediaQuery.of(context).size.width / 4,
child: Slider( child: Slider(
onChanged: (double value) { onChanged: (double value) {
@ -409,20 +411,20 @@ class _GlobalSettingsAppearanceViewState extends State<GlobalSettingsAppearanceV
return Padding( return Padding(
padding: MediaQuery.of(context).viewInsets, padding: MediaQuery.of(context).viewInsets,
child: RepaintBoundary( child: RepaintBoundary(
child: Container( child: SizedBox(
height: Platform.isAndroid ? 250 : 200, // bespoke value courtesy of the [TextField] docs height: Platform.isAndroid ? 250 : 200, // bespoke value courtesy of the [TextField] docs
child: Center( child: Center(
child: Padding( child: Padding(
padding: EdgeInsets.all(10.0), padding: const EdgeInsets.all(10.0),
child: Column(mainAxisAlignment: MainAxisAlignment.center, mainAxisSize: MainAxisSize.min, children: <Widget>[ child: Column(mainAxisAlignment: MainAxisAlignment.center, mainAxisSize: MainAxisSize.min, children: <Widget>[
Text(AppLocalizations.of(context)!.settingThemeOverwriteQuestion.replaceAll("\$themeName", themeName)), Text(AppLocalizations.of(context)!.settingThemeOverwriteQuestion.replaceAll("\$themeName", themeName)),
SizedBox( const SizedBox(
height: 20, height: 20,
), ),
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly, mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [ children: [
Spacer(), const Spacer(),
Expanded( Expanded(
child: ElevatedButton( child: ElevatedButton(
child: Text(AppLocalizations.of(context)!.settingThemeOverwriteConfirm, semanticsLabel: AppLocalizations.of(context)!.settingThemeOverwriteConfirm), child: Text(AppLocalizations.of(context)!.settingThemeOverwriteConfirm, semanticsLabel: AppLocalizations.of(context)!.settingThemeOverwriteConfirm),
@ -432,7 +434,7 @@ class _GlobalSettingsAppearanceViewState extends State<GlobalSettingsAppearanceV
Navigator.pop(context); Navigator.pop(context);
}, },
)), )),
SizedBox( const SizedBox(
width: 20, width: 20,
), ),
Expanded( Expanded(
@ -442,7 +444,7 @@ class _GlobalSettingsAppearanceViewState extends State<GlobalSettingsAppearanceV
Navigator.pop(context); Navigator.pop(context);
}, },
)), )),
Spacer(), const Spacer(),
], ],
) )
])))))); ]))))));

View File

@ -5,20 +5,21 @@ import 'package:flutter/services.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 '../config.dart';
import '../cwtch_icons_icons.dart'; import '../cwtch_icons_icons.dart';
import '../settings.dart'; import '../settings.dart';
import '../themes/opaque.dart'; import '../themes/opaque.dart';
import 'globalsettingsview.dart'; import 'globalsettingsview.dart';
class GlobalSettingsBehaviourView extends StatefulWidget { class GlobalSettingsBehaviourView extends StatefulWidget {
const GlobalSettingsBehaviourView({super.key});
@override @override
_GlobalSettingsBehaviourViewState createState() => _GlobalSettingsBehaviourViewState(); _GlobalSettingsBehaviourViewState createState() => _GlobalSettingsBehaviourViewState();
} }
class _GlobalSettingsBehaviourViewState extends State<GlobalSettingsBehaviourView> { class _GlobalSettingsBehaviourViewState extends State<GlobalSettingsBehaviourView> {
static const androidSettingsChannel = const MethodChannel('androidSettings'); static const androidSettingsChannel = MethodChannel('androidSettings');
static const androidSettingsChangeChannel = const MethodChannel('androidSettingsChanged'); static const androidSettingsChangeChannel = MethodChannel('androidSettingsChanged');
ScrollController settingsListScrollController = ScrollController(); ScrollController settingsListScrollController = ScrollController();
bool powerExempt = false; bool powerExempt = false;
@ -60,17 +61,18 @@ class _GlobalSettingsBehaviourViewState extends State<GlobalSettingsBehaviourVie
//* End Android Only Requests //* End Android Only Requests
@override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Consumer<Settings>(builder: (ccontext, settings, child) { return Consumer<Settings>(builder: (ccontext, settings, child) {
return LayoutBuilder(builder: (BuildContext context, BoxConstraints viewportConstraints) { return LayoutBuilder(builder: (BuildContext context, BoxConstraints viewportConstraints) {
return Scrollbar( return Scrollbar(
key: Key("BehaviourSettingsView"), key: const Key("BehaviourSettingsView"),
trackVisibility: true, trackVisibility: true,
controller: settingsListScrollController, controller: settingsListScrollController,
child: SingleChildScrollView( child: SingleChildScrollView(
clipBehavior: Clip.antiAlias, clipBehavior: Clip.antiAlias,
controller: settingsListScrollController, controller: settingsListScrollController,
padding: EdgeInsets.symmetric(vertical: 0, horizontal: 20), padding: const EdgeInsets.symmetric(vertical: 0, horizontal: 20),
child: ConstrainedBox( child: ConstrainedBox(
constraints: BoxConstraints( constraints: BoxConstraints(
minHeight: viewportConstraints.maxHeight, minHeight: viewportConstraints.maxHeight,
@ -98,7 +100,7 @@ class _GlobalSettingsBehaviourViewState extends State<GlobalSettingsBehaviourVie
ListTile( ListTile(
title: Text(AppLocalizations.of(context)!.notificationPolicySettingLabel), title: Text(AppLocalizations.of(context)!.notificationPolicySettingLabel),
subtitle: Text(AppLocalizations.of(context)!.notificationPolicySettingDescription), subtitle: Text(AppLocalizations.of(context)!.notificationPolicySettingDescription),
trailing: Container( trailing: SizedBox(
width: MediaQuery.of(context).size.width / 4, width: MediaQuery.of(context).size.width / 4,
child: DropdownButton( child: DropdownButton(
isExpanded: true, isExpanded: true,
@ -118,7 +120,7 @@ class _GlobalSettingsBehaviourViewState extends State<GlobalSettingsBehaviourVie
ListTile( ListTile(
title: Text(AppLocalizations.of(context)!.notificationContentSettingLabel), title: Text(AppLocalizations.of(context)!.notificationContentSettingLabel),
subtitle: Text(AppLocalizations.of(context)!.notificationContentSettingDescription), subtitle: Text(AppLocalizations.of(context)!.notificationContentSettingDescription),
trailing: Container( trailing: SizedBox(
width: MediaQuery.of(context).size.width / 4, width: MediaQuery.of(context).size.width / 4,
child: DropdownButton( child: DropdownButton(
isExpanded: true, isExpanded: true,

View File

@ -4,7 +4,6 @@ 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 '../config.dart';
import '../cwtch_icons_icons.dart'; import '../cwtch_icons_icons.dart';
import '../main.dart'; import '../main.dart';
import '../models/servers.dart'; import '../models/servers.dart';
@ -14,6 +13,8 @@ import '../widgets/folderpicker.dart';
import 'globalsettingsview.dart'; import 'globalsettingsview.dart';
class GlobalSettingsExperimentsView extends StatefulWidget { class GlobalSettingsExperimentsView extends StatefulWidget {
const GlobalSettingsExperimentsView({super.key});
@override @override
_GlobalSettingsExperimentsViewState createState() => _GlobalSettingsExperimentsViewState(); _GlobalSettingsExperimentsViewState createState() => _GlobalSettingsExperimentsViewState();
} }
@ -21,17 +22,18 @@ class GlobalSettingsExperimentsView extends StatefulWidget {
class _GlobalSettingsExperimentsViewState extends State<GlobalSettingsExperimentsView> { class _GlobalSettingsExperimentsViewState extends State<GlobalSettingsExperimentsView> {
ScrollController settingsListScrollController = ScrollController(); ScrollController settingsListScrollController = ScrollController();
@override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Consumer<Settings>(builder: (ccontext, settings, child) { return Consumer<Settings>(builder: (ccontext, settings, child) {
return LayoutBuilder(builder: (BuildContext context, BoxConstraints viewportConstraints) { return LayoutBuilder(builder: (BuildContext context, BoxConstraints viewportConstraints) {
return Scrollbar( return Scrollbar(
key: Key("ExperimentsSettingsView"), key: const Key("ExperimentsSettingsView"),
trackVisibility: true, trackVisibility: true,
controller: settingsListScrollController, controller: settingsListScrollController,
child: SingleChildScrollView( child: SingleChildScrollView(
clipBehavior: Clip.antiAlias, clipBehavior: Clip.antiAlias,
controller: settingsListScrollController, controller: settingsListScrollController,
padding: EdgeInsets.symmetric(vertical: 0, horizontal: 20), padding: const EdgeInsets.symmetric(vertical: 0, horizontal: 20),
child: ConstrainedBox( child: ConstrainedBox(
constraints: BoxConstraints( constraints: BoxConstraints(
minHeight: viewportConstraints.maxHeight, minHeight: viewportConstraints.maxHeight,
@ -81,7 +83,7 @@ class _GlobalSettingsExperimentsViewState extends State<GlobalSettingsExperiment
title: Text(AppLocalizations.of(context)!.settingServers), title: Text(AppLocalizations.of(context)!.settingServers),
subtitle: Provider.of<FlwtchState>(context, listen: false).cwtch.IsServersCompiled() subtitle: Provider.of<FlwtchState>(context, listen: false).cwtch.IsServersCompiled()
? Text(AppLocalizations.of(context)!.settingServersDescription) ? Text(AppLocalizations.of(context)!.settingServersDescription)
: Text("This version of Cwtch has been compiled without support for the server hosting experiment."), : const Text("This version of Cwtch has been compiled without support for the server hosting experiment."),
value: Provider.of<FlwtchState>(context, listen: false).cwtch.IsServersCompiled() && settings.isExperimentEnabled(ServerManagementExperiment), value: Provider.of<FlwtchState>(context, listen: false).cwtch.IsServersCompiled() && settings.isExperimentEnabled(ServerManagementExperiment),
onChanged: Provider.of<FlwtchState>(context, listen: false).cwtch.IsServersCompiled() onChanged: Provider.of<FlwtchState>(context, listen: false).cwtch.IsServersCompiled()
? (bool value) { ? (bool value) {
@ -98,7 +100,7 @@ class _GlobalSettingsExperimentsViewState extends State<GlobalSettingsExperiment
activeTrackColor: settings.theme.defaultButtonColor, activeTrackColor: settings.theme.defaultButtonColor,
inactiveTrackColor: settings.theme.defaultButtonDisabledColor, inactiveTrackColor: settings.theme.defaultButtonDisabledColor,
inactiveThumbColor: settings.theme.defaultButtonDisabledColor, inactiveThumbColor: settings.theme.defaultButtonDisabledColor,
secondary: Icon(CwtchIcons.dns_24px), secondary: const Icon(CwtchIcons.dns_24px),
)), )),
SwitchListTile( SwitchListTile(
title: Text(AppLocalizations.of(context)!.settingFileSharing), title: Text(AppLocalizations.of(context)!.settingFileSharing),
@ -148,7 +150,7 @@ class _GlobalSettingsExperimentsViewState extends State<GlobalSettingsExperiment
Visibility( Visibility(
visible: settings.isExperimentEnabled(ImagePreviewsExperiment) && !Platform.isAndroid, visible: settings.isExperimentEnabled(ImagePreviewsExperiment) && !Platform.isAndroid,
child: CwtchFolderPicker( child: CwtchFolderPicker(
testKey: Key("DownloadFolderPicker"), testKey: const Key("DownloadFolderPicker"),
label: AppLocalizations.of(context)!.settingDownloadFolder, label: AppLocalizations.of(context)!.settingDownloadFolder,
initialValue: settings.downloadPath, initialValue: settings.downloadPath,
textStyle: settings.scaleFonts(defaultDropDownMenuItemTextStyle), textStyle: settings.scaleFonts(defaultDropDownMenuItemTextStyle),
@ -188,7 +190,7 @@ class _GlobalSettingsExperimentsViewState extends State<GlobalSettingsExperiment
Visibility( Visibility(
visible: Provider.of<FlwtchState>(context, listen: false).cwtch.IsBlodeuweddSupported() && settings.isExperimentEnabled(BlodeuweddExperiment), visible: Provider.of<FlwtchState>(context, listen: false).cwtch.IsBlodeuweddSupported() && settings.isExperimentEnabled(BlodeuweddExperiment),
child: CwtchFolderPicker( child: CwtchFolderPicker(
testKey: Key("DownloadFolderPicker"), testKey: const Key("DownloadFolderPicker"),
label: AppLocalizations.of(context)!.settingDownloadFolder, label: AppLocalizations.of(context)!.settingDownloadFolder,
initialValue: settings.blodeuweddPath, initialValue: settings.blodeuweddPath,
description: AppLocalizations.of(context)!.blodeuweddPath, description: AppLocalizations.of(context)!.blodeuweddPath,

View File

@ -1,27 +1,20 @@
import 'dart:collection';
import 'dart:convert'; import 'dart:convert';
import 'dart:io';
import 'package:cwtch/cwtch_icons_icons.dart'; import 'package:cwtch/cwtch_icons_icons.dart';
import 'package:cwtch/models/servers.dart';
import 'package:cwtch/views/globalsettingsaboutview.dart'; import 'package:cwtch/views/globalsettingsaboutview.dart';
import 'package:cwtch/views/globalsettingsappearanceview.dart'; import 'package:cwtch/views/globalsettingsappearanceview.dart';
import 'package:cwtch/views/globalsettingsbehaviourview.dart'; import 'package:cwtch/views/globalsettingsbehaviourview.dart';
import 'package:cwtch/views/globalsettingsexperimentsview.dart'; import 'package:cwtch/views/globalsettingsexperimentsview.dart';
import 'package:cwtch/widgets/folderpicker.dart';
import 'package:cwtch/themes/cwtch.dart';
import 'package:cwtch/themes/opaque.dart';
import 'package:flutter/services.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:cwtch/settings.dart'; import 'package:cwtch/settings.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 '../main.dart'; import '../main.dart';
import '../config.dart';
/// Global Settings View provides access to modify all the Globally Relevant Settings including Locale, Theme and Experiments. /// Global Settings View provides access to modify all the Globally Relevant Settings including Locale, Theme and Experiments.
class GlobalSettingsView extends StatefulWidget { class GlobalSettingsView extends StatefulWidget {
const GlobalSettingsView({super.key});
@override @override
_GlobalSettingsViewState createState() => _GlobalSettingsViewState(); _GlobalSettingsViewState createState() => _GlobalSettingsViewState();
} }
@ -44,10 +37,10 @@ class _GlobalSettingsViewState extends State<GlobalSettingsView> {
bottom: TabBar( bottom: TabBar(
isScrollable: true, isScrollable: true,
tabs: [ tabs: [
Tab(key: Key("OpenSettingsAppearance"), icon: Icon(Icons.palette), text: AppLocalizations.of(context)!.settingsGroupAppearance), Tab(key: const Key("OpenSettingsAppearance"), icon: const Icon(Icons.palette), text: AppLocalizations.of(context)!.settingsGroupAppearance),
Tab(key: Key("OpenSettingsBehaviour"), icon: Icon(Icons.settings), text: AppLocalizations.of(context)!.settingGroupBehaviour), Tab(key: const Key("OpenSettingsBehaviour"), icon: const Icon(Icons.settings), text: AppLocalizations.of(context)!.settingGroupBehaviour),
Tab(key: Key("OpenSettingsExperiments"), icon: Icon(CwtchIcons.enable_experiments), text: AppLocalizations.of(context)!.settingsGroupExperiments), Tab(key: const Key("OpenSettingsExperiments"), icon: const Icon(CwtchIcons.enable_experiments), text: AppLocalizations.of(context)!.settingsGroupExperiments),
Tab(icon: Icon(Icons.info), text: AppLocalizations.of(context)!.settingsGroupAbout), Tab(icon: const Icon(Icons.info), text: AppLocalizations.of(context)!.settingsGroupAbout),
], ],
)), )),
body: _buildSettingsList(), body: _buildSettingsList(),
@ -57,7 +50,7 @@ class _GlobalSettingsViewState extends State<GlobalSettingsView> {
Widget _buildSettingsList() { Widget _buildSettingsList() {
return Consumer<Settings>(builder: (ccontext, settings, child) { return Consumer<Settings>(builder: (ccontext, settings, child) {
return LayoutBuilder(builder: (BuildContext context, BoxConstraints viewportConstraints) { return LayoutBuilder(builder: (BuildContext context, BoxConstraints viewportConstraints) {
return TabBarView(children: [ return const TabBarView(children: [
GlobalSettingsAppearanceView(), GlobalSettingsAppearanceView(),
GlobalSettingsBehaviourView(), GlobalSettingsBehaviourView(),
GlobalSettingsExperimentsView(), GlobalSettingsExperimentsView(),

View File

@ -16,6 +16,8 @@ import '../main.dart';
/// Group Settings View Provides way to Configure group settings /// Group Settings View Provides way to Configure group settings
class GroupSettingsView extends StatefulWidget { class GroupSettingsView extends StatefulWidget {
const GroupSettingsView({super.key});
@override @override
_GroupSettingsViewState createState() => _GroupSettingsViewState(); _GroupSettingsViewState createState() => _GroupSettingsViewState();
} }
@ -50,8 +52,8 @@ class _GroupSettingsViewState extends State<GroupSettingsView> {
title: Container( title: Container(
height: Provider.of<Settings>(context).fontScaling * 24.0, height: Provider.of<Settings>(context).fontScaling * 24.0,
clipBehavior: Clip.hardEdge, clipBehavior: Clip.hardEdge,
decoration: BoxDecoration(), decoration: const BoxDecoration(),
child: Text(Provider.of<ContactInfoState>(context).nickname + " " + AppLocalizations.of(context)!.conversationSettings)), child: Text("${Provider.of<ContactInfoState>(context).nickname} ${AppLocalizations.of(context)!.conversationSettings}")),
), ),
body: _buildSettingsList(), body: _buildSettingsList(),
); );
@ -71,16 +73,16 @@ class _GroupSettingsViewState extends State<GroupSettingsView> {
minHeight: viewportConstraints.maxHeight, minHeight: viewportConstraints.maxHeight,
), ),
child: Container( child: Container(
margin: EdgeInsets.all(10), margin: const EdgeInsets.all(10),
padding: EdgeInsets.all(2), padding: const EdgeInsets.all(2),
child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.stretch, children: [
// Nickname Save Button // Nickname Save Button
Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [
SizedBox( const SizedBox(
height: 20, height: 20,
), ),
CwtchLabel(label: AppLocalizations.of(context)!.displayNameLabel), CwtchLabel(label: AppLocalizations.of(context)!.displayNameLabel),
SizedBox( const SizedBox(
height: 20, height: 20,
), ),
CwtchButtonTextField( CwtchButtonTextField(
@ -92,34 +94,36 @@ class _GroupSettingsViewState extends State<GroupSettingsView> {
Provider.of<ContactInfoState>(context, listen: false).nickname = ctrlrNick.text; Provider.of<ContactInfoState>(context, listen: false).nickname = ctrlrNick.text;
Provider.of<FlwtchState>(context, listen: false).cwtch.SetConversationAttribute(profileOnion, handle, "profile.name", ctrlrNick.text); Provider.of<FlwtchState>(context, listen: false).cwtch.SetConversationAttribute(profileOnion, handle, "profile.name", ctrlrNick.text);
// todo translations // todo translations
final snackBar = SnackBar(content: Text("Group Nickname changed successfully")); const snackBar = SnackBar(content: Text("Group Nickname changed successfully"));
ScaffoldMessenger.of(context).showSnackBar(snackBar); ScaffoldMessenger.of(context).showSnackBar(snackBar);
}, },
icon: Icon(Icons.save), icon: const Icon(Icons.save),
tooltip: AppLocalizations.of(context)!.saveBtn, tooltip: AppLocalizations.of(context)!.saveBtn,
) )
]), ]),
Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [
SizedBox( const SizedBox(
height: 20, height: 20,
), ),
CwtchLabel(label: AppLocalizations.of(context)!.server), CwtchLabel(label: AppLocalizations.of(context)!.server),
SizedBox( const SizedBox(
height: 20, height: 20,
), ),
CwtchTextField( CwtchTextField(
controller: TextEditingController(text: Provider.of<ContactInfoState>(context, listen: false).server), controller: TextEditingController(text: Provider.of<ContactInfoState>(context, listen: false).server),
validator: (value) {}, validator: (value) {
return null;
},
hintText: '', hintText: '',
) )
]), ]),
Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [
SizedBox( const SizedBox(
height: 20, height: 20,
), ),
CwtchLabel(label: AppLocalizations.of(context)!.conversationSettings), CwtchLabel(label: AppLocalizations.of(context)!.conversationSettings),
SizedBox( const SizedBox(
height: 20, height: 20,
), ),
ListTile( ListTile(
@ -145,7 +149,7 @@ class _GroupSettingsViewState extends State<GroupSettingsView> {
]), ]),
Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.end, children: [ Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.end, children: [
SizedBox( const SizedBox(
height: 20, height: 20,
), ),
Tooltip( Tooltip(
@ -157,15 +161,15 @@ class _GroupSettingsViewState extends State<GroupSettingsView> {
// locally update cache... // locally update cache...
Provider.of<ContactInfoState>(context, listen: false).isArchived = true; Provider.of<ContactInfoState>(context, listen: false).isArchived = true;
Provider.of<FlwtchState>(context, listen: false).cwtch.ArchiveConversation(profileOnion, handle); Provider.of<FlwtchState>(context, listen: false).cwtch.ArchiveConversation(profileOnion, handle);
Future.delayed(Duration(milliseconds: 500), () { Future.delayed(const Duration(milliseconds: 500), () {
Provider.of<AppState>(context, listen: false).selectedConversation = null; Provider.of<AppState>(context, listen: false).selectedConversation = null;
Navigator.of(context).popUntil((route) => route.settings.name == "conversations"); // dismiss dialog Navigator.of(context).popUntil((route) => route.settings.name == "conversations"); // dismiss dialog
}); });
}, },
icon: Icon(CwtchIcons.leave_chat), icon: const Icon(CwtchIcons.leave_chat),
label: Text(AppLocalizations.of(context)!.archiveConversation), label: Text(AppLocalizations.of(context)!.archiveConversation),
)), )),
SizedBox( const SizedBox(
height: 20, height: 20,
), ),
Row(crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.end, children: [ Row(crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.end, children: [
@ -178,7 +182,7 @@ class _GroupSettingsViewState extends State<GroupSettingsView> {
style: ButtonStyle( style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(Provider.of<Settings>(context).theme.backgroundPaneColor), backgroundColor: MaterialStateProperty.all(Provider.of<Settings>(context).theme.backgroundPaneColor),
foregroundColor: MaterialStateProperty.all(Provider.of<Settings>(context).theme.mainTextColor)), foregroundColor: MaterialStateProperty.all(Provider.of<Settings>(context).theme.mainTextColor)),
icon: Icon(CwtchIcons.leave_group), icon: const Icon(CwtchIcons.leave_group),
label: Text( label: Text(
AppLocalizations.of(context)!.leaveConversation, AppLocalizations.of(context)!.leaveConversation,
style: settings.scaleFonts(defaultTextButtonStyle.copyWith(decoration: TextDecoration.underline)), style: settings.scaleFonts(defaultTextButtonStyle.copyWith(decoration: TextDecoration.underline)),
@ -192,7 +196,7 @@ class _GroupSettingsViewState extends State<GroupSettingsView> {
} }
void _copyOnion() { void _copyOnion() {
Clipboard.setData(new ClipboardData(text: Provider.of<ContactInfoState>(context, listen: false).onion)); Clipboard.setData(ClipboardData(text: Provider.of<ContactInfoState>(context, listen: false).onion));
final snackBar = SnackBar(content: Text(AppLocalizations.of(context)!.copiedToClipboardNotification)); final snackBar = SnackBar(content: Text(AppLocalizations.of(context)!.copiedToClipboardNotification));
ScaffoldMessenger.of(context).showSnackBar(snackBar); ScaffoldMessenger.of(context).showSnackBar(snackBar);
} }
@ -206,7 +210,7 @@ class _GroupSettingsViewState extends State<GroupSettingsView> {
}, },
); );
Widget continueButton = ElevatedButton( Widget continueButton = ElevatedButton(
style: ButtonStyle(padding: MaterialStateProperty.all(EdgeInsets.all(20))), style: ButtonStyle(padding: MaterialStateProperty.all(const EdgeInsets.all(20))),
child: Text(AppLocalizations.of(context)!.yesLeave), child: Text(AppLocalizations.of(context)!.yesLeave),
onPressed: () { onPressed: () {
var profileOnion = Provider.of<ContactInfoState>(context, listen: false).profileOnion; var profileOnion = Provider.of<ContactInfoState>(context, listen: false).profileOnion;
@ -215,7 +219,7 @@ class _GroupSettingsViewState extends State<GroupSettingsView> {
Provider.of<ContactInfoState>(context, listen: false).isArchived = true; Provider.of<ContactInfoState>(context, listen: false).isArchived = true;
Provider.of<ProfileInfoState>(context, listen: false).contactList.removeContact(identifier); Provider.of<ProfileInfoState>(context, listen: false).contactList.removeContact(identifier);
Provider.of<FlwtchState>(context, listen: false).cwtch.DeleteContact(profileOnion, identifier); Provider.of<FlwtchState>(context, listen: false).cwtch.DeleteContact(profileOnion, identifier);
Future.delayed(Duration(milliseconds: 500), () { Future.delayed(const Duration(milliseconds: 500), () {
Provider.of<AppState>(context, listen: false).selectedConversation = null; Provider.of<AppState>(context, listen: false).selectedConversation = null;
Navigator.of(context).popUntil((route) => route.settings.name == "conversations"); // dismiss dialog Navigator.of(context).popUntil((route) => route.settings.name == "conversations"); // dismiss dialog
}); });

View File

@ -2,7 +2,6 @@ import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'dart:math'; import 'dart:math';
import 'dart:ui';
import 'package:crypto/crypto.dart'; import 'package:crypto/crypto.dart';
import 'package:cwtch/cwtch/cwtch.dart'; import 'package:cwtch/cwtch/cwtch.dart';
import 'package:cwtch/cwtch_icons_icons.dart'; import 'package:cwtch/cwtch_icons_icons.dart';
@ -37,6 +36,8 @@ import 'filesharingview.dart';
import 'groupsettingsview.dart'; import 'groupsettingsview.dart';
class MessageView extends StatefulWidget { class MessageView extends StatefulWidget {
const MessageView({super.key});
@override @override
_MessageViewState createState() => _MessageViewState(); _MessageViewState createState() => _MessageViewState();
} }
@ -52,14 +53,14 @@ class _MessageViewState extends State<MessageView> {
@override @override
void initState() { void initState() {
scrollListener.itemPositions.addListener(() { scrollListener.itemPositions.addListener(() {
if (scrollListener.itemPositions.value.length != 0 && if (scrollListener.itemPositions.value.isNotEmpty &&
Provider.of<AppState>(context, listen: false).unreadMessagesBelow == true && Provider.of<AppState>(context, listen: false).unreadMessagesBelow == true &&
scrollListener.itemPositions.value.any((element) => element.index == 0)) { scrollListener.itemPositions.value.any((element) => element.index == 0)) {
Provider.of<AppState>(context, listen: false).initialScrollIndex = 0; Provider.of<AppState>(context, listen: false).initialScrollIndex = 0;
Provider.of<AppState>(context, listen: false).unreadMessagesBelow = false; Provider.of<AppState>(context, listen: false).unreadMessagesBelow = false;
} }
if (scrollListener.itemPositions.value.length != 0 && !scrollListener.itemPositions.value.any((element) => element.index == 0)) { if (scrollListener.itemPositions.value.isNotEmpty && !scrollListener.itemPositions.value.any((element) => element.index == 0)) {
showDown = true; showDown = true;
} else { } else {
showDown = false; showDown = false;
@ -100,13 +101,13 @@ class _MessageViewState extends State<MessageView> {
if (showFileSharing) { if (showFileSharing) {
appBarButtons.add(IconButton( appBarButtons.add(IconButton(
splashRadius: Material.defaultSplashRadius / 2, icon: Icon(CwtchIcons.manage_files), tooltip: AppLocalizations.of(context)!.manageSharedFiles, onPressed: _pushFileSharingSettings)); splashRadius: Material.defaultSplashRadius / 2, icon: const Icon(CwtchIcons.manage_files), tooltip: AppLocalizations.of(context)!.manageSharedFiles, onPressed: _pushFileSharingSettings));
} }
if (Provider.of<ContactInfoState>(context, listen: false).isOnline()) { if (Provider.of<ContactInfoState>(context, listen: false).isOnline()) {
appBarButtons.add(IconButton( appBarButtons.add(IconButton(
splashRadius: Material.defaultSplashRadius / 2, splashRadius: Material.defaultSplashRadius / 2,
icon: Icon(CwtchIcons.disconnect_from_contact), icon: const Icon(CwtchIcons.disconnect_from_contact),
tooltip: AppLocalizations.of(context)!.contactDisconnect, tooltip: AppLocalizations.of(context)!.contactDisconnect,
onPressed: () { onPressed: () {
if (Provider.of<ContactInfoState>(context, listen: false).isGroup) { if (Provider.of<ContactInfoState>(context, listen: false).isGroup) {
@ -119,7 +120,7 @@ class _MessageViewState extends State<MessageView> {
.DisconnectFromPeer(Provider.of<ProfileInfoState>(context, listen: false).onion, Provider.of<ContactInfoState>(context, listen: false).onion); .DisconnectFromPeer(Provider.of<ProfileInfoState>(context, listen: false).onion, Provider.of<ContactInfoState>(context, listen: false).onion);
} }
// reset the disconnect button to allow for immediate connection... // reset the disconnect button to allow for immediate connection...
Provider.of<ContactInfoState>(context, listen: false).lastRetryTime = DateTime.now().subtract(Duration(minutes: 2)); Provider.of<ContactInfoState>(context, listen: false).lastRetryTime = DateTime.now().subtract(const Duration(minutes: 2));
Provider.of<ContactInfoState>(context, listen: false).contactEvents.add(ContactEvent("Disconnect from Peer")); Provider.of<ContactInfoState>(context, listen: false).contactEvents.add(ContactEvent("Disconnect from Peer"));
})); }));
} }
@ -130,7 +131,7 @@ class _MessageViewState extends State<MessageView> {
if (Provider.of<FlwtchState>(context, listen: false).cwtch.IsBlodeuweddSupported() && Provider.of<Settings>(context).isExperimentEnabled(BlodeuweddExperiment)) { if (Provider.of<FlwtchState>(context, listen: false).cwtch.IsBlodeuweddSupported() && Provider.of<Settings>(context).isExperimentEnabled(BlodeuweddExperiment)) {
appBarButtons.add(IconButton( appBarButtons.add(IconButton(
splashRadius: Material.defaultSplashRadius / 2, splashRadius: Material.defaultSplashRadius / 2,
icon: Icon(Icons.summarize), icon: const Icon(Icons.summarize),
tooltip: AppLocalizations.of(context)!.blodeuweddSummarize, tooltip: AppLocalizations.of(context)!.blodeuweddSummarize,
onPressed: () async { onPressed: () async {
Provider.of<ContactInfoState>(context, listen: false).summary = ""; Provider.of<ContactInfoState>(context, listen: false).summary = "";
@ -155,7 +156,7 @@ class _MessageViewState extends State<MessageView> {
}, () { }, () {
final snackBar = SnackBar( final snackBar = SnackBar(
content: Text(AppLocalizations.of(context)!.msgFileTooBig), content: Text(AppLocalizations.of(context)!.msgFileTooBig),
duration: Duration(seconds: 4), duration: const Duration(seconds: 4),
); );
ScaffoldMessenger.of(context).showSnackBar(snackBar); ScaffoldMessenger.of(context).showSnackBar(snackBar);
}, () {}); }, () {});
@ -165,7 +166,7 @@ class _MessageViewState extends State<MessageView> {
appBarButtons.add(IconButton( appBarButtons.add(IconButton(
splashRadius: Material.defaultSplashRadius / 2, splashRadius: Material.defaultSplashRadius / 2,
icon: Icon(CwtchIcons.send_invite, size: 24), icon: const Icon(CwtchIcons.send_invite, size: 24),
tooltip: AppLocalizations.of(context)!.sendInvite, tooltip: AppLocalizations.of(context)!.sendInvite,
onPressed: () { onPressed: () {
_modalSendInvitation(context); _modalSendInvitation(context);
@ -173,7 +174,7 @@ class _MessageViewState extends State<MessageView> {
} }
appBarButtons.add(IconButton( appBarButtons.add(IconButton(
splashRadius: Material.defaultSplashRadius / 2, splashRadius: Material.defaultSplashRadius / 2,
icon: Provider.of<ContactInfoState>(context, listen: false).isGroup == true ? Icon(CwtchIcons.group_settings_24px) : Icon(CwtchIcons.peer_settings_24px), icon: Provider.of<ContactInfoState>(context, listen: false).isGroup == true ? const Icon(CwtchIcons.group_settings_24px) : const Icon(CwtchIcons.peer_settings_24px),
tooltip: AppLocalizations.of(context)!.conversationSettings, tooltip: AppLocalizations.of(context)!.conversationSettings,
onPressed: _pushContactSettings)); onPressed: _pushContactSettings));
@ -185,12 +186,12 @@ class _MessageViewState extends State<MessageView> {
floatingActionButton: showDown floatingActionButton: showDown
? FloatingActionButton( ? FloatingActionButton(
// heroTags need to be unique per screen (important when we pop up and down)... // heroTags need to be unique per screen (important when we pop up and down)...
heroTag: "popDown" + Provider.of<ContactInfoState>(context, listen: false).onion, heroTag: "popDown${Provider.of<ContactInfoState>(context, listen: false).onion}",
child: Icon(Icons.arrow_downward, color: Provider.of<Settings>(context).current().defaultButtonTextColor), child: Icon(Icons.arrow_downward, color: Provider.of<Settings>(context).current().defaultButtonTextColor),
onPressed: () { onPressed: () {
Provider.of<AppState>(context, listen: false).initialScrollIndex = 0; Provider.of<AppState>(context, listen: false).initialScrollIndex = 0;
Provider.of<AppState>(context, listen: false).unreadMessagesBelow = false; Provider.of<AppState>(context, listen: false).unreadMessagesBelow = false;
Provider.of<ContactInfoState>(context, listen: false).messageScrollController.scrollTo(index: 0, duration: Duration(milliseconds: 600)); Provider.of<ContactInfoState>(context, listen: false).messageScrollController.scrollTo(index: 0, duration: const Duration(milliseconds: 600));
}) })
: null, : null,
appBar: AppBar( appBar: AppBar(
@ -239,14 +240,14 @@ class _MessageViewState extends State<MessageView> {
size: 14.0, size: 14.0,
))) )))
: null), : null),
SizedBox( const SizedBox(
width: 10, width: 10,
), ),
Expanded( Expanded(
child: Container( child: Container(
height: 42, height: 42,
clipBehavior: Clip.hardEdge, clipBehavior: Clip.hardEdge,
decoration: BoxDecoration(), decoration: const BoxDecoration(),
child: Align( child: Align(
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: Text( child: Text(
@ -259,7 +260,7 @@ class _MessageViewState extends State<MessageView> {
actions: appBarButtons, actions: appBarButtons,
), ),
body: Padding( body: Padding(
padding: EdgeInsets.fromLTRB(8.0, 8.0, 8.0, 182.0), padding: const EdgeInsets.fromLTRB(8.0, 8.0, 8.0, 182.0),
child: MessageList( child: MessageList(
scrollListener, scrollListener,
)), )),
@ -287,11 +288,11 @@ class _MessageViewState extends State<MessageView> {
pageBuilder: (builderContext, a1, a2) { pageBuilder: (builderContext, a1, a2) {
return MultiProvider( return MultiProvider(
providers: [ChangeNotifierProvider.value(value: profileInfoState), ChangeNotifierProvider.value(value: contactInfoState)], providers: [ChangeNotifierProvider.value(value: profileInfoState), ChangeNotifierProvider.value(value: contactInfoState)],
child: FileSharingView(), child: const FileSharingView(),
); );
}, },
transitionsBuilder: (c, anim, a2, child) => FadeTransition(opacity: anim, child: child), transitionsBuilder: (c, anim, a2, child) => FadeTransition(opacity: anim, child: child),
transitionDuration: Duration(milliseconds: 200), transitionDuration: const Duration(milliseconds: 200),
), ),
); );
} }
@ -306,11 +307,11 @@ class _MessageViewState extends State<MessageView> {
pageBuilder: (builderContext, a1, a2) { pageBuilder: (builderContext, a1, a2) {
return MultiProvider( return MultiProvider(
providers: [ChangeNotifierProvider.value(value: profileInfoState), ChangeNotifierProvider.value(value: contactInfoState)], providers: [ChangeNotifierProvider.value(value: profileInfoState), ChangeNotifierProvider.value(value: contactInfoState)],
child: GroupSettingsView(), child: const GroupSettingsView(),
); );
}, },
transitionsBuilder: (c, anim, a2, child) => FadeTransition(opacity: anim, child: child), transitionsBuilder: (c, anim, a2, child) => FadeTransition(opacity: anim, child: child),
transitionDuration: Duration(milliseconds: 200), transitionDuration: const Duration(milliseconds: 200),
), ),
); );
} else { } else {
@ -319,11 +320,11 @@ class _MessageViewState extends State<MessageView> {
pageBuilder: (builderContext, a1, a2) { pageBuilder: (builderContext, a1, a2) {
return MultiProvider( return MultiProvider(
providers: [ChangeNotifierProvider.value(value: profileInfoState), ChangeNotifierProvider.value(value: contactInfoState)], providers: [ChangeNotifierProvider.value(value: profileInfoState), ChangeNotifierProvider.value(value: contactInfoState)],
child: PeerSettingsView(), child: const PeerSettingsView(),
); );
}, },
transitionsBuilder: (c, anim, a2, child) => FadeTransition(opacity: anim, child: child), transitionsBuilder: (c, anim, a2, child) => FadeTransition(opacity: anim, child: child),
transitionDuration: Duration(milliseconds: 200), transitionDuration: const Duration(milliseconds: 200),
), ),
); );
} }
@ -345,7 +346,7 @@ class _MessageViewState extends State<MessageView> {
var attachedInvite = Provider.of<ContactInfoState>(context, listen: false).messageDraft.getInviteHandle(); var attachedInvite = Provider.of<ContactInfoState>(context, listen: false).messageDraft.getInviteHandle();
if (attachedInvite != null) { if (attachedInvite != null) {
this._sendInvitation(attachedInvite); _sendInvitation(attachedInvite);
} }
// Trim message // Trim message
@ -368,18 +369,18 @@ class _MessageViewState extends State<MessageView> {
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, messageWithoutNewLine)); var quotedMessage = jsonEncode(QuotedMessageStructure(contentHash, messageWithoutNewLine));
ChatMessage cm = new ChatMessage(o: QuotedMessageOverlay, d: quotedMessage); ChatMessage cm = ChatMessage(o: QuotedMessageOverlay, d: quotedMessage);
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))
.then(_sendMessageHandler); .then(_sendMessageHandler);
} catch (e) { } catch (e) {
EnvironmentConfig.debugLog("Exception: reply to message could not be found: " + e.toString()); EnvironmentConfig.debugLog("Exception: reply to message could not be found: $e");
} }
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: messageWithoutNewLine); ChatMessage cm = 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))
@ -442,14 +443,14 @@ class _MessageViewState extends State<MessageView> {
return Wrap(children: [ return Wrap(children: [
SelectableText( SelectableText(
chrome + '\u202F', '$chrome\u202F',
style: settings.scaleFonts(defaultMessageTextStyle.copyWith(color: Provider.of<Settings>(context).theme.messageFromMeTextColor)), style: settings.scaleFonts(defaultMessageTextStyle.copyWith(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: settings.scaleFonts(defaultMessageTextStyle.copyWith(color: Provider.of<Settings>(context).theme.messageFromMeTextColor)), style: settings.scaleFonts(defaultMessageTextStyle.copyWith(color: Provider.of<Settings>(context).theme.messageFromMeTextColor)),
textAlign: TextAlign.left, textAlign: TextAlign.left,
maxLines: 2, maxLines: 2,
@ -462,11 +463,11 @@ class _MessageViewState extends State<MessageView> {
var showClickableLinks = Provider.of<Settings>(context).isExperimentEnabled(ClickableLinksExperiment); var showClickableLinks = Provider.of<Settings>(context).isExperimentEnabled(ClickableLinksExperiment);
var wdgMessage = Padding( var wdgMessage = Padding(
padding: EdgeInsets.all(8), padding: const EdgeInsets.all(8),
child: SelectableLinkify( child: SelectableLinkify(
text: Provider.of<ContactInfoState>(context).messageDraft.messageText + '\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: const [UrlLinkifier()],
onOpen: showClickableLinks ? null : null, onOpen: showClickableLinks ? null : null,
style: TextStyle( style: TextStyle(
color: Provider.of<Settings>(context).theme.messageFromMeTextColor, color: Provider.of<Settings>(context).theme.messageFromMeTextColor,
@ -488,14 +489,14 @@ class _MessageViewState extends State<MessageView> {
backgroundColor: Provider.of<Settings>(context).theme.messageFromOtherBackgroundColor), backgroundColor: Provider.of<Settings>(context).theme.messageFromOtherBackgroundColor),
textAlign: TextAlign.left, textAlign: TextAlign.left,
textWidthBasis: TextWidthBasis.longestLine, textWidthBasis: TextWidthBasis.longestLine,
constraints: BoxConstraints.expand(), constraints: const BoxConstraints.expand(),
)); ));
var showMessageFormattingPreview = Provider.of<Settings>(context).isExperimentEnabled(FormattingExperiment); var showMessageFormattingPreview = Provider.of<Settings>(context).isExperimentEnabled(FormattingExperiment);
var preview = showMessageFormattingPreview var preview = showMessageFormattingPreview
? IconButton( ? IconButton(
tooltip: AppLocalizations.of(context)!.tooltipBackToMessageEditing, tooltip: AppLocalizations.of(context)!.tooltipBackToMessageEditing,
icon: Icon(Icons.text_fields), icon: const Icon(Icons.text_fields),
onPressed: () { onPressed: () {
setState(() { setState(() {
showPreview = false; showPreview = false;
@ -505,8 +506,8 @@ class _MessageViewState extends State<MessageView> {
var composeBox = Container( var composeBox = Container(
color: Provider.of<Settings>(context).theme.backgroundMainColor, color: Provider.of<Settings>(context).theme.backgroundMainColor,
padding: EdgeInsets.all(2), padding: const EdgeInsets.all(2),
margin: EdgeInsets.all(2), margin: const 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 + ((Provider.of<ContactInfoState>(context).messageDraft.messageText.split("\n").length - 1) * 16), height: 164 + ((Provider.of<ContactInfoState>(context).messageDraft.messageText.split("\n").length - 1) * 16),
@ -534,7 +535,7 @@ class _MessageViewState extends State<MessageView> {
var numberOfBytesMoreThanChar = (expectedLength - charLength); var numberOfBytesMoreThanChar = (expectedLength - charLength);
var bold = IconButton( var bold = IconButton(
icon: Icon(Icons.format_bold), icon: const Icon(Icons.format_bold),
tooltip: AppLocalizations.of(context)!.tooltipBoldText, tooltip: AppLocalizations.of(context)!.tooltipBoldText,
onPressed: () { onPressed: () {
setState(() { setState(() {
@ -543,14 +544,14 @@ class _MessageViewState extends State<MessageView> {
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; Provider.of<ContactInfoState>(context, listen: false).messageDraft.ctrlCompose = ctrlrCompose;
}); });
}); });
var italic = IconButton( var italic = IconButton(
icon: Icon(Icons.format_italic), icon: const Icon(Icons.format_italic),
tooltip: AppLocalizations.of(context)!.tooltipItalicize, tooltip: AppLocalizations.of(context)!.tooltipItalicize,
onPressed: () { onPressed: () {
setState(() { setState(() {
@ -559,14 +560,14 @@ class _MessageViewState extends State<MessageView> {
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; Provider.of<ContactInfoState>(context, listen: false).messageDraft.ctrlCompose = ctrlrCompose;
}); });
}); });
var code = IconButton( var code = IconButton(
icon: Icon(Icons.code), icon: const Icon(Icons.code),
tooltip: AppLocalizations.of(context)!.tooltipCode, tooltip: AppLocalizations.of(context)!.tooltipCode,
onPressed: () { onPressed: () {
setState(() { setState(() {
@ -575,14 +576,14 @@ class _MessageViewState extends State<MessageView> {
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; Provider.of<ContactInfoState>(context, listen: false).messageDraft.ctrlCompose = ctrlrCompose;
}); });
}); });
var superscript = IconButton( var superscript = IconButton(
icon: Icon(Icons.superscript), icon: const Icon(Icons.superscript),
tooltip: AppLocalizations.of(context)!.tooltipSuperscript, tooltip: AppLocalizations.of(context)!.tooltipSuperscript,
onPressed: () { onPressed: () {
setState(() { setState(() {
@ -591,14 +592,14 @@ class _MessageViewState extends State<MessageView> {
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; Provider.of<ContactInfoState>(context, listen: false).messageDraft.ctrlCompose = ctrlrCompose;
}); });
}); });
var strikethrough = IconButton( var strikethrough = IconButton(
icon: Icon(Icons.format_strikethrough), icon: const Icon(Icons.format_strikethrough),
tooltip: AppLocalizations.of(context)!.tooltipStrikethrough, tooltip: AppLocalizations.of(context)!.tooltipStrikethrough,
onPressed: () { onPressed: () {
setState(() { setState(() {
@ -607,14 +608,14 @@ class _MessageViewState extends State<MessageView> {
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; Provider.of<ContactInfoState>(context, listen: false).messageDraft.ctrlCompose = ctrlrCompose;
}); });
}); });
var preview = IconButton( var preview = IconButton(
icon: Icon(Icons.text_format), icon: const Icon(Icons.text_format),
tooltip: AppLocalizations.of(context)!.tooltipPreviewFormatting, tooltip: AppLocalizations.of(context)!.tooltipPreviewFormatting,
onPressed: () { onPressed: () {
setState(() { setState(() {
@ -623,7 +624,7 @@ class _MessageViewState extends State<MessageView> {
}); });
var vline = Padding( var vline = Padding(
padding: EdgeInsets.symmetric(vertical: 1, horizontal: 2), padding: const EdgeInsets.symmetric(vertical: 1, horizontal: 2),
child: Container(height: 16, width: 1, decoration: BoxDecoration(color: Provider.of<Settings>(context).theme.messageFromMeTextColor))); child: Container(height: 16, width: 1, decoration: BoxDecoration(color: Provider.of<Settings>(context).theme.messageFromMeTextColor)));
var formattingToolbar = Container( var formattingToolbar = Container(
@ -636,9 +637,9 @@ class _MessageViewState extends State<MessageView> {
focusNode: FocusNode(), focusNode: FocusNode(),
onKey: handleKeyPress, onKey: handleKeyPress,
child: Padding( child: Padding(
padding: EdgeInsets.all(8), padding: const EdgeInsets.all(8),
child: TextFormField( child: TextFormField(
key: Key('txtCompose'), key: const Key('txtCompose'),
controller: Provider.of<ContactInfoState>(context).messageDraft.ctrlCompose, controller: Provider.of<ContactInfoState>(context).messageDraft.ctrlCompose,
focusNode: focusNode, focusNode: focusNode,
autofocus: !Platform.isAndroid, autofocus: !Platform.isAndroid,
@ -673,8 +674,9 @@ class _MessageViewState extends State<MessageView> {
focusedBorder: InputBorder.none, focusedBorder: InputBorder.none,
enabled: true, enabled: true,
suffixIcon: ElevatedButton( suffixIcon: ElevatedButton(
key: Key("btnSend"), key: const Key("btnSend"),
style: ElevatedButton.styleFrom(padding: EdgeInsets.all(0.0), shape: new RoundedRectangleBorder(borderRadius: new BorderRadius.circular(45.0))), style: ElevatedButton.styleFrom(padding: const EdgeInsets.all(0.0), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(45.0))),
onPressed: cannotSend || (isGroup && Provider.of<ContactInfoState>(context, listen: false).antispamTickets == 0) ? null : _sendMessage,
child: Tooltip( child: Tooltip(
message: cannotSend message: cannotSend
? (isGroup ? AppLocalizations.of(context)!.serverNotSynced : AppLocalizations.of(context)!.peerOfflineMessage) ? (isGroup ? AppLocalizations.of(context)!.serverNotSynced : AppLocalizations.of(context)!.peerOfflineMessage)
@ -682,21 +684,20 @@ class _MessageViewState extends State<MessageView> {
? 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: cannotSend || (isGroup && Provider.of<ContactInfoState>(context, listen: false).antispamTickets == 0) ? null : _sendMessage,
))), ))),
))); )));
var textEditChildren; List<Container> textEditChildren;
if (showToolbar) { if (showToolbar) {
textEditChildren = [formattingToolbar, textField]; textEditChildren = [formattingToolbar, textField];
} else { } else {
textEditChildren = [textField]; textEditChildren = [textField];
} }
var composeBox = var composeBox = Container(
Container(color: Provider.of<Settings>(context).theme.backgroundMainColor, padding: EdgeInsets.all(2), margin: EdgeInsets.all(2), height: 164, child: Column(children: textEditChildren)); color: Provider.of<Settings>(context).theme.backgroundMainColor, padding: const EdgeInsets.all(2), margin: const EdgeInsets.all(2), height: 164, child: Column(children: textEditChildren));
var children; List<Widget> children;
Widget invite = Container(); Widget invite = Container();
if (Provider.of<ContactInfoState>(context).messageDraft.getInviteHandle() != null) { if (Provider.of<ContactInfoState>(context).messageDraft.getInviteHandle() != null) {
invite = FutureBuilder( invite = FutureBuilder(
@ -706,31 +707,31 @@ class _MessageViewState extends State<MessageView> {
var contactInvite = snapshot.data! as int; var contactInvite = snapshot.data! as int;
var contact = Provider.of<ProfileInfoState>(context, listen: false).contactList.getContact(contactInvite); var contact = Provider.of<ProfileInfoState>(context, listen: false).contactList.getContact(contactInvite);
return Container( return Container(
margin: EdgeInsets.all(5), margin: const EdgeInsets.all(5),
padding: EdgeInsets.all(5), padding: const EdgeInsets.all(5),
color: Provider.of<Settings>(context).theme.messageFromMeBackgroundColor, color: Provider.of<Settings>(context).theme.messageFromMeBackgroundColor,
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
Stack(children: [ Stack(children: [
Container( Container(
margin: EdgeInsets.all(5), margin: const EdgeInsets.all(5),
padding: EdgeInsets.all(5), padding: const EdgeInsets.all(5),
clipBehavior: Clip.antiAlias, clipBehavior: Clip.antiAlias,
decoration: BoxDecoration(color: Provider.of<Settings>(context).theme.messageFromMeBackgroundColor), decoration: BoxDecoration(color: Provider.of<Settings>(context).theme.messageFromMeBackgroundColor),
height: 75, height: 75,
child: Row(mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.start, children: [ child: Row(mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.start, children: [
Padding(padding: EdgeInsets.symmetric(vertical: 5.0, horizontal: 10.0), child: Icon(CwtchIcons.send_invite, size: 32)), const Padding(padding: EdgeInsets.symmetric(vertical: 5.0, horizontal: 10.0), child: Icon(CwtchIcons.send_invite, size: 32)),
Flexible( Flexible(
child: DefaultTextStyle( child: DefaultTextStyle(
textWidthBasis: TextWidthBasis.parent, textWidthBasis: TextWidthBasis.parent,
child: senderInviteChrome("", contact!.nickname),
style: Provider.of<Settings>(context).scaleFonts(defaultTextStyle), style: Provider.of<Settings>(context).scaleFonts(defaultTextStyle),
overflow: TextOverflow.fade, overflow: TextOverflow.fade,
child: senderInviteChrome("", contact!.nickname),
)) ))
])), ])),
Align( Align(
alignment: Alignment.topRight, alignment: Alignment.topRight,
child: IconButton( child: IconButton(
icon: Icon(Icons.highlight_remove), icon: const Icon(Icons.highlight_remove),
splashRadius: Material.defaultSplashRadius / 2, splashRadius: Material.defaultSplashRadius / 2,
tooltip: AppLocalizations.of(context)!.tooltipRemoveThisQuotedMessage, tooltip: AppLocalizations.of(context)!.tooltipRemoveThisQuotedMessage,
onPressed: () { onPressed: () {
@ -756,16 +757,16 @@ class _MessageViewState extends State<MessageView> {
? Provider.of<Settings>(context).theme.messageFromOtherTextColor ? Provider.of<Settings>(context).theme.messageFromOtherTextColor
: Provider.of<Settings>(context).theme.messageFromMeTextColor; : Provider.of<Settings>(context).theme.messageFromMeTextColor;
return Container( return Container(
margin: EdgeInsets.all(5), margin: const EdgeInsets.all(5),
padding: EdgeInsets.all(5), padding: const EdgeInsets.all(5),
color: message.getMetadata().senderHandle != Provider.of<AppState>(context).selectedProfile color: message.getMetadata().senderHandle != Provider.of<AppState>(context).selectedProfile
? Provider.of<Settings>(context).theme.messageFromOtherBackgroundColor ? Provider.of<Settings>(context).theme.messageFromOtherBackgroundColor
: Provider.of<Settings>(context).theme.messageFromMeBackgroundColor, : Provider.of<Settings>(context).theme.messageFromMeBackgroundColor,
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
Stack(children: [ Stack(children: [
Container( Container(
margin: EdgeInsets.all(5), margin: const EdgeInsets.all(5),
padding: EdgeInsets.all(5), padding: const EdgeInsets.all(5),
clipBehavior: Clip.antiAlias, clipBehavior: Clip.antiAlias,
decoration: BoxDecoration( decoration: BoxDecoration(
color: message.getMetadata().senderHandle != Provider.of<AppState>(context).selectedProfile color: message.getMetadata().senderHandle != Provider.of<AppState>(context).selectedProfile
@ -774,19 +775,19 @@ class _MessageViewState extends State<MessageView> {
), ),
height: 75, height: 75,
child: Row(mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.start, children: [ child: Row(mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.start, children: [
Padding(padding: EdgeInsets.symmetric(vertical: 5.0, horizontal: 10.0), child: Icon(Icons.reply, size: 32, color: qTextColor)), Padding(padding: const EdgeInsets.symmetric(vertical: 5.0, horizontal: 10.0), child: Icon(Icons.reply, size: 32, color: qTextColor)),
Flexible( Flexible(
child: DefaultTextStyle( child: DefaultTextStyle(
textWidthBasis: TextWidthBasis.parent, textWidthBasis: TextWidthBasis.parent,
child: message.getPreviewWidget(context),
style: TextStyle(color: qTextColor), style: TextStyle(color: qTextColor),
overflow: TextOverflow.fade, overflow: TextOverflow.fade,
child: message.getPreviewWidget(context),
)) ))
])), ])),
Align( Align(
alignment: Alignment.topRight, alignment: Alignment.topRight,
child: IconButton( child: IconButton(
icon: Icon(Icons.highlight_remove), icon: const Icon(Icons.highlight_remove),
splashRadius: Material.defaultSplashRadius / 2, splashRadius: Material.defaultSplashRadius / 2,
tooltip: AppLocalizations.of(context)!.tooltipRemoveThisQuotedMessage, tooltip: AppLocalizations.of(context)!.tooltipRemoveThisQuotedMessage,
onPressed: () { onPressed: () {
@ -797,7 +798,7 @@ class _MessageViewState extends State<MessageView> {
]), ]),
])); ]));
} else { } else {
return MessageLoadingBubble(); return const MessageLoadingBubble();
} }
}, },
); );
@ -832,18 +833,18 @@ class _MessageViewState extends State<MessageView> {
showModalBottomSheet<void>( showModalBottomSheet<void>(
context: ctx, context: ctx,
builder: (BuildContext bcontext) { builder: (BuildContext bcontext) {
return Container( return SizedBox(
height: 200, // bespoke value courtesy of the [TextField] docs height: 200, // bespoke value courtesy of the [TextField] docs
child: Center( child: Center(
child: Padding( child: Padding(
padding: EdgeInsets.all(10.0), padding: const EdgeInsets.all(10.0),
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[ children: <Widget>[
Text(AppLocalizations.of(bcontext)!.invitationLabel), Text(AppLocalizations.of(bcontext)!.invitationLabel),
SizedBox( const SizedBox(
height: 20, height: 20,
), ),
ChangeNotifierProvider.value( ChangeNotifierProvider.value(
@ -852,17 +853,17 @@ class _MessageViewState extends State<MessageView> {
return contact.onion != Provider.of<ContactInfoState>(ctx).onion; return contact.onion != Provider.of<ContactInfoState>(ctx).onion;
}, onChanged: (newVal) { }, onChanged: (newVal) {
setState(() { setState(() {
this.selectedContact = Provider.of<ProfileInfoState>(ctx, listen: false).contactList.findContact(newVal)!.identifier; selectedContact = Provider.of<ProfileInfoState>(ctx, listen: false).contactList.findContact(newVal)!.identifier;
}); });
})), })),
SizedBox( const SizedBox(
height: 20, height: 20,
), ),
ElevatedButton( ElevatedButton(
child: Text(AppLocalizations.of(bcontext)!.inviteBtn, semanticsLabel: AppLocalizations.of(bcontext)!.inviteBtn), child: Text(AppLocalizations.of(bcontext)!.inviteBtn, semanticsLabel: AppLocalizations.of(bcontext)!.inviteBtn),
onPressed: () { onPressed: () {
if (this.selectedContact != -1) { if (selectedContact != -1) {
Provider.of<ContactInfoState>(context, listen: false).messageDraft.attachInvite(this.selectedContact); Provider.of<ContactInfoState>(context, listen: false).messageDraft.attachInvite(selectedContact);
} }
Navigator.pop(bcontext); Navigator.pop(bcontext);
setState(() {}); setState(() {});
@ -881,21 +882,19 @@ class _MessageViewState extends State<MessageView> {
var showPreview = false; var showPreview = false;
if (Provider.of<Settings>(context, listen: false).shouldPreview(path)) { if (Provider.of<Settings>(context, listen: false).shouldPreview(path)) {
showPreview = true; showPreview = true;
if (imagePreview == null) { imagePreview ??= File(path);
imagePreview = new File(path);
}
} }
return Container( return SizedBox(
height: 300, // bespoke value courtesy of the [TextField] docs height: 300, // bespoke value courtesy of the [TextField] docs
child: Center( child: Center(
child: Padding( child: Padding(
padding: EdgeInsets.all(10.0), padding: const EdgeInsets.all(10.0),
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: <Widget>[ children: <Widget>[
Text(AppLocalizations.of(context)!.msgConfirmSend + " $path?"), Text("${AppLocalizations.of(context)!.msgConfirmSend} $path?"),
SizedBox( const SizedBox(
height: 20, height: 20,
), ),
Visibility( Visibility(
@ -910,13 +909,13 @@ class _MessageViewState extends State<MessageView> {
height: 150, height: 150,
isAntiAlias: false, isAntiAlias: false,
errorBuilder: (context, error, stackTrace) { errorBuilder: (context, error, stackTrace) {
return MalformedBubble(); return const MalformedBubble();
}, },
) )
: Container()), : Container()),
Visibility( Visibility(
visible: showPreview, visible: showPreview,
child: SizedBox( child: const SizedBox(
height: 10, height: 10,
)), )),
Row(mainAxisAlignment: MainAxisAlignment.center, children: [ Row(mainAxisAlignment: MainAxisAlignment.center, children: [
@ -926,7 +925,7 @@ class _MessageViewState extends State<MessageView> {
Navigator.pop(bcontext); Navigator.pop(bcontext);
}, },
), ),
SizedBox( const SizedBox(
width: 20, width: 20,
), ),
ElevatedButton( ElevatedButton(
@ -951,7 +950,7 @@ void _summarizeConversation(BuildContext context, ProfileInfoState profile, Sett
) { ) {
return StatefulBuilder(builder: (BuildContext scontext, StateSetter setState /*You can rename this!*/) { return StatefulBuilder(builder: (BuildContext scontext, StateSetter setState /*You can rename this!*/) {
if (scontext.mounted) { if (scontext.mounted) {
new Timer.periodic(Duration(seconds: 1), (Timer t) { Timer.periodic(const Duration(seconds: 1), (Timer t) {
if (scontext.mounted) { if (scontext.mounted) {
setState(() {}); setState(() {});
} }
@ -966,13 +965,13 @@ void _summarizeConversation(BuildContext context, ProfileInfoState profile, Sett
Provider.of<ContactInfoState>(context).summary == "" Provider.of<ContactInfoState>(context).summary == ""
? Column(crossAxisAlignment: CrossAxisAlignment.center, children: [ ? Column(crossAxisAlignment: CrossAxisAlignment.center, children: [
CircularProgressIndicator(color: settings.theme.defaultButtonActiveColor), CircularProgressIndicator(color: settings.theme.defaultButtonActiveColor),
Padding(padding: EdgeInsets.all(5.0), child: Text(AppLocalizations.of(context)!.blodeuweddProcessing)) Padding(padding: const EdgeInsets.all(5.0), child: Text(AppLocalizations.of(context)!.blodeuweddProcessing))
]) ])
: Flexible(child: Text(Provider.of<ContactInfoState>(context).summary)) : Flexible(child: Text(Provider.of<ContactInfoState>(context).summary))
])); ]));
var image = Padding( var image = Padding(
padding: EdgeInsets.all(4.0), padding: const EdgeInsets.all(4.0),
child: ProfileImage( child: ProfileImage(
imagePath: "assets/blodeuwedd.png", imagePath: "assets/blodeuwedd.png",
diameter: 48.0, diameter: 48.0,
@ -981,14 +980,14 @@ void _summarizeConversation(BuildContext context, ProfileInfoState profile, Sett
badgeColor: Colors.red, badgeColor: Colors.red,
)); ));
return Container( return SizedBox(
height: 300, // bespoke value courtesy of the [TextField] docs height: 300, // bespoke value courtesy of the [TextField] docs
child: Container( child: Container(
alignment: Alignment.center, alignment: Alignment.center,
child: Padding( child: Padding(
padding: EdgeInsets.all(10.0), padding: const EdgeInsets.all(10.0),
child: Padding( child: Padding(
padding: EdgeInsets.all(10.0), padding: const EdgeInsets.all(10.0),
child: Row( child: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [image, Flexible(child: bubble)], children: [image, Flexible(child: bubble)],

View File

@ -1,5 +1,3 @@
import 'dart:convert';
import 'dart:ui';
import 'package:cwtch/config.dart'; import 'package:cwtch/config.dart';
import 'package:cwtch/cwtch_icons_icons.dart'; import 'package:cwtch/cwtch_icons_icons.dart';
import 'package:cwtch/models/appstate.dart'; import 'package:cwtch/models/appstate.dart';
@ -19,6 +17,8 @@ import '../themes/opaque.dart';
/// Peer Settings View Provides way to Configure . /// Peer Settings View Provides way to Configure .
class PeerSettingsView extends StatefulWidget { class PeerSettingsView extends StatefulWidget {
const PeerSettingsView({super.key});
@override @override
_PeerSettingsViewState createState() => _PeerSettingsViewState(); _PeerSettingsViewState createState() => _PeerSettingsViewState();
} }
@ -52,8 +52,8 @@ class _PeerSettingsViewState extends State<PeerSettingsView> {
title: Container( title: Container(
height: Provider.of<Settings>(context).fontScaling * 24.0, height: Provider.of<Settings>(context).fontScaling * 24.0,
clipBehavior: Clip.hardEdge, clipBehavior: Clip.hardEdge,
decoration: BoxDecoration(), decoration: const BoxDecoration(),
child: Text(handle + " " + AppLocalizations.of(context)!.conversationSettings)), child: Text("$handle ${AppLocalizations.of(context)!.conversationSettings}")),
), ),
body: _buildSettingsList(), body: _buildSettingsList(),
); );
@ -77,11 +77,11 @@ class _PeerSettingsViewState extends State<PeerSettingsView> {
textAlign: TextAlign.left, textAlign: TextAlign.left,
text: TextSpan( text: TextSpan(
text: country, text: country,
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 10, fontFamily: "RobotoMono"), style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 10, fontFamily: "RobotoMono"),
children: [TextSpan(text: " ($ip)", style: TextStyle(fontSize: 8, fontWeight: FontWeight.normal))])); children: [TextSpan(text: " ($ip)", style: const TextStyle(fontSize: 8, fontWeight: FontWeight.normal))]));
}).toList(growable: true); }).toList(growable: true);
paths.add(RichText(text: TextSpan(text: AppLocalizations.of(context)!.labelTorNetwork, style: TextStyle(fontWeight: FontWeight.normal, fontSize: 8, fontFamily: "monospace")))); paths.add(RichText(text: TextSpan(text: AppLocalizations.of(context)!.labelTorNetwork, style: const TextStyle(fontWeight: FontWeight.normal, fontSize: 8, fontFamily: "monospace"))));
path = Column( path = Column(
children: paths, children: paths,
@ -107,8 +107,8 @@ class _PeerSettingsViewState extends State<PeerSettingsView> {
minHeight: viewportConstraints.maxHeight, minHeight: viewportConstraints.maxHeight,
), ),
child: Container( child: Container(
margin: EdgeInsets.all(10), margin: const EdgeInsets.all(10),
padding: EdgeInsets.all(2), padding: const EdgeInsets.all(2),
child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.center, children: [ child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.center, children: [
Column(crossAxisAlignment: CrossAxisAlignment.center, children: [ Column(crossAxisAlignment: CrossAxisAlignment.center, children: [
ProfileImage( ProfileImage(
@ -125,21 +125,21 @@ class _PeerSettingsViewState extends State<PeerSettingsView> {
width: MediaQuery.of(context).size.width / 2, width: MediaQuery.of(context).size.width / 2,
child: Column(children: [ child: Column(children: [
Padding( Padding(
padding: EdgeInsets.all(1), padding: const EdgeInsets.all(1),
child: SelectableText( child: SelectableText(
Provider.of<ContactInfoState>(context, listen: false).attributes[0] ?? "", Provider.of<ContactInfoState>(context, listen: false).attributes[0] ?? "",
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
), ),
Padding( Padding(
padding: EdgeInsets.all(1), padding: const EdgeInsets.all(1),
child: SelectableText( child: SelectableText(
Provider.of<ContactInfoState>(context, listen: false).attributes[1] ?? "", Provider.of<ContactInfoState>(context, listen: false).attributes[1] ?? "",
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
), ),
Padding( Padding(
padding: EdgeInsets.all(1), padding: const EdgeInsets.all(1),
child: SelectableText( child: SelectableText(
Provider.of<ContactInfoState>(context, listen: false).attributes[2] ?? "", Provider.of<ContactInfoState>(context, listen: false).attributes[2] ?? "",
textAlign: TextAlign.center, textAlign: TextAlign.center,
@ -150,7 +150,7 @@ class _PeerSettingsViewState extends State<PeerSettingsView> {
Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [
CwtchLabel(label: AppLocalizations.of(context)!.displayNameLabel), CwtchLabel(label: AppLocalizations.of(context)!.displayNameLabel),
SizedBox( const SizedBox(
height: 20, height: 20,
), ),
CwtchButtonTextField( CwtchButtonTextField(
@ -164,7 +164,7 @@ class _PeerSettingsViewState extends State<PeerSettingsView> {
final snackBar = SnackBar(content: Text(AppLocalizations.of(context)!.nickChangeSuccess)); final snackBar = SnackBar(content: Text(AppLocalizations.of(context)!.nickChangeSuccess));
ScaffoldMessenger.of(context).showSnackBar(snackBar); ScaffoldMessenger.of(context).showSnackBar(snackBar);
}, },
icon: Icon(Icons.save), icon: const Icon(Icons.save),
tooltip: AppLocalizations.of(context)!.saveBtn, tooltip: AppLocalizations.of(context)!.saveBtn,
) )
]), ]),
@ -173,26 +173,26 @@ class _PeerSettingsViewState extends State<PeerSettingsView> {
Visibility( Visibility(
visible: settings.streamerMode == false, visible: settings.streamerMode == false,
child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [
SizedBox( const SizedBox(
height: 20, height: 20,
), ),
CwtchLabel(label: AppLocalizations.of(context)!.addressLabel), CwtchLabel(label: AppLocalizations.of(context)!.addressLabel),
SizedBox( const SizedBox(
height: 20, height: 20,
), ),
CwtchButtonTextField( CwtchButtonTextField(
controller: TextEditingController(text: Provider.of<ContactInfoState>(context, listen: false).onion), controller: TextEditingController(text: Provider.of<ContactInfoState>(context, listen: false).onion),
onPressed: _copyOnion, onPressed: _copyOnion,
icon: Icon(CwtchIcons.address_copy), icon: const Icon(CwtchIcons.address_copy),
tooltip: AppLocalizations.of(context)!.copyBtn, tooltip: AppLocalizations.of(context)!.copyBtn,
) )
])), ])),
Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [
SizedBox( const SizedBox(
height: 20, height: 20,
), ),
CwtchLabel(label: AppLocalizations.of(context)!.conversationSettings), CwtchLabel(label: AppLocalizations.of(context)!.conversationSettings),
SizedBox( const SizedBox(
height: 20, height: 20,
), ),
ListTile( ListTile(
@ -202,7 +202,7 @@ class _PeerSettingsViewState extends State<PeerSettingsView> {
subtitle: Text(AppLocalizations.of(context)!.descriptionACNCircuitInfo), subtitle: Text(AppLocalizations.of(context)!.descriptionACNCircuitInfo),
trailing: path, trailing: path,
), ),
SizedBox( const SizedBox(
height: 20, height: 20,
), ),
SwitchListTile( SwitchListTile(
@ -290,7 +290,7 @@ class _PeerSettingsViewState extends State<PeerSettingsView> {
)), )),
]), ]),
Column(mainAxisAlignment: MainAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.end, children: [ Column(mainAxisAlignment: MainAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.end, children: [
SizedBox( const SizedBox(
height: 20, height: 20,
), ),
Row(crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.end, children: [ Row(crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.end, children: [
@ -303,16 +303,16 @@ class _PeerSettingsViewState extends State<PeerSettingsView> {
// locally update cache... // locally update cache...
Provider.of<ContactInfoState>(context, listen: false).isArchived = true; Provider.of<ContactInfoState>(context, listen: false).isArchived = true;
Provider.of<FlwtchState>(context, listen: false).cwtch.ArchiveConversation(profileOnion, handle); Provider.of<FlwtchState>(context, listen: false).cwtch.ArchiveConversation(profileOnion, handle);
Future.delayed(Duration(milliseconds: 500), () { Future.delayed(const Duration(milliseconds: 500), () {
Provider.of<AppState>(context, listen: false).selectedConversation = null; Provider.of<AppState>(context, listen: false).selectedConversation = null;
Navigator.of(context).popUntil((route) => route.settings.name == "conversations"); // dismiss dialog Navigator.of(context).popUntil((route) => route.settings.name == "conversations"); // dismiss dialog
}); });
}, },
icon: Icon(Icons.archive), icon: const Icon(Icons.archive),
label: Text(AppLocalizations.of(context)!.archiveConversation), label: Text(AppLocalizations.of(context)!.archiveConversation),
)) ))
]), ]),
SizedBox( const SizedBox(
height: 20, height: 20,
), ),
Row(crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.end, children: [ Row(crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.end, children: [
@ -325,7 +325,7 @@ class _PeerSettingsViewState extends State<PeerSettingsView> {
style: ButtonStyle( style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(Provider.of<Settings>(context).theme.backgroundPaneColor), backgroundColor: MaterialStateProperty.all(Provider.of<Settings>(context).theme.backgroundPaneColor),
foregroundColor: MaterialStateProperty.all(Provider.of<Settings>(context).theme.mainTextColor)), foregroundColor: MaterialStateProperty.all(Provider.of<Settings>(context).theme.mainTextColor)),
icon: Icon(CwtchIcons.leave_group), icon: const Icon(CwtchIcons.leave_group),
label: Text( label: Text(
AppLocalizations.of(context)!.leaveConversation, AppLocalizations.of(context)!.leaveConversation,
style: settings.scaleFonts(defaultTextButtonStyle.copyWith(decoration: TextDecoration.underline)), style: settings.scaleFonts(defaultTextButtonStyle.copyWith(decoration: TextDecoration.underline)),
@ -345,7 +345,7 @@ class _PeerSettingsViewState extends State<PeerSettingsView> {
} }
void _copyOnion() { void _copyOnion() {
Clipboard.setData(new ClipboardData(text: Provider.of<ContactInfoState>(context, listen: false).onion)); Clipboard.setData(ClipboardData(text: Provider.of<ContactInfoState>(context, listen: false).onion));
final snackBar = SnackBar(content: Text(AppLocalizations.of(context)!.copiedToClipboardNotification)); final snackBar = SnackBar(content: Text(AppLocalizations.of(context)!.copiedToClipboardNotification));
ScaffoldMessenger.of(context).showSnackBar(snackBar); ScaffoldMessenger.of(context).showSnackBar(snackBar);
} }
@ -353,14 +353,14 @@ class _PeerSettingsViewState extends State<PeerSettingsView> {
showAlertDialog(BuildContext context) { showAlertDialog(BuildContext context) {
// set up the buttons // set up the buttons
Widget cancelButton = ElevatedButton( Widget cancelButton = ElevatedButton(
child: Text(AppLocalizations.of(context)!.cancel), style: ButtonStyle(padding: MaterialStateProperty.all(const EdgeInsets.all(20))),
style: ButtonStyle(padding: MaterialStateProperty.all(EdgeInsets.all(20))),
onPressed: () { onPressed: () {
Navigator.of(context).pop(); // dismiss dialog Navigator.of(context).pop(); // dismiss dialog
}, },
child: Text(AppLocalizations.of(context)!.cancel),
); );
Widget continueButton = ElevatedButton( Widget continueButton = ElevatedButton(
style: ButtonStyle(padding: MaterialStateProperty.all(EdgeInsets.all(20))), style: ButtonStyle(padding: MaterialStateProperty.all(const EdgeInsets.all(20))),
child: Text(AppLocalizations.of(context)!.yesLeave), child: Text(AppLocalizations.of(context)!.yesLeave),
onPressed: () { onPressed: () {
var profileOnion = Provider.of<ContactInfoState>(context, listen: false).profileOnion; var profileOnion = Provider.of<ContactInfoState>(context, listen: false).profileOnion;
@ -368,7 +368,7 @@ class _PeerSettingsViewState extends State<PeerSettingsView> {
// locally update cache... // locally update cache...
Provider.of<ProfileInfoState>(context, listen: false).contactList.removeContact(identifier); Provider.of<ProfileInfoState>(context, listen: false).contactList.removeContact(identifier);
Provider.of<FlwtchState>(context, listen: false).cwtch.DeleteContact(profileOnion, identifier); Provider.of<FlwtchState>(context, listen: false).cwtch.DeleteContact(profileOnion, identifier);
Future.delayed(Duration(milliseconds: 500), () { Future.delayed(const Duration(milliseconds: 500), () {
Provider.of<AppState>(context, listen: false).selectedConversation = null; Provider.of<AppState>(context, listen: false).selectedConversation = null;
Navigator.of(context).popUntil((route) => route.settings.name == "conversations"); // dismiss dialog Navigator.of(context).popUntil((route) => route.settings.name == "conversations"); // dismiss dialog
}); });

View File

@ -16,7 +16,6 @@ import 'package:flutter/services.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:cwtch/widgets/profilerow.dart'; import 'package:cwtch/widgets/profilerow.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import '../config.dart';
import '../main.dart'; import '../main.dart';
import '../torstatus.dart'; import '../torstatus.dart';
import 'addeditprofileview.dart'; import 'addeditprofileview.dart';
@ -24,7 +23,7 @@ import 'globalsettingsview.dart';
import 'serversview.dart'; import 'serversview.dart';
class ProfileMgrView extends StatefulWidget { class ProfileMgrView extends StatefulWidget {
ProfileMgrView(); const ProfileMgrView({super.key});
@override @override
_ProfileMgrViewState createState() => _ProfileMgrViewState(); _ProfileMgrViewState createState() => _ProfileMgrViewState();
@ -50,7 +49,7 @@ class _ProfileMgrViewState extends State<ProfileMgrView> {
return Provider.of<AppState>(context, listen: false).cwtchIsClosing; return Provider.of<AppState>(context, listen: false).cwtchIsClosing;
}, },
child: Scaffold( child: Scaffold(
key: Key("ProfileManagerView"), key: const Key("ProfileManagerView"),
backgroundColor: settings.theme.backgroundMainColor, backgroundColor: settings.theme.backgroundMainColor,
appBar: AppBar( appBar: AppBar(
title: Row(children: [ title: Row(children: [
@ -59,7 +58,7 @@ class _ProfileMgrViewState extends State<ProfileMgrView> {
size: 36, size: 36,
color: settings.theme.mainTextColor, color: settings.theme.mainTextColor,
), ),
SizedBox( const SizedBox(
width: 10, width: 10,
), ),
Expanded( Expanded(
@ -83,11 +82,11 @@ class _ProfileMgrViewState extends State<ProfileMgrView> {
} }
List<Widget> getActions() { List<Widget> getActions() {
List<Widget> actions = new List<Widget>.empty(growable: true); List<Widget> actions = List<Widget>.empty(growable: true);
// Tor Status // Tor Status
actions.add(IconButton( actions.add(IconButton(
icon: TorIcon(), icon: const TorIcon(),
onPressed: _pushTorStatus, onPressed: _pushTorStatus,
splashRadius: Material.defaultSplashRadius / 2, splashRadius: Material.defaultSplashRadius / 2,
tooltip: Provider.of<TorStatus>(context).progress == 100 tooltip: Provider.of<TorStatus>(context).progress == 100
@ -97,7 +96,7 @@ class _ProfileMgrViewState extends State<ProfileMgrView> {
// Unlock Profiles // Unlock Profiles
actions.add(IconButton( actions.add(IconButton(
icon: Icon(CwtchIcons.lock_open_24px), icon: const Icon(CwtchIcons.lock_open_24px),
splashRadius: Material.defaultSplashRadius / 2, splashRadius: Material.defaultSplashRadius / 2,
color: Provider.of<ProfileListState>(context).profiles.isEmpty ? Provider.of<Settings>(context).theme.defaultButtonColor : Provider.of<Settings>(context).theme.mainTextColor, color: Provider.of<ProfileListState>(context).profiles.isEmpty ? Provider.of<Settings>(context).theme.defaultButtonColor : Provider.of<Settings>(context).theme.mainTextColor,
tooltip: AppLocalizations.of(context)!.tooltipUnlockProfiles, tooltip: AppLocalizations.of(context)!.tooltipUnlockProfiles,
@ -109,26 +108,26 @@ class _ProfileMgrViewState extends State<ProfileMgrView> {
Provider.of<Settings>(context).isExperimentEnabled(ServerManagementExperiment) && Provider.of<Settings>(context).isExperimentEnabled(ServerManagementExperiment) &&
!Platform.isAndroid && !Platform.isAndroid &&
!Platform.isIOS) { !Platform.isIOS) {
actions.add( actions.add(IconButton(
IconButton(icon: Icon(CwtchIcons.dns_black_24dp), splashRadius: Material.defaultSplashRadius / 2, tooltip: AppLocalizations.of(context)!.serversManagerTitleShort, onPressed: _pushServers)); icon: const Icon(CwtchIcons.dns_black_24dp), splashRadius: Material.defaultSplashRadius / 2, tooltip: AppLocalizations.of(context)!.serversManagerTitleShort, onPressed: _pushServers));
} }
// Global Settings // Global Settings
actions.add(IconButton( actions.add(IconButton(
key: Key("OpenSettingsView"), key: const Key("OpenSettingsView"),
icon: Icon(Icons.settings), icon: const Icon(Icons.settings),
tooltip: AppLocalizations.of(context)!.tooltipOpenSettings, tooltip: AppLocalizations.of(context)!.tooltipOpenSettings,
splashRadius: Material.defaultSplashRadius / 2, splashRadius: Material.defaultSplashRadius / 2,
onPressed: _pushGlobalSettings)); onPressed: _pushGlobalSettings));
// shutdown cwtch // shutdown cwtch
actions.add(IconButton(icon: Icon(Icons.close), tooltip: AppLocalizations.of(context)!.shutdownCwtchTooltip, splashRadius: Material.defaultSplashRadius / 2, onPressed: _modalShutdown)); actions.add(IconButton(icon: const Icon(Icons.close), tooltip: AppLocalizations.of(context)!.shutdownCwtchTooltip, splashRadius: Material.defaultSplashRadius / 2, onPressed: _modalShutdown));
return actions; return actions;
} }
void _modalShutdown() { void _modalShutdown() {
Provider.of<FlwtchState>(context, listen: false).modalShutdown(MethodCall("")); Provider.of<FlwtchState>(context, listen: false).modalShutdown(const MethodCall(""));
} }
void _pushGlobalSettings() { void _pushGlobalSettings() {
@ -137,11 +136,11 @@ class _ProfileMgrViewState extends State<ProfileMgrView> {
pageBuilder: (bcontext, a1, a2) { pageBuilder: (bcontext, a1, a2) {
return Provider( return Provider(
create: (_) => Provider.of<FlwtchState>(bcontext, listen: false), create: (_) => Provider.of<FlwtchState>(bcontext, listen: false),
child: GlobalSettingsView(), child: const GlobalSettingsView(),
); );
}, },
transitionsBuilder: (c, anim, a2, child) => FadeTransition(opacity: anim, child: child), transitionsBuilder: (c, anim, a2, child) => FadeTransition(opacity: anim, child: child),
transitionDuration: Duration(milliseconds: 200), transitionDuration: const Duration(milliseconds: 200),
), ),
); );
} }
@ -149,15 +148,15 @@ class _ProfileMgrViewState extends State<ProfileMgrView> {
void _pushServers() { void _pushServers() {
Navigator.of(context).push( Navigator.of(context).push(
PageRouteBuilder( PageRouteBuilder(
settings: RouteSettings(name: "servers"), settings: const RouteSettings(name: "servers"),
pageBuilder: (bcontext, a1, a2) { pageBuilder: (bcontext, a1, a2) {
return MultiProvider( return MultiProvider(
providers: [ChangeNotifierProvider.value(value: globalServersList), Provider.value(value: Provider.of<FlwtchState>(context))], providers: [ChangeNotifierProvider.value(value: globalServersList), Provider.value(value: Provider.of<FlwtchState>(context))],
child: ServersView(), child: const ServersView(),
); );
}, },
transitionsBuilder: (c, anim, a2, child) => FadeTransition(opacity: anim, child: child), transitionsBuilder: (c, anim, a2, child) => FadeTransition(opacity: anim, child: child),
transitionDuration: Duration(milliseconds: 200), transitionDuration: const Duration(milliseconds: 200),
), ),
); );
} }
@ -165,20 +164,20 @@ class _ProfileMgrViewState extends State<ProfileMgrView> {
void _pushTorStatus() { void _pushTorStatus() {
Navigator.of(context).push( Navigator.of(context).push(
PageRouteBuilder( PageRouteBuilder(
settings: RouteSettings(name: "torconfig"), settings: const RouteSettings(name: "torconfig"),
pageBuilder: (bcontext, a1, a2) { pageBuilder: (bcontext, a1, a2) {
return MultiProvider( return MultiProvider(
providers: [Provider.value(value: Provider.of<FlwtchState>(context))], providers: [Provider.value(value: Provider.of<FlwtchState>(context))],
child: TorStatusView(), child: const TorStatusView(),
); );
}, },
transitionsBuilder: (c, anim, a2, child) => FadeTransition(opacity: anim, child: child), transitionsBuilder: (c, anim, a2, child) => FadeTransition(opacity: anim, child: child),
transitionDuration: Duration(milliseconds: 200), transitionDuration: const Duration(milliseconds: 200),
), ),
); );
} }
void _pushAddProfile(bcontext, {onion: ""}) { void _pushAddProfile(bcontext, {onion = ""}) {
Navigator.popUntil(bcontext, (route) => route.isFirst); Navigator.popUntil(bcontext, (route) => route.isFirst);
Navigator.of(context).push( Navigator.of(context).push(
@ -190,11 +189,11 @@ class _ProfileMgrViewState extends State<ProfileMgrView> {
create: (_) => ProfileInfoState(onion: onion), create: (_) => ProfileInfoState(onion: onion),
), ),
], ],
builder: (context, widget) => AddEditProfileView(key: Key('addprofile')), builder: (context, widget) => const AddEditProfileView(key: Key('addprofile')),
); );
}, },
transitionsBuilder: (c, anim, a2, child) => FadeTransition(opacity: anim, child: child), transitionsBuilder: (c, anim, a2, child) => FadeTransition(opacity: anim, child: child),
transitionDuration: Duration(milliseconds: 200), transitionDuration: const Duration(milliseconds: 200),
), ),
); );
} }
@ -207,37 +206,37 @@ class _ProfileMgrViewState extends State<ProfileMgrView> {
return Padding( return Padding(
padding: MediaQuery.of(context).viewInsets, padding: MediaQuery.of(context).viewInsets,
child: RepaintBoundary( child: RepaintBoundary(
child: Container( child: SizedBox(
height: Platform.isAndroid ? 250 : 200, // bespoke value courtesy of the [TextField] docs height: Platform.isAndroid ? 250 : 200, // bespoke value courtesy of the [TextField] docs
child: Center( child: Center(
child: Padding( child: Padding(
padding: EdgeInsets.all(10.0), padding: const EdgeInsets.all(10.0),
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: <Widget>[ children: <Widget>[
SizedBox( const SizedBox(
height: 20, height: 20,
), ),
Expanded( Expanded(
child: ElevatedButton( child: ElevatedButton(
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
minimumSize: Size(399, 20), minimumSize: const Size(399, 20),
maximumSize: Size(400, 20), maximumSize: const Size(400, 20),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.horizontal(left: Radius.circular(180), right: Radius.circular(180))), shape: const RoundedRectangleBorder(borderRadius: BorderRadius.horizontal(left: Radius.circular(180), right: Radius.circular(180))),
), ),
child: Text( child: Text(
key: Key("addNewProfileActual"), key: const Key("addNewProfileActual"),
AppLocalizations.of(context)!.addProfileTitle, AppLocalizations.of(context)!.addProfileTitle,
semanticsLabel: AppLocalizations.of(context)!.addProfileTitle, semanticsLabel: AppLocalizations.of(context)!.addProfileTitle,
style: TextStyle(fontWeight: FontWeight.bold), style: const TextStyle(fontWeight: FontWeight.bold),
), ),
onPressed: () { onPressed: () {
_pushAddProfile(context); _pushAddProfile(context);
}, },
)), )),
SizedBox( const SizedBox(
height: 20, height: 20,
), ),
Expanded( Expanded(
@ -245,12 +244,12 @@ class _ProfileMgrViewState extends State<ProfileMgrView> {
message: AppLocalizations.of(context)!.importProfileTooltip, message: AppLocalizations.of(context)!.importProfileTooltip,
child: ElevatedButton( child: ElevatedButton(
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
minimumSize: Size(399, 20), minimumSize: const Size(399, 20),
maximumSize: Size(400, 20), backgroundColor: Provider.of<Settings>(context).theme.backgroundMainColor,
maximumSize: const Size(400, 20),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
side: BorderSide(color: Provider.of<Settings>(context).theme.defaultButtonActiveColor, width: 2.0), side: BorderSide(color: Provider.of<Settings>(context).theme.defaultButtonActiveColor, width: 2.0),
borderRadius: BorderRadius.horizontal(left: Radius.circular(180), right: Radius.circular(180))), borderRadius: const BorderRadius.horizontal(left: Radius.circular(180), right: Radius.circular(180))),
primary: Provider.of<Settings>(context).theme.backgroundMainColor,
), ),
child: Text(AppLocalizations.of(context)!.importProfile, child: Text(AppLocalizations.of(context)!.importProfile,
semanticsLabel: AppLocalizations.of(context)!.importProfile, semanticsLabel: AppLocalizations.of(context)!.importProfile,
@ -273,7 +272,7 @@ class _ProfileMgrViewState extends State<ProfileMgrView> {
}, () {}, () {}); }, () {}, () {});
}, },
))), ))),
SizedBox( const SizedBox(
height: 20, height: 20,
), ),
], ],
@ -290,31 +289,33 @@ class _ProfileMgrViewState extends State<ProfileMgrView> {
return Padding( return Padding(
padding: MediaQuery.of(context).viewInsets, padding: MediaQuery.of(context).viewInsets,
child: RepaintBoundary( child: RepaintBoundary(
child: Container( child: SizedBox(
height: Platform.isAndroid ? 250 : 200, // bespoke value courtesy of the [TextField] docs height: Platform.isAndroid ? 250 : 200, // bespoke value courtesy of the [TextField] docs
child: Center( child: Center(
child: Padding( child: Padding(
padding: EdgeInsets.all(10.0), padding: const EdgeInsets.all(10.0),
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: <Widget>[ children: <Widget>[
Text(AppLocalizations.of(context)!.enterProfilePassword), Text(AppLocalizations.of(context)!.enterProfilePassword),
SizedBox( const SizedBox(
height: 20, height: 20,
), ),
CwtchPasswordField( CwtchPasswordField(
key: Key("unlockPasswordProfileElement"), key: const Key("unlockPasswordProfileElement"),
autofocus: true, autofocus: true,
controller: ctrlrPassword, controller: ctrlrPassword,
action: unlock, action: unlock,
validator: (value) {}, validator: (value) {
return null;
},
), ),
SizedBox( const SizedBox(
height: 20, height: 20,
), ),
Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [
Spacer(), const Spacer(),
Expanded( Expanded(
child: ElevatedButton( child: ElevatedButton(
child: Text(AppLocalizations.of(context)!.unlock, semanticsLabel: AppLocalizations.of(context)!.unlock), child: Text(AppLocalizations.of(context)!.unlock, semanticsLabel: AppLocalizations.of(context)!.unlock),
@ -322,7 +323,7 @@ class _ProfileMgrViewState extends State<ProfileMgrView> {
unlock(ctrlrPassword.value.text); unlock(ctrlrPassword.value.text);
}, },
)), )),
Spacer() const Spacer()
]), ]),
], ],
))), ))),
@ -343,7 +344,7 @@ class _ProfileMgrViewState extends State<ProfileMgrView> {
(ProfileInfoState profile) { (ProfileInfoState profile) {
return ChangeNotifierProvider<ProfileInfoState>.value( return ChangeNotifierProvider<ProfileInfoState>.value(
value: profile, value: profile,
builder: (context, child) => ProfileRow(), builder: (context, child) => const ProfileRow(),
); );
}, },
); );
@ -353,7 +354,7 @@ class _ProfileMgrViewState extends State<ProfileMgrView> {
value: ProfileInfoState(onion: ""), value: ProfileInfoState(onion: ""),
builder: (context, child) { builder: (context, child) {
return Container( return Container(
margin: EdgeInsets.only(top: 20), margin: const EdgeInsets.only(top: 20),
width: MediaQuery.of(context).size.width, width: MediaQuery.of(context).size.width,
child: Row(mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ child: Row(mainAxisAlignment: MainAxisAlignment.spaceAround, children: [
Tooltip( Tooltip(
@ -363,7 +364,7 @@ class _ProfileMgrViewState extends State<ProfileMgrView> {
style: TextButton.styleFrom( style: TextButton.styleFrom(
minimumSize: Size(MediaQuery.of(context).size.width * 0.79, 80), minimumSize: Size(MediaQuery.of(context).size.width * 0.79, 80),
maximumSize: Size(MediaQuery.of(context).size.width * 0.8, 80), maximumSize: Size(MediaQuery.of(context).size.width * 0.8, 80),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.horizontal(left: Radius.circular(180), right: Radius.circular(180))), shape: const RoundedRectangleBorder(borderRadius: BorderRadius.horizontal(left: Radius.circular(180), right: Radius.circular(180))),
), ),
label: Text( label: Text(
AppLocalizations.of(context)!.unlock, AppLocalizations.of(context)!.unlock,
@ -389,7 +390,7 @@ class _ProfileMgrViewState extends State<ProfileMgrView> {
Text(AppLocalizations.of(context)!.unlockProfileTip, textAlign: TextAlign.center), Text(AppLocalizations.of(context)!.unlockProfileTip, textAlign: TextAlign.center),
Container( Container(
width: MediaQuery.of(context).size.width, width: MediaQuery.of(context).size.width,
margin: EdgeInsets.only(top: 20), margin: const EdgeInsets.only(top: 20),
child: Row(mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ child: Row(mainAxisAlignment: MainAxisAlignment.spaceAround, children: [
Tooltip( Tooltip(
message: AppLocalizations.of(context)!.addProfileTitle, message: AppLocalizations.of(context)!.addProfileTitle,
@ -398,12 +399,12 @@ class _ProfileMgrViewState extends State<ProfileMgrView> {
style: TextButton.styleFrom( style: TextButton.styleFrom(
minimumSize: Size(MediaQuery.of(context).size.width * 0.79, 80), minimumSize: Size(MediaQuery.of(context).size.width * 0.79, 80),
maximumSize: Size(MediaQuery.of(context).size.width * 0.8, 80), maximumSize: Size(MediaQuery.of(context).size.width * 0.8, 80),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.horizontal(left: Radius.circular(180), right: Radius.circular(180))), shape: const RoundedRectangleBorder(borderRadius: BorderRadius.horizontal(left: Radius.circular(180), right: Radius.circular(180))),
), ),
label: Text( label: Text(
AppLocalizations.of(context)!.addProfileTitle, AppLocalizations.of(context)!.addProfileTitle,
semanticsLabel: AppLocalizations.of(context)!.addProfileTitle, semanticsLabel: AppLocalizations.of(context)!.addProfileTitle,
style: TextStyle(fontWeight: FontWeight.bold), style: const TextStyle(fontWeight: FontWeight.bold),
), ),
onPressed: () { onPressed: () {
_modalAddImportProfiles(); _modalAddImportProfiles();

View File

@ -12,6 +12,8 @@ import '../main.dart';
import '../settings.dart'; import '../settings.dart';
class ProfileServersView extends StatefulWidget { class ProfileServersView extends StatefulWidget {
const ProfileServersView({super.key});
@override @override
_ProfileServersView createState() => _ProfileServersView(); _ProfileServersView createState() => _ProfileServersView();
} }
@ -25,7 +27,7 @@ class _ProfileServersView extends State<ProfileServersView> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var knownServers = Provider.of<ProfileInfoState>(context).serverList.servers.map<String>((RemoteServerInfoState remoteServer) { var knownServers = Provider.of<ProfileInfoState>(context).serverList.servers.map<String>((RemoteServerInfoState remoteServer) {
return remoteServer.onion + ".onion"; return "${remoteServer.onion}.onion";
}).toSet(); }).toSet();
var importServerList = Provider.of<ServerListState>(context).servers.where((server) => !knownServers.contains(server.onion)).map<DropdownMenuItem<String>>((ServerInfoState serverInfo) { var importServerList = Provider.of<ServerListState>(context).servers.where((server) => !knownServers.contains(server.onion)).map<DropdownMenuItem<String>>((ServerInfoState serverInfo) {
return DropdownMenuItem<String>( return DropdownMenuItem<String>(
@ -50,7 +52,7 @@ class _ProfileServersView extends State<ProfileServersView> {
(RemoteServerInfoState server) { (RemoteServerInfoState server) {
return ChangeNotifierProvider<RemoteServerInfoState>.value( return ChangeNotifierProvider<RemoteServerInfoState>.value(
value: server, value: server,
builder: (context, child) => RemoteServerRow(), builder: (context, child) => const RemoteServerRow(),
); );
}, },
); );
@ -84,8 +86,8 @@ class _ProfileServersView extends State<ProfileServersView> {
clipBehavior: Clip.antiAlias, clipBehavior: Clip.antiAlias,
controller: controller, controller: controller,
child: Container( child: Container(
margin: EdgeInsets.fromLTRB(5, 0, 5, 10), margin: const EdgeInsets.fromLTRB(5, 0, 5, 10),
padding: EdgeInsets.fromLTRB(5, 0, 5, 10), padding: const EdgeInsets.fromLTRB(5, 0, 5, 10),
child: Column(children: [if (importServerList.length > 1) importCard, Column(children: divided)])))); child: Column(children: [if (importServerList.length > 1) importCard, Column(children: divided)]))));
}); });

View File

@ -1,31 +1,20 @@
import 'dart:convert';
import 'package:cwtch/cwtch/cwtch.dart';
import 'package:cwtch/cwtch_icons_icons.dart'; import 'package:cwtch/cwtch_icons_icons.dart';
import 'package:cwtch/models/contact.dart'; import 'package:cwtch/models/contact.dart';
import 'package:cwtch/models/profile.dart'; import 'package:cwtch/models/profile.dart';
import 'package:cwtch/models/profileservers.dart';
import 'package:cwtch/models/remoteserver.dart'; import 'package:cwtch/models/remoteserver.dart';
import 'package:cwtch/models/servers.dart';
import 'package:cwtch/widgets/buttontextfield.dart'; import 'package:cwtch/widgets/buttontextfield.dart';
import 'package:cwtch/widgets/contactrow.dart';
import 'package:cwtch/widgets/cwtchlabel.dart'; import 'package:cwtch/widgets/cwtchlabel.dart';
import 'package:cwtch/widgets/passwordfield.dart';
import 'package:cwtch/widgets/textfield.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:cwtch/settings.dart'; import 'package:cwtch/settings.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 '../errorHandler.dart';
import '../main.dart'; import '../main.dart';
import '../config.dart';
import '../models/appstate.dart';
import '../themes/opaque.dart'; import '../themes/opaque.dart';
/// Pane to add or edit a server /// Pane to add or edit a server
class RemoteServerView extends StatefulWidget { class RemoteServerView extends StatefulWidget {
const RemoteServerView(); const RemoteServerView({super.key});
@override @override
_RemoteServerViewState createState() => _RemoteServerViewState(); _RemoteServerViewState createState() => _RemoteServerViewState();
@ -57,25 +46,25 @@ class _RemoteServerViewState extends State<RemoteServerView> {
return Scaffold( return Scaffold(
appBar: AppBar(title: Text(ctrlrDesc.text.isNotEmpty ? ctrlrDesc.text : serverInfoState.onion)), appBar: AppBar(title: Text(ctrlrDesc.text.isNotEmpty ? ctrlrDesc.text : serverInfoState.onion)),
body: Container( body: Container(
margin: EdgeInsets.fromLTRB(30, 0, 30, 10), margin: const EdgeInsets.fromLTRB(30, 0, 30, 10),
padding: EdgeInsets.fromLTRB(20, 0, 20, 10), padding: const EdgeInsets.fromLTRB(20, 0, 20, 10),
child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [
SizedBox( const SizedBox(
height: 20, height: 20,
), ),
CwtchLabel(label: AppLocalizations.of(context)!.serverAddress), CwtchLabel(label: AppLocalizations.of(context)!.serverAddress),
SizedBox( const SizedBox(
height: 20, height: 20,
), ),
SelectableText(serverInfoState.onion), SelectableText(serverInfoState.onion),
// Description // Description
SizedBox( const SizedBox(
height: 20, height: 20,
), ),
CwtchLabel(label: AppLocalizations.of(context)!.serverDescriptionLabel), CwtchLabel(label: AppLocalizations.of(context)!.serverDescriptionLabel),
Text(AppLocalizations.of(context)!.serverDescriptionDescription), Text(AppLocalizations.of(context)!.serverDescriptionDescription),
SizedBox( const SizedBox(
height: 20, height: 20,
), ),
CwtchButtonTextField( CwtchButtonTextField(
@ -83,14 +72,14 @@ class _RemoteServerViewState extends State<RemoteServerView> {
readonly: false, readonly: false,
tooltip: AppLocalizations.of(context)!.saveBtn, tooltip: AppLocalizations.of(context)!.saveBtn,
labelText: AppLocalizations.of(context)!.fieldDescriptionLabel, labelText: AppLocalizations.of(context)!.fieldDescriptionLabel,
icon: Icon(Icons.save), icon: const Icon(Icons.save),
onPressed: () { onPressed: () {
Provider.of<FlwtchState>(context, listen: false).cwtch.SetConversationAttribute(profile.onion, serverInfoState.identifier, "server.description", ctrlrDesc.text); Provider.of<FlwtchState>(context, listen: false).cwtch.SetConversationAttribute(profile.onion, serverInfoState.identifier, "server.description", ctrlrDesc.text);
serverInfoState.updateDescription(ctrlrDesc.text); serverInfoState.updateDescription(ctrlrDesc.text);
}, },
), ),
SizedBox( const SizedBox(
height: 20, height: 20,
), ),
Row(crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.end, children: [ Row(crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.end, children: [
@ -102,7 +91,7 @@ class _RemoteServerViewState extends State<RemoteServerView> {
: () { : () {
showAlertDialog(context); showAlertDialog(context);
}, },
icon: Icon(CwtchIcons.leave_group), icon: const Icon(CwtchIcons.leave_group),
label: Text( label: Text(
AppLocalizations.of(context)!.deleteBtn, AppLocalizations.of(context)!.deleteBtn,
style: settings.scaleFonts(defaultTextButtonStyle), style: settings.scaleFonts(defaultTextButtonStyle),
@ -110,7 +99,7 @@ class _RemoteServerViewState extends State<RemoteServerView> {
)) ))
]), ]),
Padding( Padding(
padding: EdgeInsets.all(8), padding: const EdgeInsets.all(8),
child: Text(AppLocalizations.of(context)!.groupsOnThisServerLabel), child: Text(AppLocalizations.of(context)!.groupsOnThisServerLabel),
), ),
Expanded(child: _buildGroupsList(serverInfoState)), Expanded(child: _buildGroupsList(serverInfoState)),
@ -136,7 +125,7 @@ class _RemoteServerViewState extends State<RemoteServerView> {
var size = MediaQuery.of(context).size; var size = MediaQuery.of(context).size;
int cols = ((size.width - 50) / 500).ceil(); int cols = ((size.width - 50) / 500).ceil();
final double itemHeight = 60; // magic arbitary const double itemHeight = 60; // magic arbitary
final double itemWidth = (size.width - 50 /* magic padding guess */) / cols; final double itemWidth = (size.width - 50 /* magic padding guess */) / cols;
return GridView.count(crossAxisCount: cols, childAspectRatio: (itemWidth / itemHeight), children: divided); return GridView.count(crossAxisCount: cols, childAspectRatio: (itemWidth / itemHeight), children: divided);
@ -174,7 +163,7 @@ showAlertDialog(BuildContext context) {
}, },
); );
Widget continueButton = ElevatedButton( Widget continueButton = ElevatedButton(
style: ButtonStyle(padding: MaterialStateProperty.all(EdgeInsets.all(20))), style: ButtonStyle(padding: MaterialStateProperty.all(const EdgeInsets.all(20))),
child: Text(AppLocalizations.of(context)!.deleteServerConfirmBtn), child: Text(AppLocalizations.of(context)!.deleteServerConfirmBtn),
onPressed: () { onPressed: () {
var profileOnion = Provider.of<ProfileInfoState>(context, listen: false).onion; var profileOnion = Provider.of<ProfileInfoState>(context, listen: false).onion;

View File

@ -3,8 +3,6 @@ import 'package:cwtch/views/addeditservers.dart';
import 'package:cwtch/widgets/passwordfield.dart'; import 'package:cwtch/widgets/passwordfield.dart';
import 'package:cwtch/widgets/serverrow.dart'; import 'package:cwtch/widgets/serverrow.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:cwtch/torstatus.dart';
import 'package:cwtch/widgets/tor_icon.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';
@ -14,6 +12,8 @@ import '../settings.dart';
/// ///
class ServersView extends StatefulWidget { class ServersView extends StatefulWidget {
const ServersView({super.key});
@override @override
_ServersView createState() => _ServersView(); _ServersView createState() => _ServersView();
} }
@ -50,7 +50,7 @@ class _ServersView extends State<ServersView> {
(ServerInfoState server) { (ServerInfoState server) {
return ChangeNotifierProvider<ServerInfoState>.value( return ChangeNotifierProvider<ServerInfoState>.value(
value: server, value: server,
builder: (context, child) => RepaintBoundary(child: ServerRow()), builder: (context, child) => const RepaintBoundary(child: ServerRow()),
); );
}, },
); );
@ -74,11 +74,11 @@ class _ServersView extends State<ServersView> {
} }
List<Widget> getActions() { List<Widget> getActions() {
List<Widget> actions = new List<Widget>.empty(growable: true); List<Widget> actions = List<Widget>.empty(growable: true);
// Unlock Profiles // Unlock Profiles
actions.add(IconButton( actions.add(IconButton(
icon: Icon(CwtchIcons.lock_open_24px), icon: const Icon(CwtchIcons.lock_open_24px),
color: Provider.of<ServerListState>(context).servers.isEmpty ? Provider.of<Settings>(context).theme.defaultButtonColor : Provider.of<Settings>(context).theme.mainTextColor, color: Provider.of<ServerListState>(context).servers.isEmpty ? Provider.of<Settings>(context).theme.defaultButtonColor : Provider.of<Settings>(context).theme.mainTextColor,
tooltip: AppLocalizations.of(context)!.tooltipUnlockProfiles, tooltip: AppLocalizations.of(context)!.tooltipUnlockProfiles,
onPressed: _modalUnlockServers, onPressed: _modalUnlockServers,
@ -95,30 +95,32 @@ class _ServersView extends State<ServersView> {
return Padding( return Padding(
padding: MediaQuery.of(context).viewInsets, padding: MediaQuery.of(context).viewInsets,
child: RepaintBoundary( child: RepaintBoundary(
child: Container( child: SizedBox(
height: 200, // bespoke value courtesy of the [TextField] docs height: 200, // bespoke value courtesy of the [TextField] docs
child: Center( child: Center(
child: Padding( child: Padding(
padding: EdgeInsets.all(10.0), padding: const EdgeInsets.all(10.0),
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: <Widget>[ children: <Widget>[
Text(AppLocalizations.of(context)!.enterServerPassword), Text(AppLocalizations.of(context)!.enterServerPassword),
SizedBox( const SizedBox(
height: 20, height: 20,
), ),
CwtchPasswordField( CwtchPasswordField(
autofocus: true, autofocus: true,
controller: ctrlrPassword, controller: ctrlrPassword,
action: unlock, action: unlock,
validator: (value) {}, validator: (value) {
return null;
},
), ),
SizedBox( const SizedBox(
height: 20, height: 20,
), ),
Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [
Spacer(), const Spacer(),
Expanded( Expanded(
child: ElevatedButton( child: ElevatedButton(
child: Text(AppLocalizations.of(context)!.unlock, semanticsLabel: AppLocalizations.of(context)!.unlock), child: Text(AppLocalizations.of(context)!.unlock, semanticsLabel: AppLocalizations.of(context)!.unlock),
@ -126,7 +128,7 @@ class _ServersView extends State<ServersView> {
unlock(ctrlrPassword.value.text); unlock(ctrlrPassword.value.text);
}, },
)), )),
Spacer() const Spacer()
]), ]),
], ],
))), ))),
@ -150,11 +152,11 @@ class _ServersView extends State<ServersView> {
create: (_) => ServerInfoState(onion: "", serverBundle: "", description: "", autoStart: true, running: false, isEncrypted: true), create: (_) => ServerInfoState(onion: "", serverBundle: "", description: "", autoStart: true, running: false, isEncrypted: true),
) )
], ],
child: AddEditServerView(), child: const AddEditServerView(),
); );
}, },
transitionsBuilder: (c, anim, a2, child) => FadeTransition(opacity: anim, child: child), transitionsBuilder: (c, anim, a2, child) => FadeTransition(opacity: anim, child: child),
transitionDuration: Duration(milliseconds: 200), transitionDuration: const Duration(milliseconds: 200),
), ),
); );
} }

View File

@ -1,6 +1,5 @@
import 'package:cwtch/models/appstate.dart'; import 'package:cwtch/models/appstate.dart';
import 'package:cwtch/themes/opaque.dart'; import 'package:cwtch/themes/opaque.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; 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';
@ -9,6 +8,8 @@ import '../main.dart';
import '../settings.dart'; import '../settings.dart';
class SplashView extends StatefulWidget { class SplashView extends StatefulWidget {
const SplashView({super.key});
@override @override
_SplashViewState createState() => _SplashViewState(); _SplashViewState createState() => _SplashViewState();
} }
@ -25,17 +26,17 @@ class _SplashViewState extends State<SplashView> {
return Consumer<AppState>( return Consumer<AppState>(
builder: (context, appState, child) => Scaffold( builder: (context, appState, child) => Scaffold(
key: Key("SplashView"), key: const Key("SplashView"),
body: Center( body: Center(
child: Column(mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ child: Column(mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [
Image( const Image(
image: AssetImage("assets/core/knott-white.png"), image: AssetImage("assets/core/knott-white.png"),
filterQuality: FilterQuality.medium, filterQuality: FilterQuality.medium,
isAntiAlias: true, isAntiAlias: true,
width: 200, width: 200,
height: 200, height: 200,
), ),
Image( const Image(
image: AssetImage("assets/cwtch_title.png"), image: AssetImage("assets/cwtch_title.png"),
filterQuality: FilterQuality.medium, filterQuality: FilterQuality.medium,
isAntiAlias: true, isAntiAlias: true,
@ -44,7 +45,7 @@ class _SplashViewState extends State<SplashView> {
padding: const EdgeInsets.all(20.0), padding: const EdgeInsets.all(20.0),
child: Column(children: [ child: Column(children: [
Padding( Padding(
padding: EdgeInsets.all(6.0), padding: const EdgeInsets.all(6.0),
child: Text( child: Text(
appState.appError != "" appState.appError != ""
? appState.appError ? appState.appError
@ -61,7 +62,7 @@ class _SplashViewState extends State<SplashView> {
color: Provider.of<Settings>(context).theme.defaultButtonActiveColor, color: Provider.of<Settings>(context).theme.defaultButtonActiveColor,
)) ))
])), ])),
Image(image: AssetImage("assets/Open_Privacy_Logo_lightoutline.png")), const Image(image: AssetImage("assets/Open_Privacy_Logo_lightoutline.png")),
])), ])),
)); ));
} }

View File

@ -13,6 +13,8 @@ import 'globalsettingsview.dart';
/// Tor Status View provides all info on Tor network state and the (future) ability to configure the network in a variety /// Tor Status View provides all info on Tor network state and the (future) ability to configure the network in a variety
/// of ways (restart, enable bridges, enable pluggable transports etc) /// of ways (restart, enable bridges, enable pluggable transports etc)
class TorStatusView extends StatefulWidget { class TorStatusView extends StatefulWidget {
const TorStatusView({super.key});
@override @override
_TorStatusView createState() => _TorStatusView(); _TorStatusView createState() => _TorStatusView();
} }
@ -63,7 +65,7 @@ class _TorStatusView extends State<TorStatusView> {
), ),
child: Column(children: [ child: Column(children: [
ListTile( ListTile(
leading: TorIcon(), leading: const TorIcon(),
title: Text(AppLocalizations.of(context)!.torStatus), title: Text(AppLocalizations.of(context)!.torStatus),
subtitle: Text(torStatus.progress == 100 ? AppLocalizations.of(context)!.networkStatusOnline : torStatus.status), subtitle: Text(torStatus.progress == 100 ? AppLocalizations.of(context)!.networkStatusOnline : torStatus.status),
trailing: ElevatedButton( trailing: ElevatedButton(
@ -109,7 +111,7 @@ class _TorStatusView extends State<TorStatusView> {
title: Text(AppLocalizations.of(context)!.torSettingsCustomSocksPort), title: Text(AppLocalizations.of(context)!.torSettingsCustomSocksPort),
subtitle: Text(AppLocalizations.of(context)!.torSettingsCustomSocksPortDescription), subtitle: Text(AppLocalizations.of(context)!.torSettingsCustomSocksPortDescription),
leading: Icon(CwtchIcons.swap_horiz_24px, color: settings.current().mainTextColor), leading: Icon(CwtchIcons.swap_horiz_24px, color: settings.current().mainTextColor),
trailing: Container( trailing: SizedBox(
width: MediaQuery.of(context).size.width / 4, width: MediaQuery.of(context).size.width / 4,
child: CwtchTextField( child: CwtchTextField(
number: true, number: true,
@ -140,7 +142,7 @@ class _TorStatusView extends State<TorStatusView> {
title: Text(AppLocalizations.of(context)!.torSettingsCustomControlPort), title: Text(AppLocalizations.of(context)!.torSettingsCustomControlPort),
subtitle: Text(AppLocalizations.of(context)!.torSettingsCustomControlPortDescription), subtitle: Text(AppLocalizations.of(context)!.torSettingsCustomControlPortDescription),
leading: Icon(CwtchIcons.swap_horiz_24px, color: settings.current().mainTextColor), leading: Icon(CwtchIcons.swap_horiz_24px, color: settings.current().mainTextColor),
trailing: Container( trailing: SizedBox(
width: MediaQuery.of(context).size.width / 4, width: MediaQuery.of(context).size.width / 4,
child: CwtchTextField( child: CwtchTextField(
number: true, number: true,
@ -182,7 +184,7 @@ class _TorStatusView extends State<TorStatusView> {
Visibility( Visibility(
visible: settings.useCustomTorConfig, visible: settings.useCustomTorConfig,
child: Padding( child: Padding(
padding: EdgeInsets.all(5), padding: const EdgeInsets.all(5),
child: CwtchTextField( child: CwtchTextField(
controller: torConfigController, controller: torConfigController,
multiLine: true, multiLine: true,

View File

@ -15,7 +15,8 @@ bool noFilter(ContactInfoState peer) {
// Displays nicknames to UI but uses handles as values // Displays nicknames to UI but uses handles as values
// Pass an onChanged handler to access value // Pass an onChanged handler to access value
class DropdownContacts extends StatefulWidget { class DropdownContacts extends StatefulWidget {
DropdownContacts({ const DropdownContacts({
super.key,
required this.onChanged, required this.onChanged,
this.filter = noFilter, this.filter = noFilter,
}); });
@ -33,7 +34,7 @@ class _DropdownContactsState extends State<DropdownContacts> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return DropdownButton( return DropdownButton(
isExpanded: true, // magic property isExpanded: true, // magic property
value: this.selected, value: selected,
items: Provider.of<ProfileInfoState>(context, listen: false).contactList.contacts.where(widget.filter).map<DropdownMenuItem<String>>((ContactInfoState contact) { items: Provider.of<ProfileInfoState>(context, listen: false).contactList.contacts.where(widget.filter).map<DropdownMenuItem<String>>((ContactInfoState contact) {
return DropdownMenuItem<String>( return DropdownMenuItem<String>(
value: contact.onion, value: contact.onion,
@ -44,7 +45,7 @@ class _DropdownContactsState extends State<DropdownContacts> {
}).toList(), }).toList(),
onChanged: (String? newVal) { onChanged: (String? newVal) {
setState(() { setState(() {
this.selected = newVal; selected = newVal;
}); });
widget.onChanged(newVal); widget.onChanged(newVal);
}); });

View File

@ -6,6 +6,7 @@ import 'package:provider/provider.dart';
// Callers must provide a text controller, label helper text and a validator. // Callers must provide a text controller, label helper text and a validator.
class CwtchButtonTextField extends StatefulWidget { class CwtchButtonTextField extends StatefulWidget {
CwtchButtonTextField({ CwtchButtonTextField({
super.key,
required this.controller, required this.controller,
required this.onPressed, required this.onPressed,
required this.icon, required this.icon,
@ -48,7 +49,7 @@ class _CwtchButtonTextFieldState extends State<CwtchButtonTextField> {
return Consumer<Settings>(builder: (context, theme, child) { return Consumer<Settings>(builder: (context, theme, child) {
return Container( return Container(
clipBehavior: Clip.antiAlias, clipBehavior: Clip.antiAlias,
decoration: BoxDecoration(), decoration: const BoxDecoration(),
// Horrifying Hack: Flutter doesn't give us direct control over system menus but instead picks BG color from TextButtonThemeData ¯\_(ツ)_/¯ // Horrifying Hack: Flutter doesn't give us direct control over system menus but instead picks BG color from TextButtonThemeData ¯\_(ツ)_/¯
child: Theme( child: Theme(
data: Theme.of(context).copyWith( data: Theme.of(context).copyWith(
@ -65,7 +66,7 @@ class _CwtchButtonTextFieldState extends State<CwtchButtonTextField> {
enableIMEPersonalizedLearning: false, enableIMEPersonalizedLearning: false,
onChanged: widget.onChanged, onChanged: widget.onChanged,
maxLines: 1, maxLines: 1,
style: widget.textStyle == null ? TextStyle(overflow: TextOverflow.clip) : widget.textStyle, style: widget.textStyle ?? const TextStyle(overflow: TextOverflow.clip),
decoration: InputDecoration( decoration: InputDecoration(
labelText: widget.labelText, labelText: widget.labelText,
labelStyle: TextStyle(color: theme.current().mainTextColor, backgroundColor: theme.current().textfieldBackgroundColor), labelStyle: TextStyle(color: theme.current().mainTextColor, backgroundColor: theme.current().textfieldBackgroundColor),
@ -73,7 +74,7 @@ class _CwtchButtonTextFieldState extends State<CwtchButtonTextField> {
onPressed: widget.onPressed, onPressed: widget.onPressed,
icon: widget.icon, icon: widget.icon,
splashRadius: Material.defaultSplashRadius / 2, splashRadius: Material.defaultSplashRadius / 2,
padding: EdgeInsets.fromLTRB(0.0, 4.0, 2.0, 2.0), padding: const EdgeInsets.fromLTRB(0.0, 4.0, 2.0, 2.0),
tooltip: widget.tooltip, tooltip: widget.tooltip,
enableFeedback: true, enableFeedback: true,
color: theme.current().mainTextColor, color: theme.current().mainTextColor,
@ -91,7 +92,7 @@ class _CwtchButtonTextFieldState extends State<CwtchButtonTextField> {
color: theme.current().textfieldErrorColor, color: theme.current().textfieldErrorColor,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
contentPadding: EdgeInsets.fromLTRB(20.0, 10.0, 20.0, 10.0), contentPadding: const EdgeInsets.fromLTRB(20.0, 10.0, 20.0, 10.0),
enabledBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(6.0), borderSide: BorderSide(color: theme.current().textfieldBorderColor, width: 1.0))), enabledBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(6.0), borderSide: BorderSide(color: theme.current().textfieldBorderColor, width: 1.0))),
))); )));
}); });

View File

@ -14,28 +14,27 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import '../main.dart'; import '../main.dart';
import '../models/message.dart'; import '../models/message.dart';
import '../settings.dart'; import '../settings.dart';
import 'package:intl/intl.dart';
class ContactRow extends StatefulWidget { class ContactRow extends StatefulWidget {
int? messageIndex; int? messageIndex;
ContactRow({this.messageIndex}); ContactRow({super.key, this.messageIndex});
@override @override
_ContactRowState createState() => _ContactRowState(); _ContactRowState createState() => _ContactRowState();
} }
class _ContactRowState extends State<ContactRow> { class _ContactRowState extends State<ContactRow> {
bool isHover = false; bool isHover = false;
Message? cachedMessage = null; Message? cachedMessage;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var contact = Provider.of<ContactInfoState>(context); var contact = Provider.of<ContactInfoState>(context);
if (widget.messageIndex != null && this.cachedMessage == null) { if (widget.messageIndex != null && cachedMessage == null) {
messageHandler(context, Provider.of<ProfileInfoState>(context, listen: false).onion, contact.identifier, ByIndex(widget.messageIndex!)).then((value) { messageHandler(context, Provider.of<ProfileInfoState>(context, listen: false).onion, contact.identifier, ByIndex(widget.messageIndex!)).then((value) {
setState(() { setState(() {
this.cachedMessage = value; cachedMessage = value;
}); });
}); });
} }
@ -79,7 +78,7 @@ class _ContactRowState extends State<ContactRow> {
)), )),
Expanded( Expanded(
child: Padding( child: Padding(
padding: EdgeInsets.all(6.0), padding: const EdgeInsets.all(6.0),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
@ -88,7 +87,7 @@ class _ContactRowState extends State<ContactRow> {
Container( Container(
clipBehavior: Clip.hardEdge, clipBehavior: Clip.hardEdge,
height: Provider.of<Settings>(context).fontScaling * 14.0 + 5.0, height: Provider.of<Settings>(context).fontScaling * 14.0 + 5.0,
decoration: BoxDecoration(), decoration: const BoxDecoration(),
child: Text( child: Text(
contact.augmentedNickname(context).trim() + (contact.messageDraft.isEmpty() ? "" : "*"), contact.augmentedNickname(context).trim() + (contact.messageDraft.isEmpty() ? "" : "*"),
style: TextStyle( style: TextStyle(
@ -103,7 +102,7 @@ class _ContactRowState extends State<ContactRow> {
maxLines: 1, maxLines: 1,
)), )),
syncStatus ?? syncStatus ??
SizedBox( const SizedBox(
width: 0, width: 0,
height: 0, height: 0,
), ),
@ -111,20 +110,20 @@ class _ContactRowState extends State<ContactRow> {
IgnorePointer( IgnorePointer(
ignoring: true, ignoring: true,
child: Visibility( child: Visibility(
visible: this.cachedMessage != null, visible: cachedMessage != null,
maintainSize: false, maintainSize: false,
maintainInteractivity: false, maintainInteractivity: false,
maintainSemantics: false, maintainSemantics: false,
maintainState: false, maintainState: false,
child: this.cachedMessage == null ? CircularProgressIndicator() : this.cachedMessage!.getPreviewWidget(context), child: cachedMessage == null ? const CircularProgressIndicator() : cachedMessage!.getPreviewWidget(context),
)), )),
Container( Container(
padding: EdgeInsets.all(0), padding: const EdgeInsets.all(0),
height: contact.isInvitation ? Provider.of<Settings>(context).fontScaling * 14.0 + 35.0 : Provider.of<Settings>(context).fontScaling * 14.0 + 5.0, height: contact.isInvitation ? Provider.of<Settings>(context).fontScaling * 14.0 + 35.0 : Provider.of<Settings>(context).fontScaling * 14.0 + 5.0,
child: contact.isInvitation == true child: contact.isInvitation == true
? Wrap(alignment: WrapAlignment.start, direction: Axis.vertical, children: <Widget>[ ? Wrap(alignment: WrapAlignment.start, direction: Axis.vertical, children: <Widget>[
Padding( Padding(
padding: EdgeInsets.all(2), padding: const EdgeInsets.all(2),
child: TextButton.icon( child: TextButton.icon(
label: Text( label: Text(
AppLocalizations.of(context)!.tooltipAcceptContactRequest, AppLocalizations.of(context)!.tooltipAcceptContactRequest,
@ -138,7 +137,7 @@ class _ContactRowState extends State<ContactRow> {
onPressed: _btnApprove, onPressed: _btnApprove,
)), )),
Padding( Padding(
padding: EdgeInsets.all(2), padding: const EdgeInsets.all(2),
child: TextButton.icon( child: TextButton.icon(
label: Text( label: Text(
AppLocalizations.of(context)!.tooltipRejectContactRequest, AppLocalizations.of(context)!.tooltipRejectContactRequest,
@ -153,21 +152,21 @@ class _ContactRowState extends State<ContactRow> {
]) ])
: (contact.isBlocked : (contact.isBlocked
? IconButton( ? IconButton(
padding: EdgeInsets.symmetric(vertical: 2.0, horizontal: 0.0), padding: const EdgeInsets.symmetric(vertical: 2.0, horizontal: 0.0),
splashRadius: Material.defaultSplashRadius / 2, splashRadius: Material.defaultSplashRadius / 2,
iconSize: 16, iconSize: 16,
icon: Icon(Icons.block, color: Provider.of<Settings>(context).theme.mainTextColor), icon: Icon(Icons.block, color: Provider.of<Settings>(context).theme.mainTextColor),
onPressed: null, onPressed: null,
) )
: Text(prettyDateString(context, widget.messageIndex == null ? contact.lastMessageSentTime : (this.cachedMessage?.getMetadata().timestamp ?? DateTime.now())), : Text(prettyDateString(context, widget.messageIndex == null ? contact.lastMessageSentTime : (cachedMessage?.getMetadata().timestamp ?? DateTime.now())),
style: Provider.of<Settings>(context).scaleFonts(TextStyle( style: Provider.of<Settings>(context).scaleFonts(const TextStyle(
fontSize: 12.0, fontSize: 12.0,
fontFamily: "Inter", fontFamily: "Inter",
))))), ))))),
Visibility( Visibility(
visible: !Provider.of<Settings>(context).streamerMode, visible: !Provider.of<Settings>(context).streamerMode,
child: Container( child: Container(
padding: EdgeInsets.all(0), padding: const EdgeInsets.all(0),
height: Provider.of<Settings>(context).fontScaling * 13.0 + 5.0, height: Provider.of<Settings>(context).fontScaling * 13.0 + 5.0,
child: Text( child: Text(
contact.onion, contact.onion,

View File

@ -6,7 +6,7 @@ import '../settings.dart';
// Provides a styled Label // Provides a styled Label
// Callers must provide a label text // Callers must provide a label text
class CwtchLabel extends StatefulWidget { class CwtchLabel extends StatefulWidget {
CwtchLabel({required this.label}); const CwtchLabel({super.key, required this.label});
final String label; final String label;
@override @override

View File

@ -11,7 +11,6 @@ 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: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/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import '../main.dart'; import '../main.dart';
@ -33,13 +32,13 @@ class FileBubble extends StatefulWidget {
final bool isAuto; final bool isAuto;
final bool isPreview; final bool isPreview;
FileBubble(this.nameSuggestion, this.rootHash, this.nonce, this.fileSize, {this.isAuto = false, this.interactive = true, this.isPreview = false}); const FileBubble(this.nameSuggestion, this.rootHash, this.nonce, this.fileSize, {super.key, this.isAuto = false, this.interactive = true, this.isPreview = false});
@override @override
FileBubbleState createState() => FileBubbleState(); FileBubbleState createState() => FileBubbleState();
String fileKey() { String fileKey() {
return this.rootHash + "." + this.nonce; return "$rootHash.$nonce";
} }
} }
@ -64,7 +63,7 @@ class FileBubbleState extends State<FileBubble> {
alignment: Alignment.center, alignment: Alignment.center,
isAntiAlias: false, isAntiAlias: false,
errorBuilder: (context, error, stackTrace) { errorBuilder: (context, error, stackTrace) {
return MalformedBubble(); return const MalformedBubble();
}, },
)); ));
} }
@ -101,7 +100,7 @@ class FileBubbleState extends State<FileBubble> {
if (isImagePreview) { if (isImagePreview) {
if (myFile == null || myFile?.path != path) { if (myFile == null || myFile?.path != path) {
setState(() { setState(() {
myFile = new File(path!); myFile = File(path!);
// reset // reset
if (myFile?.existsSync() == false) { if (myFile?.existsSync() == false) {
myFile = null; myFile = null;
@ -157,18 +156,21 @@ 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, clipBehavior: Clip.hardEdge, decoration: BoxDecoration(), child: compileSenderWidget(context, null, fromMe, senderDisplayStr))); height: 14 * Provider.of<Settings>(context).fontScaling,
clipBehavior: Clip.hardEdge,
decoration: const BoxDecoration(),
child: compileSenderWidget(context, null, fromMe, senderDisplayStr)));
var isPreview = false; var isPreview = false;
var wdgMessage = !showFileSharing var wdgMessage = !showFileSharing
? Text(AppLocalizations.of(context)!.messageEnableFileSharing, style: Provider.of<Settings>(context).scaleFonts(defaultTextStyle)) ? Text(AppLocalizations.of(context)!.messageEnableFileSharing, style: Provider.of<Settings>(context).scaleFonts(defaultTextStyle))
: fromMe : fromMe
? senderFileChrome(AppLocalizations.of(context)!.messageFileSent, widget.nameSuggestion, widget.rootHash) ? senderFileChrome(AppLocalizations.of(context)!.messageFileSent, widget.nameSuggestion, widget.rootHash)
: (fileChrome(AppLocalizations.of(context)!.messageFileOffered + ":", widget.nameSuggestion, widget.rootHash, widget.fileSize, : (fileChrome("${AppLocalizations.of(context)!.messageFileOffered}:", widget.nameSuggestion, widget.rootHash, widget.fileSize,
Provider.of<ProfileInfoState>(context).downloadSpeed(widget.fileKey()))); Provider.of<ProfileInfoState>(context).downloadSpeed(widget.fileKey())));
Widget wdgDecorations; Widget wdgDecorations;
if (!showFileSharing) { if (!showFileSharing) {
wdgDecorations = Text('\u202F'); wdgDecorations = const Text('\u202F');
} else if ((fromMe || downloadComplete) && path != null) { } else if ((fromMe || downloadComplete) && path != null) {
// in this case, whatever marked download.complete would have also set the path // in this case, whatever marked download.complete would have also set the path
if (myFile != null && Provider.of<Settings>(context).shouldPreview(path)) { if (myFile != null && Provider.of<Settings>(context).shouldPreview(path)) {
@ -178,21 +180,21 @@ class FileBubbleState extends State<FileBubble> {
child: MouseRegion( child: MouseRegion(
cursor: SystemMouseCursors.click, cursor: SystemMouseCursors.click,
child: GestureDetector( child: GestureDetector(
child: Padding(padding: EdgeInsets.all(1.0), child: getPreview(context)), child: Padding(padding: const EdgeInsets.all(1.0), child: getPreview(context)),
onTap: () { onTap: () {
pop(context, myFile!, widget.nameSuggestion); pop(context, myFile!, widget.nameSuggestion);
}, },
))); )));
} else if (fromMe) { } else if (fromMe) {
wdgDecorations = Text('\u202F'); wdgDecorations = const Text('\u202F');
} else { } else {
wdgDecorations = Visibility( wdgDecorations = Visibility(
visible: widget.interactive, child: SelectableText(AppLocalizations.of(context)!.fileSavedTo + ': ' + path + '\u202F', style: Provider.of<Settings>(context).scaleFonts(defaultTextStyle))); visible: widget.interactive, child: SelectableText('${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( wdgDecorations = Visibility(
visible: widget.interactive, child: SelectableText(AppLocalizations.of(context)!.retrievingManifestMessage + '\u202F', style: Provider.of<Settings>(context).scaleFonts(defaultTextStyle))); visible: widget.interactive, child: SelectableText('${AppLocalizations.of(context)!.retrievingManifestMessage}\u202F', style: Provider.of<Settings>(context).scaleFonts(defaultTextStyle)));
} else { } else {
wdgDecorations = Visibility( wdgDecorations = Visibility(
visible: widget.interactive, visible: widget.interactive,
@ -205,14 +207,14 @@ 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', style: Provider.of<Settings>(context).scaleFonts(defaultTextStyle)); 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', style: Provider.of<Settings>(context).scaleFonts(defaultTextStyle)), 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))) ElevatedButton(onPressed: _btnResume, child: Text(AppLocalizations.of(context)!.verfiyResumeButton, style: Provider.of<Settings>(context).scaleFonts(defaultTextButtonStyle)))
])); ]));
} }
@ -227,9 +229,9 @@ class FileBubbleState extends State<FileBubble> {
widthFactor: 1, widthFactor: 1,
child: Wrap(children: [ child: Wrap(children: [
Padding( Padding(
padding: EdgeInsets.all(5), padding: const EdgeInsets.all(5),
child: ElevatedButton( child: ElevatedButton(
child: Text(AppLocalizations.of(context)!.downloadFileButton + '\u202F', style: Provider.of<Settings>(context).scaleFonts(defaultTextButtonStyle)), onPressed: _btnAccept)), onPressed: _btnAccept, child: Text('${AppLocalizations.of(context)!.downloadFileButton}\u202F', style: Provider.of<Settings>(context).scaleFonts(defaultTextButtonStyle)))),
]))); ])));
} else { } else {
wdgDecorations = Container(); wdgDecorations = Container();
@ -260,7 +262,7 @@ class FileBubbleState extends State<FileBubble> {
), ),
), ),
child: Padding( child: Padding(
padding: EdgeInsets.all(9.0), padding: const EdgeInsets.all(9.0),
child: Column( child: Column(
crossAxisAlignment: fromMe ? CrossAxisAlignment.end : CrossAxisAlignment.start, crossAxisAlignment: fromMe ? CrossAxisAlignment.end : CrossAxisAlignment.start,
mainAxisAlignment: fromMe ? MainAxisAlignment.end : MainAxisAlignment.start, mainAxisAlignment: fromMe ? MainAxisAlignment.end : MainAxisAlignment.start,
@ -292,7 +294,7 @@ class FileBubbleState extends State<FileBubble> {
Provider.of<FlwtchState>(context, listen: false).cwtch.SetMessageAttribute(profileOnion, conversation, 0, idx, "file-downloaded", "true"); Provider.of<FlwtchState>(context, listen: false).cwtch.SetMessageAttribute(profileOnion, conversation, 0, idx, "file-downloaded", "true");
ContactInfoState? contact = Provider.of<ProfileInfoState>(context, listen: false).contactList.findContact(Provider.of<MessageMetadata>(context, listen: false).senderHandle); ContactInfoState? contact = Provider.of<ProfileInfoState>(context, listen: false).contactList.findContact(Provider.of<MessageMetadata>(context, listen: false).senderHandle);
if (contact != null) { if (contact != null) {
var manifestPath = Provider.of<Settings>(context, listen: false).downloadPath + "/" + widget.fileKey() + ".manifest"; var manifestPath = "${Provider.of<Settings>(context, listen: false).downloadPath}/${widget.fileKey()}.manifest";
Provider.of<FlwtchState>(context, listen: false).cwtch.CreateDownloadableFile(profileOnion, contact.identifier, widget.nameSuggestion, widget.fileKey(), manifestPath); Provider.of<FlwtchState>(context, listen: false).cwtch.CreateDownloadableFile(profileOnion, contact.identifier, widget.nameSuggestion, widget.fileKey(), manifestPath);
} }
@ -304,8 +306,8 @@ class FileBubbleState extends State<FileBubble> {
); );
if (selectedFileName != null) { if (selectedFileName != null) {
file = File(selectedFileName); file = File(selectedFileName);
EnvironmentConfig.debugLog("saving to " + file.path); EnvironmentConfig.debugLog("saving to ${file.path}");
var manifestPath = file.path + ".manifest"; var manifestPath = "${file.path}.manifest";
Provider.of<ProfileInfoState>(context, listen: false).downloadInit(widget.fileKey(), (widget.fileSize / 4096).ceil()); Provider.of<ProfileInfoState>(context, listen: false).downloadInit(widget.fileKey(), (widget.fileSize / 4096).ceil());
Provider.of<FlwtchState>(context, listen: false).cwtch.SetMessageAttribute(profileOnion, conversation, 0, idx, "file-downloaded", "true"); Provider.of<FlwtchState>(context, listen: false).cwtch.SetMessageAttribute(profileOnion, conversation, 0, idx, "file-downloaded", "true");
ContactInfoState? contact = Provider.of<ProfileInfoState>(context, listen: false).contactList.findContact(Provider.of<MessageMetadata>(context, listen: false).senderHandle); ContactInfoState? contact = Provider.of<ProfileInfoState>(context, listen: false).contactList.findContact(Provider.of<MessageMetadata>(context, listen: false).senderHandle);
@ -331,9 +333,9 @@ class FileBubbleState extends State<FileBubble> {
var settings = Provider.of<Settings>(context); var settings = Provider.of<Settings>(context);
return ListTile( return ListTile(
visualDensity: VisualDensity.compact, visualDensity: VisualDensity.compact,
contentPadding: EdgeInsets.all(1.0), contentPadding: const EdgeInsets.all(1.0),
title: SelectableText( title: SelectableText(
fileName + '\u202F', '$fileName\u202F',
style: style:
settings.scaleFonts(defaultMessageTextStyle.copyWith(overflow: TextOverflow.ellipsis, fontWeight: FontWeight.bold, color: Provider.of<Settings>(context).theme.messageFromMeTextColor)), settings.scaleFonts(defaultMessageTextStyle.copyWith(overflow: TextOverflow.ellipsis, fontWeight: FontWeight.bold, color: Provider.of<Settings>(context).theme.messageFromMeTextColor)),
textAlign: TextAlign.left, textAlign: TextAlign.left,
@ -341,7 +343,7 @@ class FileBubbleState extends State<FileBubble> {
maxLines: 2, maxLines: 2,
), ),
subtitle: SelectableText( subtitle: SelectableText(
prettyBytes(widget.fileSize) + '\u202F' + '\n' + 'sha512: ' + rootHash + '\u202F', '${prettyBytes(widget.fileSize)}\u202F\nsha512: $rootHash\u202F',
style: settings.scaleFonts(defaultSmallTextStyle.copyWith(fontFamily: "RobotoMono", color: Provider.of<Settings>(context).theme.messageFromMeTextColor)), style: settings.scaleFonts(defaultSmallTextStyle.copyWith(fontFamily: "RobotoMono", color: Provider.of<Settings>(context).theme.messageFromMeTextColor)),
textAlign: TextAlign.left, textAlign: TextAlign.left,
maxLines: 4, maxLines: 4,
@ -355,9 +357,9 @@ class FileBubbleState extends State<FileBubble> {
var settings = Provider.of<Settings>(context); var settings = Provider.of<Settings>(context);
return ListTile( return ListTile(
visualDensity: VisualDensity.compact, visualDensity: VisualDensity.compact,
contentPadding: EdgeInsets.all(1.0), contentPadding: const EdgeInsets.all(1.0),
title: SelectableText( title: SelectableText(
fileName + '\u202F', '$fileName\u202F',
style: style:
settings.scaleFonts(defaultMessageTextStyle.copyWith(overflow: TextOverflow.ellipsis, fontWeight: FontWeight.bold, color: Provider.of<Settings>(context).theme.messageFromOtherTextColor)), settings.scaleFonts(defaultMessageTextStyle.copyWith(overflow: TextOverflow.ellipsis, fontWeight: FontWeight.bold, color: Provider.of<Settings>(context).theme.messageFromOtherTextColor)),
textAlign: TextAlign.left, textAlign: TextAlign.left,
@ -365,7 +367,7 @@ class FileBubbleState extends State<FileBubble> {
maxLines: 2, maxLines: 2,
), ),
subtitle: SelectableText( subtitle: SelectableText(
prettyBytes(widget.fileSize) + '\u202F' + '\n' + 'sha512: ' + rootHash + '\u202F', '${prettyBytes(widget.fileSize)}\u202F\nsha512: $rootHash\u202F',
style: settings.scaleFonts(defaultSmallTextStyle.copyWith(fontFamily: "RobotoMono", color: Provider.of<Settings>(context).theme.messageFromOtherTextColor)), style: settings.scaleFonts(defaultSmallTextStyle.copyWith(fontFamily: "RobotoMono", color: Provider.of<Settings>(context).theme.messageFromOtherTextColor)),
textAlign: TextAlign.left, textAlign: TextAlign.left,
maxLines: 4, maxLines: 4,
@ -376,7 +378,7 @@ class FileBubbleState extends State<FileBubble> {
trailing: speed == "0 B/s" trailing: speed == "0 B/s"
? null ? null
: SelectableText( : SelectableText(
speed + '\u202F', '$speed\u202F',
style: settings.scaleFonts(defaultSmallTextStyle.copyWith(color: Provider.of<Settings>(context).theme.messageFromOtherTextColor)), style: settings.scaleFonts(defaultSmallTextStyle.copyWith(color: Provider.of<Settings>(context).theme.messageFromOtherTextColor)),
textAlign: TextAlign.left, textAlign: TextAlign.left,
maxLines: 1, maxLines: 1,
@ -393,20 +395,20 @@ class FileBubbleState extends State<FileBubble> {
child: SingleChildScrollView( child: SingleChildScrollView(
controller: ScrollController(), controller: ScrollController(),
child: Container( child: Container(
padding: EdgeInsets.all(10), padding: const EdgeInsets.all(10),
child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.center, children: [ child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.center, children: [
ListTile( ListTile(
leading: Icon(CwtchIcons.attached_file_3), leading: const Icon(CwtchIcons.attached_file_3),
title: Text(meta), title: Text(meta),
trailing: IconButton( trailing: IconButton(
icon: Icon(Icons.close), icon: const Icon(Icons.close),
color: Provider.of<Settings>(bcontext, listen: false).theme.mainTextColor, color: Provider.of<Settings>(bcontext, listen: false).theme.mainTextColor,
iconSize: 32, iconSize: 32,
onPressed: () { onPressed: () {
Navigator.pop(bcontext, true); Navigator.pop(bcontext, true);
})), })),
Padding( Padding(
padding: EdgeInsets.all(10), padding: const EdgeInsets.all(10),
child: Image.file( child: Image.file(
myFile, myFile,
cacheWidth: (MediaQuery.of(bcontext).size.width * 0.6).floor(), cacheWidth: (MediaQuery.of(bcontext).size.width * 0.6).floor(),
@ -419,9 +421,9 @@ class FileBubbleState extends State<FileBubble> {
visible: Platform.isAndroid, visible: Platform.isAndroid,
maintainSize: false, maintainSize: false,
child: Padding( child: Padding(
padding: EdgeInsets.all(10), padding: const EdgeInsets.all(10),
child: ElevatedButton.icon( child: ElevatedButton.icon(
icon: Icon(Icons.arrow_downward), icon: const Icon(Icons.arrow_downward),
onPressed: androidExport, onPressed: androidExport,
label: Text( label: Text(
AppLocalizations.of(bcontext)!.saveBtn, AppLocalizations.of(bcontext)!.saveBtn,

View File

@ -6,7 +6,6 @@ import 'package:provider/provider.dart';
import '../settings.dart'; import '../settings.dart';
import 'buttontextfield.dart'; import 'buttontextfield.dart';
import 'package:path/path.dart' as path; import 'package:path/path.dart' as path;
import 'cwtchlabel.dart';
class CwtchFolderPicker extends StatefulWidget { class CwtchFolderPicker extends StatefulWidget {
final String label; final String label;
@ -17,8 +16,7 @@ class CwtchFolderPicker extends StatefulWidget {
final Key? testKey; final Key? testKey;
final TextStyle? textStyle; final TextStyle? textStyle;
final IconData icon; final IconData icon;
const CwtchFolderPicker({Key? key, this.testKey, this.textStyle, this.label = "", this.tooltip = "", this.initialValue = "", this.onSave, this.description = "", this.icon = Icons.file_download}) const CwtchFolderPicker({super.key, this.testKey, this.textStyle, this.label = "", this.tooltip = "", this.initialValue = "", this.onSave, this.description = "", this.icon = Icons.file_download});
: super(key: key);
@override @override
_CwtchFolderPickerState createState() => _CwtchFolderPickerState(); _CwtchFolderPickerState createState() => _CwtchFolderPickerState();
@ -39,7 +37,7 @@ class _CwtchFolderPickerState extends State<CwtchFolderPicker> {
leading: Icon(widget.icon, color: Provider.of<Settings>(context).theme.messageFromMeTextColor), leading: Icon(widget.icon, color: Provider.of<Settings>(context).theme.messageFromMeTextColor),
title: Text(widget.label), title: Text(widget.label),
subtitle: Text(widget.description), subtitle: Text(widget.description),
trailing: Container( trailing: SizedBox(
width: MediaQuery.of(context).size.width / 4, width: MediaQuery.of(context).size.width / 4,
child: CwtchButtonTextField( child: CwtchButtonTextField(
testKey: widget.testKey, testKey: widget.testKey,
@ -66,7 +64,7 @@ class _CwtchFolderPickerState extends State<CwtchFolderPicker> {
} }
}, },
onChanged: widget.onSave, onChanged: widget.onSave,
icon: Icon(Icons.folder), icon: const Icon(Icons.folder),
tooltip: widget.tooltip, tooltip: widget.tooltip,
))); )));
} }

View File

@ -1,6 +1,3 @@
import 'dart:convert';
import 'dart:io';
import 'package:cwtch/cwtch_icons_icons.dart'; 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';
@ -10,7 +7,6 @@ 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';
import '../main.dart'; import '../main.dart';
import 'package:intl/intl.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import '../models/redaction.dart'; import '../models/redaction.dart';
@ -26,7 +22,7 @@ class InvitationBubble extends StatefulWidget {
final String inviteNick; final String inviteNick;
final String invite; final String invite;
InvitationBubble(this.overlay, this.inviteTarget, this.inviteNick, this.invite); const InvitationBubble(this.overlay, this.inviteTarget, this.inviteNick, this.invite, {super.key});
@override @override
InvitationBubbleState createState() => InvitationBubbleState(); InvitationBubbleState createState() => InvitationBubbleState();
@ -62,7 +58,7 @@ class InvitationBubbleState extends State<InvitationBubble> {
// 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;
if (selfInvite) { if (selfInvite) {
return MalformedBubble(); return const MalformedBubble();
} }
var wdgMessage = isGroup && !showGroupInvite var wdgMessage = isGroup && !showGroupInvite
@ -74,25 +70,25 @@ class InvitationBubbleState extends State<InvitationBubble> {
Widget wdgDecorations; Widget wdgDecorations;
if (isGroup && !showGroupInvite) { if (isGroup && !showGroupInvite) {
wdgDecorations = Text('\u202F'); wdgDecorations = const Text('\u202F');
} 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', style: Provider.of<Settings>(context).scaleFonts(defaultTextStyle)); wdgDecorations = Text('${AppLocalizations.of(context)!.accepted}\u202F', style: Provider.of<Settings>(context).scaleFonts(defaultTextStyle));
} else if (this.rejected) { } else if (rejected) {
wdgDecorations = Text(AppLocalizations.of(context)!.rejected + '\u202F', style: Provider.of<Settings>(context).scaleFonts(defaultTextStyle)); 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(
padding: EdgeInsets.all(5), padding: const EdgeInsets.all(5),
child: ElevatedButton( child: ElevatedButton(
child: Text(AppLocalizations.of(context)!.rejectGroupBtn + '\u202F', style: Provider.of<Settings>(context).scaleFonts(defaultTextButtonStyle)), onPressed: _btnReject)), onPressed: _btnReject, child: Text('${AppLocalizations.of(context)!.rejectGroupBtn}\u202F', style: Provider.of<Settings>(context).scaleFonts(defaultTextButtonStyle)))),
Padding( Padding(
padding: EdgeInsets.all(5), padding: const EdgeInsets.all(5),
child: ElevatedButton( child: ElevatedButton(
child: Text(AppLocalizations.of(context)!.acceptGroupBtn + '\u202F', style: Provider.of<Settings>(context).scaleFonts(defaultTextButtonStyle)), onPressed: _btnAccept)), onPressed: _btnAccept, child: Text('${AppLocalizations.of(context)!.acceptGroupBtn}\u202F', style: Provider.of<Settings>(context).scaleFonts(defaultTextButtonStyle)))),
])); ]));
} }
@ -126,11 +122,11 @@ class InvitationBubbleState extends State<InvitationBubble> {
child: Center( child: Center(
widthFactor: 1.0, widthFactor: 1.0,
child: Padding( child: Padding(
padding: EdgeInsets.all(9.0), padding: const EdgeInsets.all(9.0),
child: Wrap(runAlignment: WrapAlignment.spaceEvenly, alignment: WrapAlignment.spaceEvenly, runSpacing: 1.0, crossAxisAlignment: WrapCrossAlignment.center, children: [ child: Wrap(runAlignment: WrapAlignment.spaceEvenly, alignment: WrapAlignment.spaceEvenly, runSpacing: 1.0, crossAxisAlignment: WrapCrossAlignment.center, children: [
Center( Center(
widthFactor: 1, widthFactor: 1,
child: Padding(padding: EdgeInsets.all(10.0), child: Icon(isGroup && !showGroupInvite ? CwtchIcons.enable_experiments : CwtchIcons.send_invite, size: 32))), child: Padding(padding: const EdgeInsets.all(10.0), child: Icon(isGroup && !showGroupInvite ? CwtchIcons.enable_experiments : CwtchIcons.send_invite, size: 32))),
Center( Center(
widthFactor: 1.0, widthFactor: 1.0,
child: Column( child: Column(
@ -167,14 +163,14 @@ class InvitationBubbleState extends State<InvitationBubble> {
return Wrap(children: [ return Wrap(children: [
SelectableText( SelectableText(
chrome + '\u202F', '$chrome\u202F',
style: settings.scaleFonts(defaultMessageTextStyle.copyWith(color: Provider.of<Settings>(context).theme.messageFromMeTextColor)), style: settings.scaleFonts(defaultMessageTextStyle.copyWith(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: settings.scaleFonts(defaultMessageTextStyle.copyWith(color: Provider.of<Settings>(context).theme.messageFromMeTextColor)), style: settings.scaleFonts(defaultMessageTextStyle.copyWith(color: Provider.of<Settings>(context).theme.messageFromMeTextColor)),
textAlign: TextAlign.left, textAlign: TextAlign.left,
maxLines: 2, maxLines: 2,
@ -189,14 +185,14 @@ class InvitationBubbleState extends State<InvitationBubble> {
return Wrap(children: [ return Wrap(children: [
SelectableText( SelectableText(
chrome + '\u202F', '$chrome\u202F',
style: settings.scaleFonts(defaultMessageTextStyle.copyWith(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,
textWidthBasis: TextWidthBasis.longestLine, textWidthBasis: TextWidthBasis.longestLine,
maxLines: 2, maxLines: 2,
), ),
SelectableText( SelectableText(
targetName + '\u202F', '$targetName\u202F',
style: settings.scaleFonts(defaultMessageTextStyle.copyWith(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,

View File

@ -2,10 +2,12 @@ import 'package:cwtch/cwtch_icons_icons.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart';
final Color malformedColor = Color(0xFFE85DA1); const Color malformedColor = Color(0xFFE85DA1);
// MalformedBubble is displayed in the case of a malformed message // MalformedBubble is displayed in the case of a malformed message
class MalformedBubble extends StatefulWidget { class MalformedBubble extends StatefulWidget {
const MalformedBubble({super.key});
@override @override
MalformedBubbleState createState() => MalformedBubbleState(); MalformedBubbleState createState() => MalformedBubbleState();
} }
@ -17,7 +19,7 @@ class MalformedBubbleState extends State<MalformedBubble> {
decoration: BoxDecoration( decoration: BoxDecoration(
color: malformedColor, color: malformedColor,
border: Border.all(color: malformedColor, width: 1), border: Border.all(color: malformedColor, width: 1),
borderRadius: BorderRadius.only( borderRadius: const BorderRadius.only(
topLeft: Radius.zero, topLeft: Radius.zero,
topRight: Radius.zero, topRight: Radius.zero,
bottomLeft: Radius.zero, bottomLeft: Radius.zero,
@ -29,9 +31,9 @@ class MalformedBubbleState extends State<MalformedBubble> {
child: Center( child: Center(
widthFactor: 1.0, widthFactor: 1.0,
child: Padding( child: Padding(
padding: EdgeInsets.all(9.0), padding: const EdgeInsets.all(9.0),
child: Row(mainAxisSize: MainAxisSize.min, children: [ child: Row(mainAxisSize: MainAxisSize.min, children: [
Center( const Center(
widthFactor: 1, widthFactor: 1,
child: Padding( child: Padding(
padding: EdgeInsets.all(4), padding: EdgeInsets.all(4),

View File

@ -9,7 +9,7 @@ Widget compileSenderWidget(BuildContext context, BoxConstraints? constraints, bo
return Container( return Container(
height: 14 * Provider.of<Settings>(context).fontScaling, height: 14 * Provider.of<Settings>(context).fontScaling,
clipBehavior: Clip.hardEdge, clipBehavior: Clip.hardEdge,
decoration: BoxDecoration(), decoration: const BoxDecoration(),
child: SelectableText(senderDisplayStr, child: SelectableText(senderDisplayStr,
maxLines: 1, maxLines: 1,
style: TextStyle( style: TextStyle(
@ -23,10 +23,10 @@ Widget compileSenderWidget(BuildContext context, BoxConstraints? constraints, bo
Widget compileMessageContentWidget(BuildContext context, BoxConstraints constraints, fromMe, String content, FocusNode focus, bool formatMessages, bool showClickableLinks) { Widget compileMessageContentWidget(BuildContext context, BoxConstraints constraints, fromMe, String content, FocusNode focus, bool formatMessages, bool showClickableLinks) {
return SelectableLinkify( return SelectableLinkify(
text: content + '\u202F', text: '$content\u202F',
// TODO: onOpen breaks the "selectable" functionality. Maybe something to do with gesture handler? // TODO: onOpen breaks the "selectable" functionality. Maybe something to do with gesture handler?
options: LinkifyOptions(messageFormatting: formatMessages, parseLinks: showClickableLinks, looseUrl: true, defaultToHttps: true), options: LinkifyOptions(messageFormatting: formatMessages, parseLinks: showClickableLinks, looseUrl: true, defaultToHttps: true),
linkifiers: [UrlLinkifier()], linkifiers: const [UrlLinkifier()],
onOpen: showClickableLinks onOpen: showClickableLinks
? (link) { ? (link) {
modalOpenLink(context, link); modalOpenLink(context, link);

View File

@ -1,11 +1,6 @@
import 'dart:io';
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/models/redaction.dart'; import 'package:cwtch/models/redaction.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/models/profile.dart';
import 'package:cwtch/widgets/malformedbubble.dart'; import 'package:cwtch/widgets/malformedbubble.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -18,14 +13,14 @@ import 'messagebubbledecorations.dart';
class MessageBubble extends StatefulWidget { class MessageBubble extends StatefulWidget {
final String content; final String content;
MessageBubble(this.content); const MessageBubble(this.content, {super.key});
@override @override
MessageBubbleState createState() => MessageBubbleState(); MessageBubbleState createState() => MessageBubbleState();
} }
class MessageBubbleState extends State<MessageBubble> { class MessageBubbleState extends State<MessageBubble> {
FocusNode _focus = FocusNode(); final FocusNode _focus = FocusNode();
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -67,7 +62,7 @@ class MessageBubbleState extends State<MessageBubble> {
), ),
), ),
child: Padding( child: Padding(
padding: EdgeInsets.all(9.0), padding: const EdgeInsets.all(9.0),
child: Theme( child: Theme(
data: Theme.of(context).copyWith( data: Theme.of(context).copyWith(
textSelectionTheme: TextSelectionThemeData( textSelectionTheme: TextSelectionThemeData(

View File

@ -1,6 +1,3 @@
import 'dart:io';
import 'package:flutter/cupertino.dart';
import 'package:intl/intl.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import '../models/redaction.dart'; import '../models/redaction.dart';
@ -9,7 +6,7 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart';
// Provides message decorations (acks/errors/dates etc.) for generic message bubble overlays (chats, invites etc.) // Provides message decorations (acks/errors/dates etc.) for generic message bubble overlays (chats, invites etc.)
class MessageBubbleDecoration extends StatefulWidget { class MessageBubbleDecoration extends StatefulWidget {
MessageBubbleDecoration({required this.ackd, required this.errored, required this.messageDate, required this.fromMe}); const MessageBubbleDecoration({super.key, required this.ackd, required this.errored, required this.messageDate, required this.fromMe});
final DateTime messageDate; final DateTime messageDate;
final bool fromMe; final bool fromMe;
final bool ackd; final bool ackd;
@ -38,9 +35,9 @@ class _MessageBubbleDecoration extends State<MessageBubbleDecoration> {
color: widget.fromMe ? Provider.of<Settings>(context).theme.messageFromMeTextColor : Provider.of<Settings>(context).theme.messageFromOtherTextColor), color: widget.fromMe ? Provider.of<Settings>(context).theme.messageFromMeTextColor : Provider.of<Settings>(context).theme.messageFromOtherTextColor),
textAlign: widget.fromMe ? TextAlign.right : TextAlign.left), textAlign: widget.fromMe ? TextAlign.right : TextAlign.left),
!widget.fromMe !widget.fromMe
? SizedBox(width: 1, height: 1) ? const SizedBox(width: 1, height: 1)
: Padding( : Padding(
padding: EdgeInsets.all(1.0), padding: const EdgeInsets.all(1.0),
child: widget.ackd == true child: widget.ackd == true
? Tooltip( ? Tooltip(
message: AppLocalizations.of(context)!.acknowledgedLabel, child: Icon(Icons.check_circle_outline, color: Provider.of<Settings>(context).theme.messageFromMeTextColor, size: 16)) message: AppLocalizations.of(context)!.acknowledgedLabel, child: Icon(Icons.check_circle_outline, color: Provider.of<Settings>(context).theme.messageFromMeTextColor, size: 16))

View File

@ -5,7 +5,6 @@ import 'package:cwtch/models/messagecache.dart';
import 'package:cwtch/models/profile.dart'; import 'package:cwtch/models/profile.dart';
import 'package:cwtch/widgets/messageloadingbubble.dart'; import 'package:cwtch/widgets/messageloadingbubble.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart';
@ -14,7 +13,7 @@ import '../settings.dart';
class MessageList extends StatefulWidget { class MessageList extends StatefulWidget {
ItemPositionsListener scrollListener; ItemPositionsListener scrollListener;
MessageList(this.scrollListener); MessageList(this.scrollListener, {super.key});
@override @override
_MessageListState createState() => _MessageListState(); _MessageListState createState() => _MessageListState();
@ -46,17 +45,17 @@ class _MessageListState extends State<MessageList> {
// OR if users have never clicked the button AND they appear offline, then they can click the button // OR if users have never clicked the button AND they appear offline, then they can click the button
// NOTE: all these listeners are false...this is not ideal, but if they were true we would end up rebuilding the message view every tick (which would kill performance) // NOTE: all these listeners are false...this is not ideal, but if they were true we would end up rebuilding the message view every tick (which would kill performance)
// any significant changes in state e.g. peer offline or button clicks will trigger a rebuild anyway // any significant changes in state e.g. peer offline or button clicks will trigger a rebuild anyway
bool canReconnect = DateTime.now().difference(Provider.of<ContactInfoState>(context, listen: false).lastRetryTime).abs() > Duration(seconds: 30) || bool canReconnect = DateTime.now().difference(Provider.of<ContactInfoState>(context, listen: false).lastRetryTime).abs() > const Duration(seconds: 30) ||
(Provider.of<ProfileInfoState>(context, listen: false).appearOffline && (Provider.of<ProfileInfoState>(context, listen: false).appearOffline &&
(Provider.of<ContactInfoState>(context, listen: false).lastRetryTime == Provider.of<ContactInfoState>(context, listen: false).loaded)); (Provider.of<ContactInfoState>(context, listen: false).lastRetryTime == Provider.of<ContactInfoState>(context, listen: false).loaded));
var reconnectButton = Padding( var reconnectButton = Padding(
padding: EdgeInsets.all(2), padding: const EdgeInsets.all(2),
child: canReconnect child: canReconnect
? Tooltip( ? Tooltip(
message: AppLocalizations.of(context)!.retryConnectionTooltip, message: AppLocalizations.of(context)!.retryConnectionTooltip,
child: ElevatedButton( child: ElevatedButton(
style: ButtonStyle(padding: MaterialStateProperty.all(EdgeInsets.all(20))), style: ButtonStyle(padding: MaterialStateProperty.all(const EdgeInsets.all(20))),
child: Text(AppLocalizations.of(context)!.retryConnection), child: Text(AppLocalizations.of(context)!.retryConnection),
onPressed: () { onPressed: () {
if (Provider.of<ContactInfoState>(context, listen: false).isGroup) { if (Provider.of<ContactInfoState>(context, listen: false).isGroup) {
@ -84,7 +83,7 @@ class _MessageListState extends State<MessageList> {
Visibility( Visibility(
visible: showMessageWarning, visible: showMessageWarning,
child: Container( child: Container(
padding: EdgeInsets.all(5.0), padding: const EdgeInsets.all(5.0),
color: Provider.of<Settings>(context).theme.defaultButtonActiveColor, color: Provider.of<Settings>(context).theme.defaultButtonActiveColor,
child: DefaultTextStyle( child: DefaultTextStyle(
style: TextStyle(color: Provider.of<Settings>(context).theme.defaultButtonTextColor), style: TextStyle(color: Provider.of<Settings>(context).theme.defaultButtonTextColor),
@ -104,7 +103,7 @@ class _MessageListState extends State<MessageList> {
? Text(AppLocalizations.of(context)!.chatHistoryDefault, textAlign: TextAlign.center) ? Text(AppLocalizations.of(context)!.chatHistoryDefault, textAlign: TextAlign.center)
: :
// We are not allowed to put null here, so put an empty text widget // We are not allowed to put null here, so put an empty text widget
Text("")), const Text("")),
))), ))),
Expanded( Expanded(
child: Container( child: Container(
@ -120,7 +119,7 @@ class _MessageListState extends State<MessageList> {
: DecorationImage( : DecorationImage(
fit: BoxFit.scaleDown, fit: BoxFit.scaleDown,
alignment: Alignment.center, alignment: Alignment.center,
image: AssetImage("assets/core/negative_heart_512px.png"), image: const AssetImage("assets/core/negative_heart_512px.png"),
colorFilter: ColorFilter.mode(Provider.of<Settings>(context).theme.hilightElementColor.withOpacity(0.15), BlendMode.srcIn))), colorFilter: ColorFilter.mode(Provider.of<Settings>(context).theme.hilightElementColor.withOpacity(0.15), BlendMode.srcIn))),
// Don't load messages for syncing server... // Don't load messages for syncing server...
child: loadMessages child: loadMessages
@ -146,7 +145,7 @@ class _MessageListState extends State<MessageList> {
var key = Provider.of<ContactInfoState>(itemBuilderContext, listen: false).getMessageKey(contactHandle, messageIndex); var key = Provider.of<ContactInfoState>(itemBuilderContext, listen: false).getMessageKey(contactHandle, messageIndex);
return message.getWidget(fbcontext, key, messageIndex); return message.getWidget(fbcontext, key, messageIndex);
} else { } else {
return MessageLoadingBubble(); return const MessageLoadingBubble();
} }
}, },
); );

View File

@ -1,6 +1,8 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class MessageLoadingBubble extends StatefulWidget { class MessageLoadingBubble extends StatefulWidget {
const MessageLoadingBubble({super.key});
@override @override
MessageLoadingBubbleState createState() => MessageLoadingBubbleState(); MessageLoadingBubbleState createState() => MessageLoadingBubbleState();
} }
@ -8,6 +10,6 @@ class MessageLoadingBubble extends StatefulWidget {
class MessageLoadingBubbleState extends State<MessageLoadingBubble> { class MessageLoadingBubbleState extends State<MessageLoadingBubble> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Center(child: Row(children: [SizedBox(width: 40, height: 100, child: Text(""))])); return const Center(child: Row(children: [SizedBox(width: 40, height: 100, child: Text(""))]));
} }
} }

View File

@ -25,7 +25,7 @@ class MessageRow extends StatefulWidget {
final Widget child; final Widget child;
final int index; final int index;
MessageRow(this.child, this.index, {Key? key}) : super(key: key); const MessageRow(this.child, this.index, {super.key});
@override @override
MessageRowState createState() => MessageRowState(); MessageRowState createState() => MessageRowState();
@ -51,9 +51,7 @@ class MessageRowState extends State<MessageRow> with SingleTickerProviderStateMi
@override @override
void dispose() { void dispose() {
if (_controller != null) { _controller.dispose();
_controller.dispose();
}
super.dispose(); super.dispose();
} }
@ -79,7 +77,7 @@ class MessageRowState extends State<MessageRow> with SingleTickerProviderStateMi
} }
Widget wdgReply = Platform.isAndroid Widget wdgReply = Platform.isAndroid
? SizedBox.shrink() ? const SizedBox.shrink()
: Visibility( : Visibility(
visible: EnvironmentConfig.TEST_MODE || Provider.of<ContactInfoState>(context).hoveredIndex == Provider.of<MessageMetadata>(context).messageID, visible: EnvironmentConfig.TEST_MODE || Provider.of<ContactInfoState>(context).hoveredIndex == Provider.of<MessageMetadata>(context).messageID,
maintainSize: true, maintainSize: true,
@ -104,7 +102,7 @@ class MessageRowState extends State<MessageRow> with SingleTickerProviderStateMi
var cache = Provider.of<ContactInfoState>(context).messageCache; var cache = Provider.of<ContactInfoState>(context).messageCache;
Widget wdgSeeReplies = Platform.isAndroid Widget wdgSeeReplies = Platform.isAndroid
? SizedBox.shrink() ? const SizedBox.shrink()
: Visibility( : Visibility(
visible: EnvironmentConfig.TEST_MODE || Provider.of<ContactInfoState>(context).hoveredIndex == Provider.of<MessageMetadata>(context).messageID, visible: EnvironmentConfig.TEST_MODE || Provider.of<ContactInfoState>(context).hoveredIndex == Provider.of<MessageMetadata>(context).messageID,
maintainSize: true, maintainSize: true,
@ -124,7 +122,7 @@ class MessageRowState extends State<MessageRow> with SingleTickerProviderStateMi
var message = Provider.of<MessageMetadata>(context, listen: false); var message = Provider.of<MessageMetadata>(context, listen: false);
Widget wdgTranslateMessage = Platform.isAndroid Widget wdgTranslateMessage = Platform.isAndroid
? SizedBox.shrink() ? const SizedBox.shrink()
: Visibility( : Visibility(
visible: Provider.of<FlwtchState>(context, listen: false).cwtch.IsBlodeuweddSupported() && visible: Provider.of<FlwtchState>(context, listen: false).cwtch.IsBlodeuweddSupported() &&
Provider.of<Settings>(context).isExperimentEnabled(BlodeuweddExperiment) && Provider.of<Settings>(context).isExperimentEnabled(BlodeuweddExperiment) &&
@ -156,22 +154,22 @@ class MessageRowState extends State<MessageRow> with SingleTickerProviderStateMi
]; ];
} else if (isBlocked && !showBlockedMessage) { } else if (isBlocked && !showBlockedMessage) {
Color blockedMessageBackground = Provider.of<Settings>(context).theme.messageFromOtherBackgroundColor; Color blockedMessageBackground = Provider.of<Settings>(context).theme.messageFromOtherBackgroundColor;
Widget wdgPortrait = Padding(padding: EdgeInsets.all(4.0), child: Icon(CwtchIcons.account_blocked)); Widget wdgPortrait = const Padding(padding: EdgeInsets.all(4.0), child: Icon(CwtchIcons.account_blocked));
widgetRow = <Widget>[ widgetRow = <Widget>[
wdgPortrait, wdgPortrait,
Container( Container(
padding: EdgeInsets.all(2.0), padding: const EdgeInsets.all(2.0),
decoration: BoxDecoration( decoration: BoxDecoration(
color: blockedMessageBackground, color: blockedMessageBackground,
border: Border.all(color: blockedMessageBackground, width: 2), border: Border.all(color: blockedMessageBackground, width: 2),
borderRadius: BorderRadius.only( borderRadius: const BorderRadius.only(
topLeft: Radius.circular(15.0), topLeft: Radius.circular(15.0),
topRight: Radius.circular(15.0), topRight: Radius.circular(15.0),
bottomLeft: Radius.circular(15.0), bottomLeft: Radius.circular(15.0),
bottomRight: Radius.circular(15.0), bottomRight: Radius.circular(15.0),
)), )),
child: Padding( child: Padding(
padding: EdgeInsets.all(9.0), padding: const EdgeInsets.all(9.0),
child: Column(crossAxisAlignment: CrossAxisAlignment.center, children: [ child: Column(crossAxisAlignment: CrossAxisAlignment.center, children: [
SelectableText( SelectableText(
AppLocalizations.of(context)!.blockedMessageMessage, AppLocalizations.of(context)!.blockedMessageMessage,
@ -183,18 +181,18 @@ class MessageRowState extends State<MessageRow> with SingleTickerProviderStateMi
textWidthBasis: TextWidthBasis.longestLine, textWidthBasis: TextWidthBasis.longestLine,
), ),
Padding( Padding(
padding: EdgeInsets.all(1.0), padding: const EdgeInsets.all(1.0),
child: TextButton( child: TextButton(
style: ButtonStyle( style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(blockedMessageBackground), backgroundColor: MaterialStateProperty.all(blockedMessageBackground),
), ),
child: Text( child: Text(
AppLocalizations.of(context)!.showMessageButton + '\u202F', '${AppLocalizations.of(context)!.showMessageButton}\u202F',
style: TextStyle(decoration: TextDecoration.underline), style: const TextStyle(decoration: TextDecoration.underline),
), ),
onPressed: () { onPressed: () {
setState(() { setState(() {
this.showBlockedMessage = true; showBlockedMessage = true;
}); });
})), })),
]))), ]))),
@ -217,7 +215,7 @@ class MessageRowState extends State<MessageRow> with SingleTickerProviderStateMi
? _btnGoto ? _btnGoto
: _btnAdd, : _btnAdd,
child: Padding( child: Padding(
padding: EdgeInsets.all(4.0), padding: const EdgeInsets.all(4.0),
child: ProfileImage( child: ProfileImage(
diameter: 48.0, diameter: 48.0,
// default to the contact image...otherwise use a derived sender image... // default to the contact image...otherwise use a derived sender image...
@ -278,7 +276,7 @@ class MessageRowState extends State<MessageRow> with SingleTickerProviderStateMi
} }
}, },
child: Padding( child: Padding(
padding: EdgeInsets.all(2), padding: const EdgeInsets.all(2),
child: Align( child: Align(
widthFactor: 1, widthFactor: 1,
alignment: _dragAlignment, alignment: _dragAlignment,
@ -291,7 +289,7 @@ class MessageRowState extends State<MessageRow> with SingleTickerProviderStateMi
if (Provider.of<ContactInfoState>(context).newMarkerMsgIndex == widget.index) { if (Provider.of<ContactInfoState>(context).newMarkerMsgIndex == widget.index) {
return Column( return Column(
crossAxisAlignment: fromMe ? CrossAxisAlignment.end : CrossAxisAlignment.start, crossAxisAlignment: fromMe ? CrossAxisAlignment.end : CrossAxisAlignment.start,
children: [Align(alignment: Alignment.center, child: Padding(padding: EdgeInsets.all(5.0), child: _bubbleNew())), mr]); children: [Align(alignment: Alignment.center, child: Padding(padding: const EdgeInsets.all(5.0), child: _bubbleNew())), mr]);
} else { } else {
return mr; return mr;
} }
@ -302,14 +300,14 @@ class MessageRowState extends State<MessageRow> with SingleTickerProviderStateMi
decoration: BoxDecoration( decoration: BoxDecoration(
color: Provider.of<Settings>(context).theme.messageFromMeBackgroundColor, color: Provider.of<Settings>(context).theme.messageFromMeBackgroundColor,
border: Border.all(color: Provider.of<Settings>(context).theme.messageFromMeBackgroundColor, width: 1), border: Border.all(color: Provider.of<Settings>(context).theme.messageFromMeBackgroundColor, width: 1),
borderRadius: BorderRadius.only( borderRadius: const BorderRadius.only(
topLeft: Radius.circular(8), topLeft: Radius.circular(8),
topRight: Radius.circular(8), topRight: Radius.circular(8),
bottomLeft: Radius.circular(8), bottomLeft: Radius.circular(8),
bottomRight: Radius.circular(8), bottomRight: Radius.circular(8),
), ),
), ),
child: Padding(padding: EdgeInsets.all(9.0), child: Text(AppLocalizations.of(context)!.newMessagesLabel, style: Provider.of<Settings>(context).scaleFonts(defaultTextButtonStyle)))); child: Padding(padding: const 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) {
@ -361,20 +359,20 @@ class MessageRowState extends State<MessageRow> with SingleTickerProviderStateMi
showAddContactConfirmAlertDialog(BuildContext context, String profileOnion, String senderOnion) { showAddContactConfirmAlertDialog(BuildContext context, String profileOnion, String senderOnion) {
// set up the buttons // set up the buttons
Widget cancelButton = ElevatedButton( Widget cancelButton = ElevatedButton(
child: Text(AppLocalizations.of(context)!.cancel), style: ButtonStyle(padding: MaterialStateProperty.all(const EdgeInsets.all(20))),
style: ButtonStyle(padding: MaterialStateProperty.all(EdgeInsets.all(20))),
onPressed: () { onPressed: () {
Navigator.of(context).pop(); // dismiss dialog Navigator.of(context).pop(); // dismiss dialog
}, },
child: Text(AppLocalizations.of(context)!.cancel),
); );
Widget continueButton = ElevatedButton( Widget continueButton = ElevatedButton(
style: ButtonStyle(padding: MaterialStateProperty.all(EdgeInsets.all(20))), style: ButtonStyle(padding: MaterialStateProperty.all(const EdgeInsets.all(20))),
child: Text(AppLocalizations.of(context)!.addContact), child: Text(AppLocalizations.of(context)!.addContact),
onPressed: () { onPressed: () {
Provider.of<FlwtchState>(context, listen: false).cwtch.ImportBundle(profileOnion, senderOnion); Provider.of<FlwtchState>(context, listen: false).cwtch.ImportBundle(profileOnion, senderOnion);
final snackBar = SnackBar( final snackBar = SnackBar(
content: Text(AppLocalizations.of(context)!.successfullAddedContact), content: Text(AppLocalizations.of(context)!.successfullAddedContact),
duration: Duration(seconds: 2), duration: const Duration(seconds: 2),
); );
ScaffoldMessenger.of(context).showSnackBar(snackBar); ScaffoldMessenger.of(context).showSnackBar(snackBar);
Navigator.of(context).pop(); // dismiss dialog Navigator.of(context).pop(); // dismiss dialog
@ -431,7 +429,7 @@ void modalShowReplies(
} }
var image = Padding( var image = Padding(
padding: EdgeInsets.all(4.0), padding: const EdgeInsets.all(4.0),
child: ProfileImage( child: ProfileImage(
imagePath: imagePath, imagePath: imagePath,
diameter: 48.0, diameter: 48.0,
@ -441,7 +439,7 @@ void modalShowReplies(
)); ));
return Padding( return Padding(
padding: EdgeInsets.all(10.0), padding: const EdgeInsets.all(10.0),
child: Row( child: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [image, Flexible(child: bubble)], children: [image, Flexible(child: bubble)],
@ -453,21 +451,21 @@ void modalShowReplies(
var original = var original =
StaticMessageBubble(profile, settings, cache.cache[messageID]!.metadata, Row(children: [Flexible(child: compileOverlay(cache.cache[messageID]!).getPreviewWidget(context))])); StaticMessageBubble(profile, settings, cache.cache[messageID]!.metadata, Row(children: [Flexible(child: compileOverlay(cache.cache[messageID]!).getPreviewWidget(context))]));
withHeader.insert(0, Padding(padding: EdgeInsets.fromLTRB(10.0, 10.0, 2.0, 15.0), child: Center(child: original))); withHeader.insert(0, Padding(padding: const EdgeInsets.fromLTRB(10.0, 10.0, 2.0, 15.0), child: Center(child: original)));
withHeader.insert( withHeader.insert(
1, 1,
Padding( Padding(
padding: EdgeInsets.fromLTRB(10.0, 10.0, 2.0, 15.0), padding: const EdgeInsets.fromLTRB(10.0, 10.0, 2.0, 15.0),
child: Divider( child: Divider(
color: settings.theme.mainTextColor, color: settings.theme.mainTextColor,
))); )));
if (replies.isNotEmpty) { if (replies.isNotEmpty) {
withHeader.insert(2, Padding(padding: EdgeInsets.fromLTRB(10.0, 10.0, 2.0, 15.0), child: Text(replyHeader, style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold)))); withHeader.insert(2, Padding(padding: const EdgeInsets.fromLTRB(10.0, 10.0, 2.0, 15.0), child: Text(replyHeader, style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold))));
} else { } else {
withHeader.insert( withHeader.insert(2,
2, Padding(padding: EdgeInsets.fromLTRB(10.0, 10.0, 2.0, 15.0), child: Center(child: Text(noRepliesText, style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold))))); Padding(padding: const EdgeInsets.fromLTRB(10.0, 10.0, 2.0, 15.0), child: Center(child: Text(noRepliesText, style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold)))));
} }
return Scrollbar( return Scrollbar(
@ -480,7 +478,7 @@ void modalShowReplies(
minHeight: viewportConstraints.maxHeight, minHeight: viewportConstraints.maxHeight,
), ),
child: Padding( child: Padding(
padding: EdgeInsets.symmetric(vertical: 0.0, horizontal: 20.0), padding: const EdgeInsets.symmetric(vertical: 0.0, horizontal: 20.0),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: withHeader, children: withHeader,
@ -497,7 +495,7 @@ void modalShowTranslation(BuildContext context, ProfileInfoState profile, Settin
) { ) {
return StatefulBuilder(builder: (BuildContext scontext, StateSetter setState /*You can rename this!*/) { return StatefulBuilder(builder: (BuildContext scontext, StateSetter setState /*You can rename this!*/) {
if (scontext.mounted) { if (scontext.mounted) {
new Timer.periodic(Duration(seconds: 1), (Timer t) { Timer.periodic(const Duration(seconds: 1), (Timer t) {
if (scontext.mounted) { if (scontext.mounted) {
setState(() {}); setState(() {});
} }
@ -512,13 +510,13 @@ void modalShowTranslation(BuildContext context, ProfileInfoState profile, Settin
Provider.of<MessageMetadata>(context).translation == "" Provider.of<MessageMetadata>(context).translation == ""
? Column(crossAxisAlignment: CrossAxisAlignment.center, children: [ ? Column(crossAxisAlignment: CrossAxisAlignment.center, children: [
CircularProgressIndicator(color: settings.theme.defaultButtonActiveColor), CircularProgressIndicator(color: settings.theme.defaultButtonActiveColor),
Padding(padding: EdgeInsets.all(5.0), child: Text(AppLocalizations.of(context)!.blodeuweddProcessing)) Padding(padding: const EdgeInsets.all(5.0), child: Text(AppLocalizations.of(context)!.blodeuweddProcessing))
]) ])
: Flexible(child: SelectableText(Provider.of<MessageMetadata>(context).translation)) : Flexible(child: SelectableText(Provider.of<MessageMetadata>(context).translation))
])); ]));
var image = Padding( var image = Padding(
padding: EdgeInsets.all(4.0), padding: const EdgeInsets.all(4.0),
child: ProfileImage( child: ProfileImage(
imagePath: "assets/blodeuwedd.png", imagePath: "assets/blodeuwedd.png",
diameter: 48.0, diameter: 48.0,
@ -527,14 +525,14 @@ void modalShowTranslation(BuildContext context, ProfileInfoState profile, Settin
badgeColor: Colors.red, badgeColor: Colors.red,
)); ));
return Container( return SizedBox(
height: 300, // bespoke value courtesy of the [TextField] docs height: 300, // bespoke value courtesy of the [TextField] docs
child: Container( child: Container(
alignment: Alignment.center, alignment: Alignment.center,
child: Padding( child: Padding(
padding: EdgeInsets.all(10.0), padding: const EdgeInsets.all(10.0),
child: Padding( child: Padding(
padding: EdgeInsets.all(10.0), padding: const EdgeInsets.all(10.0),
child: Row( child: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [image, Flexible(child: bubble)], children: [image, Flexible(child: bubble)],
@ -599,5 +597,5 @@ String RandomProfileImage(String onion) {
"050-unicorn" "050-unicorn"
]; ];
var encoding = base32.decode(onion.toUpperCase()); var encoding = base32.decode(onion.toUpperCase());
return "assets/profiles/" + choices[encoding[33] % choices.length] + ".png"; return "assets/profiles/${choices[encoding[33] % choices.length]}.png";
} }

View File

@ -9,13 +9,14 @@ const hints = [AutofillHints.password];
// Provides a styled Password Input Field for use in Form Widgets. // Provides a styled Password Input Field for use in Form Widgets.
// Callers must provide a text controller, label helper text and a validator. // Callers must provide a text controller, label helper text and a validator.
class CwtchPasswordField extends StatefulWidget { class CwtchPasswordField extends StatefulWidget {
CwtchPasswordField({required this.controller, required this.validator, this.action, this.autofocus = false, this.autoFillHints = hints, this.key}); const CwtchPasswordField({super.key, required this.controller, required this.validator, this.action, this.autofocus = false, this.autoFillHints = hints, this.ikey});
final TextEditingController controller; final TextEditingController controller;
final FormFieldValidator validator; final FormFieldValidator validator;
final Function(String)? action; final Function(String)? action;
final bool autofocus; final bool autofocus;
final Iterable<String> autoFillHints; final Iterable<String> autoFillHints;
final Key? key; @override
final Key? ikey;
@override @override
_CwtchPasswordTextFieldState createState() => _CwtchPasswordTextFieldState(); _CwtchPasswordTextFieldState createState() => _CwtchPasswordTextFieldState();

View File

@ -9,8 +9,9 @@ import 'package:provider/provider.dart';
import '../settings.dart'; import '../settings.dart';
class ProfileImage extends StatefulWidget { class ProfileImage extends StatefulWidget {
ProfileImage( const ProfileImage(
{required this.imagePath, {super.key,
required this.imagePath,
required this.diameter, required this.diameter,
required this.border, required this.border,
this.badgeCount = 0, this.badgeCount = 0,
@ -20,7 +21,7 @@ class ProfileImage extends StatefulWidget {
this.tooltip = "", this.tooltip = "",
this.disabled = false, this.disabled = false,
this.badgeEdit = false, this.badgeEdit = false,
this.badgeIcon = null}); this.badgeIcon});
final double diameter; final double diameter;
final String imagePath; final String imagePath;
final Color border; final Color border;
@ -40,7 +41,7 @@ class ProfileImage extends StatefulWidget {
class _ProfileImageState extends State<ProfileImage> { class _ProfileImageState extends State<ProfileImage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var file = new File(widget.imagePath); var file = File(widget.imagePath);
var image = Image.file( var image = Image.file(
file, file,
cacheWidth: (4 * widget.diameter.floor()), cacheWidth: (4 * widget.diameter.floor()),
@ -105,13 +106,13 @@ class _ProfileImageState extends State<ProfileImage> {
Icons.edit, Icons.edit,
color: widget.badgeTextColor, color: widget.badgeTextColor,
) )
: (widget.badgeIcon != null ? widget.badgeIcon : Text(widget.badgeCount > 99 ? "99+" : widget.badgeCount.toString(), style: TextStyle(color: widget.badgeTextColor, fontSize: 8.0))), : (widget.badgeIcon ?? Text(widget.badgeCount > 99 ? "99+" : widget.badgeCount.toString(), style: TextStyle(color: widget.badgeTextColor, fontSize: 8.0))),
), ),
)), )),
// disabled center icon // disabled center icon
Visibility( Visibility(
visible: widget.disabled, visible: widget.disabled,
child: Container( child: SizedBox(
width: widget.diameter, width: widget.diameter,
height: widget.diameter, height: widget.diameter,
child: Center( child: Center(

View File

@ -1,9 +1,7 @@
import 'package:cwtch/models/appstate.dart'; import 'package:cwtch/models/appstate.dart';
import 'package:cwtch/models/contactlist.dart'; import 'package:cwtch/models/contactlist.dart';
import 'package:cwtch/models/profile.dart'; import 'package:cwtch/models/profile.dart';
import 'package:cwtch/models/profilelist.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:cwtch/views/addeditprofileview.dart'; import 'package:cwtch/views/addeditprofileview.dart';
import 'package:cwtch/views/contactsview.dart'; import 'package:cwtch/views/contactsview.dart';
import 'package:cwtch/views/doublecolview.dart'; import 'package:cwtch/views/doublecolview.dart';
@ -11,11 +9,12 @@ import 'package:cwtch/widgets/profileimage.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 '../errorHandler.dart';
import '../main.dart'; import '../main.dart';
import '../settings.dart'; import '../settings.dart';
class ProfileRow extends StatefulWidget { class ProfileRow extends StatefulWidget {
const ProfileRow({super.key});
@override @override
_ProfileRowState createState() => _ProfileRowState(); _ProfileRowState createState() => _ProfileRowState();
} }
@ -26,7 +25,7 @@ class _ProfileRowState extends State<ProfileRow> {
var profile = Provider.of<ProfileInfoState>(context); var profile = Provider.of<ProfileInfoState>(context);
return Card( return Card(
clipBehavior: Clip.antiAlias, clipBehavior: Clip.antiAlias,
margin: EdgeInsets.all(0.0), margin: const EdgeInsets.all(0.0),
child: InkWell( child: InkWell(
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
@ -49,8 +48,8 @@ class _ProfileRowState extends State<ProfileRow> {
Container( Container(
height: 18.0 * Provider.of<Settings>(context).fontScaling + 10.0, height: 18.0 * Provider.of<Settings>(context).fontScaling + 10.0,
clipBehavior: Clip.hardEdge, clipBehavior: Clip.hardEdge,
decoration: BoxDecoration(), decoration: const BoxDecoration(),
padding: EdgeInsets.all(5.0), padding: const EdgeInsets.all(5.0),
child: Text( child: Text(
profile.nickname, profile.nickname,
semanticsLabel: profile.nickname, semanticsLabel: profile.nickname,
@ -75,7 +74,7 @@ class _ProfileRowState extends State<ProfileRow> {
IconButton( IconButton(
enableFeedback: true, enableFeedback: true,
splashRadius: Material.defaultSplashRadius / 2, splashRadius: Material.defaultSplashRadius / 2,
tooltip: AppLocalizations.of(context)!.editProfile + " " + profile.nickname, tooltip: "${AppLocalizations.of(context)!.editProfile} ${profile.nickname}",
icon: Icon(Icons.create, color: Provider.of<Settings>(context).current().mainTextColor), icon: Icon(Icons.create, color: Provider.of<Settings>(context).current().mainTextColor),
onPressed: () { onPressed: () {
_pushEditProfile(onion: profile.onion, displayName: profile.nickname, profileImage: profile.imagePath, encrypted: profile.isEncrypted); _pushEditProfile(onion: profile.onion, displayName: profile.nickname, profileImage: profile.imagePath, encrypted: profile.isEncrypted);
@ -98,7 +97,7 @@ class _ProfileRowState extends State<ProfileRow> {
void _pushContactList(ProfileInfoState profile, bool isLandscape) { void _pushContactList(ProfileInfoState profile, bool isLandscape) {
Navigator.of(context).push( Navigator.of(context).push(
PageRouteBuilder( PageRouteBuilder(
settings: RouteSettings(name: "conversations"), settings: const RouteSettings(name: "conversations"),
pageBuilder: (c, a1, a2) { pageBuilder: (c, a1, a2) {
return OrientationBuilder(builder: (orientationBuilderContext, orientation) { return OrientationBuilder(builder: (orientationBuilderContext, orientation) {
return MultiProvider( return MultiProvider(
@ -106,17 +105,17 @@ class _ProfileRowState extends State<ProfileRow> {
builder: (innercontext, widget) { builder: (innercontext, widget) {
var appState = Provider.of<AppState>(context); var appState = Provider.of<AppState>(context);
var settings = Provider.of<Settings>(context); var settings = Provider.of<Settings>(context);
return settings.uiColumns(appState.isLandscape(innercontext)).length > 1 ? DoubleColumnView() : ContactsView(); return settings.uiColumns(appState.isLandscape(innercontext)).length > 1 ? const DoubleColumnView() : const ContactsView();
}); });
}); });
}, },
transitionsBuilder: (c, anim, a2, child) => FadeTransition(opacity: anim, child: child), transitionsBuilder: (c, anim, a2, child) => FadeTransition(opacity: anim, child: child),
transitionDuration: Duration(milliseconds: 200), transitionDuration: const Duration(milliseconds: 200),
), ),
); );
} }
void _pushEditProfile({onion: "", displayName: "", profileImage: "", encrypted: true}) { void _pushEditProfile({onion = "", displayName = "", profileImage = "", encrypted = true}) {
Navigator.of(context).push( Navigator.of(context).push(
PageRouteBuilder( PageRouteBuilder(
pageBuilder: (bcontext, a1, a2) { pageBuilder: (bcontext, a1, a2) {
@ -127,11 +126,11 @@ class _ProfileRowState extends State<ProfileRow> {
value: profile, value: profile,
), ),
], ],
builder: (context, widget) => AddEditProfileView(), builder: (context, widget) => const AddEditProfileView(),
); );
}, },
transitionsBuilder: (c, anim, a2, child) => FadeTransition(opacity: anim, child: child), transitionsBuilder: (c, anim, a2, child) => FadeTransition(opacity: anim, child: child),
transitionDuration: Duration(milliseconds: 200), transitionDuration: const Duration(milliseconds: 200),
), ),
); );
} }

View File

@ -15,14 +15,14 @@ class QuotedMessageBubble extends StatefulWidget {
final Future<Message> quotedMessage; final Future<Message> quotedMessage;
final String body; final String body;
QuotedMessageBubble(this.body, this.quotedMessage); const QuotedMessageBubble(this.body, this.quotedMessage, {super.key});
@override @override
QuotedMessageBubbleState createState() => QuotedMessageBubbleState(); QuotedMessageBubbleState createState() => QuotedMessageBubbleState();
} }
class QuotedMessageBubbleState extends State<QuotedMessageBubble> { class QuotedMessageBubbleState extends State<QuotedMessageBubble> {
FocusNode _focus = FocusNode(); final FocusNode _focus = FocusNode();
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -84,14 +84,12 @@ class QuotedMessageBubbleState extends State<QuotedMessageBubble> {
var messageInfo = Provider.of<ContactInfoState>(context, listen: false).messageCache.getByContentHash(qMessage.getMetadata().contenthash); var messageInfo = Provider.of<ContactInfoState>(context, listen: false).messageCache.getByContentHash(qMessage.getMetadata().contenthash);
if (messageInfo != null) { if (messageInfo != null) {
var index = Provider.of<ContactInfoState>(context, listen: false).messageCache.findIndex(messageInfo.metadata.messageID); var index = Provider.of<ContactInfoState>(context, listen: false).messageCache.findIndex(messageInfo.metadata.messageID);
if (index != null) { Provider.of<ContactInfoState>(context, listen: false).messageScrollController.scrollTo(index: index, duration: const Duration(milliseconds: 100));
Provider.of<ContactInfoState>(context, listen: false).messageScrollController.scrollTo(index: index, duration: Duration(milliseconds: 100));
}
} }
}, },
child: Container( child: Container(
margin: EdgeInsets.all(5), margin: const EdgeInsets.all(5),
padding: EdgeInsets.all(5), padding: const EdgeInsets.all(5),
clipBehavior: Clip.antiAliasWithSaveLayer, clipBehavior: Clip.antiAliasWithSaveLayer,
decoration: BoxDecoration( decoration: BoxDecoration(
color: fromMe ? Provider.of<Settings>(context).theme.messageFromOtherBackgroundColor : Provider.of<Settings>(context).theme.messageFromMeBackgroundColor, color: fromMe ? Provider.of<Settings>(context).theme.messageFromOtherBackgroundColor : Provider.of<Settings>(context).theme.messageFromMeBackgroundColor,
@ -101,18 +99,18 @@ class QuotedMessageBubbleState extends State<QuotedMessageBubble> {
Align(alignment: Alignment.centerLeft, child: wdgReplyingTo), Align(alignment: Alignment.centerLeft, child: wdgReplyingTo),
Flexible( Flexible(
child: Row(mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.center, children: [ child: Row(mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.center, children: [
Padding(padding: EdgeInsets.symmetric(vertical: 5.0, horizontal: 10.0), child: Icon(Icons.reply, size: 32, color: qTextColor)), Padding(padding: const EdgeInsets.symmetric(vertical: 5.0, horizontal: 10.0), child: Icon(Icons.reply, size: 32, color: qTextColor)),
Flexible(child: IntrinsicWidth(child: qMessage.getPreviewWidget(context))), Flexible(child: IntrinsicWidth(child: qMessage.getPreviewWidget(context))),
])) ]))
])), ])),
), ),
); );
} catch (e) { } catch (e) {
return MalformedBubble(); return const MalformedBubble();
} }
} else { } else {
// This should be almost instantly resolved, any failure likely means an issue in decoding... // This should be almost instantly resolved, any failure likely means an issue in decoding...
return MessageLoadingBubble(); return const MessageLoadingBubble();
} }
}, },
); );
@ -135,7 +133,7 @@ class QuotedMessageBubbleState extends State<QuotedMessageBubble> {
), ),
), ),
child: Padding( child: Padding(
padding: EdgeInsets.all(9.0), padding: const EdgeInsets.all(9.0),
child: Theme( child: Theme(
data: Theme.of(context).copyWith( data: Theme.of(context).copyWith(
textSelectionTheme: TextSelectionThemeData( textSelectionTheme: TextSelectionThemeData(

View File

@ -1,22 +1,17 @@
import 'package:cwtch/main.dart'; import 'package:cwtch/main.dart';
import 'package:cwtch/models/profile.dart'; import 'package:cwtch/models/profile.dart';
import 'package:cwtch/models/profileservers.dart';
import 'package:cwtch/models/remoteserver.dart'; import 'package:cwtch/models/remoteserver.dart';
import 'package:cwtch/models/servers.dart';
import 'package:cwtch/themes/opaque.dart'; import 'package:cwtch/themes/opaque.dart';
import 'package:cwtch/views/addeditservers.dart';
import 'package:cwtch/views/remoteserverview.dart'; import 'package:cwtch/views/remoteserverview.dart';
import 'package:cwtch/widgets/profileimage.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import '../cwtch_icons_icons.dart'; import '../cwtch_icons_icons.dart';
import '../errorHandler.dart';
import '../settings.dart'; import '../settings.dart';
class RemoteServerRow extends StatefulWidget { class RemoteServerRow extends StatefulWidget {
const RemoteServerRow({super.key});
@override @override
_RemoteServerRowState createState() => _RemoteServerRowState(); _RemoteServerRowState createState() => _RemoteServerRowState();
} }
@ -30,7 +25,7 @@ class _RemoteServerRowState extends State<RemoteServerRow> {
return Consumer<ProfileInfoState>(builder: (context, profile, child) { return Consumer<ProfileInfoState>(builder: (context, profile, child) {
return Card( return Card(
clipBehavior: Clip.antiAlias, clipBehavior: Clip.antiAlias,
margin: EdgeInsets.all(0.0), margin: const EdgeInsets.all(0.0),
child: InkWell( child: InkWell(
child: Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ child: Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
Padding( Padding(
@ -80,15 +75,15 @@ class _RemoteServerRowState extends State<RemoteServerRow> {
onTap: () { onTap: () {
Navigator.of(context).push( Navigator.of(context).push(
PageRouteBuilder( PageRouteBuilder(
settings: RouteSettings(name: "remoteserverview"), settings: const RouteSettings(name: "remoteserverview"),
pageBuilder: (bcontext, a1, a2) { pageBuilder: (bcontext, a1, a2) {
return MultiProvider( return MultiProvider(
providers: [ChangeNotifierProvider.value(value: profile), ChangeNotifierProvider.value(value: server), Provider.value(value: Provider.of<FlwtchState>(context))], providers: [ChangeNotifierProvider.value(value: profile), ChangeNotifierProvider.value(value: server), Provider.value(value: Provider.of<FlwtchState>(context))],
child: RemoteServerView(), child: const RemoteServerView(),
); );
}, },
transitionsBuilder: (c, anim, a2, child) => FadeTransition(opacity: anim, child: child), transitionsBuilder: (c, anim, a2, child) => FadeTransition(opacity: anim, child: child),
transitionDuration: Duration(milliseconds: 200), transitionDuration: const Duration(milliseconds: 200),
), ),
); );
})); }));

View File

@ -11,6 +11,8 @@ import '../errorHandler.dart';
import '../settings.dart'; import '../settings.dart';
class ServerRow extends StatefulWidget { class ServerRow extends StatefulWidget {
const ServerRow({super.key});
@override @override
_ServerRowState createState() => _ServerRowState(); _ServerRowState createState() => _ServerRowState();
} }
@ -21,7 +23,7 @@ class _ServerRowState extends State<ServerRow> {
var server = Provider.of<ServerInfoState>(context); var server = Provider.of<ServerInfoState>(context);
return Card( return Card(
clipBehavior: Clip.antiAlias, clipBehavior: Clip.antiAlias,
margin: EdgeInsets.all(0.0), margin: const EdgeInsets.all(0.0),
child: InkWell( child: InkWell(
child: Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ child: Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
Padding( Padding(
@ -69,7 +71,7 @@ class _ServerRowState extends State<ServerRow> {
tooltip: AppLocalizations.of(context)!.copyServerKeys, tooltip: AppLocalizations.of(context)!.copyServerKeys,
icon: Icon(CwtchIcons.address_copy, color: Provider.of<Settings>(context).current().mainTextColor), icon: Icon(CwtchIcons.address_copy, color: Provider.of<Settings>(context).current().mainTextColor),
onPressed: () { onPressed: () {
Clipboard.setData(new ClipboardData(text: server.serverBundle)); Clipboard.setData(ClipboardData(text: server.serverBundle));
final snackBar = SnackBar(content: Text(AppLocalizations.of(context)!.copiedToClipboardNotification)); final snackBar = SnackBar(content: Text(AppLocalizations.of(context)!.copiedToClipboardNotification));
ScaffoldMessenger.of(context).showSnackBar(snackBar); ScaffoldMessenger.of(context).showSnackBar(snackBar);
}, },
@ -94,11 +96,11 @@ class _ServerRowState extends State<ServerRow> {
void _pushEditServer(ServerInfoState server) { void _pushEditServer(ServerInfoState server) {
Provider.of<ErrorHandler>(context, listen: false).reset(); Provider.of<ErrorHandler>(context, listen: false).reset();
Navigator.of(context).push(MaterialPageRoute<void>( Navigator.of(context).push(MaterialPageRoute<void>(
settings: RouteSettings(name: "serveraddedit"), settings: const RouteSettings(name: "serveraddedit"),
builder: (BuildContext context) { builder: (BuildContext context) {
return MultiProvider( return MultiProvider(
providers: [ChangeNotifierProvider.value(value: server)], providers: [ChangeNotifierProvider.value(value: server)],
child: AddEditServerView(), child: const AddEditServerView(),
); );
}, },
)); ));

View File

@ -3,7 +3,6 @@ import 'package:cwtch/models/message.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';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../models/redaction.dart'; import '../models/redaction.dart';
import '../settings.dart'; import '../settings.dart';
@ -16,7 +15,7 @@ class StaticMessageBubble extends StatefulWidget {
final MessageMetadata metadata; final MessageMetadata metadata;
final Widget child; final Widget child;
StaticMessageBubble(this.profile, this.settings, this.metadata, this.child); const StaticMessageBubble(this.profile, this.settings, this.metadata, this.child, {super.key});
@override @override
StaticMessageBubbleState createState() => StaticMessageBubbleState(); StaticMessageBubbleState createState() => StaticMessageBubbleState();
@ -61,7 +60,7 @@ class StaticMessageBubbleState extends State<StaticMessageBubble> {
), ),
), ),
child: Padding( child: Padding(
padding: EdgeInsets.all(9.0), padding: const EdgeInsets.all(9.0),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,

View File

@ -1,5 +1,4 @@
import 'package:cwtch/themes/opaque.dart'; import 'package:cwtch/themes/opaque.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:provider/provider.dart'; import 'package:provider/provider.dart';
@ -10,8 +9,17 @@ doNothing(String x) {}
// Provides a styled Text Field for use in Form Widgets. // Provides a styled Text Field for use in Form Widgets.
// Callers must provide a text controller, label helper text and a validator. // Callers must provide a text controller, label helper text and a validator.
class CwtchTextField extends StatefulWidget { class CwtchTextField extends StatefulWidget {
CwtchTextField( const CwtchTextField(
{required this.controller, this.hintText = "", this.validator, this.autofocus = false, this.onChanged = doNothing, this.number = false, this.multiLine = false, this.key, this.testKey}); {super.key,
required this.controller,
this.hintText = "",
this.validator,
this.autofocus = false,
this.onChanged = doNothing,
this.number = false,
this.multiLine = false,
this.ikey,
this.testKey});
final TextEditingController controller; final TextEditingController controller;
final String hintText; final String hintText;
final FormFieldValidator? validator; final FormFieldValidator? validator;
@ -19,7 +27,8 @@ class CwtchTextField extends StatefulWidget {
final bool autofocus; final bool autofocus;
final bool multiLine; final bool multiLine;
final bool number; final bool number;
final Key? key; @override
final Key? ikey;
final Key? testKey; final Key? testKey;
@override @override
@ -46,7 +55,7 @@ class _CwtchTextFieldState extends State<CwtchTextField> {
return Consumer<Settings>(builder: (context, theme, child) { return Consumer<Settings>(builder: (context, theme, child) {
return Container( return Container(
clipBehavior: Clip.antiAlias, clipBehavior: Clip.antiAlias,
decoration: BoxDecoration(), decoration: const BoxDecoration(),
// Horrifying Hack: Flutter doesn't give us direct control over system menus but instead picks BG color from TextButtonThemeData ¯\_(ツ)_/¯ // Horrifying Hack: Flutter doesn't give us direct control over system menus but instead picks BG color from TextButtonThemeData ¯\_(ツ)_/¯
child: Theme( child: Theme(
data: Theme.of(context).copyWith( data: Theme.of(context).copyWith(
@ -84,7 +93,7 @@ class _CwtchTextFieldState extends State<CwtchTextField> {
errorBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(6.0), borderSide: BorderSide(color: theme.current().textfieldErrorColor, width: 1.0)), errorBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(6.0), borderSide: BorderSide(color: theme.current().textfieldErrorColor, width: 1.0)),
errorStyle: TextStyle(color: theme.current().textfieldErrorColor, fontWeight: FontWeight.bold, overflow: TextOverflow.visible), errorStyle: TextStyle(color: theme.current().textfieldErrorColor, fontWeight: FontWeight.bold, overflow: TextOverflow.visible),
fillColor: theme.current().textfieldBackgroundColor, fillColor: theme.current().textfieldBackgroundColor,
contentPadding: EdgeInsets.fromLTRB(10.0, 5.0, 10.0, 5.0), contentPadding: const EdgeInsets.fromLTRB(10.0, 5.0, 10.0, 5.0),
enabledBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(6.0), borderSide: BorderSide(color: theme.current().textfieldBorderColor, width: 1.0))), enabledBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(6.0), borderSide: BorderSide(color: theme.current().textfieldBorderColor, width: 1.0))),
))); )));
}); });

View File

@ -7,7 +7,7 @@ import '../torstatus.dart';
/// A reusable Tor Icon Widget that displays the current status of the underlying Tor connections /// A reusable Tor Icon Widget that displays the current status of the underlying Tor connections
class TorIcon extends StatefulWidget { class TorIcon extends StatefulWidget {
TorIcon(); const TorIcon({super.key});
@override @override
State<StatefulWidget> createState() => _TorIconState(); State<StatefulWidget> createState() => _TorIconState();

View File

@ -961,7 +961,7 @@ packages:
source: hosted source: hosted
version: "6.3.0" version: "6.3.0"
yaml: yaml:
dependency: transitive dependency: "direct main"
description: description:
name: yaml name: yaml
sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5"

View File

@ -15,13 +15,13 @@ import 'package:provider/provider.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart';
var settingsEnglishDark = Settings(Locale("en", ''), CwtchDark()); var settingsEnglishDark = Settings(const Locale("en", ''), CwtchDark());
var settingsEnglishLight = Settings(Locale("en", ''), CwtchLight()); var settingsEnglishLight = Settings(const Locale("en", ''), CwtchLight());
ChangeNotifierProvider<Settings> getSettingsEnglishDark() => ChangeNotifierProvider.value(value: settingsEnglishDark); ChangeNotifierProvider<Settings> getSettingsEnglishDark() => ChangeNotifierProvider.value(value: settingsEnglishDark);
void main() { void main() {
testWidgets('CwtchButtonTextField widget test', (WidgetTester tester) async { testWidgets('CwtchButtonTextField widget test', (WidgetTester tester) async {
final String testingStr = "A wonderful label"; const String testingStr = "A wonderful label";
final TextEditingController ctrlr1 = TextEditingController(text: testingStr); final TextEditingController ctrlr1 = TextEditingController(text: testingStr);
// await tester.pumpWidget(MultiProvider( // await tester.pumpWidget(MultiProvider(
@ -38,7 +38,7 @@ void main() {
title: 'Test', title: 'Test',
theme: mkThemeData(Provider.of<Settings>(context)), theme: mkThemeData(Provider.of<Settings>(context)),
home: Card(child: CwtchButtonTextField( home: Card(child: CwtchButtonTextField(
icon: Icon(Icons.bug_report_outlined), icon: const Icon(Icons.bug_report_outlined),
tooltip: testingStr, tooltip: testingStr,
controller: ctrlr1, onPressed: () { }, controller: ctrlr1, onPressed: () { },
)), )),

View File

@ -15,13 +15,13 @@ import 'package:provider/provider.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart';
var settingsEnglishDark = Settings(Locale("en", ''), CwtchDark()); var settingsEnglishDark = Settings(const Locale("en", ''), CwtchDark());
var settingsEnglishLight = Settings(Locale("en", ''), CwtchLight()); var settingsEnglishLight = Settings(const Locale("en", ''), CwtchLight());
ChangeNotifierProvider<Settings> getSettingsEnglishDark() => ChangeNotifierProvider.value(value: settingsEnglishDark); ChangeNotifierProvider<Settings> getSettingsEnglishDark() => ChangeNotifierProvider.value(value: settingsEnglishDark);
void main() { void main() {
testWidgets('CwtchLabel widget test', (WidgetTester tester) async { testWidgets('CwtchLabel widget test', (WidgetTester tester) async {
final String testingStr = "A wonderful label"; const String testingStr = "A wonderful label";
// await tester.pumpWidget(MultiProvider( // await tester.pumpWidget(MultiProvider(
// providers:[getSettingsEnglishDark()], // providers:[getSettingsEnglishDark()],
@ -36,7 +36,7 @@ void main() {
supportedLocales: AppLocalizations.supportedLocales, supportedLocales: AppLocalizations.supportedLocales,
title: 'Test', title: 'Test',
theme: mkThemeData(Provider.of<Settings>(context)), theme: mkThemeData(Provider.of<Settings>(context)),
home: CwtchLabel(label: testingStr), home: const CwtchLabel(label: testingStr),
);} );}
)); ));

View File

@ -15,18 +15,18 @@ import 'package:provider/provider.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart';
var settingsEnglishDark = Settings(Locale("en", ''), CwtchDark()); var settingsEnglishDark = Settings(const Locale("en", ''), CwtchDark());
var settingsEnglishLight = Settings(Locale("en", ''), CwtchLight()); var settingsEnglishLight = Settings(const Locale("en", ''), CwtchLight());
ChangeNotifierProvider<Settings> getSettingsEnglishDark() => ChangeNotifierProvider.value(value: settingsEnglishDark); ChangeNotifierProvider<Settings> getSettingsEnglishDark() => ChangeNotifierProvider.value(value: settingsEnglishDark);
String file(String slug) { String file(String slug) {
return "profileimage_" + slug + ".png"; return "profileimage_$slug.png";
} }
void main() { void main() {
testWidgets('ProfileImage widget test', (WidgetTester tester) async { testWidgets('ProfileImage widget test', (WidgetTester tester) async {
tester.binding.window.physicalSizeTestValue = Size(200, 200); tester.binding.window.physicalSizeTestValue = const Size(200, 200);
// await tester.pumpWidget(MultiProvider( // await tester.pumpWidget(MultiProvider(
// providers:[getSettingsEnglishDark()], // providers:[getSettingsEnglishDark()],
// child: Directionality(textDirection: TextDirection.ltr, child: CwtchLabel(label: testingStr)) // child: Directionality(textDirection: TextDirection.ltr, child: CwtchLabel(label: testingStr))

View File

@ -15,20 +15,22 @@ import 'package:provider/provider.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart';
var settingsEnglishDark = Settings(Locale("en", ''), CwtchDark()); var settingsEnglishDark = Settings(const Locale("en", ''), CwtchDark());
var settingsEnglishLight = Settings(Locale("en", ''), CwtchLight()); var settingsEnglishLight = Settings(const Locale("en", ''), CwtchLight());
ChangeNotifierProvider<Settings> getSettingsEnglishDark() => ChangeNotifierProvider.value(value: settingsEnglishDark); ChangeNotifierProvider<Settings> getSettingsEnglishDark() => ChangeNotifierProvider.value(value: settingsEnglishDark);
String file(String slug) { String file(String slug) {
return "textfield_" + slug + ".png"; return "textfield_$slug.png";
} }
void main() { void main() {
testWidgets('Textfield widget test', (WidgetTester tester) async { testWidgets('Textfield widget test', (WidgetTester tester) async {
tester.binding.window.physicalSizeTestValue = Size(800, 300); tester.binding.window.physicalSizeTestValue = const Size(800, 300);
final TextEditingController ctrlr1 = TextEditingController(); final TextEditingController ctrlr1 = TextEditingController();
Widget testWidget = CwtchTextField(controller: ctrlr1, validator: (value) { }, hintText: '',); Widget testWidget = CwtchTextField(controller: ctrlr1, validator: (value) {
return null;
}, hintText: '',);
Widget testHarness = MultiProvider( Widget testHarness = MultiProvider(
providers:[getSettingsEnglishDark()], providers:[getSettingsEnglishDark()],
@ -61,12 +63,12 @@ void main() {
//=\\ //=\\
testWidgets('Textfield form validation test', (WidgetTester tester) async { testWidgets('Textfield form validation test', (WidgetTester tester) async {
tester.binding.window.physicalSizeTestValue = Size(800, 300); tester.binding.window.physicalSizeTestValue = const Size(800, 300);
final formKey = GlobalKey<FormState>(); final formKey = GlobalKey<FormState>();
final TextEditingController ctrlr1 = TextEditingController(); final TextEditingController ctrlr1 = TextEditingController();
final String strLabel1 = "Age (*Required)"; const String strLabel1 = "Age (*Required)";
final String strFail1 = "Required field"; const String strFail1 = "Required field";
final String strFail2 = "Please enter an integer"; const String strFail2 = "Please enter an integer";
Widget testWidget = CwtchTextField( Widget testWidget = CwtchTextField(
controller: ctrlr1, controller: ctrlr1,

View File

@ -54,7 +54,7 @@ Future<void> writeGherkinReports(
final version = reports.length == 1 ? "" : "_v${i + 1}"; final version = reports.length == 1 ? "" : "_v${i + 1}";
//The filename of the report //The filename of the report
final fileName = const fileName =
// "${currentTime.day}-${currentTime.month}-${currentTime.year}T${currentTime.hour}_${currentTime.minute}$version.json"; // "${currentTime.day}-${currentTime.month}-${currentTime.year}T${currentTime.hour}_${currentTime.minute}$version.json";
"integration_response_data.json"; "integration_response_data.json";