Merge branch 'gherkin' of git.openprivacy.ca:cwtch.im/cwtch-ui into gherkin
This commit is contained in:
commit
4d0bb92495
|
@ -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"
|
||||
|
@ -42,3 +47,44 @@ Feature: Basic Profile Management
|
|||
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
|
||||
|
||||
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
|
||||
|
|
|
@ -38,7 +38,9 @@ void main() {
|
|||
// overrides
|
||||
TapWidgetWithType(),
|
||||
TapWidgetWithLabel(),
|
||||
TapWidgetWithTooltip(),
|
||||
ExpectWidgetWithText(),
|
||||
AbsentWidgetWithText(),
|
||||
WaitUntilTypeExists(),
|
||||
ExpectTextToBePresent(),
|
||||
ExpectWidgetWithTextWithin(),
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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),
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
)),
|
||||
],
|
||||
)),
|
||||
]),
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
Loading…
Reference in New Issue