Dart Analyzer
continuous-integration/drone/pr Build is pending Details

This commit is contained in:
Sarah Jamie Lewis 2024-02-12 12:43:52 -08:00
parent 6ba6f76ee1
commit c66cc19f22
Signed by: sarah
GPG Key ID: F27FD21A270837EF
84 changed files with 1544 additions and 1267 deletions

View File

@ -1,6 +1,5 @@
import 'package:cwtch/themes/opaque.dart';
import 'package:cwtch/third_party/linkify/linkify.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
@ -13,11 +12,11 @@ void modalOpenLink(BuildContext ctx, LinkableElement link) {
showModalBottomSheet<void>(
context: ctx,
builder: (BuildContext bcontext) {
return Container(
return SizedBox(
height: 200,
child: Center(
child: Padding(
padding: EdgeInsets.all(30.0),
padding: const EdgeInsets.all(30.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
@ -28,12 +27,12 @@ void modalOpenLink(BuildContext ctx, LinkableElement link) {
),
Flex(direction: Axis.horizontal, mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[
Container(
margin: EdgeInsets.symmetric(vertical: 20, horizontal: 10),
margin: const EdgeInsets.symmetric(vertical: 20, horizontal: 10),
child: ElevatedButton(
child: Text(AppLocalizations.of(bcontext)!.clickableLinksCopy,
style: Provider.of<Settings>(bcontext).scaleFonts(defaultTextButtonStyle), semanticsLabel: AppLocalizations.of(bcontext)!.clickableLinksCopy),
onPressed: () {
Clipboard.setData(new ClipboardData(text: link.url));
Clipboard.setData(ClipboardData(text: link.url));
final snackBar = SnackBar(
content: Text(
@ -48,7 +47,7 @@ void modalOpenLink(BuildContext ctx, LinkableElement link) {
),
),
Container(
margin: EdgeInsets.symmetric(vertical: 20, horizontal: 10),
margin: const EdgeInsets.symmetric(vertical: 20, horizontal: 10),
child: ElevatedButton(
child: Text(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/models/appstate.dart';
import 'package:cwtch/models/contact.dart';
import 'package:cwtch/models/message.dart';
import 'package:cwtch/models/profilelist.dart';
import 'package:cwtch/models/profileservers.dart';
import 'package:cwtch/models/remoteserver.dart';
import 'package:cwtch/models/servers.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';
@ -111,7 +106,7 @@ class CwtchNotifier {
defaultImagePath: data["defaultPicture"],
blocked: data["blocked"] == "true",
accepted: data["accepted"] == "true",
savePeerHistory: data["saveConversationHistory"] == null ? "DeleteHistoryConfirmed" : data["saveConversationHistory"],
savePeerHistory: data["saveConversationHistory"] ?? "DeleteHistoryConfirmed",
numMessages: int.parse(data["numMessages"]),
numUnread: int.parse(data["unread"]),
isGroup: false, // by definition
@ -254,7 +249,7 @@ class CwtchNotifier {
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...
if (currentTotal != null && idx >= currentTotal) {
if (idx >= currentTotal) {
// 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
// Received refers to the time we actually saw the message from the server
@ -311,7 +306,7 @@ class CwtchNotifier {
// local.conversation.filekey.path
List<String> keyparts = data["Key"].toString().split(".");
if (keyparts.length == 5) {
String filekey = keyparts[2] + "." + keyparts[3];
String filekey = "${keyparts[2]}.${keyparts[3]}";
profileCN.getProfile(data["ProfileOnion"])?.downloadSetPathForSender(filekey, data["Data"]);
}
}
@ -343,10 +338,10 @@ class CwtchNotifier {
try {
List<dynamic> associatedGroups = jsonDecode(data["Data"]);
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;
});
EnvironmentConfig.debugLog("update server token count for ${associatedGroups}, $count");
}
EnvironmentConfig.debugLog("update server token count for $associatedGroups, $count");
} catch (e) {
// No tokens in data...
}
@ -354,7 +349,7 @@ class CwtchNotifier {
case "NewGroup":
String invite = data["GroupInvite"].toString();
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);
// Retrieve Server Status from Cache...
@ -396,7 +391,7 @@ class CwtchNotifier {
break;
case "UpdatedConversationAttribute":
if (data["Path"] == "profile.name") {
if (data["Data"].toString().trim().length > 0) {
if (data["Data"].toString().trim().isNotEmpty) {
// Update locally on the UI...
if (profileCN.getProfile(data["ProfileOnion"])?.contactList.findContact(data["RemotePeer"]) != null) {
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:io';
import 'dart:isolate';
import 'dart:io' show Platform;
import 'package:cwtch/cwtch/cwtchNotifier.dart';
import 'package:path/path.dart' as path;
@ -13,7 +12,6 @@ import 'package:cwtch/cwtch/cwtch.dart';
import '../config.dart';
import "package:path/path.dart" show dirname, join;
import 'dart:io' show Platform;
/////////////////////
/// Cwtch API ///
@ -132,7 +130,7 @@ class CwtchFfi implements Cwtch {
late DynamicLibrary library;
late CwtchNotifier cwtchNotifier;
late Isolate cwtchIsolate;
ReceivePort _receivePort = ReceivePort();
final ReceivePort _receivePort = ReceivePort();
bool _isL10nInit = false;
String _assetsDir = path.join(Directory.current.path, "data", "flutter_assets");
String _cwtchDir = "";
@ -162,10 +160,11 @@ class CwtchFfi implements Cwtch {
}
library = DynamicLibrary.open(libraryPath);
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
@override
Future<void> Start() async {
String home = "";
String bundledTor = "";
@ -264,15 +263,18 @@ class CwtchFfi implements Cwtch {
});
}
@override
String getAssetsDir() {
return _assetsDir;
}
@override
Future<String> getCwtchDir() async {
return _cwtchDir;
}
// ignore: non_constant_identifier_names
@override
Future<void> ReconnectCwtchForeground() async {
var reconnectCwtch = library.lookup<NativeFunction<Void Function()>>("c_ReconnectCwtchForeground");
// ignore: non_constant_identifier_names
@ -310,13 +312,13 @@ class CwtchFfi implements Cwtch {
final Free = free.asFunction<FreeFn>();
// ignore: non_constant_identifier_names
final GetAppBusEvent = () {
GetAppBusEvent() {
// ignore: non_constant_identifier_names
Pointer<Utf8> result = GetAppbusEvent();
String event = result.toDartString();
Free(result);
return event;
};
}
while (true) {
final event = GetAppBusEvent();
@ -330,6 +332,7 @@ class CwtchFfi implements Cwtch {
}
// ignore: non_constant_identifier_names
@override
void CreateProfile(String nick, String pass, bool autostart) {
var createProfileC = library.lookup<NativeFunction<void_from_string_string_byte_function>>("c_CreateProfile");
// ignore: non_constant_identifier_names
@ -342,6 +345,7 @@ class CwtchFfi implements Cwtch {
}
// ignore: non_constant_identifier_names
@override
void ActivatePeerEngine(String profile) {
var activatePeerEngineC = library.lookup<NativeFunction<string_to_void_function>>("c_ActivatePeerEngine");
final ActivatePeerEngine = activatePeerEngineC.asFunction<StringFn>();
@ -351,6 +355,7 @@ class CwtchFfi implements Cwtch {
}
// ignore: non_constant_identifier_names
@override
void DeactivatePeerEngine(String profile) {
var deactivatePeerEngineC = library.lookup<NativeFunction<string_to_void_function>>("c_DeactivatePeerEngine");
final DeactivatePeerEngine = deactivatePeerEngineC.asFunction<StringFn>();
@ -360,6 +365,7 @@ class CwtchFfi implements Cwtch {
}
// ignore: non_constant_identifier_names
@override
void LoadProfiles(String pass) {
var loadProfileC = library.lookup<NativeFunction<string_to_void_function>>("c_LoadProfiles");
// ignore: non_constant_identifier_names
@ -370,6 +376,7 @@ class CwtchFfi implements Cwtch {
}
// ignore: non_constant_identifier_names
@override
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");
// ignore: non_constant_identifier_names
@ -383,6 +390,7 @@ class CwtchFfi implements Cwtch {
}
// ignore: non_constant_identifier_names
@override
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");
// ignore: non_constant_identifier_names
@ -499,6 +507,7 @@ class CwtchFfi implements Cwtch {
}
// ignore: non_constant_identifier_names
@override
void ExportPreviewedFile(String sourceFile, String suggestion) {
// android only - do nothing
}

View File

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

View File

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

View File

@ -2,7 +2,7 @@ import 'package:flutter/foundation.dart';
Stream<LicenseEntry> licenses() async* {
/// 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
@ -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.''');
/// 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) 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.''');
/// 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
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
THE SOFTWARE.''');
yield LicenseEntryWithLineBreaks(["pidusage"], '''MIT License
yield const LicenseEntryWithLineBreaks(["pidusage"], '''MIT License
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.''');
/// 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
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
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
@ -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
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.
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
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
*
* This file is part of Purple i2pd project and licensed under BSD3
*
* 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
@ -549,7 +549,7 @@ Exhibit B - "Incompatible With Secondary Licenses" Notice
This Source Code Form is "Incompatible With Secondary Licenses", as
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)
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.
''');
yield LicenseEntryWithLineBreaks(["Noto Color Emoji Fonts"], '''
yield const LicenseEntryWithLineBreaks(["Noto Color Emoji Fonts"], '''
Copyright 2021 Google Inc. All Rights Reserved.
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.
''');
yield LicenseEntryWithLineBreaks(["Roboto fonts"], '''
yield const LicenseEntryWithLineBreaks(["Roboto fonts"], '''
Apache License
Version 2.0, January 2004

View File

@ -1,6 +1,5 @@
import 'dart:async';
import 'dart:convert';
import 'dart:isolate';
import 'package:cwtch/config.dart';
import 'package:cwtch/notification_manager.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:connectivity_plus/connectivity_plus.dart';
import 'package:intl/intl.dart' as intl;
var globalSettings = Settings(Locale("en", ''), CwtchDark());
var globalSettings = Settings(const Locale("en", ''), CwtchDark());
var globalErrorHandler = ErrorHandler();
var globalTorStatus = TorStatus();
var globalAppState = AppState();
@ -54,6 +51,8 @@ Future<void> main() async {
class Flwtch extends StatefulWidget {
final Key flwtch = GlobalKey();
Flwtch({super.key});
@override
FlwtchState createState() => FlwtchState();
}
@ -63,9 +62,9 @@ enum ConnectivityState { assumed_online, confirmed_offline, confirmed_online }
class FlwtchState extends State<Flwtch> with WindowListener {
late Cwtch cwtch;
late ProfileListState profs;
final MethodChannel notificationClickChannel = MethodChannel('im.cwtch.flwtch/notificationClickHandler');
final MethodChannel shutdownMethodChannel = MethodChannel('im.cwtch.flwtch/shutdownClickHandler');
final MethodChannel shutdownLinuxMethodChannel = MethodChannel('im.cwtch.linux.shutdown');
final MethodChannel notificationClickChannel = const MethodChannel('im.cwtch.flwtch/notificationClickHandler');
final MethodChannel shutdownMethodChannel = const MethodChannel('im.cwtch.flwtch/shutdownClickHandler');
final MethodChannel shutdownLinuxMethodChannel = const MethodChannel('im.cwtch.linux.shutdown');
late StreamSubscription? connectivityStream;
ConnectivityState connectivityState = ConnectivityState.assumed_online;
@ -80,7 +79,7 @@ class FlwtchState extends State<Flwtch> with WindowListener {
@override
initState() {
print("initState() started, setting up handlers");
globalSettings = Settings(Locale("en", ''), CwtchDark());
globalSettings = Settings(const Locale("en", ''), CwtchDark());
globalErrorHandler = ErrorHandler();
globalTorStatus = TorStatus();
globalAppState = AppState();
@ -96,15 +95,13 @@ class FlwtchState extends State<Flwtch> with WindowListener {
shutdownLinuxMethodChannel.setMethodCallHandler(shutdownDirect);
print("initState: creating cwtchnotifier, ffi");
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);
} else if (Platform.isLinux) {
var cwtchNotifier =
new CwtchNotifier(profs, globalSettings, globalErrorHandler, globalTorStatus, newDesktopNotificationsManager(_notificationSelectConvo), globalAppState, globalServersList, this);
var cwtchNotifier = CwtchNotifier(profs, globalSettings, globalErrorHandler, globalTorStatus, newDesktopNotificationsManager(_notificationSelectConvo), globalAppState, globalServersList, this);
cwtch = CwtchFfi(cwtchNotifier);
} else {
var cwtchNotifier =
new CwtchNotifier(profs, globalSettings, globalErrorHandler, globalTorStatus, newDesktopNotificationsManager(_notificationSelectConvo), globalAppState, globalServersList, this);
var cwtchNotifier = CwtchNotifier(profs, globalSettings, globalErrorHandler, globalTorStatus, newDesktopNotificationsManager(_notificationSelectConvo), globalAppState, globalServersList, this);
cwtch = CwtchFfi(cwtchNotifier);
}
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
startConnectivityListener() async {
try {
connectivityStream = await Connectivity().onConnectivityChanged.listen((ConnectivityResult result) {
connectivityStream = Connectivity().onConnectivityChanged.listen((ConnectivityResult result) {
// Got a new connectivity status!
if (result == ConnectivityResult.none) {
connectivityState = ConnectivityState.confirmed_offline;
@ -174,11 +171,11 @@ class FlwtchState extends State<Flwtch> with WindowListener {
builder: (context, widget) {
// in test mode...rebuild everything every second...if cwtch isn't loaded...
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>(
builder: (context, settings, appState, child) => MaterialApp(
key: Key('app'),
key: const Key('app'),
navigatorKey: navKey,
locale: settings.locale,
showPerformanceOverlay: settings.profileMode,
@ -193,7 +190,7 @@ class FlwtchState extends State<Flwtch> with WindowListener {
title: 'Cwtch',
showSemanticsDebugger: settings.useSemanticDebugger,
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);
}
}
;
}
// Invoked via notificationClickChannel by MyBroadcastReceiver in MainActivity.kt
@ -286,7 +282,7 @@ class FlwtchState extends State<Flwtch> with WindowListener {
Navigator.of(navKey.currentContext!).push(
PageRouteBuilder(
settings: RouteSettings(name: "conversations"),
settings: const RouteSettings(name: "conversations"),
pageBuilder: (c, a1, a2) {
return OrientationBuilder(builder: (orientationBuilderContext, orientation) {
return MultiProvider(
@ -294,12 +290,12 @@ class FlwtchState extends State<Flwtch> with WindowListener {
builder: (innercontext, widget) {
var appState = Provider.of<AppState>(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),
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
@ -320,6 +316,7 @@ class FlwtchState extends State<Flwtch> with WindowListener {
globalAppState.focus = false;
}
@override
void onWindowClose() {}
@override

View File

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

View File

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

View File

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

View File

@ -6,7 +6,7 @@ import 'profileservers.dart';
class ContactListState extends ChangeNotifier {
ProfileServerListState? servers;
List<ContactInfoState> _contacts = [];
final List<ContactInfoState> _contacts = [];
String _filter = "";
int get num => _contacts.length;
int get numFiltered => isFiltered ? filteredList().length : num;
@ -29,11 +29,11 @@ class ContactListState extends ChangeNotifier {
void addAll(Iterable<ContactInfoState> newContacts) {
_contacts.addAll(newContacts);
servers?.clearGroups();
_contacts.forEach((contact) {
for (var contact in _contacts) {
if (contact.isGroup) {
servers?.addGroup(contact);
}
});
}
resort();
notifyListeners();
}
@ -54,7 +54,7 @@ class ContactListState extends ChangeNotifier {
if (otherGroups != null && otherGroups.isNotEmpty) {
EnvironmentConfig.debugLog("sharing antispam tickets to new group. FIXME: in Cwtch 1.14");
var antispamTickets = otherGroups[0].antispamTickets;
_contacts.last!.antispamTickets = antispamTickets;
_contacts.last.antispamTickets = antispamTickets;
}
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)
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
@ -33,12 +33,12 @@ class FileDownloadProgress {
String prettyBytes(int bytes) {
if (bytes > 1000000000) {
return (1.0 * bytes / 1000000000).toStringAsFixed(1) + " GB";
return "${(1.0 * bytes / 1000000000).toStringAsFixed(1)} GB";
} else if (bytes > 1000000) {
return (1.0 * bytes / 1000000).toStringAsFixed(1) + " MB";
return "${(1.0 * bytes / 1000000).toStringAsFixed(1)} MB";
} else if (bytes > 1000) {
return (1.0 * bytes / 1000).toStringAsFixed(1) + " kB";
return "${(1.0 * bytes / 1000).toStringAsFixed(1)} kB";
} else {
return bytes.toString() + " B";
return "$bytes B";
}
}

View File

@ -76,6 +76,7 @@ class ByIndex implements CacheHandler {
return msg;
}
@override
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 (index < cache.cacheByIndex.length) {
@ -132,7 +133,7 @@ class ByIndex implements CacheHandler {
cache.addIndexed(messageInfo, start + i);
}
} 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 {
if (i != amount) {
cache.malformIndexes(start + i, start + amount);
@ -165,6 +166,7 @@ class ById implements CacheHandler {
return Future.value(messageInfo);
}
@override
Future<MessageInfo?> get(Cwtch cwtch, String profileOnion, int conversationIdentifier, MessageCache cache) async {
var messageInfo = await lookup(cache);
if (messageInfo != null) {
@ -193,6 +195,7 @@ class ByContentHash implements CacheHandler {
return Future.value(messageInfo);
}
@override
Future<MessageInfo?> get(Cwtch cwtch, String profileOnion, int conversationIdentifier, MessageCache cache) async {
var messageInfo = await lookup(cache);
if (messageInfo != null) {
@ -274,7 +277,7 @@ MessageInfo? messageJsonToInfo(String profileOnion, int conversationIdentifier,
return messageWrapperToInfo(profileOnion, conversationIdentifier, messageWrapper);
} 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;
}
}
@ -291,7 +294,7 @@ MessageInfo messageWrapperToInfo(String profileOnion, int conversationIdentifier
var signature = messageWrapper['Signature'];
var contenthash = messageWrapper['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;
}
@ -313,9 +316,9 @@ class MessageMetadata extends ChangeNotifier {
final String? signature;
final String contenthash;
dynamic get attributes => this._attributes;
dynamic get attributes => _attributes;
bool get ackd => this._ackd;
bool get ackd => _ackd;
String translation = "";
void updateTranslationEvent(String translation) {
@ -324,14 +327,14 @@ class MessageMetadata extends ChangeNotifier {
}
set ackd(bool newVal) {
this._ackd = newVal;
_ackd = newVal;
notifyListeners();
}
bool get error => this._error;
bool get error => _error;
set error(bool newVal) {
this._error = newVal;
_error = newVal;
notifyListeners();
}

View File

@ -1,5 +1,4 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
/// A "MessageDraft" structure that stores information about in-progress message drafts.
/// MessageDraft stores text, quoted replies, and attached images.
@ -14,43 +13,43 @@ class MessageDraft extends ChangeNotifier {
}
bool isEmpty() {
return (this._quotedReference == null) || (this.messageText.isEmpty);
return (_quotedReference == null) || (messageText.isEmpty);
}
String get messageText => ctrlCompose.text;
set messageText(String text) {
this.ctrlCompose.text = text;
ctrlCompose.text = text;
notifyListeners();
}
set quotedReference(int index) {
this._quotedReference = QuotedReference(index);
_quotedReference = QuotedReference(index);
notifyListeners();
}
void attachInvite(int handle) {
this._inviteHandle = handle;
_inviteHandle = handle;
notifyListeners();
}
int? getInviteHandle() {
return this._inviteHandle;
return _inviteHandle;
}
QuotedReference? getQuotedMessage() {
return this._quotedReference;
return _quotedReference;
}
void clearQuotedReference() {
this._quotedReference = null;
_quotedReference = null;
notifyListeners();
}
void clearDraft() {
this._quotedReference = null;
this.ctrlCompose.clear();
this._inviteHandle = null;
_quotedReference = null;
ctrlCompose.clear();
_inviteHandle = null;
notifyListeners();
}
@ -61,7 +60,7 @@ class MessageDraft extends ChangeNotifier {
}
void clearInvite() {
this._inviteHandle = null;
_inviteHandle = null;
}
}

View File

@ -26,10 +26,7 @@ class LocalIndexMessage {
late int? messageId;
LocalIndexMessage(int? messageId, {cacheOnly = false, isLoading = false}) {
this.messageId = messageId;
this.cacheOnly = cacheOnly;
this.isLoading = isLoading;
LocalIndexMessage(this.messageId, {this.cacheOnly = false, this.isLoading = false}) {
loader = Completer<void>();
loaded = loader.future;
if (!isLoading) {
@ -46,7 +43,7 @@ class LocalIndexMessage {
}
void failLoad() {
this.messageId = null;
messageId = null;
if (!loader.isCompleted) {
isLoading = false;
loader.complete(true);
@ -92,17 +89,17 @@ class MessageCache extends ChangeNotifier {
cache = {};
cacheByIndex = List.empty(growable: true);
cacheByHash = {};
this._storageMessageCount = storageMessageCount;
_storageMessageCount = storageMessageCount;
}
int get storageMessageCount => _storageMessageCount;
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
void addFrontIndexGap(int count) {
this._indexUnsynced = count;
_indexUnsynced = count;
}
int get indexUnsynced => _indexUnsynced;
@ -127,10 +124,10 @@ class MessageCache extends ChangeNotifier {
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) {
this.cache[messageID] = MessageInfo(MessageMetadata(profileOnion, conversation, messageID, timestamp, senderHandle, senderImage, "", {}, false, false, isAuto, contenthash), data);
this.cacheByIndex.insert(0, LocalIndexMessage(messageID));
if (contenthash != null && contenthash != "") {
this.cacheByHash[contenthash] = messageID;
cache[messageID] = MessageInfo(MessageMetadata(profileOnion, conversation, messageID, timestamp, senderHandle, senderImage, "", {}, false, false, isAuto, contenthash), data);
cacheByIndex.insert(0, LocalIndexMessage(messageID));
if (contenthash != "") {
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
void lockIndexes(int start, int end) {
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
// there for we can decrement the count as this will be one of them
if (this._indexUnsynced > 0) {
this._indexUnsynced--;
if (_indexUnsynced > 0) {
_indexUnsynced--;
}
}
}
void malformIndexes(int start, int end) {
for (var i = start; i < end; i++) {
this.cacheByIndex[i].failLoad();
cacheByIndex[i].failLoad();
}
}
void addIndexed(MessageInfo messageInfo, int index) {
this.cache[messageInfo.metadata.messageID] = messageInfo;
if (index < this.cacheByIndex.length) {
this.cacheByIndex[index].finishLoad(messageInfo.metadata.messageID);
cache[messageInfo.metadata.messageID] = messageInfo;
if (index < cacheByIndex.length) {
cacheByIndex[index].finishLoad(messageInfo.metadata.messageID);
} 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) {
this.cache[messageInfo.metadata.messageID] = messageInfo;
cache[messageInfo.metadata.messageID] = messageInfo;
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
Widget getWidget(BuildContext context, Key key, int index) {
return ChangeNotifierProvider.value(
value: this.metadata,
value: metadata,
builder: (bcontext, child) {
try {
dynamic shareObj = jsonDecode(this.content);
dynamic shareObj = jsonDecode(content);
if (shareObj == null) {
return MessageRow(MalformedBubble(), index);
return MessageRow(const MalformedBubble(), index);
}
String nameSuggestion = shareObj['f'] as String;
String rootHash = shareObj['h'] as String;
String nonce = shareObj['n'] as String;
int fileSize = shareObj['s'] as int;
String fileKey = rootHash + "." + nonce;
String fileKey = "$rootHash.$nonce";
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);
}
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);
} catch (e) {
return MessageRow(MalformedBubble(), index);
return MessageRow(const MalformedBubble(), index);
}
});
}
@ -51,24 +51,24 @@ class FileMessage extends Message {
@override
Widget getPreviewWidget(BuildContext context, {BoxConstraints? constraints}) {
return ChangeNotifierProvider.value(
value: this.metadata,
value: metadata,
builder: (bcontext, child) {
dynamic shareObj = jsonDecode(this.content);
dynamic shareObj = jsonDecode(content);
if (shareObj == null) {
return MalformedBubble();
return const MalformedBubble();
}
String nameSuggestion = shareObj['f'] as String;
String rootHash = shareObj['h'] as String;
String nonce = shareObj['n'] as String;
int fileSize = shareObj['s'] as int;
if (!validHash(rootHash, nonce)) {
return MalformedBubble();
return const MalformedBubble();
}
return Container(
padding: EdgeInsets.all(1.0),
decoration: BoxDecoration(),
padding: const EdgeInsets.all(1.0),
decoration: const BoxDecoration(),
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,
child: FileBubble(
nameSuggestion,
@ -84,7 +84,7 @@ class FileMessage extends Message {
@override
MessageMetadata getMetadata() {
return this.metadata;
return metadata;
}
bool validHash(String hash, String nonce) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,3 @@
import 'dart:io';
import 'package:flutter/cupertino.dart';
import 'package:provider/provider.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);
if (settings.streamerMode) {
if (handle == nick) {
return handle.substring(0, 7) + "...";
return "${handle.substring(0, 7)}...";
}
}
return nick;

View File

@ -1,4 +1,3 @@
import 'package:cwtch/config.dart';
import 'package:flutter/cupertino.dart';
import 'contact.dart';
@ -11,17 +10,17 @@ class RemoteServerInfoState extends ChangeNotifier {
List<ContactInfoState> _groups = [];
double syncProgress = 0;
DateTime lastPreSyncMessagTime = new DateTime(2020);
DateTime lastPreSyncMessagTime = DateTime(2020);
RemoteServerInfoState(this.onion, this.identifier, this.description, this._status, {lastPreSyncMessageTime, mostRecentMessageTime}) {
if (_status == "Authenticated" || _status == "Synced") {
this.lastPreSyncMessagTime = lastPreSyncMessageTime;
lastPreSyncMessagTime = lastPreSyncMessageTime;
updateSyncProgressFor(mostRecentMessageTime);
}
}
void updateDescription(String newDescription) {
this.description = newDescription;
description = newDescription;
notifyListeners();
}
@ -39,11 +38,11 @@ class RemoteServerInfoState extends ChangeNotifier {
_status = newStatus;
if (status == "Authenticated") {
// syncing, set lastPreSyncMessageTime
_groups.forEach((g) {
for (var g in _groups) {
if (g.lastMessageReceivedTime.isAfter(lastPreSyncMessagTime)) {
lastPreSyncMessagTime = g.lastMessageReceivedTime;
}
});
}
}
notifyListeners();
}
@ -57,7 +56,7 @@ class RemoteServerInfoState extends ChangeNotifier {
// ! 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
// 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());
}
syncProgress = pointFromStart.inSeconds / range.inSeconds;

View File

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

View File

@ -3,13 +3,8 @@ import 'dart:convert';
import 'dart:io';
import 'package:cwtch/main.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:win_toast/win_toast.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;
@ -33,8 +28,7 @@ class WindowsNotificationManager implements NotificationsManager {
bool initialized = false;
late Future<void> Function(String, int) notificationSelectConvo;
WindowsNotificationManager(Future<void> Function(String, int) notificationSelectConvo) {
this.notificationSelectConvo = notificationSelectConvo;
WindowsNotificationManager(this.notificationSelectConvo) {
scheduleMicrotask(() async {
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 {
if (initialized && !globalAppState.focus) {
if (!active) {
@ -137,7 +132,7 @@ class NixNotificationManager implements NotificationsManager {
Future<String> detectLinuxAssetsPath() async {
var devStat = FileStat.stat("assets");
var localStat = FileStat.stat("data/flutter_assets");
var homeStat = FileStat.stat((Platform.environment["HOME"] ?? "") + "/.local/share/cwtch/data/flutter_assets");
var homeStat = FileStat.stat("${Platform.environment["HOME"] ?? ""}/.local/share/cwtch/data/flutter_assets");
var rootStat = FileStat.stat("/usr/share/cwtch/data/flutter_assets");
if ((await devStat).type == FileSystemEntityType.directory) {
@ -145,15 +140,14 @@ class NixNotificationManager implements NotificationsManager {
} else if ((await localStat).type == FileSystemEntityType.directory) {
return path.join(Directory.current.path, "data/flutter_assets/");
} else if ((await homeStat).type == FileSystemEntityType.directory) {
return (Platform.environment["HOME"] ?? "") + "/.local/share/cwtch/data/flutter_assets/";
return "${Platform.environment["HOME"] ?? ""}/.local/share/cwtch/data/flutter_assets/";
} else if ((await rootStat).type == FileSystemEntityType.directory) {
return "/usr/share/cwtch/data/flutter_assets/";
}
return "";
}
NixNotificationManager(Future<void> Function(String, int) notificationSelectConvo) {
this.notificationSelectConvo = notificationSelectConvo;
NixNotificationManager(this.notificationSelectConvo) {
flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
scheduleMicrotask(() async {
@ -168,7 +162,7 @@ class NixNotificationManager implements NotificationsManager {
final LinuxInitializationSettings initializationSettingsLinux = LinuxInitializationSettings(defaultActionName: 'Open notification', defaultIcon: linuxIcon, defaultSuppressSound: true);
final InitializationSettings initializationSettings =
InitializationSettings(android: null, iOS: null, macOS: DarwinInitializationSettings(defaultPresentSound: false), linux: initializationSettingsLinux);
InitializationSettings(android: null, iOS: null, macOS: const DarwinInitializationSettings(defaultPresentSound: false), linux: initializationSettingsLinux);
flutterLocalNotificationsPlugin.resolvePlatformSpecificImplementation<MacOSFlutterLocalNotificationsPlugin>()?.requestPermissions(
alert: true,
@ -182,6 +176,7 @@ class NixNotificationManager implements NotificationsManager {
});
}
@override
Future<void> notify(String message, String profile, int conversationId) async {
if (!globalAppState.focus) {
// Warning: Only use title field on Linux, body field will render links as clickable

View File

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

View File

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

View File

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

View File

@ -1,13 +1,10 @@
import 'dart:convert';
import 'dart:io';
import 'dart:ui';
import 'package:cwtch/config.dart';
import 'package:cwtch/themes/cwtch.dart';
import 'package:cwtch/themes/opaque.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:glob/list_local_fs.dart';
import 'package:provider/provider.dart';
import 'package:yaml/yaml.dart';
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 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) {
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 {
Map<String, Map<String, OpaqueThemeType>> themes = Map();
Map<String, Map<String, OpaqueThemeType>> themes = {};
Directory themesDir = Directory(themesDirPath);
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 {
Map<String, OpaqueThemeType> subthemes = Map();
Map<String, OpaqueThemeType> subthemes = {};
if ((yml["themes"] as YamlMap).containsKey(mode_dark)) {
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 {
late YamlMap yml;
@override
late String mode;
@override
late String theme;
late String? assetsDir;
late OpaqueThemeType fallbackTheme;
YmlTheme(YamlMap yml, theme, mode, assetsDir) {
this.yml = yml;
this.theme = theme;
this.mode = mode;
this.assetsDir = assetsDir;
YmlTheme(this.yml, this.theme, this.mode, this.assetsDir) {
if (mode == mode_dark) {
fallbackTheme = CwtchDark();
} else {
@ -128,16 +123,16 @@ class YmlTheme extends OpaqueThemeType {
Color? getColor(String name) {
var val = yml["themes"][mode]["theme"][name];
if (!(val is int)) {
if ((val is! int)) {
val = yml["themes"][mode]["theme"][val] ?? val;
}
if (!(val is int)) {
if ((val is! int)) {
val = yml["themes"][mode]?["colors"][val] ?? val;
}
if (!(val is int)) {
if ((val is! int)) {
val = yml["colors"]?[val];
}
if (!(val is int)) {
if ((val is! int)) {
return null;
}
@ -160,51 +155,93 @@ class YmlTheme extends OpaqueThemeType {
return null;
}
@override
get backgroundMainColor => getColor("backgroundMainColor") ?? fallbackTheme.backgroundMainColor;
@override
get backgroundPaneColor => getColor("backgroundPaneColor") ?? fallbackTheme.backgroundPaneColor;
@override
get topbarColor => getColor("topbarColor") ?? fallbackTheme.topbarColor;
@override
get mainTextColor => getColor("mainTextColor") ?? fallbackTheme.mainTextColor;
@override
get hilightElementColor => getColor("hilightElementColor") ?? fallbackTheme.hilightElementColor;
@override
get backgroundHilightElementColor => getColor("backgroundHilightElementColor") ?? fallbackTheme.backgroundHilightElementColor;
@override
get sendHintTextColor => getColor("sendHintTextColor") ?? fallbackTheme.sendHintTextColor;
@override
get defaultButtonColor => getColor("defaultButtonColor") ?? fallbackTheme.defaultButtonColor;
@override
get defaultButtonActiveColor => /*mode == mode_light ? darken(defaultButtonColor) :*/ lighten(getColor("defaultButtonColor") ?? fallbackTheme.defaultButtonColor);
@override
get defaultButtonTextColor => getColor("defaultButtonTextColor") ?? fallbackTheme.defaultButtonTextColor;
@override
get defaultButtonDisabledColor => getColor("defaultButtonDisabledColor") ?? fallbackTheme.defaultButtonDisabledColor;
@override
get textfieldBackgroundColor => getColor("textfieldBackgroundColor") ?? fallbackTheme.textfieldBackgroundColor;
@override
get textfieldBorderColor => getColor("textfieldBorderColor") ?? fallbackTheme.textfieldBorderColor;
@override
get textfieldHintColor => getColor("textfieldHintColor") ?? fallbackTheme.textfieldHintColor;
@override
get textfieldErrorColor => getColor("textfieldErrorColor") ?? fallbackTheme.textfieldErrorColor;
@override
get textfieldSelectionColor => getColor("textfieldSelectionColor") ?? fallbackTheme.textfieldSelectionColor;
@override
get scrollbarDefaultColor => getColor("scrollbarDefaultColor") ?? fallbackTheme.scrollbarDefaultColor;
@override
get portraitBackgroundColor => getColor("portraitBackgroundColor") ?? fallbackTheme.portraitBackgroundColor;
@override
get portraitOnlineBorderColor => getColor("portraitOnlineBorderColor") ?? fallbackTheme.portraitOnlineBorderColor;
@override
get portraitOfflineBorderColor => getColor("portraitOfflineBorderColor") ?? fallbackTheme.portraitOfflineBorderColor;
@override
get portraitBlockedBorderColor => getColor("portraitBlockedBorderColor") ?? fallbackTheme.portraitBlockedBorderColor;
@override
get portraitBlockedTextColor => getColor("portraitBlockedTextColor") ?? fallbackTheme.portraitBlockedTextColor;
@override
get portraitContactBadgeColor => getColor("portraitContactBadgeColor") ?? fallbackTheme.portraitContactBadgeColor;
@override
get portraitContactBadgeTextColor => getColor("portraitContactBadgeTextColor") ?? fallbackTheme.portraitContactBadgeTextColor;
@override
get portraitProfileBadgeColor => getColor("portraitProfileBadgeColor") ?? fallbackTheme.portraitProfileBadgeColor;
@override
get portraitProfileBadgeTextColor => getColor("portraitProfileBadgeTextColor") ?? fallbackTheme.portraitProfileBadgeTextColor;
@override
get portraitOnlineAwayColor => getColor("portraitOnlineAwayColor") ?? fallbackTheme.portraitOnlineAwayColor;
@override
get portraitOnlineBusyColor => getColor("portraitOnlineBusyColor") ?? fallbackTheme.portraitOnlineBusyColor;
@override
get dropShadowColor => getColor("dropShadowColor") ?? fallbackTheme.dropShadowColor;
@override
get toolbarIconColor => getColor("toolbarIconColor") ?? fallbackTheme.toolbarIconColor;
@override
get chatReactionIconColor => getColor("chatReactionIconColor") ?? fallbackTheme.chatReactionIconColor;
@override
get messageFromMeBackgroundColor => getColor("messageFromMeBackgroundColor") ?? fallbackTheme.messageFromMeBackgroundColor;
@override
get messageFromMeTextColor => getColor("messageFromMeTextColor") ?? fallbackTheme.messageFromMeTextColor;
@override
get messageFromOtherBackgroundColor => getColor("messageFromOtherBackgroundColor") ?? fallbackTheme.messageFromOtherBackgroundColor;
@override
get messageFromOtherTextColor => getColor("messageFromOtherTextColor") ?? fallbackTheme.messageFromOtherTextColor;
@override
get messageSelectionColor => getColor("messageSelectionColor") ?? fallbackTheme.messageSelectionColor;
@override
get menuBackgroundColor => getColor("menuBackgroundColor") ?? fallbackTheme.menuBackgroundColor;
@override
get snackbarBackgroundColor => getColor("snackbarBackgroundColor") ?? fallbackTheme.snackbarBackgroundColor;
@override
get snackbarTextColor => getColor("snackbarTextColor") ?? fallbackTheme.snackbarTextColor;
// Images
@override
get chatImageColor => getColor("chatImageColor") ?? fallbackTheme.chatImageColor;
@override
get chatImage => getImage("chatImage") ?? fallbackTheme.chatImage;
@override
ImageProvider loadImage(String key, {BuildContext? context}) {
File f = File(key);
if (f.existsSync()) {

View File

@ -109,7 +109,7 @@ class base32 {
base32 = _pad(base32, encoding: encoding);
if (!_isValid(base32, encoding: encoding)) {
throw FormatException('Invalid Base32 characters');
throw const FormatException('Invalid Base32 characters');
}
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
// SOFTWARE.
import 'dart:ui';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'linkify.dart';
@ -132,7 +129,7 @@ class SelectableLinkify extends StatelessWidget {
final BoxConstraints constraints;
const SelectableLinkify({
Key? key,
super.key,
required this.text,
this.linkifiers = defaultLinkifiers,
this.onOpen,
@ -165,7 +162,7 @@ class SelectableLinkify extends StatelessWidget {
this.selectionControls,
this.onSelectionChanged,
required this.constraints,
}) : super(key: key);
});
@override
Widget build(BuildContext context) {
@ -304,7 +301,7 @@ TextSpan buildTextSpan(
semanticsLabel: element.text);
} else if (element is CodeElement) {
return TextSpan(
text: element.text.replaceAll("\`", ""),
text: element.text.replaceAll("`", ""),
// monospace fonts at the same size as regular text makes them appear
// slightly larger, so we compensate by making them slightly smaller...
style: codeStyle?.copyWith(fontFamily: "RobotoMono", fontSize: codeStyle.fontSize! - 1.5),

View File

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

View File

@ -148,7 +148,7 @@ class UrlLinkifier extends Linkifier {
List<LinkifyElement> parse(elements, options) {
var list = <LinkifyElement>[];
elements.forEach((element) {
for (var element in elements) {
if (element is TextElement) {
if (options.parseLinks == false && options.messageFormatting == false) {
list.add(element);
@ -188,7 +188,7 @@ class UrlLinkifier extends Linkifier {
// If protocol has not been specified then append a protocol
// to the start of the URL so that it can be opened...
if (!url.startsWith("https://") && !url.startsWith("http://")) {
url = "https://" + url;
url = "https://$url";
}
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");
}
}
});
}
return list;
}

View File

@ -252,7 +252,7 @@ class _NetworkManagerObject extends DBusRemoteObject {
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);
}
}
@ -284,7 +284,7 @@ class NetworkManagerClient {
}
/// 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.
/// 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 = ""});
/// Called by the event bus.
handleUpdate(int new_progress, String new_status) {
handleUpdate(int newProgress, String newStatus) {
if (progress == 100) {
connected = true;
} else {
connected = false;
}
progress = new_progress;
status = new_status;
if (new_progress != 100) {
status = "$new_progress% - $new_status";
progress = newProgress;
status = newStatus;
if (newProgress != 100) {
status = "$newProgress% - $newStatus";
}
notifyListeners();
}
updateVersion(String new_version) {
version = new_version;
updateVersion(String newVersion) {
version = newVersion;
notifyListeners();
}
}

View File

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

View File

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

View File

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

View File

@ -19,7 +19,6 @@ import 'package:provider/provider.dart';
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
import '../config.dart';
import '../main.dart';
import '../models/message.dart';
import '../models/redaction.dart';
import '../settings.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 }
class ContactsView extends StatefulWidget {
const ContactsView({Key? key}) : super(key: key);
const ContactsView({super.key});
@override
_ContactsViewState createState() => _ContactsViewState();
@ -43,18 +42,18 @@ class ContactsView extends StatefulWidget {
// selectConversation can be called from anywhere to set the active conversation
void selectConversation(BuildContext context, int handle, int? messageIndex) {
int? index = null;
int? index;
if (messageIndex != null) {
// this message is loaded
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");
}
if (handle == Provider.of<AppState>(context, listen: false).selectedConversation) {
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;
}
@ -89,7 +88,7 @@ void _pushMessageView(BuildContext context, int handle) {
Navigator.of(context).push(
PageRouteBuilder(
settings: RouteSettings(name: "messages"),
settings: const RouteSettings(name: "messages"),
pageBuilder: (builderContext, a1, a2) {
var profile = Provider.of<FlwtchState>(builderContext).profs.getProfile(profileOnion)!;
return MultiProvider(
@ -97,11 +96,11 @@ void _pushMessageView(BuildContext context, int handle) {
ChangeNotifierProvider.value(value: profile),
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),
transitionDuration: Duration(milliseconds: 200),
transitionDuration: const Duration(milliseconds: 200),
),
);
}
@ -114,7 +113,7 @@ class _ContactsViewState extends State<ContactsView> {
@override
void 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
@ -132,7 +131,7 @@ class _ContactsViewState extends State<ContactsView> {
Align(
alignment: Alignment.center,
child: IconButton(
icon: Icon(Icons.arrow_back),
icon: const Icon(Icons.arrow_back),
tooltip: MaterialLocalizations.of(context).backButtonTooltip,
onPressed: () {
Provider.of<ProfileInfoState>(context, listen: false).recountUnread();
@ -198,11 +197,11 @@ class _ContactsViewState extends State<ContactsView> {
providers: [
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),
transitionDuration: Duration(milliseconds: 200),
transitionDuration: const Duration(milliseconds: 200),
),
);
break;
@ -238,12 +237,12 @@ class _ContactsViewState extends State<ContactsView> {
value: ProfileStatusMenu.available,
enabled: enabled,
child: Row(children: [
Icon(
const Icon(
CwtchIcons.account_circle_24px,
color: Colors.white,
),
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)))
]),
),
@ -251,12 +250,12 @@ class _ContactsViewState extends State<ContactsView> {
value: ProfileStatusMenu.away,
enabled: enabled,
child: Row(children: [
Icon(
const Icon(
CwtchIcons.account_circle_24px,
color: Colors.yellowAccent,
),
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)))
]),
),
@ -264,23 +263,23 @@ class _ContactsViewState extends State<ContactsView> {
value: ProfileStatusMenu.busy,
enabled: enabled,
child: Row(children: [
Icon(
const Icon(
CwtchIcons.account_circle_24px,
color: Colors.redAccent,
),
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)))
]),
),
PopupMenuDivider(),
const PopupMenuDivider(),
PopupMenuItem<ProfileStatusMenu>(
value: ProfileStatusMenu.appearOffline,
enabled: enabled && !appearOffline,
child: Row(children: [
Icon(CwtchIcons.disconnect_from_contact),
const Icon(CwtchIcons.disconnect_from_contact),
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)))
]),
),
@ -288,13 +287,13 @@ class _ContactsViewState extends State<ContactsView> {
value: ProfileStatusMenu.appearOnline,
enabled: enabled && appearOffline,
child: Row(children: [
Icon(CwtchIcons.disconnect_from_contact),
const Icon(CwtchIcons.disconnect_from_contact),
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)))
]),
),
PopupMenuDivider(),
const PopupMenuDivider(),
PopupMenuItem<ProfileStatusMenu>(
value: !settings.blockUnknownConnections ? ProfileStatusMenu.blockUnknownContacts : ProfileStatusMenu.allowUnknownContacts,
child: Row(children: [
@ -303,17 +302,17 @@ class _ContactsViewState extends State<ContactsView> {
color: settings.theme.mainTextColor,
),
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)))
]),
),
PopupMenuDivider(),
const PopupMenuDivider(),
PopupMenuItem<ProfileStatusMenu>(
value: enabled ? ProfileStatusMenu.disableProfile : ProfileStatusMenu.enableProfile,
child: Row(children: [
Icon(CwtchIcons.favorite_24dp, color: settings.theme.mainTextColor),
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)))
]),
),
@ -326,13 +325,12 @@ class _ContactsViewState extends State<ContactsView> {
color: settings.theme.mainTextColor,
),
Expanded(
child:
Text(AppLocalizations.of(context)!.editProfile!, textAlign: TextAlign.right, style: Provider.of<Settings>(context, listen: false).scaleFonts(defaultTextButtonStyle)))
child: Text(AppLocalizations.of(context)!.editProfile, textAlign: TextAlign.right, style: Provider.of<Settings>(context, listen: false).scaleFonts(defaultTextButtonStyle)))
]),
),
],
),
SizedBox(
const SizedBox(
width: 10,
),
Expanded(
@ -363,26 +361,26 @@ class _ContactsViewState extends State<ContactsView> {
List<Widget> getActions(context) {
var actions = List<Widget>.empty(growable: true);
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)) {
actions.add(PopupMenuButton<ShareMenu>(
icon: Icon(CwtchIcons.address_copy),
icon: const Icon(CwtchIcons.address_copy),
tooltip: AppLocalizations.of(context)!.shareProfileMenuTooltop,
splashRadius: Material.defaultSplashRadius / 2,
onSelected: (ShareMenu item) {
switch (item) {
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));
scaffoldKey.currentState?.showSnackBar(snackBar);
}
break;
case ShareMenu.qrcode:
{
_showQRCode("cwtch:" + Provider.of<ProfileInfoState>(context, listen: false).onion);
_showQRCode("cwtch:${Provider.of<ProfileInfoState>(context, listen: false).onion}");
}
break;
}
@ -400,11 +398,11 @@ class _ContactsViewState extends State<ContactsView> {
));
} else {
actions.add(IconButton(
icon: Icon(CwtchIcons.address_copy),
icon: const Icon(CwtchIcons.address_copy),
tooltip: AppLocalizations.of(context)!.copyAddress,
splashRadius: Material.defaultSplashRadius / 2,
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));
scaffoldKey.currentState?.showSnackBar(snackBar);
}));
@ -413,7 +411,7 @@ class _ContactsViewState extends State<ContactsView> {
// Manage known Servers
if (Provider.of<Settings>(context, listen: false).isExperimentEnabled(TapirGroupsExperiment) || Provider.of<Settings>(context, listen: false).isExperimentEnabled(ServerManagementExperiment)) {
actions.add(IconButton(
icon: Icon(CwtchIcons.dns_24px),
icon: const Icon(CwtchIcons.dns_24px),
tooltip: AppLocalizations.of(context)!.manageKnownServersButton,
splashRadius: Material.defaultSplashRadius / 2,
onPressed: () {
@ -447,7 +445,7 @@ class _ContactsViewState extends State<ContactsView> {
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() {
@ -485,7 +483,7 @@ class _ContactsViewState extends State<ContactsView> {
itemCount: tilesSearchResult.length,
initialScrollIndex: initialScroll,
shrinkWrap: true,
physics: BouncingScrollPhysics(),
physics: const BouncingScrollPhysics(),
semanticChildCount: tilesSearchResult.length,
itemBuilder: (context, index) {
if (tilesSearchResult.length > index) {
@ -494,7 +492,7 @@ class _ContactsViewState extends State<ContactsView> {
return Container();
},
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(
PageRouteBuilder(
settings: RouteSettings(name: "addcontact"),
settings: const RouteSettings(name: "addcontact"),
pageBuilder: (builderContext, a1, a2) {
return MultiProvider(
providers: [
@ -517,7 +515,7 @@ class _ContactsViewState extends State<ContactsView> {
);
},
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(
PageRouteBuilder(
settings: RouteSettings(name: "profileremoteservers"),
settings: const RouteSettings(name: "profileremoteservers"),
pageBuilder: (bcontext, a1, a2) {
return MultiProvider(
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),
transitionDuration: Duration(milliseconds: 200),
transitionDuration: const Duration(milliseconds: 200),
),
);
}
@ -550,17 +548,17 @@ class _ContactsViewState extends State<ContactsView> {
return Padding(
padding: MediaQuery.of(context).viewInsets,
child: RepaintBoundary(
child: Container(
child: SizedBox(
height: Platform.isAndroid ? 250 : 200, // bespoke value courtesy of the [TextField] docs
child: Center(
child: Padding(
padding: EdgeInsets.all(2.0),
padding: const EdgeInsets.all(2.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
SizedBox(
const SizedBox(
height: 20,
),
Expanded(
@ -568,9 +566,9 @@ class _ContactsViewState extends State<ContactsView> {
message: AppLocalizations.of(context)!.tooltipAddContact,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
minimumSize: Size.fromWidth(399),
maximumSize: Size.fromWidth(400),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.horizontal(left: Radius.circular(180), right: Radius.circular(180))),
minimumSize: const Size.fromWidth(399),
maximumSize: const Size.fromWidth(400),
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.horizontal(left: Radius.circular(180), right: Radius.circular(180))),
),
child: Text(
AppLocalizations.of(context)!.addContact,
@ -582,7 +580,7 @@ class _ContactsViewState extends State<ContactsView> {
_pushAddContact(false);
},
))),
SizedBox(
const SizedBox(
height: 20,
),
Expanded(
@ -590,24 +588,24 @@ class _ContactsViewState extends State<ContactsView> {
message: groupsEnabled ? AppLocalizations.of(context)!.addServerTooltip : AppLocalizations.of(context)!.thisFeatureRequiresGroupExpermientsToBeEnabled,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
minimumSize: Size.fromWidth(399),
maximumSize: Size.fromWidth(400),
shape: 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),
minimumSize: const Size.fromWidth(399),
maximumSize: const Size.fromWidth(400),
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.horizontal(left: Radius.circular(180), right: Radius.circular(180))),
),
onPressed: groupsEnabled
? () {
_pushAddContact(false);
}
: 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,
),
Expanded(
@ -615,23 +613,23 @@ class _ContactsViewState extends State<ContactsView> {
message: groupsEnabled ? AppLocalizations.of(context)!.createGroupTitle : AppLocalizations.of(context)!.thisFeatureRequiresGroupExpermientsToBeEnabled,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
minimumSize: Size.fromWidth(399),
maximumSize: Size.fromWidth(400),
shape: 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),
minimumSize: const Size.fromWidth(399),
maximumSize: const Size.fromWidth(400),
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.horizontal(left: Radius.circular(180), right: Radius.circular(180))),
),
onPressed: groupsEnabled
? () {
_pushAddContact(true);
}
: 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,
),
],
@ -640,14 +638,14 @@ class _ContactsViewState extends State<ContactsView> {
});
}
void _showQRCode(String profile_code) {
void _showQRCode(String profileCode) {
showModalBottomSheet<dynamic>(
context: context,
builder: (BuildContext context) {
return Wrap(children: <Widget>[
Center(
child: QrImageView(
data: profile_code,
data: profileCode,
version: QrVersions.auto,
size: 400.0,
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:provider/provider.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import '../main.dart';
import '../settings.dart';
import 'contactsview.dart';
import 'messageview.dart';
class DoubleColumnView extends StatefulWidget {
const DoubleColumnView({super.key});
@override
_DoubleColumnViewState createState() => _DoubleColumnViewState();
}
@ -35,8 +36,8 @@ class _DoubleColumnViewState extends State<DoubleColumnView> {
? Container(
color: Provider.of<Settings>(context).theme.backgroundMainColor,
child: Card(
margin: 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)),
margin: const EdgeInsets.all(0.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))))
: //dev
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
// 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, "")),
], 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';
class FileSharingView extends StatefulWidget {
const FileSharingView({super.key});
@override
_FileSharingViewState createState() => _FileSharingViewState();
}
@ -28,7 +30,7 @@ class _FileSharingViewState extends State<FileSharingView> {
return Scaffold(
appBar: AppBar(
title: Text(handle + " » " + AppLocalizations.of(context)!.manageSharedFiles),
title: Text("$handle » ${AppLocalizations.of(context)!.manageSharedFiles}"),
),
body: FutureBuilder(
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,
shrinkWrap: true,
reverse: true,
physics: BouncingScrollPhysics(),
physics: const BouncingScrollPhysics(),
semanticChildCount: sharedFiles.length,
itemBuilder: (context, index) {
String filekey = sharedFiles[index]["FileKey"];
@ -69,7 +71,7 @@ class _FileSharingViewState extends State<FileSharingView> {
});
},
separatorBuilder: (BuildContext context, int index) {
return Divider(height: 1);
return const Divider(height: 1);
},
);
return fileList;

View File

@ -10,10 +10,11 @@ import '../config.dart';
import '../cwtch_icons_icons.dart';
import '../main.dart';
import '../settings.dart';
import '../themes/opaque.dart';
import 'globalsettingsview.dart';
class GlobalSettingsAboutView extends StatefulWidget {
const GlobalSettingsAboutView({super.key});
@override
_GlobalSettingsAboutViewState createState() => _GlobalSettingsAboutViewState();
}
@ -21,18 +22,19 @@ class GlobalSettingsAboutView extends StatefulWidget {
class _GlobalSettingsAboutViewState extends State<GlobalSettingsAboutView> {
ScrollController settingsListScrollController = ScrollController();
@override
Widget build(BuildContext context) {
return Consumer<Settings>(builder: (ccontext, settings, child) {
return LayoutBuilder(builder: (BuildContext context, BoxConstraints viewportConstraints) {
var appIcon = Icon(Icons.info, color: settings.current().mainTextColor);
return Scrollbar(
key: Key("AboutSettingsView"),
key: const Key("AboutSettingsView"),
trackVisibility: true,
controller: settingsListScrollController,
child: SingleChildScrollView(
clipBehavior: Clip.antiAlias,
controller: settingsListScrollController,
padding: EdgeInsets.symmetric(vertical: 0, horizontal: 20),
padding: const EdgeInsets.symmetric(vertical: 0, horizontal: 20),
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: viewportConstraints.maxHeight,
@ -40,7 +42,7 @@ class _GlobalSettingsAboutViewState extends State<GlobalSettingsAboutView> {
child: Column(children: [
AboutListTile(
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",
applicationLegalese: '\u{a9} 2021-2023 Open Privacy Research Society',
aboutBoxChildren: <Widget>[
@ -52,8 +54,8 @@ class _GlobalSettingsAboutViewState extends State<GlobalSettingsAboutView> {
]),
SwitchListTile(
// TODO: Translate, Remove, OR Hide Prior to Release
title: Text("Show Performance Overlay"),
subtitle: Text("Display an overlay graph of render time."),
title: const Text("Show Performance Overlay"),
subtitle: const Text("Display an overlay graph of render time."),
value: settings.profileMode,
onChanged: (bool value) {
setState(() {
@ -71,8 +73,8 @@ class _GlobalSettingsAboutViewState extends State<GlobalSettingsAboutView> {
Visibility(
visible: EnvironmentConfig.BUILD_VER == dev_version && !Platform.isAndroid,
child: SwitchListTile(
title: Text("Show Semantic Debugger"),
subtitle: Text("Show Accessibility Debugging View"),
title: const Text("Show Semantic Debugger"),
subtitle: const Text("Show Accessibility Debugging View"),
value: settings.useSemanticDebugger,
onChanged: (bool value) {
if (value) {
@ -93,10 +95,7 @@ class _GlobalSettingsAboutViewState extends State<GlobalSettingsAboutView> {
builder: (context, snapshot) {
if (snapshot.hasData) {
return Column(
children: [
Text("libCwtch Debug Info: " + snapshot.data.toString()),
Text("Message Cache Size (Mb): " + (Provider.of<FlwtchState>(context).profs.cacheMemUsage() / (1024 * 1024)).toString())
],
children: [Text("libCwtch Debug Info: ${snapshot.data}"), Text("Message Cache Size (Mb): ${Provider.of<FlwtchState>(context).profs.cacheMemUsage() / (1024 * 1024)}")],
);
} else {
return Container();
@ -124,13 +123,13 @@ class _GlobalSettingsAboutViewState extends State<GlobalSettingsAboutView> {
var sortedKeys = platformChannelInfo.keys.toList();
sortedKeys.sort();
var widgets = List<Widget>.empty(growable: true);
sortedKeys.forEach((element) {
for (var element in sortedKeys) {
widgets.add(ListTile(
leading: Icon(Icons.android, color: settings.current().mainTextColor),
title: Text(element),
subtitle: Text(platformChannelInfo[element]!),
));
});
}
return Column(
children: widgets,
);
@ -142,6 +141,6 @@ class _GlobalSettingsAboutViewState extends State<GlobalSettingsAboutView> {
if (pinfo == null) {
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/opaque.dart';
import '../themes/yamltheme.dart';
import '../widgets/folderpicker.dart';
import 'globalsettingsview.dart';
class GlobalSettingsAppearanceView extends StatefulWidget {
const GlobalSettingsAppearanceView({super.key});
@override
_GlobalSettingsAppearanceViewState createState() => _GlobalSettingsAppearanceViewState();
}
@ -25,17 +26,18 @@ class GlobalSettingsAppearanceView extends StatefulWidget {
class _GlobalSettingsAppearanceViewState extends State<GlobalSettingsAppearanceView> {
ScrollController settingsListScrollController = ScrollController();
@override
Widget build(BuildContext context) {
return Consumer<Settings>(builder: (ccontext, settings, child) {
return LayoutBuilder(builder: (BuildContext context, BoxConstraints viewportConstraints) {
return Scrollbar(
key: Key("AppearanceSettingsView"),
key: const Key("AppearanceSettingsView"),
trackVisibility: true,
controller: settingsListScrollController,
child: SingleChildScrollView(
clipBehavior: Clip.antiAlias,
controller: settingsListScrollController,
padding: EdgeInsets.symmetric(vertical: 0, horizontal: 20),
padding: const EdgeInsets.symmetric(vertical: 0, horizontal: 20),
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: viewportConstraints.maxHeight,
@ -44,10 +46,10 @@ class _GlobalSettingsAppearanceViewState extends State<GlobalSettingsAppearanceV
ListTile(
title: Text(AppLocalizations.of(context)!.settingLanguage),
leading: Icon(CwtchIcons.change_language, color: settings.current().mainTextColor),
trailing: Container(
trailing: SizedBox(
width: MediaQuery.of(context).size.width / 4,
child: DropdownButton(
key: Key("languagelist"),
key: const Key("languagelist"),
isExpanded: true,
value: Provider.of<Settings>(context).locale.toString(),
onChanged: (String? newValue) {
@ -61,7 +63,7 @@ class _GlobalSettingsAppearanceViewState extends State<GlobalSettingsAppearanceV
return DropdownMenuItem<String>(
value: value.toString(),
child: Text(
key: Key("dropdownLanguage" + value.languageCode),
key: Key("dropdownLanguage${value.languageCode}"),
getLanguageFull(context, value.languageCode, value.countryCode),
style: settings.scaleFonts(defaultDropDownMenuItemTextStyle),
overflow: TextOverflow.ellipsis,
@ -87,10 +89,10 @@ class _GlobalSettingsAppearanceViewState extends State<GlobalSettingsAppearanceV
),
ListTile(
title: Text(AppLocalizations.of(context)!.themeColorLabel),
trailing: Container(
trailing: SizedBox(
width: MediaQuery.of(context).size.width / 4,
child: DropdownButton<String>(
key: Key("DropdownTheme"),
key: const Key("DropdownTheme"),
isExpanded: true,
value: Provider.of<Settings>(context).theme.theme,
onChanged: (String? newValue) {
@ -117,7 +119,7 @@ class _GlobalSettingsAppearanceViewState extends State<GlobalSettingsAppearanceV
//AppLocalizations.of(
//context)!
//.fileSharingSettingsDownloadFolderDescription,
trailing: Container(
trailing: SizedBox(
width: MediaQuery.of(context).size.width / 4,
child: ElevatedButton.icon(
label: Text(AppLocalizations.of(context)!.settingsImportThemeButton),
@ -137,7 +139,7 @@ class _GlobalSettingsAppearanceViewState extends State<GlobalSettingsAppearanceV
}
},
//onChanged: widget.onSave,
icon: Icon(Icons.folder),
icon: const Icon(Icons.folder),
//tooltip: widget.tooltip,
)))),
SwitchListTile(
@ -155,7 +157,7 @@ class _GlobalSettingsAppearanceViewState extends State<GlobalSettingsAppearanceV
ListTile(
title: Text(AppLocalizations.of(context)!.settingUIColumnPortrait),
leading: Icon(Icons.table_chart, color: settings.current().mainTextColor),
trailing: Container(
trailing: SizedBox(
width: MediaQuery.of(context).size.width / 4,
child: DropdownButton(
isExpanded: true,
@ -177,9 +179,9 @@ class _GlobalSettingsAppearanceViewState extends State<GlobalSettingsAppearanceV
softWrap: true,
),
leading: Icon(Icons.stay_primary_landscape, color: settings.current().mainTextColor),
trailing: Container(
trailing: SizedBox(
width: MediaQuery.of(context).size.width / 4,
child: Container(
child: SizedBox(
width: MediaQuery.of(context).size.width / 4,
child: DropdownButton(
isExpanded: true,
@ -197,7 +199,7 @@ class _GlobalSettingsAppearanceViewState extends State<GlobalSettingsAppearanceV
ListTile(
title: Text(AppLocalizations.of(context)!.defaultScalingText),
subtitle: Text(AppLocalizations.of(context)!.fontScalingDescription),
trailing: Container(
trailing: SizedBox(
width: MediaQuery.of(context).size.width / 4,
child: Slider(
onChanged: (double value) {
@ -409,20 +411,20 @@ class _GlobalSettingsAppearanceViewState extends State<GlobalSettingsAppearanceV
return Padding(
padding: MediaQuery.of(context).viewInsets,
child: RepaintBoundary(
child: Container(
child: SizedBox(
height: Platform.isAndroid ? 250 : 200, // bespoke value courtesy of the [TextField] docs
child: Center(
child: Padding(
padding: EdgeInsets.all(10.0),
padding: const EdgeInsets.all(10.0),
child: Column(mainAxisAlignment: MainAxisAlignment.center, mainAxisSize: MainAxisSize.min, children: <Widget>[
Text(AppLocalizations.of(context)!.settingThemeOverwriteQuestion.replaceAll("\$themeName", themeName)),
SizedBox(
const SizedBox(
height: 20,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Spacer(),
const Spacer(),
Expanded(
child: ElevatedButton(
child: Text(AppLocalizations.of(context)!.settingThemeOverwriteConfirm, semanticsLabel: AppLocalizations.of(context)!.settingThemeOverwriteConfirm),
@ -432,7 +434,7 @@ class _GlobalSettingsAppearanceViewState extends State<GlobalSettingsAppearanceV
Navigator.pop(context);
},
)),
SizedBox(
const SizedBox(
width: 20,
),
Expanded(
@ -442,7 +444,7 @@ class _GlobalSettingsAppearanceViewState extends State<GlobalSettingsAppearanceV
Navigator.pop(context);
},
)),
Spacer(),
const Spacer(),
],
)
]))))));

View File

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

View File

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

View File

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

View File

@ -16,6 +16,8 @@ import '../main.dart';
/// Group Settings View Provides way to Configure group settings
class GroupSettingsView extends StatefulWidget {
const GroupSettingsView({super.key});
@override
_GroupSettingsViewState createState() => _GroupSettingsViewState();
}
@ -50,8 +52,8 @@ class _GroupSettingsViewState extends State<GroupSettingsView> {
title: Container(
height: Provider.of<Settings>(context).fontScaling * 24.0,
clipBehavior: Clip.hardEdge,
decoration: BoxDecoration(),
child: Text(Provider.of<ContactInfoState>(context).nickname + " " + AppLocalizations.of(context)!.conversationSettings)),
decoration: const BoxDecoration(),
child: Text("${Provider.of<ContactInfoState>(context).nickname} ${AppLocalizations.of(context)!.conversationSettings}")),
),
body: _buildSettingsList(),
);
@ -71,16 +73,16 @@ class _GroupSettingsViewState extends State<GroupSettingsView> {
minHeight: viewportConstraints.maxHeight,
),
child: Container(
margin: EdgeInsets.all(10),
padding: EdgeInsets.all(2),
margin: const EdgeInsets.all(10),
padding: const EdgeInsets.all(2),
child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.stretch, children: [
// Nickname Save Button
Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [
SizedBox(
const SizedBox(
height: 20,
),
CwtchLabel(label: AppLocalizations.of(context)!.displayNameLabel),
SizedBox(
const SizedBox(
height: 20,
),
CwtchButtonTextField(
@ -92,34 +94,36 @@ class _GroupSettingsViewState extends State<GroupSettingsView> {
Provider.of<ContactInfoState>(context, listen: false).nickname = ctrlrNick.text;
Provider.of<FlwtchState>(context, listen: false).cwtch.SetConversationAttribute(profileOnion, handle, "profile.name", ctrlrNick.text);
// 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);
},
icon: Icon(Icons.save),
icon: const Icon(Icons.save),
tooltip: AppLocalizations.of(context)!.saveBtn,
)
]),
Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [
SizedBox(
const SizedBox(
height: 20,
),
CwtchLabel(label: AppLocalizations.of(context)!.server),
SizedBox(
const SizedBox(
height: 20,
),
CwtchTextField(
controller: TextEditingController(text: Provider.of<ContactInfoState>(context, listen: false).server),
validator: (value) {},
validator: (value) {
return null;
},
hintText: '',
)
]),
Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [
SizedBox(
const SizedBox(
height: 20,
),
CwtchLabel(label: AppLocalizations.of(context)!.conversationSettings),
SizedBox(
const SizedBox(
height: 20,
),
ListTile(
@ -145,7 +149,7 @@ class _GroupSettingsViewState extends State<GroupSettingsView> {
]),
Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.end, children: [
SizedBox(
const SizedBox(
height: 20,
),
Tooltip(
@ -157,15 +161,15 @@ class _GroupSettingsViewState extends State<GroupSettingsView> {
// locally update cache...
Provider.of<ContactInfoState>(context, listen: false).isArchived = true;
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;
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),
)),
SizedBox(
const SizedBox(
height: 20,
),
Row(crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.end, children: [
@ -178,7 +182,7 @@ class _GroupSettingsViewState extends State<GroupSettingsView> {
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(Provider.of<Settings>(context).theme.backgroundPaneColor),
foregroundColor: MaterialStateProperty.all(Provider.of<Settings>(context).theme.mainTextColor)),
icon: Icon(CwtchIcons.leave_group),
icon: const Icon(CwtchIcons.leave_group),
label: Text(
AppLocalizations.of(context)!.leaveConversation,
style: settings.scaleFonts(defaultTextButtonStyle.copyWith(decoration: TextDecoration.underline)),
@ -192,7 +196,7 @@ class _GroupSettingsViewState extends State<GroupSettingsView> {
}
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));
ScaffoldMessenger.of(context).showSnackBar(snackBar);
}
@ -206,7 +210,7 @@ class _GroupSettingsViewState extends State<GroupSettingsView> {
},
);
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),
onPressed: () {
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<ProfileInfoState>(context, listen: false).contactList.removeContact(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;
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:io';
import 'dart:math';
import 'dart:ui';
import 'package:crypto/crypto.dart';
import 'package:cwtch/cwtch/cwtch.dart';
import 'package:cwtch/cwtch_icons_icons.dart';
@ -37,6 +36,8 @@ import 'filesharingview.dart';
import 'groupsettingsview.dart';
class MessageView extends StatefulWidget {
const MessageView({super.key});
@override
_MessageViewState createState() => _MessageViewState();
}
@ -52,14 +53,14 @@ class _MessageViewState extends State<MessageView> {
@override
void initState() {
scrollListener.itemPositions.addListener(() {
if (scrollListener.itemPositions.value.length != 0 &&
if (scrollListener.itemPositions.value.isNotEmpty &&
Provider.of<AppState>(context, listen: false).unreadMessagesBelow == true &&
scrollListener.itemPositions.value.any((element) => element.index == 0)) {
Provider.of<AppState>(context, listen: false).initialScrollIndex = 0;
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;
} else {
showDown = false;
@ -100,13 +101,13 @@ class _MessageViewState extends State<MessageView> {
if (showFileSharing) {
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()) {
appBarButtons.add(IconButton(
splashRadius: Material.defaultSplashRadius / 2,
icon: Icon(CwtchIcons.disconnect_from_contact),
icon: const Icon(CwtchIcons.disconnect_from_contact),
tooltip: AppLocalizations.of(context)!.contactDisconnect,
onPressed: () {
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);
}
// 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"));
}));
}
@ -130,7 +131,7 @@ class _MessageViewState extends State<MessageView> {
if (Provider.of<FlwtchState>(context, listen: false).cwtch.IsBlodeuweddSupported() && Provider.of<Settings>(context).isExperimentEnabled(BlodeuweddExperiment)) {
appBarButtons.add(IconButton(
splashRadius: Material.defaultSplashRadius / 2,
icon: Icon(Icons.summarize),
icon: const Icon(Icons.summarize),
tooltip: AppLocalizations.of(context)!.blodeuweddSummarize,
onPressed: () async {
Provider.of<ContactInfoState>(context, listen: false).summary = "";
@ -155,7 +156,7 @@ class _MessageViewState extends State<MessageView> {
}, () {
final snackBar = SnackBar(
content: Text(AppLocalizations.of(context)!.msgFileTooBig),
duration: Duration(seconds: 4),
duration: const Duration(seconds: 4),
);
ScaffoldMessenger.of(context).showSnackBar(snackBar);
}, () {});
@ -165,7 +166,7 @@ class _MessageViewState extends State<MessageView> {
appBarButtons.add(IconButton(
splashRadius: Material.defaultSplashRadius / 2,
icon: Icon(CwtchIcons.send_invite, size: 24),
icon: const Icon(CwtchIcons.send_invite, size: 24),
tooltip: AppLocalizations.of(context)!.sendInvite,
onPressed: () {
_modalSendInvitation(context);
@ -173,7 +174,7 @@ class _MessageViewState extends State<MessageView> {
}
appBarButtons.add(IconButton(
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,
onPressed: _pushContactSettings));
@ -185,12 +186,12 @@ class _MessageViewState extends State<MessageView> {
floatingActionButton: showDown
? FloatingActionButton(
// 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),
onPressed: () {
Provider.of<AppState>(context, listen: false).initialScrollIndex = 0;
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,
appBar: AppBar(
@ -239,14 +240,14 @@ class _MessageViewState extends State<MessageView> {
size: 14.0,
)))
: null),
SizedBox(
const SizedBox(
width: 10,
),
Expanded(
child: Container(
height: 42,
clipBehavior: Clip.hardEdge,
decoration: BoxDecoration(),
decoration: const BoxDecoration(),
child: Align(
alignment: Alignment.centerLeft,
child: Text(
@ -259,7 +260,7 @@ class _MessageViewState extends State<MessageView> {
actions: appBarButtons,
),
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(
scrollListener,
)),
@ -287,11 +288,11 @@ class _MessageViewState extends State<MessageView> {
pageBuilder: (builderContext, a1, a2) {
return MultiProvider(
providers: [ChangeNotifierProvider.value(value: profileInfoState), ChangeNotifierProvider.value(value: contactInfoState)],
child: FileSharingView(),
child: const FileSharingView(),
);
},
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) {
return MultiProvider(
providers: [ChangeNotifierProvider.value(value: profileInfoState), ChangeNotifierProvider.value(value: contactInfoState)],
child: GroupSettingsView(),
child: const GroupSettingsView(),
);
},
transitionsBuilder: (c, anim, a2, child) => FadeTransition(opacity: anim, child: child),
transitionDuration: Duration(milliseconds: 200),
transitionDuration: const Duration(milliseconds: 200),
),
);
} else {
@ -319,11 +320,11 @@ class _MessageViewState extends State<MessageView> {
pageBuilder: (builderContext, a1, a2) {
return MultiProvider(
providers: [ChangeNotifierProvider.value(value: profileInfoState), ChangeNotifierProvider.value(value: contactInfoState)],
child: PeerSettingsView(),
child: const PeerSettingsView(),
);
},
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();
if (attachedInvite != null) {
this._sendInvitation(attachedInvite);
_sendInvitation(attachedInvite);
}
// Trim message
@ -368,18 +369,18 @@ class _MessageViewState extends State<MessageView> {
var digest1 = sha256.convert(bytes1);
var contentHash = base64Encode(digest1.bytes);
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)
.cwtch
.SendMessage(Provider.of<ContactInfoState>(context, listen: false).profileOnion, Provider.of<ContactInfoState>(context, listen: false).identifier, jsonEncode(cm))
.then(_sendMessageHandler);
} 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();
});
} else {
ChatMessage cm = new ChatMessage(o: TextMessageOverlay, d: messageWithoutNewLine);
ChatMessage cm = ChatMessage(o: TextMessageOverlay, d: messageWithoutNewLine);
Provider.of<FlwtchState>(context, listen: false)
.cwtch
.SendMessage(Provider.of<ContactInfoState>(context, listen: false).profileOnion, Provider.of<ContactInfoState>(context, listen: false).identifier, jsonEncode(cm))
@ -442,14 +443,14 @@ class _MessageViewState extends State<MessageView> {
return Wrap(children: [
SelectableText(
chrome + '\u202F',
'$chrome\u202F',
style: settings.scaleFonts(defaultMessageTextStyle.copyWith(color: Provider.of<Settings>(context).theme.messageFromMeTextColor)),
textAlign: TextAlign.left,
maxLines: 2,
textWidthBasis: TextWidthBasis.longestLine,
),
SelectableText(
targetName + '\u202F',
'$targetName\u202F',
style: settings.scaleFonts(defaultMessageTextStyle.copyWith(color: Provider.of<Settings>(context).theme.messageFromMeTextColor)),
textAlign: TextAlign.left,
maxLines: 2,
@ -462,11 +463,11 @@ class _MessageViewState extends State<MessageView> {
var showClickableLinks = Provider.of<Settings>(context).isExperimentEnabled(ClickableLinksExperiment);
var wdgMessage = Padding(
padding: EdgeInsets.all(8),
padding: const EdgeInsets.all(8),
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),
linkifiers: [UrlLinkifier()],
linkifiers: const [UrlLinkifier()],
onOpen: showClickableLinks ? null : null,
style: TextStyle(
color: Provider.of<Settings>(context).theme.messageFromMeTextColor,
@ -488,14 +489,14 @@ class _MessageViewState extends State<MessageView> {
backgroundColor: Provider.of<Settings>(context).theme.messageFromOtherBackgroundColor),
textAlign: TextAlign.left,
textWidthBasis: TextWidthBasis.longestLine,
constraints: BoxConstraints.expand(),
constraints: const BoxConstraints.expand(),
));
var showMessageFormattingPreview = Provider.of<Settings>(context).isExperimentEnabled(FormattingExperiment);
var preview = showMessageFormattingPreview
? IconButton(
tooltip: AppLocalizations.of(context)!.tooltipBackToMessageEditing,
icon: Icon(Icons.text_fields),
icon: const Icon(Icons.text_fields),
onPressed: () {
setState(() {
showPreview = false;
@ -505,8 +506,8 @@ class _MessageViewState extends State<MessageView> {
var composeBox = Container(
color: Provider.of<Settings>(context).theme.backgroundMainColor,
padding: EdgeInsets.all(2),
margin: EdgeInsets.all(2),
padding: const 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.
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 bold = IconButton(
icon: Icon(Icons.format_bold),
icon: const Icon(Icons.format_bold),
tooltip: AppLocalizations.of(context)!.tooltipBoldText,
onPressed: () {
setState(() {
@ -543,14 +544,14 @@ class _MessageViewState extends State<MessageView> {
var selection = ctrlrCompose.selection;
var start = ctrlrCompose.selection.start;
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);
Provider.of<ContactInfoState>(context, listen: false).messageDraft.ctrlCompose = ctrlrCompose;
});
});
var italic = IconButton(
icon: Icon(Icons.format_italic),
icon: const Icon(Icons.format_italic),
tooltip: AppLocalizations.of(context)!.tooltipItalicize,
onPressed: () {
setState(() {
@ -559,14 +560,14 @@ class _MessageViewState extends State<MessageView> {
var selection = ctrlrCompose.selection;
var start = ctrlrCompose.selection.start;
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);
Provider.of<ContactInfoState>(context, listen: false).messageDraft.ctrlCompose = ctrlrCompose;
});
});
var code = IconButton(
icon: Icon(Icons.code),
icon: const Icon(Icons.code),
tooltip: AppLocalizations.of(context)!.tooltipCode,
onPressed: () {
setState(() {
@ -575,14 +576,14 @@ class _MessageViewState extends State<MessageView> {
var selection = ctrlrCompose.selection;
var start = ctrlrCompose.selection.start;
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);
Provider.of<ContactInfoState>(context, listen: false).messageDraft.ctrlCompose = ctrlrCompose;
});
});
var superscript = IconButton(
icon: Icon(Icons.superscript),
icon: const Icon(Icons.superscript),
tooltip: AppLocalizations.of(context)!.tooltipSuperscript,
onPressed: () {
setState(() {
@ -591,14 +592,14 @@ class _MessageViewState extends State<MessageView> {
var selection = ctrlrCompose.selection;
var start = ctrlrCompose.selection.start;
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);
Provider.of<ContactInfoState>(context, listen: false).messageDraft.ctrlCompose = ctrlrCompose;
});
});
var strikethrough = IconButton(
icon: Icon(Icons.format_strikethrough),
icon: const Icon(Icons.format_strikethrough),
tooltip: AppLocalizations.of(context)!.tooltipStrikethrough,
onPressed: () {
setState(() {
@ -607,14 +608,14 @@ class _MessageViewState extends State<MessageView> {
var selection = ctrlrCompose.selection;
var start = ctrlrCompose.selection.start;
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);
Provider.of<ContactInfoState>(context, listen: false).messageDraft.ctrlCompose = ctrlrCompose;
});
});
var preview = IconButton(
icon: Icon(Icons.text_format),
icon: const Icon(Icons.text_format),
tooltip: AppLocalizations.of(context)!.tooltipPreviewFormatting,
onPressed: () {
setState(() {
@ -623,7 +624,7 @@ class _MessageViewState extends State<MessageView> {
});
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)));
var formattingToolbar = Container(
@ -636,9 +637,9 @@ class _MessageViewState extends State<MessageView> {
focusNode: FocusNode(),
onKey: handleKeyPress,
child: Padding(
padding: EdgeInsets.all(8),
padding: const EdgeInsets.all(8),
child: TextFormField(
key: Key('txtCompose'),
key: const Key('txtCompose'),
controller: Provider.of<ContactInfoState>(context).messageDraft.ctrlCompose,
focusNode: focusNode,
autofocus: !Platform.isAndroid,
@ -673,8 +674,9 @@ class _MessageViewState extends State<MessageView> {
focusedBorder: InputBorder.none,
enabled: true,
suffixIcon: ElevatedButton(
key: Key("btnSend"),
style: ElevatedButton.styleFrom(padding: EdgeInsets.all(0.0), shape: new RoundedRectangleBorder(borderRadius: new BorderRadius.circular(45.0))),
key: const Key("btnSend"),
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(
message: cannotSend
? (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)!.sendMessage,
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) {
textEditChildren = [formattingToolbar, textField];
} else {
textEditChildren = [textField];
}
var composeBox =
Container(color: Provider.of<Settings>(context).theme.backgroundMainColor, padding: EdgeInsets.all(2), margin: EdgeInsets.all(2), height: 164, child: Column(children: textEditChildren));
var composeBox = Container(
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();
if (Provider.of<ContactInfoState>(context).messageDraft.getInviteHandle() != null) {
invite = FutureBuilder(
@ -706,31 +707,31 @@ class _MessageViewState extends State<MessageView> {
var contactInvite = snapshot.data! as int;
var contact = Provider.of<ProfileInfoState>(context, listen: false).contactList.getContact(contactInvite);
return Container(
margin: EdgeInsets.all(5),
padding: EdgeInsets.all(5),
margin: const EdgeInsets.all(5),
padding: const EdgeInsets.all(5),
color: Provider.of<Settings>(context).theme.messageFromMeBackgroundColor,
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
Stack(children: [
Container(
margin: EdgeInsets.all(5),
padding: EdgeInsets.all(5),
margin: const EdgeInsets.all(5),
padding: const EdgeInsets.all(5),
clipBehavior: Clip.antiAlias,
decoration: BoxDecoration(color: Provider.of<Settings>(context).theme.messageFromMeBackgroundColor),
height: 75,
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(
child: DefaultTextStyle(
textWidthBasis: TextWidthBasis.parent,
child: senderInviteChrome("", contact!.nickname),
style: Provider.of<Settings>(context).scaleFonts(defaultTextStyle),
overflow: TextOverflow.fade,
child: senderInviteChrome("", contact!.nickname),
))
])),
Align(
alignment: Alignment.topRight,
child: IconButton(
icon: Icon(Icons.highlight_remove),
icon: const Icon(Icons.highlight_remove),
splashRadius: Material.defaultSplashRadius / 2,
tooltip: AppLocalizations.of(context)!.tooltipRemoveThisQuotedMessage,
onPressed: () {
@ -756,16 +757,16 @@ class _MessageViewState extends State<MessageView> {
? Provider.of<Settings>(context).theme.messageFromOtherTextColor
: Provider.of<Settings>(context).theme.messageFromMeTextColor;
return Container(
margin: EdgeInsets.all(5),
padding: EdgeInsets.all(5),
margin: const EdgeInsets.all(5),
padding: const EdgeInsets.all(5),
color: message.getMetadata().senderHandle != Provider.of<AppState>(context).selectedProfile
? Provider.of<Settings>(context).theme.messageFromOtherBackgroundColor
: Provider.of<Settings>(context).theme.messageFromMeBackgroundColor,
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
Stack(children: [
Container(
margin: EdgeInsets.all(5),
padding: EdgeInsets.all(5),
margin: const EdgeInsets.all(5),
padding: const EdgeInsets.all(5),
clipBehavior: Clip.antiAlias,
decoration: BoxDecoration(
color: message.getMetadata().senderHandle != Provider.of<AppState>(context).selectedProfile
@ -774,19 +775,19 @@ class _MessageViewState extends State<MessageView> {
),
height: 75,
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(
child: DefaultTextStyle(
textWidthBasis: TextWidthBasis.parent,
child: message.getPreviewWidget(context),
style: TextStyle(color: qTextColor),
overflow: TextOverflow.fade,
child: message.getPreviewWidget(context),
))
])),
Align(
alignment: Alignment.topRight,
child: IconButton(
icon: Icon(Icons.highlight_remove),
icon: const Icon(Icons.highlight_remove),
splashRadius: Material.defaultSplashRadius / 2,
tooltip: AppLocalizations.of(context)!.tooltipRemoveThisQuotedMessage,
onPressed: () {
@ -797,7 +798,7 @@ class _MessageViewState extends State<MessageView> {
]),
]));
} else {
return MessageLoadingBubble();
return const MessageLoadingBubble();
}
},
);
@ -832,18 +833,18 @@ class _MessageViewState extends State<MessageView> {
showModalBottomSheet<void>(
context: ctx,
builder: (BuildContext bcontext) {
return Container(
return SizedBox(
height: 200, // bespoke value courtesy of the [TextField] docs
child: Center(
child: Padding(
padding: EdgeInsets.all(10.0),
padding: const EdgeInsets.all(10.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Text(AppLocalizations.of(bcontext)!.invitationLabel),
SizedBox(
const SizedBox(
height: 20,
),
ChangeNotifierProvider.value(
@ -852,17 +853,17 @@ class _MessageViewState extends State<MessageView> {
return contact.onion != Provider.of<ContactInfoState>(ctx).onion;
}, onChanged: (newVal) {
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,
),
ElevatedButton(
child: Text(AppLocalizations.of(bcontext)!.inviteBtn, semanticsLabel: AppLocalizations.of(bcontext)!.inviteBtn),
onPressed: () {
if (this.selectedContact != -1) {
Provider.of<ContactInfoState>(context, listen: false).messageDraft.attachInvite(this.selectedContact);
if (selectedContact != -1) {
Provider.of<ContactInfoState>(context, listen: false).messageDraft.attachInvite(selectedContact);
}
Navigator.pop(bcontext);
setState(() {});
@ -881,21 +882,19 @@ class _MessageViewState extends State<MessageView> {
var showPreview = false;
if (Provider.of<Settings>(context, listen: false).shouldPreview(path)) {
showPreview = true;
if (imagePreview == null) {
imagePreview = new File(path);
}
imagePreview ??= File(path);
}
return Container(
return SizedBox(
height: 300, // bespoke value courtesy of the [TextField] docs
child: Center(
child: Padding(
padding: EdgeInsets.all(10.0),
padding: const EdgeInsets.all(10.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text(AppLocalizations.of(context)!.msgConfirmSend + " $path?"),
SizedBox(
Text("${AppLocalizations.of(context)!.msgConfirmSend} $path?"),
const SizedBox(
height: 20,
),
Visibility(
@ -910,13 +909,13 @@ class _MessageViewState extends State<MessageView> {
height: 150,
isAntiAlias: false,
errorBuilder: (context, error, stackTrace) {
return MalformedBubble();
return const MalformedBubble();
},
)
: Container()),
Visibility(
visible: showPreview,
child: SizedBox(
child: const SizedBox(
height: 10,
)),
Row(mainAxisAlignment: MainAxisAlignment.center, children: [
@ -926,7 +925,7 @@ class _MessageViewState extends State<MessageView> {
Navigator.pop(bcontext);
},
),
SizedBox(
const SizedBox(
width: 20,
),
ElevatedButton(
@ -951,7 +950,7 @@ void _summarizeConversation(BuildContext context, ProfileInfoState profile, Sett
) {
return StatefulBuilder(builder: (BuildContext scontext, StateSetter setState /*You can rename this!*/) {
if (scontext.mounted) {
new Timer.periodic(Duration(seconds: 1), (Timer t) {
Timer.periodic(const Duration(seconds: 1), (Timer t) {
if (scontext.mounted) {
setState(() {});
}
@ -966,13 +965,13 @@ void _summarizeConversation(BuildContext context, ProfileInfoState profile, Sett
Provider.of<ContactInfoState>(context).summary == ""
? Column(crossAxisAlignment: CrossAxisAlignment.center, children: [
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))
]));
var image = Padding(
padding: EdgeInsets.all(4.0),
padding: const EdgeInsets.all(4.0),
child: ProfileImage(
imagePath: "assets/blodeuwedd.png",
diameter: 48.0,
@ -981,14 +980,14 @@ void _summarizeConversation(BuildContext context, ProfileInfoState profile, Sett
badgeColor: Colors.red,
));
return Container(
return SizedBox(
height: 300, // bespoke value courtesy of the [TextField] docs
child: Container(
alignment: Alignment.center,
child: Padding(
padding: EdgeInsets.all(10.0),
padding: const EdgeInsets.all(10.0),
child: Padding(
padding: EdgeInsets.all(10.0),
padding: const EdgeInsets.all(10.0),
child: Row(
mainAxisSize: MainAxisSize.min,
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/cwtch_icons_icons.dart';
import 'package:cwtch/models/appstate.dart';
@ -19,6 +17,8 @@ import '../themes/opaque.dart';
/// Peer Settings View Provides way to Configure .
class PeerSettingsView extends StatefulWidget {
const PeerSettingsView({super.key});
@override
_PeerSettingsViewState createState() => _PeerSettingsViewState();
}
@ -52,8 +52,8 @@ class _PeerSettingsViewState extends State<PeerSettingsView> {
title: Container(
height: Provider.of<Settings>(context).fontScaling * 24.0,
clipBehavior: Clip.hardEdge,
decoration: BoxDecoration(),
child: Text(handle + " " + AppLocalizations.of(context)!.conversationSettings)),
decoration: const BoxDecoration(),
child: Text("$handle ${AppLocalizations.of(context)!.conversationSettings}")),
),
body: _buildSettingsList(),
);
@ -77,11 +77,11 @@ class _PeerSettingsViewState extends State<PeerSettingsView> {
textAlign: TextAlign.left,
text: TextSpan(
text: country,
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 10, fontFamily: "RobotoMono"),
children: [TextSpan(text: " ($ip)", style: TextStyle(fontSize: 8, fontWeight: FontWeight.normal))]));
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 10, fontFamily: "RobotoMono"),
children: [TextSpan(text: " ($ip)", style: const TextStyle(fontSize: 8, fontWeight: FontWeight.normal))]));
}).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(
children: paths,
@ -107,8 +107,8 @@ class _PeerSettingsViewState extends State<PeerSettingsView> {
minHeight: viewportConstraints.maxHeight,
),
child: Container(
margin: EdgeInsets.all(10),
padding: EdgeInsets.all(2),
margin: const EdgeInsets.all(10),
padding: const EdgeInsets.all(2),
child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.center, children: [
Column(crossAxisAlignment: CrossAxisAlignment.center, children: [
ProfileImage(
@ -125,21 +125,21 @@ class _PeerSettingsViewState extends State<PeerSettingsView> {
width: MediaQuery.of(context).size.width / 2,
child: Column(children: [
Padding(
padding: EdgeInsets.all(1),
padding: const EdgeInsets.all(1),
child: SelectableText(
Provider.of<ContactInfoState>(context, listen: false).attributes[0] ?? "",
textAlign: TextAlign.center,
),
),
Padding(
padding: EdgeInsets.all(1),
padding: const EdgeInsets.all(1),
child: SelectableText(
Provider.of<ContactInfoState>(context, listen: false).attributes[1] ?? "",
textAlign: TextAlign.center,
),
),
Padding(
padding: EdgeInsets.all(1),
padding: const EdgeInsets.all(1),
child: SelectableText(
Provider.of<ContactInfoState>(context, listen: false).attributes[2] ?? "",
textAlign: TextAlign.center,
@ -150,7 +150,7 @@ class _PeerSettingsViewState extends State<PeerSettingsView> {
Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [
CwtchLabel(label: AppLocalizations.of(context)!.displayNameLabel),
SizedBox(
const SizedBox(
height: 20,
),
CwtchButtonTextField(
@ -164,7 +164,7 @@ class _PeerSettingsViewState extends State<PeerSettingsView> {
final snackBar = SnackBar(content: Text(AppLocalizations.of(context)!.nickChangeSuccess));
ScaffoldMessenger.of(context).showSnackBar(snackBar);
},
icon: Icon(Icons.save),
icon: const Icon(Icons.save),
tooltip: AppLocalizations.of(context)!.saveBtn,
)
]),
@ -173,26 +173,26 @@ class _PeerSettingsViewState extends State<PeerSettingsView> {
Visibility(
visible: settings.streamerMode == false,
child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [
SizedBox(
const SizedBox(
height: 20,
),
CwtchLabel(label: AppLocalizations.of(context)!.addressLabel),
SizedBox(
const SizedBox(
height: 20,
),
CwtchButtonTextField(
controller: TextEditingController(text: Provider.of<ContactInfoState>(context, listen: false).onion),
onPressed: _copyOnion,
icon: Icon(CwtchIcons.address_copy),
icon: const Icon(CwtchIcons.address_copy),
tooltip: AppLocalizations.of(context)!.copyBtn,
)
])),
Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [
SizedBox(
const SizedBox(
height: 20,
),
CwtchLabel(label: AppLocalizations.of(context)!.conversationSettings),
SizedBox(
const SizedBox(
height: 20,
),
ListTile(
@ -202,7 +202,7 @@ class _PeerSettingsViewState extends State<PeerSettingsView> {
subtitle: Text(AppLocalizations.of(context)!.descriptionACNCircuitInfo),
trailing: path,
),
SizedBox(
const SizedBox(
height: 20,
),
SwitchListTile(
@ -290,7 +290,7 @@ class _PeerSettingsViewState extends State<PeerSettingsView> {
)),
]),
Column(mainAxisAlignment: MainAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.end, children: [
SizedBox(
const SizedBox(
height: 20,
),
Row(crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.end, children: [
@ -303,16 +303,16 @@ class _PeerSettingsViewState extends State<PeerSettingsView> {
// locally update cache...
Provider.of<ContactInfoState>(context, listen: false).isArchived = true;
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;
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),
))
]),
SizedBox(
const SizedBox(
height: 20,
),
Row(crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.end, children: [
@ -325,7 +325,7 @@ class _PeerSettingsViewState extends State<PeerSettingsView> {
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(Provider.of<Settings>(context).theme.backgroundPaneColor),
foregroundColor: MaterialStateProperty.all(Provider.of<Settings>(context).theme.mainTextColor)),
icon: Icon(CwtchIcons.leave_group),
icon: const Icon(CwtchIcons.leave_group),
label: Text(
AppLocalizations.of(context)!.leaveConversation,
style: settings.scaleFonts(defaultTextButtonStyle.copyWith(decoration: TextDecoration.underline)),
@ -345,7 +345,7 @@ class _PeerSettingsViewState extends State<PeerSettingsView> {
}
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));
ScaffoldMessenger.of(context).showSnackBar(snackBar);
}
@ -353,14 +353,14 @@ class _PeerSettingsViewState extends State<PeerSettingsView> {
showAlertDialog(BuildContext context) {
// set up the buttons
Widget cancelButton = ElevatedButton(
child: Text(AppLocalizations.of(context)!.cancel),
style: ButtonStyle(padding: MaterialStateProperty.all(EdgeInsets.all(20))),
style: ButtonStyle(padding: MaterialStateProperty.all(const EdgeInsets.all(20))),
onPressed: () {
Navigator.of(context).pop(); // dismiss dialog
},
child: Text(AppLocalizations.of(context)!.cancel),
);
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),
onPressed: () {
var profileOnion = Provider.of<ContactInfoState>(context, listen: false).profileOnion;
@ -368,7 +368,7 @@ class _PeerSettingsViewState extends State<PeerSettingsView> {
// locally update cache...
Provider.of<ProfileInfoState>(context, listen: false).contactList.removeContact(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;
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:cwtch/widgets/profilerow.dart';
import 'package:provider/provider.dart';
import '../config.dart';
import '../main.dart';
import '../torstatus.dart';
import 'addeditprofileview.dart';
@ -24,7 +23,7 @@ import 'globalsettingsview.dart';
import 'serversview.dart';
class ProfileMgrView extends StatefulWidget {
ProfileMgrView();
const ProfileMgrView({super.key});
@override
_ProfileMgrViewState createState() => _ProfileMgrViewState();
@ -50,7 +49,7 @@ class _ProfileMgrViewState extends State<ProfileMgrView> {
return Provider.of<AppState>(context, listen: false).cwtchIsClosing;
},
child: Scaffold(
key: Key("ProfileManagerView"),
key: const Key("ProfileManagerView"),
backgroundColor: settings.theme.backgroundMainColor,
appBar: AppBar(
title: Row(children: [
@ -59,7 +58,7 @@ class _ProfileMgrViewState extends State<ProfileMgrView> {
size: 36,
color: settings.theme.mainTextColor,
),
SizedBox(
const SizedBox(
width: 10,
),
Expanded(
@ -83,11 +82,11 @@ class _ProfileMgrViewState extends State<ProfileMgrView> {
}
List<Widget> getActions() {
List<Widget> actions = new List<Widget>.empty(growable: true);
List<Widget> actions = List<Widget>.empty(growable: true);
// Tor Status
actions.add(IconButton(
icon: TorIcon(),
icon: const TorIcon(),
onPressed: _pushTorStatus,
splashRadius: Material.defaultSplashRadius / 2,
tooltip: Provider.of<TorStatus>(context).progress == 100
@ -97,7 +96,7 @@ class _ProfileMgrViewState extends State<ProfileMgrView> {
// Unlock Profiles
actions.add(IconButton(
icon: Icon(CwtchIcons.lock_open_24px),
icon: const Icon(CwtchIcons.lock_open_24px),
splashRadius: Material.defaultSplashRadius / 2,
color: Provider.of<ProfileListState>(context).profiles.isEmpty ? Provider.of<Settings>(context).theme.defaultButtonColor : Provider.of<Settings>(context).theme.mainTextColor,
tooltip: AppLocalizations.of(context)!.tooltipUnlockProfiles,
@ -109,26 +108,26 @@ class _ProfileMgrViewState extends State<ProfileMgrView> {
Provider.of<Settings>(context).isExperimentEnabled(ServerManagementExperiment) &&
!Platform.isAndroid &&
!Platform.isIOS) {
actions.add(
IconButton(icon: Icon(CwtchIcons.dns_black_24dp), splashRadius: Material.defaultSplashRadius / 2, tooltip: AppLocalizations.of(context)!.serversManagerTitleShort, onPressed: _pushServers));
actions.add(IconButton(
icon: const Icon(CwtchIcons.dns_black_24dp), splashRadius: Material.defaultSplashRadius / 2, tooltip: AppLocalizations.of(context)!.serversManagerTitleShort, onPressed: _pushServers));
}
// Global Settings
actions.add(IconButton(
key: Key("OpenSettingsView"),
icon: Icon(Icons.settings),
key: const Key("OpenSettingsView"),
icon: const Icon(Icons.settings),
tooltip: AppLocalizations.of(context)!.tooltipOpenSettings,
splashRadius: Material.defaultSplashRadius / 2,
onPressed: _pushGlobalSettings));
// 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;
}
void _modalShutdown() {
Provider.of<FlwtchState>(context, listen: false).modalShutdown(MethodCall(""));
Provider.of<FlwtchState>(context, listen: false).modalShutdown(const MethodCall(""));
}
void _pushGlobalSettings() {
@ -137,11 +136,11 @@ class _ProfileMgrViewState extends State<ProfileMgrView> {
pageBuilder: (bcontext, a1, a2) {
return Provider(
create: (_) => Provider.of<FlwtchState>(bcontext, listen: false),
child: GlobalSettingsView(),
child: const GlobalSettingsView(),
);
},
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() {
Navigator.of(context).push(
PageRouteBuilder(
settings: RouteSettings(name: "servers"),
settings: const RouteSettings(name: "servers"),
pageBuilder: (bcontext, a1, a2) {
return MultiProvider(
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),
transitionDuration: Duration(milliseconds: 200),
transitionDuration: const Duration(milliseconds: 200),
),
);
}
@ -165,20 +164,20 @@ class _ProfileMgrViewState extends State<ProfileMgrView> {
void _pushTorStatus() {
Navigator.of(context).push(
PageRouteBuilder(
settings: RouteSettings(name: "torconfig"),
settings: const RouteSettings(name: "torconfig"),
pageBuilder: (bcontext, a1, a2) {
return MultiProvider(
providers: [Provider.value(value: Provider.of<FlwtchState>(context))],
child: TorStatusView(),
child: const TorStatusView(),
);
},
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.of(context).push(
@ -190,11 +189,11 @@ class _ProfileMgrViewState extends State<ProfileMgrView> {
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),
transitionDuration: Duration(milliseconds: 200),
transitionDuration: const Duration(milliseconds: 200),
),
);
}
@ -207,37 +206,37 @@ class _ProfileMgrViewState extends State<ProfileMgrView> {
return Padding(
padding: MediaQuery.of(context).viewInsets,
child: RepaintBoundary(
child: Container(
child: SizedBox(
height: Platform.isAndroid ? 250 : 200, // bespoke value courtesy of the [TextField] docs
child: Center(
child: Padding(
padding: EdgeInsets.all(10.0),
padding: const EdgeInsets.all(10.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
SizedBox(
const SizedBox(
height: 20,
),
Expanded(
child: ElevatedButton(
style: ElevatedButton.styleFrom(
minimumSize: Size(399, 20),
maximumSize: Size(400, 20),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.horizontal(left: Radius.circular(180), right: Radius.circular(180))),
minimumSize: const Size(399, 20),
maximumSize: const Size(400, 20),
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.horizontal(left: Radius.circular(180), right: Radius.circular(180))),
),
child: Text(
key: Key("addNewProfileActual"),
key: const Key("addNewProfileActual"),
AppLocalizations.of(context)!.addProfileTitle,
semanticsLabel: AppLocalizations.of(context)!.addProfileTitle,
style: TextStyle(fontWeight: FontWeight.bold),
style: const TextStyle(fontWeight: FontWeight.bold),
),
onPressed: () {
_pushAddProfile(context);
},
)),
SizedBox(
const SizedBox(
height: 20,
),
Expanded(
@ -245,12 +244,12 @@ class _ProfileMgrViewState extends State<ProfileMgrView> {
message: AppLocalizations.of(context)!.importProfileTooltip,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
minimumSize: Size(399, 20),
maximumSize: Size(400, 20),
minimumSize: const Size(399, 20),
backgroundColor: Provider.of<Settings>(context).theme.backgroundMainColor,
maximumSize: const Size(400, 20),
shape: RoundedRectangleBorder(
side: BorderSide(color: Provider.of<Settings>(context).theme.defaultButtonActiveColor, width: 2.0),
borderRadius: BorderRadius.horizontal(left: Radius.circular(180), right: Radius.circular(180))),
primary: Provider.of<Settings>(context).theme.backgroundMainColor,
borderRadius: const BorderRadius.horizontal(left: Radius.circular(180), right: Radius.circular(180))),
),
child: Text(AppLocalizations.of(context)!.importProfile,
semanticsLabel: AppLocalizations.of(context)!.importProfile,
@ -273,7 +272,7 @@ class _ProfileMgrViewState extends State<ProfileMgrView> {
}, () {}, () {});
},
))),
SizedBox(
const SizedBox(
height: 20,
),
],
@ -290,31 +289,33 @@ class _ProfileMgrViewState extends State<ProfileMgrView> {
return Padding(
padding: MediaQuery.of(context).viewInsets,
child: RepaintBoundary(
child: Container(
child: SizedBox(
height: Platform.isAndroid ? 250 : 200, // bespoke value courtesy of the [TextField] docs
child: Center(
child: Padding(
padding: EdgeInsets.all(10.0),
padding: const EdgeInsets.all(10.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text(AppLocalizations.of(context)!.enterProfilePassword),
SizedBox(
const SizedBox(
height: 20,
),
CwtchPasswordField(
key: Key("unlockPasswordProfileElement"),
key: const Key("unlockPasswordProfileElement"),
autofocus: true,
controller: ctrlrPassword,
action: unlock,
validator: (value) {},
validator: (value) {
return null;
},
),
SizedBox(
const SizedBox(
height: 20,
),
Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [
Spacer(),
const Spacer(),
Expanded(
child: ElevatedButton(
child: Text(AppLocalizations.of(context)!.unlock, semanticsLabel: AppLocalizations.of(context)!.unlock),
@ -322,7 +323,7 @@ class _ProfileMgrViewState extends State<ProfileMgrView> {
unlock(ctrlrPassword.value.text);
},
)),
Spacer()
const Spacer()
]),
],
))),
@ -343,7 +344,7 @@ class _ProfileMgrViewState extends State<ProfileMgrView> {
(ProfileInfoState profile) {
return ChangeNotifierProvider<ProfileInfoState>.value(
value: profile,
builder: (context, child) => ProfileRow(),
builder: (context, child) => const ProfileRow(),
);
},
);
@ -353,7 +354,7 @@ class _ProfileMgrViewState extends State<ProfileMgrView> {
value: ProfileInfoState(onion: ""),
builder: (context, child) {
return Container(
margin: EdgeInsets.only(top: 20),
margin: const EdgeInsets.only(top: 20),
width: MediaQuery.of(context).size.width,
child: Row(mainAxisAlignment: MainAxisAlignment.spaceAround, children: [
Tooltip(
@ -363,7 +364,7 @@ class _ProfileMgrViewState extends State<ProfileMgrView> {
style: TextButton.styleFrom(
minimumSize: Size(MediaQuery.of(context).size.width * 0.79, 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(
AppLocalizations.of(context)!.unlock,
@ -389,7 +390,7 @@ class _ProfileMgrViewState extends State<ProfileMgrView> {
Text(AppLocalizations.of(context)!.unlockProfileTip, textAlign: TextAlign.center),
Container(
width: MediaQuery.of(context).size.width,
margin: EdgeInsets.only(top: 20),
margin: const EdgeInsets.only(top: 20),
child: Row(mainAxisAlignment: MainAxisAlignment.spaceAround, children: [
Tooltip(
message: AppLocalizations.of(context)!.addProfileTitle,
@ -398,12 +399,12 @@ class _ProfileMgrViewState extends State<ProfileMgrView> {
style: TextButton.styleFrom(
minimumSize: Size(MediaQuery.of(context).size.width * 0.79, 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(
AppLocalizations.of(context)!.addProfileTitle,
semanticsLabel: AppLocalizations.of(context)!.addProfileTitle,
style: TextStyle(fontWeight: FontWeight.bold),
style: const TextStyle(fontWeight: FontWeight.bold),
),
onPressed: () {
_modalAddImportProfiles();

View File

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

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/models/contact.dart';
import 'package:cwtch/models/profile.dart';
import 'package:cwtch/models/profileservers.dart';
import 'package:cwtch/models/remoteserver.dart';
import 'package:cwtch/models/servers.dart';
import 'package:cwtch/widgets/buttontextfield.dart';
import 'package:cwtch/widgets/contactrow.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:cwtch/settings.dart';
import 'package:provider/provider.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import '../errorHandler.dart';
import '../main.dart';
import '../config.dart';
import '../models/appstate.dart';
import '../themes/opaque.dart';
/// Pane to add or edit a server
class RemoteServerView extends StatefulWidget {
const RemoteServerView();
const RemoteServerView({super.key});
@override
_RemoteServerViewState createState() => _RemoteServerViewState();
@ -57,25 +46,25 @@ class _RemoteServerViewState extends State<RemoteServerView> {
return Scaffold(
appBar: AppBar(title: Text(ctrlrDesc.text.isNotEmpty ? ctrlrDesc.text : serverInfoState.onion)),
body: Container(
margin: EdgeInsets.fromLTRB(30, 0, 30, 10),
padding: EdgeInsets.fromLTRB(20, 0, 20, 10),
margin: const EdgeInsets.fromLTRB(30, 0, 30, 10),
padding: const EdgeInsets.fromLTRB(20, 0, 20, 10),
child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [
SizedBox(
const SizedBox(
height: 20,
),
CwtchLabel(label: AppLocalizations.of(context)!.serverAddress),
SizedBox(
const SizedBox(
height: 20,
),
SelectableText(serverInfoState.onion),
// Description
SizedBox(
const SizedBox(
height: 20,
),
CwtchLabel(label: AppLocalizations.of(context)!.serverDescriptionLabel),
Text(AppLocalizations.of(context)!.serverDescriptionDescription),
SizedBox(
const SizedBox(
height: 20,
),
CwtchButtonTextField(
@ -83,14 +72,14 @@ class _RemoteServerViewState extends State<RemoteServerView> {
readonly: false,
tooltip: AppLocalizations.of(context)!.saveBtn,
labelText: AppLocalizations.of(context)!.fieldDescriptionLabel,
icon: Icon(Icons.save),
icon: const Icon(Icons.save),
onPressed: () {
Provider.of<FlwtchState>(context, listen: false).cwtch.SetConversationAttribute(profile.onion, serverInfoState.identifier, "server.description", ctrlrDesc.text);
serverInfoState.updateDescription(ctrlrDesc.text);
},
),
SizedBox(
const SizedBox(
height: 20,
),
Row(crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.end, children: [
@ -102,7 +91,7 @@ class _RemoteServerViewState extends State<RemoteServerView> {
: () {
showAlertDialog(context);
},
icon: Icon(CwtchIcons.leave_group),
icon: const Icon(CwtchIcons.leave_group),
label: Text(
AppLocalizations.of(context)!.deleteBtn,
style: settings.scaleFonts(defaultTextButtonStyle),
@ -110,7 +99,7 @@ class _RemoteServerViewState extends State<RemoteServerView> {
))
]),
Padding(
padding: EdgeInsets.all(8),
padding: const EdgeInsets.all(8),
child: Text(AppLocalizations.of(context)!.groupsOnThisServerLabel),
),
Expanded(child: _buildGroupsList(serverInfoState)),
@ -136,7 +125,7 @@ class _RemoteServerViewState extends State<RemoteServerView> {
var size = MediaQuery.of(context).size;
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;
return GridView.count(crossAxisCount: cols, childAspectRatio: (itemWidth / itemHeight), children: divided);
@ -174,7 +163,7 @@ showAlertDialog(BuildContext context) {
},
);
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),
onPressed: () {
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/serverrow.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:flutter_gen/gen_l10n/app_localizations.dart';
@ -14,6 +12,8 @@ import '../settings.dart';
///
class ServersView extends StatefulWidget {
const ServersView({super.key});
@override
_ServersView createState() => _ServersView();
}
@ -50,7 +50,7 @@ class _ServersView extends State<ServersView> {
(ServerInfoState server) {
return ChangeNotifierProvider<ServerInfoState>.value(
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> actions = new List<Widget>.empty(growable: true);
List<Widget> actions = List<Widget>.empty(growable: true);
// Unlock Profiles
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,
tooltip: AppLocalizations.of(context)!.tooltipUnlockProfiles,
onPressed: _modalUnlockServers,
@ -95,30 +95,32 @@ class _ServersView extends State<ServersView> {
return Padding(
padding: MediaQuery.of(context).viewInsets,
child: RepaintBoundary(
child: Container(
child: SizedBox(
height: 200, // bespoke value courtesy of the [TextField] docs
child: Center(
child: Padding(
padding: EdgeInsets.all(10.0),
padding: const EdgeInsets.all(10.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text(AppLocalizations.of(context)!.enterServerPassword),
SizedBox(
const SizedBox(
height: 20,
),
CwtchPasswordField(
autofocus: true,
controller: ctrlrPassword,
action: unlock,
validator: (value) {},
validator: (value) {
return null;
},
),
SizedBox(
const SizedBox(
height: 20,
),
Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [
Spacer(),
const Spacer(),
Expanded(
child: ElevatedButton(
child: Text(AppLocalizations.of(context)!.unlock, semanticsLabel: AppLocalizations.of(context)!.unlock),
@ -126,7 +128,7 @@ class _ServersView extends State<ServersView> {
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),
)
],
child: AddEditServerView(),
child: const AddEditServerView(),
);
},
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/themes/opaque.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
@ -9,6 +8,8 @@ import '../main.dart';
import '../settings.dart';
class SplashView extends StatefulWidget {
const SplashView({super.key});
@override
_SplashViewState createState() => _SplashViewState();
}
@ -25,17 +26,17 @@ class _SplashViewState extends State<SplashView> {
return Consumer<AppState>(
builder: (context, appState, child) => Scaffold(
key: Key("SplashView"),
key: const Key("SplashView"),
body: Center(
child: Column(mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [
Image(
const Image(
image: AssetImage("assets/core/knott-white.png"),
filterQuality: FilterQuality.medium,
isAntiAlias: true,
width: 200,
height: 200,
),
Image(
const Image(
image: AssetImage("assets/cwtch_title.png"),
filterQuality: FilterQuality.medium,
isAntiAlias: true,
@ -44,7 +45,7 @@ class _SplashViewState extends State<SplashView> {
padding: const EdgeInsets.all(20.0),
child: Column(children: [
Padding(
padding: EdgeInsets.all(6.0),
padding: const EdgeInsets.all(6.0),
child: Text(
appState.appError != ""
? appState.appError
@ -61,7 +62,7 @@ class _SplashViewState extends State<SplashView> {
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
/// of ways (restart, enable bridges, enable pluggable transports etc)
class TorStatusView extends StatefulWidget {
const TorStatusView({super.key});
@override
_TorStatusView createState() => _TorStatusView();
}
@ -63,7 +65,7 @@ class _TorStatusView extends State<TorStatusView> {
),
child: Column(children: [
ListTile(
leading: TorIcon(),
leading: const TorIcon(),
title: Text(AppLocalizations.of(context)!.torStatus),
subtitle: Text(torStatus.progress == 100 ? AppLocalizations.of(context)!.networkStatusOnline : torStatus.status),
trailing: ElevatedButton(
@ -109,7 +111,7 @@ class _TorStatusView extends State<TorStatusView> {
title: Text(AppLocalizations.of(context)!.torSettingsCustomSocksPort),
subtitle: Text(AppLocalizations.of(context)!.torSettingsCustomSocksPortDescription),
leading: Icon(CwtchIcons.swap_horiz_24px, color: settings.current().mainTextColor),
trailing: Container(
trailing: SizedBox(
width: MediaQuery.of(context).size.width / 4,
child: CwtchTextField(
number: true,
@ -140,7 +142,7 @@ class _TorStatusView extends State<TorStatusView> {
title: Text(AppLocalizations.of(context)!.torSettingsCustomControlPort),
subtitle: Text(AppLocalizations.of(context)!.torSettingsCustomControlPortDescription),
leading: Icon(CwtchIcons.swap_horiz_24px, color: settings.current().mainTextColor),
trailing: Container(
trailing: SizedBox(
width: MediaQuery.of(context).size.width / 4,
child: CwtchTextField(
number: true,
@ -182,7 +184,7 @@ class _TorStatusView extends State<TorStatusView> {
Visibility(
visible: settings.useCustomTorConfig,
child: Padding(
padding: EdgeInsets.all(5),
padding: const EdgeInsets.all(5),
child: CwtchTextField(
controller: torConfigController,
multiLine: true,

View File

@ -15,7 +15,8 @@ bool noFilter(ContactInfoState peer) {
// Displays nicknames to UI but uses handles as values
// Pass an onChanged handler to access value
class DropdownContacts extends StatefulWidget {
DropdownContacts({
const DropdownContacts({
super.key,
required this.onChanged,
this.filter = noFilter,
});
@ -33,7 +34,7 @@ class _DropdownContactsState extends State<DropdownContacts> {
Widget build(BuildContext context) {
return DropdownButton(
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) {
return DropdownMenuItem<String>(
value: contact.onion,
@ -44,7 +45,7 @@ class _DropdownContactsState extends State<DropdownContacts> {
}).toList(),
onChanged: (String? newVal) {
setState(() {
this.selected = newVal;
selected = 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.
class CwtchButtonTextField extends StatefulWidget {
CwtchButtonTextField({
super.key,
required this.controller,
required this.onPressed,
required this.icon,
@ -48,7 +49,7 @@ class _CwtchButtonTextFieldState extends State<CwtchButtonTextField> {
return Consumer<Settings>(builder: (context, theme, child) {
return Container(
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 ¯\_(ツ)_/¯
child: Theme(
data: Theme.of(context).copyWith(
@ -65,7 +66,7 @@ class _CwtchButtonTextFieldState extends State<CwtchButtonTextField> {
enableIMEPersonalizedLearning: false,
onChanged: widget.onChanged,
maxLines: 1,
style: widget.textStyle == null ? TextStyle(overflow: TextOverflow.clip) : widget.textStyle,
style: widget.textStyle ?? const TextStyle(overflow: TextOverflow.clip),
decoration: InputDecoration(
labelText: widget.labelText,
labelStyle: TextStyle(color: theme.current().mainTextColor, backgroundColor: theme.current().textfieldBackgroundColor),
@ -73,7 +74,7 @@ class _CwtchButtonTextFieldState extends State<CwtchButtonTextField> {
onPressed: widget.onPressed,
icon: widget.icon,
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,
enableFeedback: true,
color: theme.current().mainTextColor,
@ -91,7 +92,7 @@ class _CwtchButtonTextFieldState extends State<CwtchButtonTextField> {
color: theme.current().textfieldErrorColor,
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))),
)));
});

View File

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

View File

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

View File

@ -11,7 +11,6 @@ import 'package:cwtch/themes/opaque.dart';
import 'package:cwtch/widgets/malformedbubble.dart';
import 'package:cwtch/widgets/messageBubbleWidgetHelpers.dart';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../main.dart';
@ -33,13 +32,13 @@ class FileBubble extends StatefulWidget {
final bool isAuto;
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
FileBubbleState createState() => FileBubbleState();
String fileKey() {
return this.rootHash + "." + this.nonce;
return "$rootHash.$nonce";
}
}
@ -64,7 +63,7 @@ class FileBubbleState extends State<FileBubble> {
alignment: Alignment.center,
isAntiAlias: false,
errorBuilder: (context, error, stackTrace) {
return MalformedBubble();
return const MalformedBubble();
},
));
}
@ -101,7 +100,7 @@ class FileBubbleState extends State<FileBubble> {
if (isImagePreview) {
if (myFile == null || myFile?.path != path) {
setState(() {
myFile = new File(path!);
myFile = File(path!);
// reset
if (myFile?.existsSync() == false) {
myFile = null;
@ -157,18 +156,21 @@ class FileBubbleState extends State<FileBubble> {
var wdgSender = Visibility(
visible: widget.interactive,
child: Container(
height: 14 * Provider.of<Settings>(context).fontScaling, clipBehavior: Clip.hardEdge, decoration: BoxDecoration(), child: 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 wdgMessage = !showFileSharing
? Text(AppLocalizations.of(context)!.messageEnableFileSharing, style: Provider.of<Settings>(context).scaleFonts(defaultTextStyle))
: fromMe
? 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())));
Widget wdgDecorations;
if (!showFileSharing) {
wdgDecorations = Text('\u202F');
wdgDecorations = const Text('\u202F');
} else if ((fromMe || downloadComplete) && path != null) {
// in this case, whatever marked download.complete would have also set the path
if (myFile != null && Provider.of<Settings>(context).shouldPreview(path)) {
@ -178,21 +180,21 @@ class FileBubbleState extends State<FileBubble> {
child: MouseRegion(
cursor: SystemMouseCursors.click,
child: GestureDetector(
child: Padding(padding: EdgeInsets.all(1.0), child: getPreview(context)),
child: Padding(padding: const EdgeInsets.all(1.0), child: getPreview(context)),
onTap: () {
pop(context, myFile!, widget.nameSuggestion);
},
)));
} else if (fromMe) {
wdgDecorations = Text('\u202F');
wdgDecorations = const Text('\u202F');
} else {
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) {
if (!downloadGotManifest) {
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 {
wdgDecorations = Visibility(
visible: widget.interactive,
@ -205,14 +207,14 @@ class FileBubbleState extends State<FileBubble> {
// in this case, the download was done in a previous application launch,
// so we probably have to request an info lookup
if (!downloadInterrupted) {
wdgDecorations = Text(AppLocalizations.of(context)!.fileCheckingStatus + '...' + '\u202F', 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...
} else {
var path = Provider.of<ProfileInfoState>(context).downloadFinalPath(widget.fileKey()) ?? "";
wdgDecorations = Visibility(
visible: widget.interactive,
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
Text(AppLocalizations.of(context)!.fileInterrupted + ': ' + path + '\u202F', 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)))
]));
}
@ -227,9 +229,9 @@ class FileBubbleState extends State<FileBubble> {
widthFactor: 1,
child: Wrap(children: [
Padding(
padding: EdgeInsets.all(5),
padding: const EdgeInsets.all(5),
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 {
wdgDecorations = Container();
@ -260,7 +262,7 @@ class FileBubbleState extends State<FileBubble> {
),
),
child: Padding(
padding: EdgeInsets.all(9.0),
padding: const EdgeInsets.all(9.0),
child: Column(
crossAxisAlignment: fromMe ? CrossAxisAlignment.end : CrossAxisAlignment.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");
ContactInfoState? contact = Provider.of<ProfileInfoState>(context, listen: false).contactList.findContact(Provider.of<MessageMetadata>(context, listen: false).senderHandle);
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);
}
@ -304,8 +306,8 @@ class FileBubbleState extends State<FileBubble> {
);
if (selectedFileName != null) {
file = File(selectedFileName);
EnvironmentConfig.debugLog("saving to " + file.path);
var manifestPath = file.path + ".manifest";
EnvironmentConfig.debugLog("saving to ${file.path}");
var manifestPath = "${file.path}.manifest";
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");
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);
return ListTile(
visualDensity: VisualDensity.compact,
contentPadding: EdgeInsets.all(1.0),
contentPadding: const EdgeInsets.all(1.0),
title: SelectableText(
fileName + '\u202F',
'$fileName\u202F',
style:
settings.scaleFonts(defaultMessageTextStyle.copyWith(overflow: TextOverflow.ellipsis, fontWeight: FontWeight.bold, color: Provider.of<Settings>(context).theme.messageFromMeTextColor)),
textAlign: TextAlign.left,
@ -341,7 +343,7 @@ class FileBubbleState extends State<FileBubble> {
maxLines: 2,
),
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)),
textAlign: TextAlign.left,
maxLines: 4,
@ -355,9 +357,9 @@ class FileBubbleState extends State<FileBubble> {
var settings = Provider.of<Settings>(context);
return ListTile(
visualDensity: VisualDensity.compact,
contentPadding: EdgeInsets.all(1.0),
contentPadding: const EdgeInsets.all(1.0),
title: SelectableText(
fileName + '\u202F',
'$fileName\u202F',
style:
settings.scaleFonts(defaultMessageTextStyle.copyWith(overflow: TextOverflow.ellipsis, fontWeight: FontWeight.bold, color: Provider.of<Settings>(context).theme.messageFromOtherTextColor)),
textAlign: TextAlign.left,
@ -365,7 +367,7 @@ class FileBubbleState extends State<FileBubble> {
maxLines: 2,
),
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)),
textAlign: TextAlign.left,
maxLines: 4,
@ -376,7 +378,7 @@ class FileBubbleState extends State<FileBubble> {
trailing: speed == "0 B/s"
? null
: SelectableText(
speed + '\u202F',
'$speed\u202F',
style: settings.scaleFonts(defaultSmallTextStyle.copyWith(color: Provider.of<Settings>(context).theme.messageFromOtherTextColor)),
textAlign: TextAlign.left,
maxLines: 1,
@ -393,20 +395,20 @@ class FileBubbleState extends State<FileBubble> {
child: SingleChildScrollView(
controller: ScrollController(),
child: Container(
padding: EdgeInsets.all(10),
padding: const EdgeInsets.all(10),
child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.center, children: [
ListTile(
leading: Icon(CwtchIcons.attached_file_3),
leading: const Icon(CwtchIcons.attached_file_3),
title: Text(meta),
trailing: IconButton(
icon: Icon(Icons.close),
icon: const Icon(Icons.close),
color: Provider.of<Settings>(bcontext, listen: false).theme.mainTextColor,
iconSize: 32,
onPressed: () {
Navigator.pop(bcontext, true);
})),
Padding(
padding: EdgeInsets.all(10),
padding: const EdgeInsets.all(10),
child: Image.file(
myFile,
cacheWidth: (MediaQuery.of(bcontext).size.width * 0.6).floor(),
@ -419,9 +421,9 @@ class FileBubbleState extends State<FileBubble> {
visible: Platform.isAndroid,
maintainSize: false,
child: Padding(
padding: EdgeInsets.all(10),
padding: const EdgeInsets.all(10),
child: ElevatedButton.icon(
icon: Icon(Icons.arrow_downward),
icon: const Icon(Icons.arrow_downward),
onPressed: androidExport,
label: Text(
AppLocalizations.of(bcontext)!.saveBtn,

View File

@ -6,7 +6,6 @@ import 'package:provider/provider.dart';
import '../settings.dart';
import 'buttontextfield.dart';
import 'package:path/path.dart' as path;
import 'cwtchlabel.dart';
class CwtchFolderPicker extends StatefulWidget {
final String label;
@ -17,8 +16,7 @@ class CwtchFolderPicker extends StatefulWidget {
final Key? testKey;
final TextStyle? textStyle;
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})
: super(key: key);
const CwtchFolderPicker({super.key, this.testKey, this.textStyle, this.label = "", this.tooltip = "", this.initialValue = "", this.onSave, this.description = "", this.icon = Icons.file_download});
@override
_CwtchFolderPickerState createState() => _CwtchFolderPickerState();
@ -39,7 +37,7 @@ class _CwtchFolderPickerState extends State<CwtchFolderPicker> {
leading: Icon(widget.icon, color: Provider.of<Settings>(context).theme.messageFromMeTextColor),
title: Text(widget.label),
subtitle: Text(widget.description),
trailing: Container(
trailing: SizedBox(
width: MediaQuery.of(context).size.width / 4,
child: CwtchButtonTextField(
testKey: widget.testKey,
@ -66,7 +64,7 @@ class _CwtchFolderPickerState extends State<CwtchFolderPicker> {
}
},
onChanged: widget.onSave,
icon: Icon(Icons.folder),
icon: const Icon(Icons.folder),
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/models/contact.dart';
import 'package:cwtch/models/message.dart';
@ -10,7 +7,6 @@ import 'package:cwtch/widgets/malformedbubble.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../main.dart';
import 'package:intl/intl.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import '../models/redaction.dart';
@ -26,7 +22,7 @@ class InvitationBubble extends StatefulWidget {
final String inviteNick;
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
InvitationBubbleState createState() => InvitationBubbleState();
@ -62,7 +58,7 @@ class InvitationBubbleState extends State<InvitationBubble> {
// some kind of malfeasance.
var selfInvite = widget.inviteNick == Provider.of<ProfileInfoState>(context).onion;
if (selfInvite) {
return MalformedBubble();
return const MalformedBubble();
}
var wdgMessage = isGroup && !showGroupInvite
@ -74,25 +70,25 @@ class InvitationBubbleState extends State<InvitationBubble> {
Widget wdgDecorations;
if (isGroup && !showGroupInvite) {
wdgDecorations = Text('\u202F');
wdgDecorations = const Text('\u202F');
} else if (fromMe) {
wdgDecorations = MessageBubbleDecoration(ackd: Provider.of<MessageMetadata>(context).ackd, errored: Provider.of<MessageMetadata>(context).error, fromMe: fromMe, messageDate: messageDate);
} else if (isAccepted) {
wdgDecorations = Text(AppLocalizations.of(context)!.accepted + '\u202F', style: Provider.of<Settings>(context).scaleFonts(defaultTextStyle));
} else if (this.rejected) {
wdgDecorations = Text(AppLocalizations.of(context)!.rejected + '\u202F', style: Provider.of<Settings>(context).scaleFonts(defaultTextStyle));
wdgDecorations = Text('${AppLocalizations.of(context)!.accepted}\u202F', style: Provider.of<Settings>(context).scaleFonts(defaultTextStyle));
} else if (rejected) {
wdgDecorations = Text('${AppLocalizations.of(context)!.rejected}\u202F', style: Provider.of<Settings>(context).scaleFonts(defaultTextStyle));
} else {
wdgDecorations = Center(
widthFactor: 1,
child: Wrap(children: [
Padding(
padding: EdgeInsets.all(5),
padding: const EdgeInsets.all(5),
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: EdgeInsets.all(5),
padding: const EdgeInsets.all(5),
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(
widthFactor: 1.0,
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: [
Center(
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(
widthFactor: 1.0,
child: Column(
@ -167,14 +163,14 @@ class InvitationBubbleState extends State<InvitationBubble> {
return Wrap(children: [
SelectableText(
chrome + '\u202F',
'$chrome\u202F',
style: settings.scaleFonts(defaultMessageTextStyle.copyWith(color: Provider.of<Settings>(context).theme.messageFromMeTextColor)),
textAlign: TextAlign.left,
maxLines: 2,
textWidthBasis: TextWidthBasis.longestLine,
),
SelectableText(
targetName + '\u202F',
'$targetName\u202F',
style: settings.scaleFonts(defaultMessageTextStyle.copyWith(color: Provider.of<Settings>(context).theme.messageFromMeTextColor)),
textAlign: TextAlign.left,
maxLines: 2,
@ -189,14 +185,14 @@ class InvitationBubbleState extends State<InvitationBubble> {
return Wrap(children: [
SelectableText(
chrome + '\u202F',
'$chrome\u202F',
style: settings.scaleFonts(defaultMessageTextStyle.copyWith(color: Provider.of<Settings>(context).theme.messageFromOtherTextColor)),
textAlign: TextAlign.left,
textWidthBasis: TextWidthBasis.longestLine,
maxLines: 2,
),
SelectableText(
targetName + '\u202F',
'$targetName\u202F',
style: settings.scaleFonts(defaultMessageTextStyle.copyWith(color: Provider.of<Settings>(context).theme.messageFromOtherTextColor)),
textAlign: TextAlign.left,
maxLines: 2,

View File

@ -2,10 +2,12 @@ import 'package:cwtch/cwtch_icons_icons.dart';
import 'package:flutter/material.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
class MalformedBubble extends StatefulWidget {
const MalformedBubble({super.key});
@override
MalformedBubbleState createState() => MalformedBubbleState();
}
@ -17,7 +19,7 @@ class MalformedBubbleState extends State<MalformedBubble> {
decoration: BoxDecoration(
color: malformedColor,
border: Border.all(color: malformedColor, width: 1),
borderRadius: BorderRadius.only(
borderRadius: const BorderRadius.only(
topLeft: Radius.zero,
topRight: Radius.zero,
bottomLeft: Radius.zero,
@ -29,9 +31,9 @@ class MalformedBubbleState extends State<MalformedBubble> {
child: Center(
widthFactor: 1.0,
child: Padding(
padding: EdgeInsets.all(9.0),
padding: const EdgeInsets.all(9.0),
child: Row(mainAxisSize: MainAxisSize.min, children: [
Center(
const Center(
widthFactor: 1,
child: Padding(
padding: EdgeInsets.all(4),

View File

@ -9,7 +9,7 @@ Widget compileSenderWidget(BuildContext context, BoxConstraints? constraints, bo
return Container(
height: 14 * Provider.of<Settings>(context).fontScaling,
clipBehavior: Clip.hardEdge,
decoration: BoxDecoration(),
decoration: const BoxDecoration(),
child: SelectableText(senderDisplayStr,
maxLines: 1,
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) {
return SelectableLinkify(
text: content + '\u202F',
text: '$content\u202F',
// TODO: onOpen breaks the "selectable" functionality. Maybe something to do with gesture handler?
options: LinkifyOptions(messageFormatting: formatMessages, parseLinks: showClickableLinks, looseUrl: true, defaultToHttps: true),
linkifiers: [UrlLinkifier()],
linkifiers: const [UrlLinkifier()],
onOpen: showClickableLinks
? (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/message.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/widgets/malformedbubble.dart';
import 'package:flutter/material.dart';
@ -18,14 +13,14 @@ import 'messagebubbledecorations.dart';
class MessageBubble extends StatefulWidget {
final String content;
MessageBubble(this.content);
const MessageBubble(this.content, {super.key});
@override
MessageBubbleState createState() => MessageBubbleState();
}
class MessageBubbleState extends State<MessageBubble> {
FocusNode _focus = FocusNode();
final FocusNode _focus = FocusNode();
@override
Widget build(BuildContext context) {
@ -67,7 +62,7 @@ class MessageBubbleState extends State<MessageBubble> {
),
),
child: Padding(
padding: EdgeInsets.all(9.0),
padding: const EdgeInsets.all(9.0),
child: Theme(
data: Theme.of(context).copyWith(
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:provider/provider.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.)
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 bool fromMe;
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),
textAlign: widget.fromMe ? TextAlign.right : TextAlign.left),
!widget.fromMe
? SizedBox(width: 1, height: 1)
? const SizedBox(width: 1, height: 1)
: Padding(
padding: EdgeInsets.all(1.0),
padding: const EdgeInsets.all(1.0),
child: widget.ackd == true
? Tooltip(
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/widgets/messageloadingbubble.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:provider/provider.dart';
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
@ -14,7 +13,7 @@ import '../settings.dart';
class MessageList extends StatefulWidget {
ItemPositionsListener scrollListener;
MessageList(this.scrollListener);
MessageList(this.scrollListener, {super.key});
@override
_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
// 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
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<ContactInfoState>(context, listen: false).lastRetryTime == Provider.of<ContactInfoState>(context, listen: false).loaded));
var reconnectButton = Padding(
padding: EdgeInsets.all(2),
padding: const EdgeInsets.all(2),
child: canReconnect
? Tooltip(
message: AppLocalizations.of(context)!.retryConnectionTooltip,
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),
onPressed: () {
if (Provider.of<ContactInfoState>(context, listen: false).isGroup) {
@ -84,7 +83,7 @@ class _MessageListState extends State<MessageList> {
Visibility(
visible: showMessageWarning,
child: Container(
padding: EdgeInsets.all(5.0),
padding: const EdgeInsets.all(5.0),
color: Provider.of<Settings>(context).theme.defaultButtonActiveColor,
child: DefaultTextStyle(
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)
:
// We are not allowed to put null here, so put an empty text widget
Text("")),
const Text("")),
))),
Expanded(
child: Container(
@ -120,7 +119,7 @@ class _MessageListState extends State<MessageList> {
: DecorationImage(
fit: BoxFit.scaleDown,
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))),
// Don't load messages for syncing server...
child: loadMessages
@ -146,7 +145,7 @@ class _MessageListState extends State<MessageList> {
var key = Provider.of<ContactInfoState>(itemBuilderContext, listen: false).getMessageKey(contactHandle, messageIndex);
return message.getWidget(fbcontext, key, messageIndex);
} else {
return MessageLoadingBubble();
return const MessageLoadingBubble();
}
},
);

View File

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

View File

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

View File

@ -1,9 +1,7 @@
import 'package:cwtch/models/appstate.dart';
import 'package:cwtch/models/contactlist.dart';
import 'package:cwtch/models/profile.dart';
import 'package:cwtch/models/profilelist.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:cwtch/views/addeditprofileview.dart';
import 'package:cwtch/views/contactsview.dart';
import 'package:cwtch/views/doublecolview.dart';
@ -11,11 +9,12 @@ import 'package:cwtch/widgets/profileimage.dart';
import 'package:provider/provider.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import '../errorHandler.dart';
import '../main.dart';
import '../settings.dart';
class ProfileRow extends StatefulWidget {
const ProfileRow({super.key});
@override
_ProfileRowState createState() => _ProfileRowState();
}
@ -26,7 +25,7 @@ class _ProfileRowState extends State<ProfileRow> {
var profile = Provider.of<ProfileInfoState>(context);
return Card(
clipBehavior: Clip.antiAlias,
margin: EdgeInsets.all(0.0),
margin: const EdgeInsets.all(0.0),
child: InkWell(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
@ -49,8 +48,8 @@ class _ProfileRowState extends State<ProfileRow> {
Container(
height: 18.0 * Provider.of<Settings>(context).fontScaling + 10.0,
clipBehavior: Clip.hardEdge,
decoration: BoxDecoration(),
padding: EdgeInsets.all(5.0),
decoration: const BoxDecoration(),
padding: const EdgeInsets.all(5.0),
child: Text(
profile.nickname,
semanticsLabel: profile.nickname,
@ -75,7 +74,7 @@ class _ProfileRowState extends State<ProfileRow> {
IconButton(
enableFeedback: true,
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),
onPressed: () {
_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) {
Navigator.of(context).push(
PageRouteBuilder(
settings: RouteSettings(name: "conversations"),
settings: const RouteSettings(name: "conversations"),
pageBuilder: (c, a1, a2) {
return OrientationBuilder(builder: (orientationBuilderContext, orientation) {
return MultiProvider(
@ -106,17 +105,17 @@ class _ProfileRowState extends State<ProfileRow> {
builder: (innercontext, widget) {
var appState = Provider.of<AppState>(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),
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(
PageRouteBuilder(
pageBuilder: (bcontext, a1, a2) {
@ -127,11 +126,11 @@ class _ProfileRowState extends State<ProfileRow> {
value: profile,
),
],
builder: (context, widget) => AddEditProfileView(),
builder: (context, widget) => const AddEditProfileView(),
);
},
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 String body;
QuotedMessageBubble(this.body, this.quotedMessage);
const QuotedMessageBubble(this.body, this.quotedMessage, {super.key});
@override
QuotedMessageBubbleState createState() => QuotedMessageBubbleState();
}
class QuotedMessageBubbleState extends State<QuotedMessageBubble> {
FocusNode _focus = FocusNode();
final FocusNode _focus = FocusNode();
@override
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);
if (messageInfo != null) {
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: Duration(milliseconds: 100));
}
Provider.of<ContactInfoState>(context, listen: false).messageScrollController.scrollTo(index: index, duration: const Duration(milliseconds: 100));
}
},
child: Container(
margin: EdgeInsets.all(5),
padding: EdgeInsets.all(5),
margin: const EdgeInsets.all(5),
padding: const EdgeInsets.all(5),
clipBehavior: Clip.antiAliasWithSaveLayer,
decoration: BoxDecoration(
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),
Flexible(
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))),
]))
])),
),
);
} catch (e) {
return MalformedBubble();
return const MalformedBubble();
}
} else {
// 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(
padding: EdgeInsets.all(9.0),
padding: const EdgeInsets.all(9.0),
child: Theme(
data: Theme.of(context).copyWith(
textSelectionTheme: TextSelectionThemeData(

View File

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

View File

@ -11,6 +11,8 @@ import '../errorHandler.dart';
import '../settings.dart';
class ServerRow extends StatefulWidget {
const ServerRow({super.key});
@override
_ServerRowState createState() => _ServerRowState();
}
@ -21,7 +23,7 @@ class _ServerRowState extends State<ServerRow> {
var server = Provider.of<ServerInfoState>(context);
return Card(
clipBehavior: Clip.antiAlias,
margin: EdgeInsets.all(0.0),
margin: const EdgeInsets.all(0.0),
child: InkWell(
child: Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
Padding(
@ -69,7 +71,7 @@ class _ServerRowState extends State<ServerRow> {
tooltip: AppLocalizations.of(context)!.copyServerKeys,
icon: Icon(CwtchIcons.address_copy, color: Provider.of<Settings>(context).current().mainTextColor),
onPressed: () {
Clipboard.setData(new ClipboardData(text: server.serverBundle));
Clipboard.setData(ClipboardData(text: server.serverBundle));
final snackBar = SnackBar(content: Text(AppLocalizations.of(context)!.copiedToClipboardNotification));
ScaffoldMessenger.of(context).showSnackBar(snackBar);
},
@ -94,11 +96,11 @@ class _ServerRowState extends State<ServerRow> {
void _pushEditServer(ServerInfoState server) {
Provider.of<ErrorHandler>(context, listen: false).reset();
Navigator.of(context).push(MaterialPageRoute<void>(
settings: RouteSettings(name: "serveraddedit"),
settings: const RouteSettings(name: "serveraddedit"),
builder: (BuildContext context) {
return MultiProvider(
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/widgets/malformedbubble.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../models/redaction.dart';
import '../settings.dart';
@ -16,7 +15,7 @@ class StaticMessageBubble extends StatefulWidget {
final MessageMetadata metadata;
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
StaticMessageBubbleState createState() => StaticMessageBubbleState();
@ -61,7 +60,7 @@ class StaticMessageBubbleState extends State<StaticMessageBubble> {
),
),
child: Padding(
padding: EdgeInsets.all(9.0),
padding: const EdgeInsets.all(9.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.start,

View File

@ -1,5 +1,4 @@
import 'package:cwtch/themes/opaque.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart';
@ -10,8 +9,17 @@ doNothing(String x) {}
// Provides a styled Text Field for use in Form Widgets.
// Callers must provide a text controller, label helper text and a validator.
class CwtchTextField extends StatefulWidget {
CwtchTextField(
{required this.controller, this.hintText = "", this.validator, this.autofocus = false, this.onChanged = doNothing, this.number = false, this.multiLine = false, this.key, this.testKey});
const CwtchTextField(
{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 String hintText;
final FormFieldValidator? validator;
@ -19,7 +27,8 @@ class CwtchTextField extends StatefulWidget {
final bool autofocus;
final bool multiLine;
final bool number;
final Key? key;
@override
final Key? ikey;
final Key? testKey;
@override
@ -46,7 +55,7 @@ class _CwtchTextFieldState extends State<CwtchTextField> {
return Consumer<Settings>(builder: (context, theme, child) {
return Container(
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 ¯\_(ツ)_/¯
child: Theme(
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)),
errorStyle: TextStyle(color: theme.current().textfieldErrorColor, fontWeight: FontWeight.bold, overflow: TextOverflow.visible),
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))),
)));
});

View File

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