Merge branch 'gherkin' of git.openprivacy.ca:cwtch.im/cwtch-ui into gherkin

This commit is contained in:
erinn 2022-02-02 14:40:49 -08:00
commit 4d0bb92495
32 changed files with 243 additions and 85 deletions

View File

@ -1,3 +1,4 @@
@env:persist
Feature: Basic Profile Management
Scenario: Error on Creating a Profile without a Display Name
Given I wait until the widget with type 'ProfileMgrView' is present
@ -18,14 +19,18 @@ Feature: Basic Profile Management
Then I tap the "passwordCheckBox" widget
And I expect the text 'New Password' to be absent
And I take a screenshot
Then I fill the "displayNameFormElement" field with "Alice (<h1>hello</h1>)"
Then I fill the "displayNameFormElement" field with "Alice (Unencrypted)"
Then I tap the "button" widget with label "Add new profile"
And I expect a "ProfileRow" widget with text "Alice (<h1>hello</h1>)"
And I expect a "ProfileRow" widget with text "Alice (Unencrypted)"
And I take a screenshot
Then I tap the "ProfileRow" widget with label "Alice (<h1>hello</h1>)"
And I expect the text 'Alice (<h1>hello</h1>) » Conversations' to be present
Then I tap the "ProfileRow" widget with label "Alice (Unencrypted)"
And I expect the text "Alice (Unencrypted) » Conversations" to be present
And I take a screenshot
Scenario: Load Unencrypted Profile
Given I wait until the widget with type 'ProfileMgrView' is present
Then I expect a "ProfileRow" widget with text "Alice (Unencrypted)"
Scenario: Create Encrypted Profile
Given I wait until the widget with type 'ProfileMgrView' is present
And I tap the button with tooltip "Add new profile"
@ -41,4 +46,45 @@ Feature: Basic Profile Management
And I take a screenshot
Then I tap the "ProfileRow" widget with label "Alice (Encrypted)"
And I expect the text 'Alice (Encrypted) » Conversations' to be present
And I take a screenshot
And I take a screenshot
Scenario: Load an Encrypted Profile by Unlocking it with a Password
Given I wait until the widget with type 'ProfileMgrView' is present
Then I expect the text 'Enter a password to view your profiles' to be absent
And I tap the button with tooltip "Unlock encrypted profiles by entering their password."
Then I expect the text 'Enter a password to view your profiles' to be present
When I fill the "unlockPasswordProfileElement" field with "password1"
And I tap the "button" widget with label "Unlock"
Then I expect a "ProfileRow" widget with text "Alice (Encrypted)"
Scenario: Load an Encrypted Profile by Unlocking it with a Password and Change the Name
Given I wait until the widget with type 'ProfileMgrView' is present
Then I expect the text 'Enter a password to view your profiles' to be absent
And I tap the button with tooltip "Unlock encrypted profiles by entering their password."
Then I expect the text 'Enter a password to view your profiles' to be present
When I fill the "unlockPasswordProfileElement" field with "password1"
And I tap the "button" widget with label "Unlock"
Then I expect a "ProfileRow" widget with text "Alice (Encrypted)"
When I tap the "IconButton" widget with tooltip "Edit Profile Alice (Encrypted)"
Then I expect the text 'Display Name' to be present
Then I fill the "displayNameFormElement" field with "Carol (Encrypted)"
And I tap the "button" widget with label "Save Profile"
And I wait until the widget with type 'ProfileMgrView' is present
Then I expect a "ProfileRow" widget with text "Carol (Encrypted)"
Scenario: Delete an Encrypted Profile
Given I wait until the widget with type 'ProfileMgrView' is present
Then I expect the text 'Enter a password to view your profiles' to be absent
And I tap the button with tooltip "Unlock encrypted profiles by entering their password."
Then I expect the text 'Enter a password to view your profiles' to be present
When I fill the "unlockPasswordProfileElement" field with "password1"
And I tap the "button" widget with label "Unlock"
Then I expect a "ProfileRow" widget with text "Carol (Encrypted)"
And I take a screenshot
When I tap the "IconButton" widget with tooltip "Edit Profile Carol (Encrypted)"
Then I expect the text 'Display Name' to be present
When I tap the button that contains the text "Delete"
Then I expect the text "Really Delete Profile" to be present
When I tap the "button" widget with label "Really Delete Profile"
And I wait until the widget with type 'ProfileMgrView' is present
Then I expect a "ProfileRow" widget with text "Carol (Encrypted)" to be absent

View File

@ -38,7 +38,9 @@ void main() {
// overrides
TapWidgetWithType(),
TapWidgetWithLabel(),
TapWidgetWithTooltip(),
ExpectWidgetWithText(),
AbsentWidgetWithText(),
WaitUntilTypeExists(),
ExpectTextToBePresent(),
ExpectWidgetWithTextWithin(),

View File

@ -37,6 +37,22 @@ StepDefinitionGeneric TapWidgetWithLabel() {
firstMatchOnly: true);
//Text wdg = await context.world.appDriver.widget(finder, ExpectedWidgetResultType.first);
//print(wdg.debugDescribeChildren().first.)
await context.world.appDriver.scrollIntoView(finder);
await context.world.appDriver.tap(finder);
await context.world.appDriver.waitForAppToSettle();
},
);
}
StepDefinitionGeneric TapWidgetWithTooltip() {
return given2<String, String, FlutterWorld>(
RegExp(r'I tap the {string} widget with tooltip {string}$'),
(ofType, text, context) async {
final finder = context.world.appDriver.findByDescendant(
context.world.appDriver.findBy(widgetTypeByName(ofType), FindType.type),
context.world.appDriver.findBy(text, FindType.tooltip),
firstMatchOnly: true);
await context.world.appDriver.scrollIntoView(finder);
await context.world.appDriver.tap(finder);
await context.world.appDriver.waitForAppToSettle();
},
@ -59,6 +75,23 @@ StepDefinitionGeneric ExpectWidgetWithText() {
);
}
StepDefinitionGeneric AbsentWidgetWithText() {
return given2<String, String, FlutterWorld>(
RegExp(r'I expect a {string} widget with text {string} to be absent$'),
(ofType, text, context) async {
final finder = context.world.appDriver.findByDescendant(
context.world.appDriver.findBy(widgetTypeByName(ofType), FindType.type),
context.world.appDriver.findBy(text, FindType.text),
firstMatchOnly: true);
//Text wdg = await context.world.appDriver.widget(finder, ExpectedWidgetResultType.first);
//print(wdg.debugDescribeChildren().first.)
await context.world.appDriver.isAbsent(finder);
await context.world.appDriver.waitForAppToSettle();
},
);
}
StepDefinitionGeneric TapButtonWithText() {
return given1<String, FlutterWorld>(
RegExp(r'I tap the {string} (?:button|element|label|icon|field|text|widget)$'),
@ -67,8 +100,7 @@ StepDefinitionGeneric TapButtonWithText() {
context.world.appDriver.findBy(Flwtch, FindType.type),
context.world.appDriver.findBy(input1, FindType.key),
firstMatchOnly: true);
//Text wdg = await context.world.appDriver.widget(finder, ExpectedWidgetResultType.first);
//print(wdg.debugDescribeChildren().first.)
await context.world.appDriver.scrollIntoView(finder);
await context.world.appDriver.tap(finder);
await context.world.appDriver.waitForAppToSettle();
},
@ -229,6 +261,8 @@ Type widgetTypeByName(String input1) {
return TorIcon;
case "button":
return ElevatedButton;
case "IconButton":
return IconButton;
case "ProfileRow":
return ProfileRow;
default:

View File

@ -5,6 +5,7 @@ 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:provider/provider.dart';
@ -197,7 +198,8 @@ class CwtchNotifier {
var senderHandle = data['RemotePeer'];
var senderImage = data['Picture'];
var timestampSent = DateTime.tryParse(data['TimestampSent'])!;
var currentTotal = profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(identifier)!.totalMessages;
var contact = profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(identifier);
var currentTotal = contact!.totalMessages;
var isAuto = data['Auto'] == "true";
String? contenthash = data['ContentHash'];
var selectedConversation = appState.selectedProfile == data["ProfileOnion"] && appState.selectedConversation == identifier;
@ -218,6 +220,8 @@ class CwtchNotifier {
notificationManager.notify("New Message From Group!");
}
RemoteServerInfoState? server = profileCN.getProfile(data["ProfileOnion"])?.serverList.getServer(contact.server);
server?.updateSyncProgressFor(timestampSent);
} else {
// This is dealt with by IndexedAcknowledgment
EnvironmentConfig.debugLog("new message from group from yourself - this should not happen");

View File

@ -1,6 +1,6 @@
{
"@@locale": "de",
"@@last_modified": "2022-01-18T00:38:14+01:00",
"@@last_modified": "2022-01-28T19:57:41+01:00",
"torSettingsEnabledCacheDescription": "Cache the current downloaded Tor consensus to reuse next time Cwtch is opened. This will allow Tor to start faster. When disabled, Cwtch will purge cached data on start up.",
"torSettingsEnableCache": "Cache Tor Consensus",
"labelTorNetwork": "Tor Network",

View File

@ -1,6 +1,7 @@
{
"@@locale": "en",
"@@last_modified": "2022-01-18T00:38:14+01:00",
"@@last_modified": "2022-01-28T19:57:41+01:00",
"editProfile": "Edit Profile",
"torSettingsEnabledCacheDescription": "Cache the current downloaded Tor consensus to reuse next time Cwtch is opened. This will allow Tor to start faster. When disabled, Cwtch will purge cached data on start up.",
"torSettingsEnableCache": "Cache Tor Consensus",
"labelTorNetwork": "Tor Network",
@ -239,7 +240,6 @@
"radioNoPassword": "Unencrypted (No password)",
"radioUsePassword": "Password",
"copiedToClipboardNotification": "Copied to Clipboard",
"editProfile": "Edit Profille",
"newProfile": "New Profile",
"defaultProfileName": "Alice",
"profileName": "Display name",

View File

@ -1,6 +1,6 @@
{
"@@locale": "es",
"@@last_modified": "2022-01-18T00:38:14+01:00",
"@@last_modified": "2022-01-28T19:57:41+01:00",
"torSettingsEnabledCacheDescription": "Cache the current downloaded Tor consensus to reuse next time Cwtch is opened. This will allow Tor to start faster. When disabled, Cwtch will purge cached data on start up.",
"torSettingsEnableCache": "Cache Tor Consensus",
"labelTorNetwork": "Tor Network",

View File

@ -1,22 +1,24 @@
{
"@@locale": "fr",
"@@last_modified": "2022-01-18T00:38:14+01:00",
"torSettingsEnabledCacheDescription": "Cache the current downloaded Tor consensus to reuse next time Cwtch is opened. This will allow Tor to start faster. When disabled, Cwtch will purge cached data on start up.",
"torSettingsEnableCache": "Cache Tor Consensus",
"labelTorNetwork": "Tor Network",
"descriptionACNCircuitInfo": "In depth information about the path that the anonymous communication network is using to connect to this conversation.",
"labelACNCircuitInfo": "ACN Circuit Info",
"fileSharingSettingsDownloadFolderTooltip": "Browse to select a different default folder for downloaded files.",
"fileSharingSettingsDownloadFolderDescription": "When files are downloaded automatically (e.g. image files, when image previews are enabled) a default location to download the files to is needed.",
"torSettingsErrorSettingPort": "Port Number must be between 1 and 65535",
"torSettingsUseCustomTorServiceConfigurastionDescription": "Override the default tor configuration. Warning: This could be dangerous. Only turn this on if you know what you are doing.",
"torSettingsUseCustomTorServiceConfiguration": "Use a Custom Tor Service Configuration (torrc)",
"torSettingsCustomControlPortDescription": "Use a custom port for control connections to the Tor proxy",
"torSettingsCustomControlPort": "Custom Control Port",
"torSettingsCustomSocksPortDescription": "Use a custom port for data connections to the Tor proxy",
"torSettingsCustomSocksPort": "Custom SOCKS Port",
"torSettingsEnabledAdvancedDescription": "Use an existing Tor service on your system, or change the parameters of the Cwtch Tor Service",
"torSettingsEnabledAdvanced": "Enable Advanced Tor Configuration",
"@@last_modified": "2022-01-28T19:57:41+01:00",
"editProfile": "Modifier le profil",
"settingTheme": "Utilisez des thèmes clairs",
"torSettingsUseCustomTorServiceConfiguration": "Utiliser une configuration personnalisée du service Tor (torrc)",
"torSettingsUseCustomTorServiceConfigurastionDescription": "Remplacer la configuration par défaut de tor. Avertissement : Cela peut être dangereux. Ne l'activez que si vous savez ce que vous faites.",
"torSettingsErrorSettingPort": "Le numéro de port doit être compris entre 1 et 65535.",
"torSettingsEnableCache": "Cache du Consensus Tor",
"torSettingsEnabledCacheDescription": "Mettre en cache le consensus Tor actuellement téléchargé pour le réutiliser la prochaine fois que Cwtch est ouvert. Cela permettra à Tor de démarrer plus rapidement. Lorsqu'il est désactivé, Cwtch purgera les données mises en cache au démarrage.",
"torSettingsEnabledAdvancedDescription": "Utilisez un service Tor existant sur votre système ou modifiez les paramètres du service Tor de Cwtch.",
"torSettingsEnabledAdvanced": "Activer la configuration avancée de Tor",
"torSettingsCustomSocksPortDescription": "Utiliser un port personnalisé pour les connexions de données au proxy Tor",
"torSettingsCustomSocksPort": "Port SOCKS personnalisé",
"torSettingsCustomControlPortDescription": "Utiliser un port personnalisé pour contrôler les connexions au proxy Tor",
"torSettingsCustomControlPort": "Port de contrôle personnalisé",
"labelTorNetwork": "Réseau Tor",
"labelACNCircuitInfo": "Informations sur le circuit ACN",
"fileSharingSettingsDownloadFolderTooltip": "Parcourir pour sélectionner un autre dossier par défaut pour les fichiers téléchargés.",
"fileSharingSettingsDownloadFolderDescription": "Lorsque les fichiers sont téléchargés automatiquement (par exemple, les fichiers image, lorsque les aperçus d'image sont activés), un emplacement par défaut pour télécharger les fichiers est nécessaire.",
"descriptionACNCircuitInfo": "Informations détaillées sur le chemin que le réseau de communication anonyme utilise pour se connecter à cette conversation.",
"msgConfirmSend": "Êtes-vous sûr de vouloir envoyer",
"acceptGroupInviteLabel": "Voulez-vous accepter l'invitation au groupe de",
"msgFileTooBig": "La taille du fichier ne peut pas dépasser 10 Go",
@ -187,7 +189,6 @@
"deleteConfirmLabel": "Tapez SUPPRIMER pour confirmer",
"addNewProfileBtn": "Ajouter un nouveau profil",
"enterProfilePassword": "Entrez un mot de passe pour consulter vos profils",
"editProfile": "Modifier le profil",
"radioUsePassword": "Mot de passe",
"radioNoPassword": "Non chiffré (pas de mot de passe)",
"saveProfileBtn": "Sauvegarder le profil",
@ -205,7 +206,6 @@
"localePt": "Portugais",
"localeDe": "Allemand",
"settingInterfaceZoom": "Niveau de zoom",
"settingTheme": "Thème",
"themeLight": "Clair",
"themeDark": "Sombre",
"experimentsEnabled": "Activer les expériences",

View File

@ -1,6 +1,6 @@
{
"@@locale": "it",
"@@last_modified": "2022-01-18T00:38:14+01:00",
"@@last_modified": "2022-01-28T19:57:41+01:00",
"torSettingsEnabledCacheDescription": "Cache the current downloaded Tor consensus to reuse next time Cwtch is opened. This will allow Tor to start faster. When disabled, Cwtch will purge cached data on start up.",
"torSettingsEnableCache": "Cache Tor Consensus",
"labelTorNetwork": "Tor Network",

View File

@ -1,6 +1,6 @@
{
"@@locale": "pl",
"@@last_modified": "2022-01-18T00:38:14+01:00",
"@@last_modified": "2022-01-28T19:57:41+01:00",
"torSettingsEnabledCacheDescription": "Cache the current downloaded Tor consensus to reuse next time Cwtch is opened. This will allow Tor to start faster. When disabled, Cwtch will purge cached data on start up.",
"torSettingsEnableCache": "Cache Tor Consensus",
"labelTorNetwork": "Tor Network",

View File

@ -1,6 +1,6 @@
{
"@@locale": "pt",
"@@last_modified": "2022-01-18T00:38:14+01:00",
"@@last_modified": "2022-01-28T19:57:41+01:00",
"torSettingsEnabledCacheDescription": "Cache the current downloaded Tor consensus to reuse next time Cwtch is opened. This will allow Tor to start faster. When disabled, Cwtch will purge cached data on start up.",
"torSettingsEnableCache": "Cache Tor Consensus",
"labelTorNetwork": "Tor Network",
@ -227,7 +227,7 @@
"radioNoPassword": "Unencrypted (No password)",
"radioUsePassword": "Password",
"copiedToClipboardNotification": "Copiado",
"editProfile": "Edit Profille",
"editProfile": "Edit Profile",
"newProfile": "New Profile",
"defaultProfileName": "Alice",
"profileName": "Display name",

View File

@ -1,6 +1,6 @@
{
"@@locale": "ru",
"@@last_modified": "2022-01-18T00:38:14+01:00",
"@@last_modified": "2022-01-28T19:57:41+01:00",
"torSettingsEnabledCacheDescription": "Cache the current downloaded Tor consensus to reuse next time Cwtch is opened. This will allow Tor to start faster. When disabled, Cwtch will purge cached data on start up.",
"torSettingsEnableCache": "Cache Tor Consensus",
"labelTorNetwork": "Tor Network",

View File

@ -37,7 +37,7 @@ Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
print("runApp()");
runApp(Flwtch());
sleep(Duration(seconds:1));
sleep(Duration(seconds: 1));
}
class Flwtch extends StatefulWidget {
@ -51,13 +51,14 @@ class Flwtch extends StatefulWidget {
}
}
class FlwtchState extends State<Flwtch> with WindowListener, WidgetsBindingObserver {
class FlwtchState extends State<Flwtch> with WindowListener {
final TextStyle biggerFont = const TextStyle(fontSize: 18);
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 GlobalKey<NavigatorState> navKey = GlobalKey<NavigatorState>();
Future<dynamic> shutdownDirect(MethodCall call) {
@ -186,7 +187,6 @@ class FlwtchState extends State<Flwtch> with WindowListener, WidgetsBindingObser
// coder beware: args["RemotePeer"] is actually a handle, and could be eg a groupID
Future<void> _externalNotificationClicked(MethodCall call) async {
var args = jsonDecode(call.arguments);
var profile = profs.getProfile(args["ProfileOnion"])!;
var convo = profile.contactList.getContact(args["Handle"])!;
Provider.of<AppState>(navKey.currentContext!, listen: false).initialScrollIndex = convo.unreadMessages;
@ -231,7 +231,6 @@ class FlwtchState extends State<Flwtch> with WindowListener, WidgetsBindingObser
globalAppState.focus = false;
}
@override
void dispose() {
cwtch.Shutdown();

View File

@ -211,6 +211,7 @@ class ContactInfoState extends ChangeNotifier {
newMarker++;
}
this._lastMessageTime = timestamp;
this.messageCache.addNew(profileOnion, identifier, messageID, timestamp, senderHandle, senderImage, isAuto, data, contenthash);
this.totalMessages += 1;

View File

@ -1,5 +1,6 @@
import 'dart:convert';
import 'package:cwtch/models/remoteserver.dart';
import 'package:flutter/widgets.dart';
import 'contact.dart';
@ -72,7 +73,7 @@ class ProfileInfoState extends ChangeNotifier {
List<dynamic> servers = jsonDecode(serversJson);
this._servers.replace(servers.map((server) {
// TODO Keys...
return RemoteServerInfoState(onion: server["onion"], identifier: server["identifier"], description: server["description"], status: server["status"]);
return RemoteServerInfoState(server["onion"], server["identifier"], server["description"], server["status"]);
}));
this._contacts.contacts.forEach((contact) {

View File

@ -1,3 +1,4 @@
import 'package:cwtch/models/remoteserver.dart';
import 'package:flutter/material.dart';
import 'contact.dart';
@ -33,12 +34,17 @@ class ProfileServerListState extends ChangeNotifier {
// return -1 = a first in list
// return 1 = b first in list
// online v offline
// online v syncing v offline
if (a.status == "Synced" && b.status != "Synced") {
return -1;
} else if (a.status != "Synced" && b.status == "Synced") {
return 1;
}
if (a.status == "Authenticated" && b.status != "Authenticated") {
return -1;
} else if (a.status != "Authenticated" && b.status == "Authenticated") {
return 1;
}
// num of groups
if (a.groups.length > b.groups.length) {
@ -65,30 +71,3 @@ class ProfileServerListState extends ChangeNotifier {
List<RemoteServerInfoState> get servers => _servers.sublist(0); //todo: copy?? dont want caller able to bypass changenotifier
}
class RemoteServerInfoState extends ChangeNotifier {
final String onion;
final int identifier;
String status;
String description;
List<ContactInfoState> _groups = [];
RemoteServerInfoState({required this.onion, required this.identifier, required this.description, required this.status});
void updateDescription(String newDescription) {
this.description = newDescription;
notifyListeners();
}
void clearGroups() {
_groups = [];
}
void addGroup(ContactInfoState group) {
_groups.add(group);
notifyListeners();
}
List<ContactInfoState> get groups => _groups.sublist(0); //todo: copy?? dont want caller able to bypass changenotifier
}

View File

@ -0,0 +1,55 @@
import 'package:flutter/cupertino.dart';
import 'contact.dart';
class RemoteServerInfoState extends ChangeNotifier {
final String onion;
final int identifier;
String _status;
String description;
List<ContactInfoState> _groups = [];
double syncProgress = 0;
DateTime lastPreSyncMessagTime = new DateTime(2020);
RemoteServerInfoState(this.onion, this.identifier, this.description, this._status);
void updateDescription(String newDescription) {
this.description = newDescription;
notifyListeners();
}
void clearGroups() {
_groups = [];
}
void addGroup(ContactInfoState group) {
_groups.add(group);
notifyListeners();
}
String get status => _status;
set status(String newStatus) {
_status = newStatus;
if (status == "Authenticated") {
// syncing, set lastPreSyncMessageTime
_groups.forEach((g) {
if (g.lastMessageTime.isAfter(lastPreSyncMessagTime)) {
lastPreSyncMessagTime = g.lastMessageTime;
}
});
}
notifyListeners();
}
// updateSyncProgressFor point takes a message's time, and updates the server sync progress,
// based on that point in time between the precalculated lastPreSyncMessagTime and Now
void updateSyncProgressFor(DateTime point) {
var range = lastPreSyncMessagTime.difference(DateTime.now());
var pointFromStart = lastPreSyncMessagTime.difference(point);
syncProgress = pointFromStart.inSeconds / range.inSeconds;
notifyListeners();
}
List<ContactInfoState> get groups => _groups.sublist(0); //todo: copy?? dont want caller able to bypass changenotifier
}

View File

@ -183,7 +183,11 @@ ThemeData mkThemeData(Settings opaque) {
scrollbarTheme: ScrollbarThemeData(isAlwaysShown: false, thumbColor: MaterialStateProperty.all(opaque.current().scrollbarDefaultColor)),
tabBarTheme: TabBarTheme(indicator: UnderlineTabIndicator(borderSide: BorderSide(color: opaque.current().defaultButtonActiveColor))),
dialogTheme: DialogTheme(
backgroundColor: opaque.current().backgroundPaneColor, titleTextStyle: TextStyle(color: opaque.current().mainTextColor), contentTextStyle: TextStyle(color: opaque.current().mainTextColor)),
backgroundColor: opaque.current().backgroundPaneColor,
titleTextStyle: TextStyle(color: opaque.current().mainTextColor),
contentTextStyle: TextStyle(
color: opaque.current().mainTextColor,
)),
textTheme: TextTheme(
headline1: TextStyle(color: opaque.current().mainTextColor),
headline2: TextStyle(color: opaque.current().mainTextColor),

View File

@ -2,6 +2,7 @@ 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';

View File

@ -186,13 +186,13 @@ class _AddEditProfileViewState extends State<AddEditProfileView> {
autoFillHints: [AutofillHints.newPassword],
validator: (value) {
// Password field can be empty when just updating the profile, not on creation
if (Provider.of<ProfileInfoState>(context).isEncrypted &&
if (Provider.of<ProfileInfoState>(context, listen: false).isEncrypted &&
Provider.of<ProfileInfoState>(context, listen: false).onion.isEmpty &&
value.isEmpty &&
usePassword) {
return AppLocalizations.of(context)!.passwordErrorEmpty;
}
if (Provider.of<ErrorHandler>(context).deleteProfileError == true) {
if (Provider.of<ErrorHandler>(context, listen: false).deleteProfileError == true) {
return AppLocalizations.of(context)!.enterCurrentPasswordForDelete;
}
return null;

View File

@ -3,6 +3,7 @@ import 'package:cwtch/models/appstate.dart';
import 'package:cwtch/models/contact.dart';
import 'package:cwtch/models/contactlist.dart';
import 'package:cwtch/models/profile.dart';
import 'package:cwtch/models/profilelist.dart';
import 'package:cwtch/views/profileserversview.dart';
import 'package:flutter/material.dart';
import 'package:cwtch/widgets/contactrow.dart';
@ -154,8 +155,15 @@ class _ContactsViewState extends State<ContactsView> {
Widget _buildContactList() {
final tiles = Provider.of<ContactListState>(context).filteredList().map((ContactInfoState contact) {
return ChangeNotifierProvider<ContactInfoState>.value(key: ValueKey(contact.profileOnion + "" + contact.onion), value: contact, builder: (_, __) => RepaintBoundary(child: ContactRow()));
return MultiProvider(
providers: [
ChangeNotifierProvider.value(value: contact),
ChangeNotifierProvider.value(value: Provider.of<ProfileInfoState>(context).serverList),
],
builder: (context, child) => RepaintBoundary(child: ContactRow()),
);
});
final divided = ListTile.divideTiles(
context: context,
tiles: tiles,

View File

@ -107,7 +107,7 @@ class _GlobalSettingsViewState extends State<GlobalSettingsView> {
items: themes.keys.map<DropdownMenuItem<String>>((String themeId) {
return DropdownMenuItem<String>(
value: themeId,
child: Text("ddi_$themeId",key: Key("ddi_$themeId")),//getThemeName(context, themeId)),
child: Text("ddi_$themeId", key: Key("ddi_$themeId")), //getThemeName(context, themeId)),
);
}).toList()),
leading: Icon(CwtchIcons.change_theme, color: settings.current().mainTextColor),

View File

@ -108,7 +108,12 @@ class _ProfileMgrViewState extends State<ProfileMgrView> {
}
// Global Settings
actions.add(IconButton(key: Key("OpenSettingsView"), icon: Icon(Icons.settings), tooltip: AppLocalizations.of(context)!.tooltipOpenSettings, splashRadius: Material.defaultSplashRadius / 2, onPressed: _pushGlobalSettings));
actions.add(IconButton(
key: Key("OpenSettingsView"),
icon: 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));
@ -191,6 +196,7 @@ class _ProfileMgrViewState extends State<ProfileMgrView> {
height: 20,
),
CwtchPasswordField(
key: Key("unlockPasswordProfileElement"),
autofocus: true,
controller: ctrlrPassword,
action: unlock,

View File

@ -1,5 +1,6 @@
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/remoteserverrow.dart';
import 'package:flutter/material.dart';

View File

@ -4,6 +4,7 @@ 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';

View File

@ -5,7 +5,16 @@ import 'package:provider/provider.dart';
// Provides a styled Text Field for use in Form Widgets.
// Callers must provide a text controller, label helper text and a validator.
class CwtchButtonTextField extends StatefulWidget {
CwtchButtonTextField({required this.controller, required this.onPressed, required this.icon, required this.tooltip, this.readonly = true, this.labelText, this.testKey, this.onChanged,});
CwtchButtonTextField({
required this.controller,
required this.onPressed,
required this.icon,
required this.tooltip,
this.readonly = true,
this.labelText,
this.testKey,
this.onChanged,
});
final TextEditingController controller;
final Function()? onPressed;
final Function(String)? onChanged;

View File

@ -3,6 +3,7 @@ import 'dart:io';
import 'package:cwtch/models/appstate.dart';
import 'package:cwtch/models/contact.dart';
import 'package:cwtch/models/profile.dart';
import 'package:cwtch/models/profileservers.dart';
import 'package:cwtch/views/contactsview.dart';
import 'package:flutter/material.dart';
import 'package:flutter/painting.dart';
@ -66,6 +67,7 @@ class _ContactRowState extends State<ContactRow> {
visible: contact.isGroup && contact.status == "Authenticated",
child: LinearProgressIndicator(
color: Provider.of<Settings>(context).theme.defaultButtonActiveColor,
value: Provider.of<ProfileInfoState>(context).serverList.getServer(contact.server)?.syncProgress,
)),
Visibility(
visible: !Provider.of<Settings>(context).streamerMode,

View File

@ -89,10 +89,7 @@ class _ProfileRowState extends State<ProfileRow> {
builder: (BuildContext buildcontext) {
return OrientationBuilder(builder: (orientationBuilderContext, orientation) {
return MultiProvider(
providers: [
ChangeNotifierProvider<ProfileInfoState>.value(value: profile),
ChangeNotifierProvider<ContactListState>.value(value: profile.contactList),
],
providers: [ChangeNotifierProvider<ProfileInfoState>.value(value: profile), ChangeNotifierProvider<ContactListState>.value(value: profile.contactList)],
builder: (innercontext, widget) {
var appState = Provider.of<AppState>(context);
var settings = Provider.of<Settings>(context);

View File

@ -1,6 +1,7 @@
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/views/addeditservers.dart';
import 'package:cwtch/views/remoteserverview.dart';
@ -55,7 +56,13 @@ class _RemoteServerRowState extends State<RemoteServerRow> {
softWrap: true,
overflow: TextOverflow.ellipsis,
style: TextStyle(color: running ? Provider.of<Settings>(context).theme.portraitOnlineBorderColor : Provider.of<Settings>(context).theme.portraitOfflineBorderColor),
)))
))),
Visibility(
visible: server.status == "Authenticated",
child: LinearProgressIndicator(
color: Provider.of<Settings>(context).theme.defaultButtonActiveColor,
value: server.syncProgress,
)),
],
)),
]),

View File

@ -132,14 +132,15 @@ static void my_application_activate(GApplication* application) {
gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view));
fl_register_plugins(FL_PLUGIN_REGISTRY(view));
// Create a specific channel for shutting down cwtch when the close button is triggered
// We have registered the "destroy" handle above for this reason
FlEngine *engine = fl_view_get_engine(view);
g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new();
g_autoptr(FlBinaryMessenger) messenger = fl_engine_get_binary_messenger(engine);
channel =
fl_method_channel_new(messenger,
"im.cwtch.linux.shutdown", FL_METHOD_CODEC(codec));
channel = fl_method_channel_new(messenger, "im.cwtch.linux.shutdown", FL_METHOD_CODEC(codec));
gtk_widget_grab_focus(GTK_WIDGET(view));

View File

@ -41,7 +41,7 @@ dependencies:
scrollable_positioned_list: ^0.2.0-nullsafety.0
file_picker: ^4.3.2
file_picker_desktop: ^1.1.0
url_launcher: ^6.0.12
url_launcher: ^6.0.18
desktoasts: ^0.0.2
window_manager: ^0.1.4