forked from cwtch.im/cwtch-ui
Merge branch 'trunk' into nima/clickable-links
This commit is contained in:
commit
b8d50f234a
88
.drone.yml
88
.drone.yml
|
@ -117,7 +117,7 @@ steps:
|
|||
- echo $BUILDFILES_KEY > ~/id_rsab64
|
||||
- base64 -d ~/id_rsab64 > ~/id_rsa
|
||||
- chmod 400 ~/id_rsa
|
||||
- export DIR=flwtch-`cat VERSION`-`cat BUILDDATE`
|
||||
- export DIR=flwtch-`cat BUILDDATE`-`cat VERSION`
|
||||
- mv deploy $DIR
|
||||
- cp -r coverage/html $DIR/coverage-tests
|
||||
- cp -r test/failures $DIR/test-failures || true
|
||||
|
@ -125,7 +125,6 @@ steps:
|
|||
- find . -type f -exec sha256sum {} \; > ./../sha256s.txt
|
||||
- mv ./../sha256s.txt .
|
||||
- cd ..
|
||||
# TODO: do deployment once files actaully compile
|
||||
- scp -r -o StrictHostKeyChecking=no -i ~/id_rsa $DIR buildfiles@build.openprivacy.ca:/home/buildfiles/buildfiles/
|
||||
|
||||
- name: notify-email
|
||||
|
@ -238,7 +237,7 @@ steps:
|
|||
- $Env:zipsha = $Env:zip + '.sha512'
|
||||
- $Env:msix = 'cwtch-install-' + $Env:version + '.msix'
|
||||
- $Env:msixsha = $Env:msix + '.sha512'
|
||||
- $Env:buildname = 'flwtch-win-' + $Env:version + '-' + $Env:builddate
|
||||
- $Env:buildname = 'flwtch-win-' + $Env:builddate + '-' + $Env:version
|
||||
- $Env:builddir = $Env:buildname
|
||||
- echo $Env:pfx > codesign.pfx.b64
|
||||
- certutil -decode codesign.pfx.b64 codesign.pfx
|
||||
|
@ -276,3 +275,86 @@ trigger:
|
|||
event:
|
||||
- push
|
||||
- pull_request
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
type: exec
|
||||
name: macos
|
||||
|
||||
platform:
|
||||
os: darwin
|
||||
arch: amd64
|
||||
|
||||
clone:
|
||||
disable: true
|
||||
|
||||
steps:
|
||||
- name: clone
|
||||
environment:
|
||||
buildbot_key_b64:
|
||||
from_secret: buildbot_key_b64
|
||||
commands:
|
||||
- mkdir ~/.ssh
|
||||
- echo $buildbot_key_b64 > ~/.ssh/id_rsa.b64
|
||||
- base64 -d ~/.ssh/id_rsa.b64 > ~/.ssh/id_rsa
|
||||
- chmod 400 ~/.ssh/id_rsa
|
||||
# force by pass of ssh host key check, less secure
|
||||
- ssh-keyscan -H git.openprivacy.ca >> ~/.ssh/known_hosts
|
||||
- git init
|
||||
- git config core.sshCommand 'ssh -o StrictHostKeyChecking=no -i ~/.ssh/id_rsa'
|
||||
- git remote add origin gogs@git.openprivacy.ca:$DRONE_REPO.git
|
||||
- git pull origin trunk
|
||||
- git fetch --tags
|
||||
- git checkout $DRONE_COMMIT
|
||||
# use Drone ssh var instead of hardcode to allow forks to build (gogs@git.openprivacy.ca:cwtch.im/cwtch-ui.git)
|
||||
#- git clone gogs@git.openprivacy.ca:$DRONE_REPO.git .
|
||||
#- git checkout $DRONE_COMMIT
|
||||
|
||||
- name: fetch
|
||||
commands:
|
||||
- ./fetch-tor-macos.sh
|
||||
- echo `git describe --tags --abbrev=1` > VERSION
|
||||
- echo `date +%G-%m-%d-%H-%M` > BUILDDATE
|
||||
- export PATH=$PATH:/Users/Dan/development/flutter/bin
|
||||
- flutter pub get
|
||||
- mkdir deploy
|
||||
- ./fetch-libcwtch-go-macos.sh
|
||||
- gem install --user-install cocoapods
|
||||
|
||||
- name: build-macos
|
||||
commands:
|
||||
- export PATH=$PATH:/Users/Dan/development/flutter/bin
|
||||
- export GEM_HOME=$HOME/.gem
|
||||
- export PATH=$GEM_HOME/ruby/2.6.0/bin:$PATH
|
||||
- flutter config --enable-macos-desktop
|
||||
- flutter build macos --dart-define BUILD_VER=`cat VERSION` --dart-define BUILD_DATE=`cat BUILDDATE`
|
||||
- export PATH=$PATH:/usr/local/bin #create-dmg
|
||||
- macos/package-release.sh
|
||||
- mkdir -p deploy
|
||||
- mv Cwtch.dmg deploy
|
||||
|
||||
- name: deploy-buildfiles
|
||||
environment:
|
||||
BUILDFILES_KEY:
|
||||
from_secret: buildfiles_key
|
||||
when:
|
||||
event: push
|
||||
status: [ success ]
|
||||
commands:
|
||||
- echo $BUILDFILES_KEY > ~/id_rsab64
|
||||
- base64 -d ~/id_rsab64 > ~/id_rsa
|
||||
- chmod 400 ~/id_rsa
|
||||
- export DIR=flwtch-macos-`cat BUILDDATE`-`cat VERSION`
|
||||
- mv deploy $DIR
|
||||
- cd $DIR
|
||||
- find . -type f -exec shasum -a 512 {} \; > ./../sha512s.txt
|
||||
- mv ./../sha512s.txt .
|
||||
- cd ..
|
||||
- scp -r -o StrictHostKeyChecking=no -i ~/id_rsa $DIR buildfiles@build.openprivacy.ca:/home/buildfiles/buildfiles/
|
||||
|
||||
trigger:
|
||||
#repo: cwtch.im/cwtch-ui # allow forks to build?
|
||||
branch: trunk
|
||||
event:
|
||||
- push
|
||||
- pull_request
|
|
@ -0,0 +1 @@
|
|||
2021-11-05-20-13-v1.4.1
|
|
@ -1 +1 @@
|
|||
v1.3.0-3-gfcc9d71-2021-09-30-23-09
|
||||
2021-11-06-00-13-v1.4.1
|
|
@ -44,6 +44,7 @@ To build a release version and load normal profiles, use `build-release.sh X` in
|
|||
- run `flutter config --enable-linux-desktop` if you've never done so before
|
||||
- optional: launch cwtch-ui directly by running `flutter run -d linux`
|
||||
- to build cwtch-ui, run `flutter build linux`
|
||||
- optional: launch cwtch-ui build with `env LD_LIBRARY_PATH=linux ./build/linux/x64/release/bundle/cwtch`
|
||||
- to package the build, run `linux/package-release.sh`
|
||||
|
||||
### Building on Windows (for Windows)
|
||||
|
@ -60,9 +61,11 @@ To build a release version and load normal profiles, use `build-release.sh X` in
|
|||
|
||||
### Building on MacOS
|
||||
|
||||
- Navigate to https://git.openprivacy.ca/cwtch.im/libcwtch-go/releases and download the latest libCwtch.dylib into this folder
|
||||
- Download and install Tor Browser (it's currently the only way to get tor for macos)
|
||||
- Cocaopods is required, you may need to `gem install cocaopods -v 1.9.3`
|
||||
- copy `libCwtch.dylib` into the root folder, or run `fetch-libcwtch-go-macos.sh` to download it
|
||||
- run `fetch-tor-macos.sh` to fetch Tor or Download and install Tor Browser and `cp -r /Applications/Tor\ Browser.app/Contents/MacOS/Tor ./macos/`
|
||||
- `flutter build macos`
|
||||
- optional: launch cwtch-ui build with `./build/linux/x64/release/bundle/cwtch`
|
||||
- `./macos/package-release.sh`
|
||||
|
||||
results in a Cwtch.dmg that has libCwtch.dylib and tor in it as well and can be installed into Applications
|
||||
|
|
|
@ -112,24 +112,26 @@ class FlwtchWorker(context: Context, parameters: WorkerParameters) :
|
|||
if (dlID == null) {
|
||||
dlID = 0;
|
||||
}
|
||||
val channelId =
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
createDownloadNotificationChannel(fileKey, fileKey)
|
||||
} else {
|
||||
// If earlier version channel ID is not used
|
||||
// https://developer.android.com/reference/android/support/v4/app/NotificationCompat.Builder.html#NotificationCompat.Builder(android.content.Context)
|
||||
""
|
||||
};
|
||||
val newNotification = NotificationCompat.Builder(applicationContext, channelId)
|
||||
.setOngoing(true)
|
||||
.setContentTitle("Downloading")//todo: translate
|
||||
.setContentText(title)
|
||||
.setSmallIcon(android.R.drawable.stat_sys_download)
|
||||
.setProgress(progressMax, progress, false)
|
||||
.setSound(null)
|
||||
//.setSilent(true)
|
||||
.build();
|
||||
notificationManager.notify(dlID, newNotification);
|
||||
if (progress >= 0) {
|
||||
val channelId =
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
createDownloadNotificationChannel(fileKey, fileKey)
|
||||
} else {
|
||||
// If earlier version channel ID is not used
|
||||
// https://developer.android.com/reference/android/support/v4/app/NotificationCompat.Builder.html#NotificationCompat.Builder(android.content.Context)
|
||||
""
|
||||
};
|
||||
val newNotification = NotificationCompat.Builder(applicationContext, channelId)
|
||||
.setOngoing(true)
|
||||
.setContentTitle("Downloading")//todo: translate
|
||||
.setContentText(title)
|
||||
.setSmallIcon(android.R.drawable.stat_sys_download)
|
||||
.setProgress(progressMax, progress, false)
|
||||
.setSound(null)
|
||||
//.setSilent(true)
|
||||
.build();
|
||||
notificationManager.notify(dlID, newNotification);
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.i("FlwtchWorker->FileDownloadProgressUpdate", e.toString() + " :: " + e.getStackTrace());
|
||||
}
|
||||
|
@ -241,6 +243,12 @@ class FlwtchWorker(context: Context, parameters: WorkerParameters) :
|
|||
val fileKey = (a.get("fileKey") as? String) ?: ""
|
||||
Cwtch.checkDownloadStatus(profile, fileKey)
|
||||
}
|
||||
"VerifyOrResumeDownload" -> {
|
||||
val profile = (a.get("ProfileOnion") as? String) ?: ""
|
||||
val handle = (a.get("handle") as? String) ?: ""
|
||||
val fileKey = (a.get("fileKey") as? String) ?: ""
|
||||
Cwtch.verifyOrResumeDownload(profile, handle, fileKey)
|
||||
}
|
||||
"SendProfileEvent" -> {
|
||||
val onion = (a.get("onion") as? String) ?: ""
|
||||
val jsonEvent = (a.get("jsonEvent") as? String) ?: ""
|
||||
|
@ -291,10 +299,61 @@ class FlwtchWorker(context: Context, parameters: WorkerParameters) :
|
|||
val groupHandle = (a.get("groupHandle") as? String) ?: ""
|
||||
Cwtch.rejectInvite(profile, groupHandle)
|
||||
}
|
||||
"SetProfileAttribute" -> {
|
||||
val profile = (a.get("ProfileOnion") as? String) ?: ""
|
||||
val key = (a.get("Key") as? String) ?: ""
|
||||
val v = (a.get("Val") as? String) ?: ""
|
||||
Cwtch.setProfileAttribute(profile, key, v)
|
||||
}
|
||||
"SetContactAttribute" -> {
|
||||
val profile = (a.get("ProfileOnion") as? String) ?: ""
|
||||
val contact = (a.get("Contact") as? String) ?: ""
|
||||
val key = (a.get("Key") as? String) ?: ""
|
||||
val v = (a.get("Val") as? String) ?: ""
|
||||
Cwtch.setContactAttribute(profile, contact, key, v)
|
||||
}
|
||||
"Shutdown" -> {
|
||||
Cwtch.shutdownCwtch();
|
||||
return Result.success()
|
||||
}
|
||||
"LoadServers" -> {
|
||||
val password = (a.get("Password") as? String) ?: ""
|
||||
Cwtch.loadServers(password)
|
||||
}
|
||||
"CreateServer" -> {
|
||||
val password = (a.get("Password") as? String) ?: ""
|
||||
val desc = (a.get("Description") as? String) ?: ""
|
||||
val autostart = (a.get("Autostart") as? Boolean) ?: false
|
||||
Cwtch.createServer(password, desc, autostart)
|
||||
}
|
||||
"DeleteServer" -> {
|
||||
val serverOnion = (a.get("ServerOnion") as? String) ?: ""
|
||||
val password = (a.get("Password") as? String) ?: ""
|
||||
Cwtch.deleteServer(serverOnion, password)
|
||||
}
|
||||
"LaunchServers" -> {
|
||||
Cwtch.launchServers()
|
||||
}
|
||||
"LaunchServer" -> {
|
||||
val serverOnion = (a.get("ServerOnion") as? String) ?: ""
|
||||
Cwtch.launchServer(serverOnion)
|
||||
}
|
||||
"StopServer" -> {
|
||||
val serverOnion = (a.get("ServerOnion") as? String) ?: ""
|
||||
Cwtch.stopServer(serverOnion)
|
||||
}
|
||||
"StopServers" -> {
|
||||
Cwtch.stopServers()
|
||||
}
|
||||
"DestroyServers" -> {
|
||||
Cwtch.destroyServers()
|
||||
}
|
||||
"SetServerAttribute" -> {
|
||||
val serverOnion = (a.get("ServerOnion") as? String) ?: ""
|
||||
val key = (a.get("Key") as? String) ?: ""
|
||||
val v = (a.get("Val") as? String) ?: ""
|
||||
Cwtch.setServerAttribute(serverOnion, key, v)
|
||||
}
|
||||
else -> return Result.failure()
|
||||
}
|
||||
return Result.success()
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
#!/bin/sh
|
||||
|
||||
VERSION=`cat LIBCWTCH-GO-MACOS.version`
|
||||
echo $VERSION
|
||||
|
||||
curl https://build.openprivacy.ca/files/libCwtch-go-macos-$VERSION/libCwtch.dylib --output libCwtch.dylib
|
|
@ -5,5 +5,3 @@ echo $VERSION
|
|||
|
||||
wget https://build.openprivacy.ca/files/libCwtch-go-$VERSION/cwtch.aar -O android/cwtch/cwtch.aar
|
||||
wget https://build.openprivacy.ca/files/libCwtch-go-$VERSION/libCwtch.so -O linux/libCwtch.so
|
||||
|
||||
# wget https://build.openprivacy.ca/files/libCwtch-go-$VERSION/libCwtch.dll -O windows/libCwtch.dll
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
#!/bin/sh
|
||||
|
||||
cd macos
|
||||
curl https://git.openprivacy.ca/openprivacy/buildfiles/raw/branch/master/tor/tor-macos-0.4.6.7.tar.gz --output tor.tar.gz
|
||||
tar -xzf tor.tar.gz
|
||||
chmod a+x Tor/tor.real
|
||||
cd ..
|
|
@ -1,5 +1,9 @@
|
|||
import 'package:flutter/src/services/text_input.dart';
|
||||
|
||||
// To handle profiles that are "unencrypted" (i.e don't require a password to open) we currently create a profile with a defacto, hardcoded password.
|
||||
// Details: https://docs.openprivacy.ca/cwtch-security-handbook/profile_encryption_and_storage.html
|
||||
const DefaultPassword = "be gay do crime";
|
||||
|
||||
abstract class Cwtch {
|
||||
// ignore: non_constant_identifier_names
|
||||
Future<void> Start();
|
||||
|
@ -48,6 +52,8 @@ abstract class Cwtch {
|
|||
void CreateDownloadableFile(String profile, String handle, String filenameSuggestion, String filekey);
|
||||
// ignore: non_constant_identifier_names
|
||||
void CheckDownloadStatus(String profile, String fileKey);
|
||||
// ignore: non_constant_identifier_names
|
||||
void VerifyOrResumeDownload(String profile, String handle, String filekey);
|
||||
|
||||
// ignore: non_constant_identifier_names
|
||||
void ArchiveConversation(String profile, String handle);
|
||||
|
@ -63,6 +69,29 @@ abstract class Cwtch {
|
|||
void SetGroupAttribute(String profile, String groupHandle, String key, String value);
|
||||
// ignore: non_constant_identifier_names
|
||||
void RejectInvite(String profileOnion, String groupHandle);
|
||||
// ignore: non_constant_identifier_names
|
||||
void SetProfileAttribute(String profile, String key, String val);
|
||||
// ignore: non_constant_identifier_names
|
||||
void SetContactAttribute(String profile, String contact, String key, String val);
|
||||
|
||||
// ignore: non_constant_identifier_names
|
||||
void LoadServers(String password);
|
||||
// ignore: non_constant_identifier_names
|
||||
void CreateServer(String password, String description, bool autostart);
|
||||
// ignore: non_constant_identifier_names
|
||||
void DeleteServer(String serverOnion, String password);
|
||||
// ignore: non_constant_identifier_names
|
||||
void LaunchServers();
|
||||
// ignore: non_constant_identifier_names
|
||||
void LaunchServer(String serverOnion);
|
||||
// ignore: non_constant_identifier_names
|
||||
void StopServer(String serverOnion);
|
||||
// ignore: non_constant_identifier_names
|
||||
void StopServers();
|
||||
// ignore: non_constant_identifier_names
|
||||
void DestroyServers();
|
||||
// ignore: non_constant_identifier_names
|
||||
void SetServerAttribute(String serverOnion, String key, String val);
|
||||
|
||||
// ignore: non_constant_identifier_names
|
||||
void Shutdown();
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import 'dart:convert';
|
||||
import 'package:cwtch/models/message.dart';
|
||||
import 'package:cwtch/models/profileservers.dart';
|
||||
import 'package:cwtch/models/servers.dart';
|
||||
import 'package:cwtch/notification_manager.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
@ -20,14 +21,16 @@ class CwtchNotifier {
|
|||
late TorStatus torStatus;
|
||||
late NotificationsManager notificationManager;
|
||||
late AppState appState;
|
||||
late ServerListState serverListState;
|
||||
|
||||
CwtchNotifier(ProfileListState pcn, Settings settingsCN, ErrorHandler errorCN, TorStatus torStatusCN, NotificationsManager notificationManagerP, AppState appStateCN) {
|
||||
CwtchNotifier(ProfileListState pcn, Settings settingsCN, ErrorHandler errorCN, TorStatus torStatusCN, NotificationsManager notificationManagerP, AppState appStateCN, ServerListState serverListStateCN) {
|
||||
profileCN = pcn;
|
||||
settings = settingsCN;
|
||||
error = errorCN;
|
||||
torStatus = torStatusCN;
|
||||
notificationManager = notificationManagerP;
|
||||
appState = appStateCN;
|
||||
serverListState = serverListStateCN;
|
||||
}
|
||||
|
||||
void handleMessage(String type, dynamic data) {
|
||||
|
@ -59,11 +62,28 @@ class CwtchNotifier {
|
|||
lastMessageTime: DateTime.now(), //show at the top of the contact list even if no messages yet
|
||||
));
|
||||
break;
|
||||
case "NewServer":
|
||||
EnvironmentConfig.debugLog("NewServer $data");
|
||||
serverListState.add(
|
||||
data["Onion"],
|
||||
data["ServerBundle"],
|
||||
data["Running"] == "true",
|
||||
data["Description"],
|
||||
data["Autostart"] == "true",
|
||||
data["StorageType"] == "storage-password");
|
||||
break;
|
||||
case "ServerIntentUpdate":
|
||||
EnvironmentConfig.debugLog("ServerIntentUpdate $data");
|
||||
var server = serverListState.getServer(data["Identity"]);
|
||||
if (server != null) {
|
||||
server.setRunning(data["Intent"] == "running");
|
||||
}
|
||||
break;
|
||||
case "GroupCreated":
|
||||
|
||||
// Retrieve Server Status from Cache...
|
||||
String status = "";
|
||||
ServerInfoState? serverInfoState = profileCN.getProfile(data["ProfileOnion"])?.serverList.getServer(data["GroupServer"]);
|
||||
RemoteServerInfoState? serverInfoState = profileCN.getProfile(data["ProfileOnion"])?.serverList.getServer(data["GroupServer"]);
|
||||
if (serverInfoState != null) {
|
||||
status = serverInfoState.status;
|
||||
}
|
||||
|
@ -84,6 +104,12 @@ class CwtchNotifier {
|
|||
// todo standarize
|
||||
error.handleUpdate("deleteprofile.success");
|
||||
break;
|
||||
case "ServerDeleted":
|
||||
error.handleUpdate("deletedserver." + data["Status"]);
|
||||
if (data["Status"] == "success") {
|
||||
serverListState.delete(data["Identity"]);
|
||||
}
|
||||
break;
|
||||
case "DeleteContact":
|
||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.removeContact(data["RemotePeer"]);
|
||||
break;
|
||||
|
@ -145,24 +171,32 @@ class CwtchNotifier {
|
|||
break;
|
||||
case "NewMessageFromGroup":
|
||||
if (data["ProfileOnion"] != data["RemotePeer"]) {
|
||||
//if not currently open
|
||||
if (appState.selectedProfile != data["ProfileOnion"] || appState.selectedConversation != data["GroupID"]) {
|
||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["GroupID"])!.unreadMessages++;
|
||||
var idx = int.parse(data["Index"]);
|
||||
var currentTotal = profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["GroupID"])!.totalMessages;
|
||||
|
||||
// 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) {
|
||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["GroupID"])!.totalMessages = idx + 1;
|
||||
|
||||
//if not currently open
|
||||
if (appState.selectedProfile != data["ProfileOnion"] || appState.selectedConversation != data["GroupID"]) {
|
||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["GroupID"])!.unreadMessages++;
|
||||
}
|
||||
|
||||
var timestampSent = DateTime.tryParse(data['TimestampSent'])!;
|
||||
// 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
|
||||
// These can obviously be very different for legitimate reasons.
|
||||
// We also maintain a relative hash-link through PreviousMessageSignature which is the ground truth for
|
||||
// order.
|
||||
// In the future we will want to combine these 3 ordering mechanisms into a cohesive view of the timeline
|
||||
// For now we perform some minimal checks on the sent timestamp to use to provide a useful ordering for honest contacts
|
||||
// and ensure that malicious contacts in groups can only set this timestamp to a value within the range of `last seen message time`
|
||||
// and `local now`.
|
||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.updateLastMessageTime(data["GroupID"], timestampSent.toLocal());
|
||||
notificationManager.notify("New Message From Group!");
|
||||
}
|
||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["GroupID"])!.totalMessages++;
|
||||
var timestampSent = DateTime.tryParse(data['TimestampSent'])!;
|
||||
// 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
|
||||
// These can obviously be very different for legitimate reasons.
|
||||
// We also maintain a relative hash-link through PreviousMessageSignature which is the ground truth for
|
||||
// order.
|
||||
// In the future we will want to combine these 3 ordering mechanisms into a cohesive view of the timeline
|
||||
// For now we perform some minimal checks on the sent timestamp to use to provide a useful ordering for honest contacts
|
||||
// and ensure that malicious contacts in groups can only set this timestamp to a value within the range of `last seen message time`
|
||||
// and `local now`.
|
||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.updateLastMessageTime(data["GroupID"], timestampSent.toLocal());
|
||||
notificationManager.notify("New Message From Group!");
|
||||
} else {
|
||||
// from me (already displayed - do not update counter)
|
||||
var idx = data["Signature"];
|
||||
|
@ -180,7 +214,10 @@ class CwtchNotifier {
|
|||
case "MessageCounterResync":
|
||||
var contactHandle = data["RemotePeer"];
|
||||
if (contactHandle == null || contactHandle == "") contactHandle = data["GroupID"];
|
||||
profileCN.getProfile(data["Identity"])?.contactList.getContact(contactHandle)!.totalMessages = int.parse(data["Data"]);
|
||||
var total = int.parse(data["Data"]);
|
||||
if (total != profileCN.getProfile(data["Identity"])?.contactList.getContact(contactHandle)!.totalMessages) {
|
||||
profileCN.getProfile(data["Identity"])?.contactList.getContact(contactHandle)!.totalMessages = total;
|
||||
}
|
||||
break;
|
||||
case "SendMessageToPeerError":
|
||||
// Ignore
|
||||
|
@ -252,7 +289,7 @@ class CwtchNotifier {
|
|||
|
||||
// Retrieve Server Status from Cache...
|
||||
String status = "";
|
||||
ServerInfoState? serverInfoState = profileCN.getProfile(data["ProfileOnion"])!.serverList.getServer(groupInvite["ServerHost"]);
|
||||
RemoteServerInfoState? serverInfoState = profileCN.getProfile(data["ProfileOnion"])!.serverList.getServer(groupInvite["ServerHost"]);
|
||||
if (serverInfoState != null) {
|
||||
status = serverInfoState.status;
|
||||
}
|
||||
|
@ -316,7 +353,7 @@ class CwtchNotifier {
|
|||
}
|
||||
break;
|
||||
case "NewRetValMessageFromPeer":
|
||||
if (data["Path"] == "name") {
|
||||
if (data["Path"] == "name" && data["Data"].toString().trim().length > 0) {
|
||||
// Update locally on the UI...
|
||||
if (profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["RemotePeer"]) != null) {
|
||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(data["RemotePeer"])!.nickname = data["Data"];
|
||||
|
@ -329,7 +366,12 @@ class CwtchNotifier {
|
|||
profileCN.getProfile(data["ProfileOnion"])?.downloadMarkManifest(data["FileKey"]);
|
||||
break;
|
||||
case "FileDownloadProgressUpdate":
|
||||
profileCN.getProfile(data["ProfileOnion"])?.downloadUpdate(data["FileKey"], int.parse(data["Progress"]));
|
||||
var progress = int.parse(data["Progress"]);
|
||||
profileCN.getProfile(data["ProfileOnion"])?.downloadUpdate(data["FileKey"], progress, int.parse(data["FileSizeInChunks"]));
|
||||
// progress == -1 is a "download was interrupted" message and should contain a path
|
||||
if (progress < 0) {
|
||||
profileCN.getProfile(data["ProfileOnion"])?.downloadSetPath(data["FileKey"], data["FilePath"]);
|
||||
}
|
||||
break;
|
||||
case "FileDownloaded":
|
||||
profileCN.getProfile(data["ProfileOnion"])?.downloadMarkFinished(data["FileKey"], data["FilePath"]);
|
||||
|
|
|
@ -42,6 +42,9 @@ typedef VoidFromStringStringStringStringStringFn = void Function(Pointer<Utf8>,
|
|||
typedef void_from_string_string_int_int_function = Void Function(Pointer<Utf8>, Int32, Pointer<Utf8>, Int32, Int64, Int64);
|
||||
typedef VoidFromStringStringIntIntFn = void Function(Pointer<Utf8>, int, Pointer<Utf8>, int, int, int);
|
||||
|
||||
typedef void_from_string_string_byte_function = Void Function(Pointer<Utf8>, Int32, Pointer<Utf8>, Int32, Int8);
|
||||
typedef VoidFromStringStringByteFn = void Function(Pointer<Utf8>, int, Pointer<Utf8>, int, int);
|
||||
|
||||
typedef string_to_void_function = Void Function(Pointer<Utf8> str, Int32 length);
|
||||
typedef StringFn = void Function(Pointer<Utf8> dir, int);
|
||||
|
||||
|
@ -410,6 +413,22 @@ class CwtchFfi implements Cwtch {
|
|||
malloc.free(u2);
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void VerifyOrResumeDownload(String profileOnion, String contactHandle, String filekey) {
|
||||
var fn = library.lookup<NativeFunction<void_from_string_string_string_function>>("c_VerifyOrResumeDownload");
|
||||
// ignore: non_constant_identifier_names
|
||||
final VerifyOrResumeDownload = fn.asFunction<VoidFromStringStringStringFn>();
|
||||
final u1 = profileOnion.toNativeUtf8();
|
||||
final u2 = contactHandle.toNativeUtf8();
|
||||
final u3 = filekey.toNativeUtf8();
|
||||
VerifyOrResumeDownload(u1, u1.length, u2, u2.length, u3, u3.length);
|
||||
malloc.free(u1);
|
||||
malloc.free(u2);
|
||||
malloc.free(u3);
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void ResetTor() {
|
||||
|
@ -530,6 +549,139 @@ class CwtchFfi implements Cwtch {
|
|||
malloc.free(u2);
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void SetProfileAttribute(String profile, String key, String val) {
|
||||
var setProfileAttribute = library.lookup<NativeFunction<void_from_string_string_string_function>>("c_SetProfileAttribute");
|
||||
// ignore: non_constant_identifier_names
|
||||
final SetProfileAttribute = setProfileAttribute.asFunction<VoidFromStringStringStringFn>();
|
||||
final u1 = profile.toNativeUtf8();
|
||||
final u2 = key.toNativeUtf8();
|
||||
final u3 = key.toNativeUtf8();
|
||||
SetProfileAttribute(u1, u1.length, u2, u2.length, u3, u3.length);
|
||||
malloc.free(u1);
|
||||
malloc.free(u2);
|
||||
malloc.free(u3);
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void SetContactAttribute(String profile, String contact, String key, String val) {
|
||||
var setContactAttribute = library.lookup<NativeFunction<void_from_string_string_string_string_function>>("c_SetContactAttribute");
|
||||
// ignore: non_constant_identifier_names
|
||||
final SetContactAttribute = setContactAttribute.asFunction<VoidFromStringStringStringStringFn>();
|
||||
final u1 = profile.toNativeUtf8();
|
||||
final u2 = contact.toNativeUtf8();
|
||||
final u3 = key.toNativeUtf8();
|
||||
final u4 = key.toNativeUtf8();
|
||||
SetContactAttribute(u1, u1.length, u2, u2.length, u3, u3.length, u4, u4.length);
|
||||
malloc.free(u1);
|
||||
malloc.free(u2);
|
||||
malloc.free(u3);
|
||||
malloc.free(u4);
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void LoadServers(String password) {
|
||||
var loadServers = library.lookup<NativeFunction<string_to_void_function>>("c_LoadServers");
|
||||
// ignore: non_constant_identifier_names
|
||||
final LoadServers = loadServers.asFunction<StringFn>();
|
||||
final u1 = password.toNativeUtf8();
|
||||
LoadServers(u1, u1.length);
|
||||
malloc.free(u1);
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void CreateServer(String password, String description, bool autostart) {
|
||||
var createServer = library.lookup<NativeFunction<void_from_string_string_byte_function>>("c_CreateServer");
|
||||
// ignore: non_constant_identifier_names
|
||||
final CreateServer = createServer.asFunction<VoidFromStringStringByteFn>();
|
||||
final u1 = password.toNativeUtf8();
|
||||
final u2 = description.toNativeUtf8();
|
||||
CreateServer(u1, u1.length, u2, u2.length, autostart ? 1 : 0);
|
||||
malloc.free(u1);
|
||||
malloc.free(u2);
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void DeleteServer(String serverOnion, String password) {
|
||||
var deleteServer = library.lookup<NativeFunction<string_string_to_void_function>>("c_DeleteServer");
|
||||
// ignore: non_constant_identifier_names
|
||||
final DeleteServer = deleteServer.asFunction<VoidFromStringStringFn>();
|
||||
final u1 = serverOnion.toNativeUtf8();
|
||||
final u2 = password.toNativeUtf8();
|
||||
DeleteServer(u1, u1.length, u2, u2.length);
|
||||
malloc.free(u1);
|
||||
malloc.free(u2);
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void LaunchServers() {
|
||||
var launchServers = library.lookup<NativeFunction<Void Function()>>("c_LaunchServers");
|
||||
// ignore: non_constant_identifier_names
|
||||
final LaunchServers = launchServers.asFunction<void Function()>();
|
||||
LaunchServers();
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void LaunchServer(String serverOnion) {
|
||||
var launchServer = library.lookup<NativeFunction<string_to_void_function>>("c_LaunchServer");
|
||||
// ignore: non_constant_identifier_names
|
||||
final LaunchServer = launchServer.asFunction<StringFn>();
|
||||
final u1 = serverOnion.toNativeUtf8();
|
||||
LaunchServer(u1, u1.length);
|
||||
malloc.free(u1);
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void StopServer(String serverOnion) {
|
||||
var shutdownServer = library.lookup<NativeFunction<string_to_void_function>>("c_StopServer");
|
||||
// ignore: non_constant_identifier_names
|
||||
final ShutdownServer = shutdownServer.asFunction<StringFn>();
|
||||
final u1 = serverOnion.toNativeUtf8();
|
||||
ShutdownServer(u1, u1.length);
|
||||
malloc.free(u1);
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void StopServers() {
|
||||
var shutdownServers = library.lookup<NativeFunction<Void Function()>>("c_StopServers");
|
||||
// ignore: non_constant_identifier_names
|
||||
final ShutdownServers = shutdownServers.asFunction<void Function()>();
|
||||
ShutdownServers();
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void DestroyServers() {
|
||||
var destroyServers = library.lookup<NativeFunction<Void Function()>>("c_DestroyServers");
|
||||
// ignore: non_constant_identifier_names
|
||||
final DestroyServers = destroyServers.asFunction<void Function()>();
|
||||
DestroyServers();
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void SetServerAttribute(String serverOnion, String key, String val) {
|
||||
var setServerAttribute = library.lookup<NativeFunction<void_from_string_string_string_function>>("c_SetServerAttribute");
|
||||
// ignore: non_constant_identifier_names
|
||||
final SetServerAttribute = setServerAttribute.asFunction<VoidFromStringStringStringFn>();
|
||||
final u1 = serverOnion.toNativeUtf8();
|
||||
final u2 = key.toNativeUtf8();
|
||||
final u3 = val.toNativeUtf8();
|
||||
SetServerAttribute(u1, u1.length, u2, u2.length, u3, u3.length);
|
||||
malloc.free(u1);
|
||||
malloc.free(u2);
|
||||
malloc.free(u3);
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
Future<void> Shutdown() async {
|
||||
|
|
|
@ -154,6 +154,12 @@ class CwtchGomobile implements Cwtch {
|
|||
cwtchPlatform.invokeMethod("CheckDownloadStatus", {"ProfileOnion": profileOnion, "fileKey": fileKey});
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void VerifyOrResumeDownload(String profileOnion, String contactHandle, String filekey) {
|
||||
cwtchPlatform.invokeMethod("VerifyOrResumeDownload", {"ProfileOnion": profileOnion, "handle": contactHandle, "filekey": filekey});
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void ResetTor() {
|
||||
|
@ -202,6 +208,72 @@ class CwtchGomobile implements Cwtch {
|
|||
}
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void SetProfileAttribute(String profile, String key, String val) {
|
||||
cwtchPlatform.invokeMethod("SetProfileAttribute", {"ProfileOnion": profile, "Key": key, "Val": val});
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void SetContactAttribute(String profile, String contact, String key, String val) {
|
||||
cwtchPlatform.invokeMethod("SetContactAttribute", {"ProfileOnion": profile, "Contact": contact, "Key": key, "Val": val});
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void LoadServers(String password) {
|
||||
cwtchPlatform.invokeMethod("LoadServers", {"Password": password});
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void CreateServer(String password, String description, bool autostart) {
|
||||
cwtchPlatform.invokeMethod("CreateServer", {"Password": password, "Description": description, "Autostart": autostart});
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void DeleteServer(String serverOnion, String password) {
|
||||
cwtchPlatform.invokeMethod("DeleteServer", {"ServerOnion": serverOnion, "Password": password});
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void LaunchServers() {
|
||||
cwtchPlatform.invokeMethod("LaunchServers", {});
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void LaunchServer(String serverOnion) {
|
||||
cwtchPlatform.invokeMethod("LaunchServer", {"ServerOnion": serverOnion});
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void StopServer(String serverOnion) {
|
||||
cwtchPlatform.invokeMethod("StopServer", {"ServerOnion": serverOnion});
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void StopServers() {
|
||||
cwtchPlatform.invokeMethod("StopServers", {});
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void DestroyServers() {
|
||||
cwtchPlatform.invokeMethod("DestroyServers", {});
|
||||
}
|
||||
|
||||
@override
|
||||
// ignore: non_constant_identifier_names
|
||||
void SetServerAttribute(String serverOnion, String key, String val) {
|
||||
cwtchPlatform.invokeMethod("SetServerAttribute", {"ServerOnion": serverOnion, "Key": key, "Val": val});
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> Shutdown() async {
|
||||
print("gomobile.dart Shutdown");
|
||||
cwtchPlatform.invokeMethod("Shutdown", {});
|
||||
|
|
|
@ -21,6 +21,27 @@ class ErrorHandler extends ChangeNotifier {
|
|||
bool deleteProfileError = false;
|
||||
bool deleteProfileSuccess = false;
|
||||
|
||||
static const String deletedServerErrorPrefix = "deletedserver";
|
||||
bool deletedServerError = false;
|
||||
bool deletedServerSuccess = false;
|
||||
|
||||
reset() {
|
||||
invalidImportStringError = false;
|
||||
contactAlreadyExistsError = false;
|
||||
explicitAddContactSuccess = false;
|
||||
|
||||
importBundleError = false;
|
||||
importBundleSuccess = false;
|
||||
|
||||
deleteProfileError = false;
|
||||
deleteProfileSuccess = false;
|
||||
|
||||
deletedServerError = false;
|
||||
deletedServerSuccess = false;
|
||||
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Called by the event bus.
|
||||
handleUpdate(String error) {
|
||||
var parts = error.split(".");
|
||||
|
@ -37,6 +58,8 @@ class ErrorHandler extends ChangeNotifier {
|
|||
case deleteProfileErrorPrefix:
|
||||
handleDeleteProfileError(errorType);
|
||||
break;
|
||||
case deletedServerErrorPrefix:
|
||||
handleDeletedServerError(errorType);
|
||||
}
|
||||
|
||||
notifyListeners();
|
||||
|
@ -91,4 +114,19 @@ class ErrorHandler extends ChangeNotifier {
|
|||
break;
|
||||
}
|
||||
}
|
||||
|
||||
handleDeletedServerError(String errorType) {
|
||||
// reset
|
||||
deletedServerError = false;
|
||||
deletedServerSuccess = false;
|
||||
|
||||
switch (errorType) {
|
||||
case successErrorType:
|
||||
deletedServerSuccess = true;
|
||||
break;
|
||||
default:
|
||||
deletedServerError = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,37 @@
|
|||
{
|
||||
"@@locale": "de",
|
||||
"@@last_modified": "2021-09-21T23:09:19+02:00",
|
||||
"@@last_modified": "2021-11-05T21:38:20+01:00",
|
||||
"copyServerKeys": "Copy keys",
|
||||
"verfiyResumeButton": "Verify\/resume",
|
||||
"fileCheckingStatus": "Checking download status",
|
||||
"fileInterrupted": "Interrupted",
|
||||
"fileSavedTo": "Saved to",
|
||||
"plainServerDescription": "We recommend that you protect your Cwtch servers with a password. If you do not set a password on this server then anyone who has access to this device may be able to access information about this server, including sensitive cryptographic keys.",
|
||||
"encryptedServerDescription": "Encrypting a server with a password protects it from other people who may also use this device. Encrypted servers cannot be decrypted, displayed or accessed until the correct password is entered to unlock them.",
|
||||
"deleteServerConfirmBtn": "Really delete server",
|
||||
"deleteServerSuccess": "Successfully deleted server",
|
||||
"enterCurrentPasswordForDeleteServer": "Please enter current password to delete this server",
|
||||
"copyAddress": "Copy Address",
|
||||
"settingServersDescription": "The hosting servers experiment enables hosting and managing Cwtch servers",
|
||||
"settingServers": "Hosting Servers",
|
||||
"enterServerPassword": "Enter password to unlock server",
|
||||
"unlockProfileTip": "Please create or unlock a profile to begin!",
|
||||
"unlockServerTip": "Please create or unlock a server to begin!",
|
||||
"addServerTooltip": "Add new server",
|
||||
"serversManagerTitleShort": "Servers",
|
||||
"serversManagerTitleLong": "Servers You Host",
|
||||
"saveServerButton": "Save Server",
|
||||
"serverAutostartDescription": "Controls if the application will automatically launch the server on start",
|
||||
"serverAutostartLabel": "Autostart",
|
||||
"serverEnabledDescription": "Start or stop the server",
|
||||
"serverEnabled": "Server Enabled",
|
||||
"serverDescriptionDescription": "Your description of the server for personal management use only, will never be shared",
|
||||
"serverDescriptionLabel": "Server Description",
|
||||
"serverAddress": "Server Address",
|
||||
"editServerTitle": "Edit Server",
|
||||
"addServerTitle": "Add Server",
|
||||
"titleManageProfilesShort": "Profiles",
|
||||
"descriptionStreamerMode": "If turned on, this option makes the app more visually private for streaming or presenting with, for example, hiding profile and contact addresses",
|
||||
"descriptionFileSharing": "The file sharing experiment allows you to send and receive files from Cwtch contacts and groups. Note that sharing a file with a group will result in members of that group connecting with you directly over Cwtch to download it.",
|
||||
"settingFileSharing": "File Sharing",
|
||||
"tooltipSendFile": "Send File",
|
||||
|
@ -9,10 +40,9 @@
|
|||
"messageEnableFileSharing": "Enable the file sharing experiment to view this message.",
|
||||
"labelFilesize": "Size",
|
||||
"labelFilename": "Filename",
|
||||
"downloadFileButton": "Download",
|
||||
"downloadFileButton": "Herunterladen",
|
||||
"openFolderButton": "Open Folder",
|
||||
"retrievingManifestMessage": "Retrieving file information...",
|
||||
"descriptionStreamerMode": "If turned on, this option makes the app more visually private for streaming or presenting with, for example, hiding profile and contact onions",
|
||||
"streamerModeLabel": "Streamer\/Presentation Mode",
|
||||
"archiveConversation": "Archive this Conversation",
|
||||
"profileOnionLabel": "Senden Sie diese Adresse an Peers, mit denen Sie sich verbinden möchten",
|
||||
|
|
|
@ -1,6 +1,37 @@
|
|||
{
|
||||
"@@locale": "en",
|
||||
"@@last_modified": "2021-09-21T23:09:19+02:00",
|
||||
"@@last_modified": "2021-11-05T21:38:20+01:00",
|
||||
"copyServerKeys": "Copy keys",
|
||||
"verfiyResumeButton": "Verify\/resume",
|
||||
"fileCheckingStatus": "Checking download status",
|
||||
"fileInterrupted": "Interrupted",
|
||||
"fileSavedTo": "Saved to",
|
||||
"plainServerDescription": "We recommend that you protect your Cwtch servers with a password. If you do not set a password on this server then anyone who has access to this device may be able to access information about this server, including sensitive cryptographic keys.",
|
||||
"encryptedServerDescription": "Encrypting a server with a password protects it from other people who may also use this device. Encrypted servers cannot be decrypted, displayed or accessed until the correct password is entered to unlock them.",
|
||||
"deleteServerConfirmBtn": "Really delete server",
|
||||
"deleteServerSuccess": "Successfully deleted server",
|
||||
"enterCurrentPasswordForDeleteServer": "Please enter current password to delete this server",
|
||||
"copyAddress": "Copy Address",
|
||||
"settingServersDescription": "The hosting servers experiment enables hosting and managing Cwtch servers",
|
||||
"settingServers": "Hosting Servers",
|
||||
"enterServerPassword": "Enter password to unlock server",
|
||||
"unlockProfileTip": "Please create or unlock a profile to begin!",
|
||||
"unlockServerTip": "Please create or unlock a server to begin!",
|
||||
"addServerTooltip": "Add new server",
|
||||
"serversManagerTitleShort": "Servers",
|
||||
"serversManagerTitleLong": "Servers You Host",
|
||||
"saveServerButton": "Save Server",
|
||||
"serverAutostartDescription": "Controls if the application will automatically launch the server on start",
|
||||
"serverAutostartLabel": "Autostart",
|
||||
"serverEnabledDescription": "Start or stop the server",
|
||||
"serverEnabled": "Server Enabled",
|
||||
"serverDescriptionDescription": "Your description of the server for personal management use only, will never be shared",
|
||||
"serverDescriptionLabel": "Server Description",
|
||||
"serverAddress": "Server Address",
|
||||
"editServerTitle": "Edit Server",
|
||||
"addServerTitle": "Add Server",
|
||||
"titleManageProfilesShort": "Profiles",
|
||||
"descriptionStreamerMode": "If turned on, this option makes the app more visually private for streaming or presenting with, for example, hiding profile and contact addresses",
|
||||
"descriptionFileSharing": "The file sharing experiment allows you to send and receive files from Cwtch contacts and groups. Note that sharing a file with a group will result in members of that group connecting with you directly over Cwtch to download it.",
|
||||
"settingFileSharing": "File Sharing",
|
||||
"tooltipSendFile": "Send File",
|
||||
|
@ -12,7 +43,6 @@
|
|||
"downloadFileButton": "Download",
|
||||
"openFolderButton": "Open Folder",
|
||||
"retrievingManifestMessage": "Retrieving file information...",
|
||||
"descriptionStreamerMode": "If turned on, this option makes the app more visually private for streaming or presenting with, for example, hiding profile and contact onions",
|
||||
"streamerModeLabel": "Streamer\/Presentation Mode",
|
||||
"archiveConversation": "Archive this Conversation",
|
||||
"profileOnionLabel": "Send this address to people you want to connect with",
|
||||
|
|
|
@ -1,6 +1,37 @@
|
|||
{
|
||||
"@@locale": "es",
|
||||
"@@last_modified": "2021-09-21T23:09:19+02:00",
|
||||
"@@last_modified": "2021-11-05T21:38:20+01:00",
|
||||
"copyServerKeys": "Copy keys",
|
||||
"verfiyResumeButton": "Verify\/resume",
|
||||
"fileCheckingStatus": "Checking download status",
|
||||
"fileInterrupted": "Interrupted",
|
||||
"fileSavedTo": "Saved to",
|
||||
"plainServerDescription": "We recommend that you protect your Cwtch servers with a password. If you do not set a password on this server then anyone who has access to this device may be able to access information about this server, including sensitive cryptographic keys.",
|
||||
"encryptedServerDescription": "Encrypting a server with a password protects it from other people who may also use this device. Encrypted servers cannot be decrypted, displayed or accessed until the correct password is entered to unlock them.",
|
||||
"deleteServerConfirmBtn": "Really delete server",
|
||||
"deleteServerSuccess": "Successfully deleted server",
|
||||
"enterCurrentPasswordForDeleteServer": "Please enter current password to delete this server",
|
||||
"copyAddress": "Copy Address",
|
||||
"settingServersDescription": "The hosting servers experiment enables hosting and managing Cwtch servers",
|
||||
"settingServers": "Hosting Servers",
|
||||
"enterServerPassword": "Enter password to unlock server",
|
||||
"unlockProfileTip": "Please create or unlock a profile to begin!",
|
||||
"unlockServerTip": "Please create or unlock a server to begin!",
|
||||
"addServerTooltip": "Add new server",
|
||||
"serversManagerTitleShort": "Servers",
|
||||
"serversManagerTitleLong": "Servers You Host",
|
||||
"saveServerButton": "Save Server",
|
||||
"serverAutostartDescription": "Controls if the application will automatically launch the server on start",
|
||||
"serverAutostartLabel": "Autostart",
|
||||
"serverEnabledDescription": "Start or stop the server",
|
||||
"serverEnabled": "Server Enabled",
|
||||
"serverDescriptionDescription": "Your description of the server for personal management use only, will never be shared",
|
||||
"serverDescriptionLabel": "Server Description",
|
||||
"serverAddress": "Server Address",
|
||||
"editServerTitle": "Edit Server",
|
||||
"addServerTitle": "Add Server",
|
||||
"titleManageProfilesShort": "Profiles",
|
||||
"descriptionStreamerMode": "If turned on, this option makes the app more visually private for streaming or presenting with, for example, hiding profile and contact addresses",
|
||||
"descriptionFileSharing": "The file sharing experiment allows you to send and receive files from Cwtch contacts and groups. Note that sharing a file with a group will result in members of that group connecting with you directly over Cwtch to download it.",
|
||||
"settingFileSharing": "File Sharing",
|
||||
"tooltipSendFile": "Send File",
|
||||
|
@ -12,7 +43,6 @@
|
|||
"downloadFileButton": "Download",
|
||||
"openFolderButton": "Open Folder",
|
||||
"retrievingManifestMessage": "Retrieving file information...",
|
||||
"descriptionStreamerMode": "If turned on, this option makes the app more visually private for streaming or presenting with, for example, hiding profile and contact onions",
|
||||
"streamerModeLabel": "Streamer\/Presentation Mode",
|
||||
"archiveConversation": "Archive this Conversation",
|
||||
"profileOnionLabel": "Envía esta dirección a los contactos con los que quieras conectarte",
|
||||
|
|
|
@ -1,18 +1,48 @@
|
|||
{
|
||||
"@@locale": "fr",
|
||||
"@@last_modified": "2021-09-21T23:09:19+02:00",
|
||||
"descriptionFileSharing": "The file sharing experiment allows you to send and receive files from Cwtch contacts and groups. Note that sharing a file with a group will result in members of that group connecting with you directly over Cwtch to download it.",
|
||||
"settingFileSharing": "File Sharing",
|
||||
"tooltipSendFile": "Send File",
|
||||
"messageFileOffered": "Contact is offering to send you a file",
|
||||
"messageFileSent": "You sent a file",
|
||||
"messageEnableFileSharing": "Enable the file sharing experiment to view this message.",
|
||||
"labelFilesize": "Size",
|
||||
"labelFilename": "Filename",
|
||||
"downloadFileButton": "Download",
|
||||
"openFolderButton": "Open Folder",
|
||||
"retrievingManifestMessage": "Retrieving file information...",
|
||||
"descriptionStreamerMode": "Si elle est activée, cette option donne un rendu visuel plus privé à l'application pour la diffusion ou la présentation, par exemple en masquant les profils et les contacts.",
|
||||
"@@last_modified": "2021-11-05T21:38:20+01:00",
|
||||
"copyServerKeys": "Copy keys",
|
||||
"verfiyResumeButton": "Vérifier\/reprendre",
|
||||
"fileCheckingStatus": "Vérification de l'état du téléchargement",
|
||||
"fileInterrupted": "Interrompu",
|
||||
"fileSavedTo": "Enregistré dans",
|
||||
"plainServerDescription": "Nous vous recommandons de protéger vos serveurs Cwtch par un mot de passe. Si vous ne définissez pas de mot de passe sur ce serveur, toute personne ayant accès à cet appareil peut être en mesure d'accéder aux informations concernant ce serveur, y compris les clés cryptographiques sensibles.",
|
||||
"encryptedServerDescription": "Le chiffrement d’un serveur avec un mot de passe le protège des autres personnes qui peuvent également utiliser cet appareil. Les serveurs cryptés ne peuvent pas être déchiffrés, affichés ou accessibles tant que le mot de passe correct n’est pas entré pour les déverrouiller.",
|
||||
"deleteServerConfirmBtn": "Supprimer vraiment le serveur",
|
||||
"deleteServerSuccess": "Le serveur a été supprimé avec succès",
|
||||
"enterCurrentPasswordForDeleteServer": "Veuillez saisir le mot de passe actuel pour supprimer ce serveur",
|
||||
"copyAddress": "Copier l'adresse",
|
||||
"settingServersDescription": "L'expérience des serveurs d'hébergement permet d'héberger et de gérer les serveurs Cwtch.",
|
||||
"settingServers": "Serveurs d'hébergement",
|
||||
"enterServerPassword": "Entrez le mot de passe pour déverrouiller le serveur",
|
||||
"unlockProfileTip": "Veuillez créer ou déverrouiller un profil pour commencer !",
|
||||
"unlockServerTip": "Veuillez créer ou déverrouiller un serveur pour commencer !",
|
||||
"addServerTooltip": "Ajouter un nouveau serveur",
|
||||
"serversManagerTitleShort": "Serveurs",
|
||||
"serversManagerTitleLong": "Serveurs que vous hébergez",
|
||||
"saveServerButton": "Enregistrer le serveur",
|
||||
"serverAutostartDescription": "Contrôle si l'application lance automatiquement le serveur au démarrage.",
|
||||
"serverAutostartLabel": "Démarrage automatique",
|
||||
"serverEnabledDescription": "Démarrer ou arrêter le serveur",
|
||||
"serverEnabled": "Serveur activé",
|
||||
"serverDescriptionDescription": "Votre description du serveur est à des fins de gestion personnelle uniquement, elle ne sera jamais partagée.",
|
||||
"serverDescriptionLabel": "Description du serveur",
|
||||
"serverAddress": "Adresse du serveur",
|
||||
"editServerTitle": "Modifier le serveur",
|
||||
"addServerTitle": "Ajouter un serveur",
|
||||
"titleManageProfilesShort": "Profils",
|
||||
"descriptionStreamerMode": "Si elle est activée, cette option donne un rendu visuel plus privé à l'application pour la diffusion en direct ou la présentation, par exemple, en masquant profil et adresses de contacts.",
|
||||
"descriptionFileSharing": "L'expérience de partage de fichiers vous permet d'envoyer et de recevoir des fichiers à partir de contacts et de groupes Cwtch. Notez que si vous partagez un fichier avec un groupe, les membres de ce groupe se connecteront avec vous directement via Cwtch pour le télécharger.",
|
||||
"settingFileSharing": "Partage de fichiers",
|
||||
"tooltipSendFile": "Envoyer le fichier",
|
||||
"messageFileOffered": "Contact vous propose de vous envoyer un fichier",
|
||||
"messageFileSent": "Vous avez envoyé un fichier",
|
||||
"messageEnableFileSharing": "Activez l'expérience de partage de fichiers pour afficher ce message.",
|
||||
"labelFilesize": "Taille",
|
||||
"labelFilename": "Nom de fichier",
|
||||
"downloadFileButton": "Télécharger",
|
||||
"openFolderButton": "Ouvrir le dossier",
|
||||
"retrievingManifestMessage": "Récupération des informations sur le fichier...",
|
||||
"streamerModeLabel": "Mode Streamer\/Présentation",
|
||||
"archiveConversation": "Archiver cette conversation",
|
||||
"profileOnionLabel": "Envoyez cette adresse aux personnes avec lesquelles vous souhaitez entrer en contact.",
|
||||
|
|
|
@ -1,10 +1,41 @@
|
|||
{
|
||||
"@@locale": "it",
|
||||
"@@last_modified": "2021-09-21T23:09:19+02:00",
|
||||
"descriptionFileSharing": "The file sharing experiment allows you to send and receive files from Cwtch contacts and groups. Note that sharing a file with a group will result in members of that group connecting with you directly over Cwtch to download it.",
|
||||
"settingFileSharing": "File Sharing",
|
||||
"tooltipSendFile": "Send File",
|
||||
"messageFileOffered": "Contact is offering to send you a file",
|
||||
"@@last_modified": "2021-11-05T21:38:20+01:00",
|
||||
"copyServerKeys": "Copy keys",
|
||||
"verfiyResumeButton": "Verify\/resume",
|
||||
"fileCheckingStatus": "Checking download status",
|
||||
"fileInterrupted": "Interrupted",
|
||||
"fileSavedTo": "Saved to",
|
||||
"plainServerDescription": "We recommend that you protect your Cwtch servers with a password. If you do not set a password on this server then anyone who has access to this device may be able to access information about this server, including sensitive cryptographic keys.",
|
||||
"encryptedServerDescription": "Encrypting a server with a password protects it from other people who may also use this device. Encrypted servers cannot be decrypted, displayed or accessed until the correct password is entered to unlock them.",
|
||||
"deleteServerConfirmBtn": "Really delete server",
|
||||
"deleteServerSuccess": "Successfully deleted server",
|
||||
"enterCurrentPasswordForDeleteServer": "Please enter current password to delete this server",
|
||||
"copyAddress": "Copy Address",
|
||||
"settingServersDescription": "The hosting servers experiment enables hosting and managing Cwtch servers",
|
||||
"settingServers": "Hosting Servers",
|
||||
"enterServerPassword": "Enter password to unlock server",
|
||||
"unlockProfileTip": "Please create or unlock a profile to begin!",
|
||||
"unlockServerTip": "Please create or unlock a server to begin!",
|
||||
"addServerTooltip": "Add new server",
|
||||
"serversManagerTitleShort": "Servers",
|
||||
"serversManagerTitleLong": "Servers You Host",
|
||||
"saveServerButton": "Save Server",
|
||||
"serverAutostartDescription": "Controls if the application will automatically launch the server on start",
|
||||
"serverAutostartLabel": "Autostart",
|
||||
"serverEnabledDescription": "Start or stop the server",
|
||||
"serverEnabled": "Server Enabled",
|
||||
"serverDescriptionDescription": "Your description of the server for personal management use only, will never be shared",
|
||||
"serverDescriptionLabel": "Server Description",
|
||||
"serverAddress": "Server Address",
|
||||
"editServerTitle": "Edit Server",
|
||||
"addServerTitle": "Add Server",
|
||||
"titleManageProfilesShort": "Profiles",
|
||||
"descriptionStreamerMode": "If turned on, this option makes the app more visually private for streaming or presenting with, for example, hiding profile and contact addresses",
|
||||
"descriptionFileSharing": "L'esperimento di condivisione dei file ti consente di inviare e ricevere file dai contatti e dai gruppi di Cwtch. Tieni presente che la condivisione di un file con un gruppo farà sì che i membri di quel gruppo si colleghino con te direttamente su Cwtch per scaricarlo.",
|
||||
"settingFileSharing": "Condivisione file",
|
||||
"tooltipSendFile": "Invia file",
|
||||
"messageFileOffered": "Il contatto offre l'invio di un file",
|
||||
"messageFileSent": "You sent a file",
|
||||
"messageEnableFileSharing": "Enable the file sharing experiment to view this message.",
|
||||
"labelFilesize": "Size",
|
||||
|
@ -12,7 +43,6 @@
|
|||
"downloadFileButton": "Download",
|
||||
"openFolderButton": "Open Folder",
|
||||
"retrievingManifestMessage": "Retrieving file information...",
|
||||
"descriptionStreamerMode": "If turned on, this option makes the app more visually private for streaming or presenting with, for example, hiding profile and contact onions",
|
||||
"streamerModeLabel": "Streamer\/Presentation Mode",
|
||||
"archiveConversation": "Archive this Conversation",
|
||||
"profileOnionLabel": "Inviare questo indirizzo ai peer con cui si desidera connettersi",
|
||||
|
|
|
@ -1,20 +1,50 @@
|
|||
{
|
||||
"@@locale": "pl",
|
||||
"@@last_modified": "2021-09-21T23:09:19+02:00",
|
||||
"descriptionFileSharing": "The file sharing experiment allows you to send and receive files from Cwtch contacts and groups. Note that sharing a file with a group will result in members of that group connecting with you directly over Cwtch to download it.",
|
||||
"settingFileSharing": "File Sharing",
|
||||
"tooltipSendFile": "Send File",
|
||||
"messageFileOffered": "Contact is offering to send you a file",
|
||||
"messageFileSent": "You sent a file",
|
||||
"messageEnableFileSharing": "Enable the file sharing experiment to view this message.",
|
||||
"labelFilesize": "Size",
|
||||
"labelFilename": "Filename",
|
||||
"downloadFileButton": "Download",
|
||||
"openFolderButton": "Open Folder",
|
||||
"retrievingManifestMessage": "Retrieving file information...",
|
||||
"descriptionStreamerMode": "If turned on, this option makes the app more visually private for streaming or presenting with, for example, hiding profile and contact onions",
|
||||
"streamerModeLabel": "Streamer\/Presentation Mode",
|
||||
"archiveConversation": "Archive this Conversation",
|
||||
"@@last_modified": "2021-11-05T21:38:20+01:00",
|
||||
"copyServerKeys": "Copy keys",
|
||||
"verfiyResumeButton": "Verify\/resume",
|
||||
"fileCheckingStatus": "Checking download status",
|
||||
"fileInterrupted": "Interrupted",
|
||||
"fileSavedTo": "Saved to",
|
||||
"plainServerDescription": "We recommend that you protect your Cwtch servers with a password. If you do not set a password on this server then anyone who has access to this device may be able to access information about this server, including sensitive cryptographic keys.",
|
||||
"encryptedServerDescription": "Encrypting a server with a password protects it from other people who may also use this device. Encrypted servers cannot be decrypted, displayed or accessed until the correct password is entered to unlock them.",
|
||||
"deleteServerConfirmBtn": "Really delete server",
|
||||
"deleteServerSuccess": "Successfully deleted server",
|
||||
"enterCurrentPasswordForDeleteServer": "Please enter current password to delete this server",
|
||||
"copyAddress": "Copy Address",
|
||||
"settingServersDescription": "The hosting servers experiment enables hosting and managing Cwtch servers",
|
||||
"settingServers": "Hosting Servers",
|
||||
"enterServerPassword": "Enter password to unlock server",
|
||||
"unlockProfileTip": "Please create or unlock a profile to begin!",
|
||||
"unlockServerTip": "Please create or unlock a server to begin!",
|
||||
"addServerTooltip": "Add new server",
|
||||
"serversManagerTitleShort": "Servers",
|
||||
"serversManagerTitleLong": "Servers You Host",
|
||||
"saveServerButton": "Save Server",
|
||||
"serverAutostartDescription": "Controls if the application will automatically launch the server on start",
|
||||
"serverAutostartLabel": "Autostart",
|
||||
"serverEnabledDescription": "Start or stop the server",
|
||||
"serverEnabled": "Server Enabled",
|
||||
"serverDescriptionDescription": "Your description of the server for personal management use only, will never be shared",
|
||||
"serverDescriptionLabel": "Server Description",
|
||||
"serverAddress": "Server Address",
|
||||
"editServerTitle": "Edit Server",
|
||||
"addServerTitle": "Add Server",
|
||||
"titleManageProfilesShort": "Profile",
|
||||
"descriptionStreamerMode": "If turned on, this option makes the app more visually private for streaming or presenting with, for example, hiding profile and contact addresses",
|
||||
"descriptionFileSharing": "Eksperyment udostępniania plików pozwala na wysyłanie i odbieranie plików od kontaktów i grup Cwtch. Zauważ, że udostępnienie pliku grupie spowoduje, że członkowie tej grupy połączą się z Tobą bezpośrednio przez Cwtch, aby go pobrać.",
|
||||
"settingFileSharing": "Udostępnianie plików",
|
||||
"tooltipSendFile": "Wyślij plik",
|
||||
"messageFileOffered": "Kontakt proponuje wysłanie Ci pliku",
|
||||
"messageFileSent": "Plik został wysłany",
|
||||
"messageEnableFileSharing": "Włącz eksperyment udostępniania plików, aby wyświetlić tę wiadomość.",
|
||||
"labelFilesize": "Rozmiar",
|
||||
"labelFilename": "Nazwa pliku",
|
||||
"downloadFileButton": "Pobierz",
|
||||
"openFolderButton": "Otwórz folder",
|
||||
"retrievingManifestMessage": "Pobieranie informacji o pliku...",
|
||||
"streamerModeLabel": "Tryb streamera\/prezentacji",
|
||||
"archiveConversation": "Zarchiwizuj tę rozmowę",
|
||||
"profileOnionLabel": "Send this address to contacts you want to connect with",
|
||||
"addPeerTab": "Add a contact",
|
||||
"addPeer": "Add Contact",
|
||||
|
@ -27,7 +57,7 @@
|
|||
"dontSavePeerHistory": "Delete History",
|
||||
"unblockBtn": "Unblock Contact",
|
||||
"blockUnknownLabel": "Block Unknown Contacts",
|
||||
"blockUnknownConnectionsEnabledDescription": "Connections from unknown contacts are blocked. You can change this in Settings",
|
||||
"blockUnknownConnectionsEnabledDescription": "Połączenia od nieznanych kontaktów są blokowane. Można to zmienić w Ustawieniach",
|
||||
"networkStatusConnecting": "Connecting to network and contacts...",
|
||||
"showMessageButton": "Show Message",
|
||||
"blockedMessageMessage": "This message is from a profile you have blocked.",
|
||||
|
|
|
@ -1,6 +1,37 @@
|
|||
{
|
||||
"@@locale": "pt",
|
||||
"@@last_modified": "2021-09-21T23:09:19+02:00",
|
||||
"@@last_modified": "2021-11-05T21:38:20+01:00",
|
||||
"copyServerKeys": "Copy keys",
|
||||
"verfiyResumeButton": "Verify\/resume",
|
||||
"fileCheckingStatus": "Checking download status",
|
||||
"fileInterrupted": "Interrupted",
|
||||
"fileSavedTo": "Saved to",
|
||||
"plainServerDescription": "We recommend that you protect your Cwtch servers with a password. If you do not set a password on this server then anyone who has access to this device may be able to access information about this server, including sensitive cryptographic keys.",
|
||||
"encryptedServerDescription": "Encrypting a server with a password protects it from other people who may also use this device. Encrypted servers cannot be decrypted, displayed or accessed until the correct password is entered to unlock them.",
|
||||
"deleteServerConfirmBtn": "Really delete server",
|
||||
"deleteServerSuccess": "Successfully deleted server",
|
||||
"enterCurrentPasswordForDeleteServer": "Please enter current password to delete this server",
|
||||
"copyAddress": "Copy Address",
|
||||
"settingServersDescription": "The hosting servers experiment enables hosting and managing Cwtch servers",
|
||||
"settingServers": "Hosting Servers",
|
||||
"enterServerPassword": "Enter password to unlock server",
|
||||
"unlockProfileTip": "Please create or unlock a profile to begin!",
|
||||
"unlockServerTip": "Please create or unlock a server to begin!",
|
||||
"addServerTooltip": "Add new server",
|
||||
"serversManagerTitleShort": "Servers",
|
||||
"serversManagerTitleLong": "Servers You Host",
|
||||
"saveServerButton": "Save Server",
|
||||
"serverAutostartDescription": "Controls if the application will automatically launch the server on start",
|
||||
"serverAutostartLabel": "Autostart",
|
||||
"serverEnabledDescription": "Start or stop the server",
|
||||
"serverEnabled": "Server Enabled",
|
||||
"serverDescriptionDescription": "Your description of the server for personal management use only, will never be shared",
|
||||
"serverDescriptionLabel": "Server Description",
|
||||
"serverAddress": "Server Address",
|
||||
"editServerTitle": "Edit Server",
|
||||
"addServerTitle": "Add Server",
|
||||
"titleManageProfilesShort": "Profiles",
|
||||
"descriptionStreamerMode": "If turned on, this option makes the app more visually private for streaming or presenting with, for example, hiding profile and contact addresses",
|
||||
"descriptionFileSharing": "The file sharing experiment allows you to send and receive files from Cwtch contacts and groups. Note that sharing a file with a group will result in members of that group connecting with you directly over Cwtch to download it.",
|
||||
"settingFileSharing": "File Sharing",
|
||||
"tooltipSendFile": "Send File",
|
||||
|
@ -12,7 +43,6 @@
|
|||
"downloadFileButton": "Download",
|
||||
"openFolderButton": "Open Folder",
|
||||
"retrievingManifestMessage": "Retrieving file information...",
|
||||
"descriptionStreamerMode": "If turned on, this option makes the app more visually private for streaming or presenting with, for example, hiding profile and contact onions",
|
||||
"streamerModeLabel": "Streamer\/Presentation Mode",
|
||||
"archiveConversation": "Archive this Conversation",
|
||||
"profileOnionLabel": "Send this address to contacts you want to connect with",
|
||||
|
|
|
@ -17,6 +17,7 @@ import 'cwtch/cwtch.dart';
|
|||
import 'cwtch/cwtchNotifier.dart';
|
||||
import 'licenses.dart';
|
||||
import 'model.dart';
|
||||
import 'models/servers.dart';
|
||||
import 'views/profilemgrview.dart';
|
||||
import 'views/splashView.dart';
|
||||
import 'dart:io' show Platform, exit;
|
||||
|
@ -27,6 +28,7 @@ var globalSettings = Settings(Locale("en", ''), OpaqueDark());
|
|||
var globalErrorHandler = ErrorHandler();
|
||||
var globalTorStatus = TorStatus();
|
||||
var globalAppState = AppState();
|
||||
var globalServersList = ServerListState();
|
||||
|
||||
void main() {
|
||||
print("Cwtch version: ${EnvironmentConfig.BUILD_VER} built on: ${EnvironmentConfig.BUILD_DATE}");
|
||||
|
@ -62,13 +64,13 @@ class FlwtchState extends State<Flwtch> {
|
|||
shutdownMethodChannel.setMethodCallHandler(modalShutdown);
|
||||
print("initState: creating cwtchnotifier, ffi");
|
||||
if (Platform.isAndroid) {
|
||||
var cwtchNotifier = new CwtchNotifier(profs, globalSettings, globalErrorHandler, globalTorStatus, NullNotificationsManager(), globalAppState);
|
||||
var cwtchNotifier = new CwtchNotifier(profs, globalSettings, globalErrorHandler, globalTorStatus, NullNotificationsManager(), globalAppState, globalServersList);
|
||||
cwtch = CwtchGomobile(cwtchNotifier);
|
||||
} else if (Platform.isLinux) {
|
||||
var cwtchNotifier = new CwtchNotifier(profs, globalSettings, globalErrorHandler, globalTorStatus, newDesktopNotificationsManager(), globalAppState);
|
||||
var cwtchNotifier = new CwtchNotifier(profs, globalSettings, globalErrorHandler, globalTorStatus, newDesktopNotificationsManager(), globalAppState, globalServersList);
|
||||
cwtch = CwtchFfi(cwtchNotifier);
|
||||
} else {
|
||||
var cwtchNotifier = new CwtchNotifier(profs, globalSettings, globalErrorHandler, globalTorStatus, NullNotificationsManager(), globalAppState);
|
||||
var cwtchNotifier = new CwtchNotifier(profs, globalSettings, globalErrorHandler, globalTorStatus, NullNotificationsManager(), globalAppState, globalServersList);
|
||||
cwtch = CwtchFfi(cwtchNotifier);
|
||||
}
|
||||
print("initState: invoking cwtch.Start()");
|
||||
|
@ -82,6 +84,7 @@ class FlwtchState extends State<Flwtch> {
|
|||
ChangeNotifierProvider<AppState> getAppStateProvider() => ChangeNotifierProvider.value(value: globalAppState);
|
||||
Provider<FlwtchState> getFlwtchStateProvider() => Provider<FlwtchState>(create: (_) => this);
|
||||
ChangeNotifierProvider<ProfileListState> getProfileListProvider() => ChangeNotifierProvider(create: (context) => profs);
|
||||
ChangeNotifierProvider<ServerListState> getServerListStateProvider() => ChangeNotifierProvider.value(value: globalServersList);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
@ -94,6 +97,7 @@ class FlwtchState extends State<Flwtch> {
|
|||
getErrorHandlerProvider(),
|
||||
getTorStatusProvider(),
|
||||
getAppStateProvider(),
|
||||
getServerListStateProvider(),
|
||||
],
|
||||
builder: (context, widget) {
|
||||
return Consumer2<Settings, AppState>(
|
||||
|
|
|
@ -2,7 +2,7 @@ import 'dart:convert';
|
|||
|
||||
import 'package:cwtch/widgets/messagerow.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:cwtch/models/servers.dart';
|
||||
import 'package:cwtch/models/profileservers.dart';
|
||||
|
||||
////////////////////
|
||||
/// UI State ///
|
||||
|
@ -207,7 +207,7 @@ class ContactListState extends ChangeNotifier {
|
|||
|
||||
class ProfileInfoState extends ChangeNotifier {
|
||||
ContactListState _contacts = ContactListState();
|
||||
ServerListState _servers = ServerListState();
|
||||
ProfileServerListState _servers = ProfileServerListState();
|
||||
final String onion;
|
||||
String _nickname = "";
|
||||
String _imagePath = "";
|
||||
|
@ -267,7 +267,7 @@ class ProfileInfoState extends ChangeNotifier {
|
|||
List<dynamic> servers = jsonDecode(serversJson);
|
||||
this._servers.replace(servers.map((server) {
|
||||
// TODO Keys...
|
||||
return ServerInfoState(onion: server["onion"], status: server["status"]);
|
||||
return RemoteServerInfoState(onion: server["onion"], status: server["status"]);
|
||||
}));
|
||||
notifyListeners();
|
||||
}
|
||||
|
@ -316,7 +316,7 @@ class ProfileInfoState extends ChangeNotifier {
|
|||
}
|
||||
|
||||
ContactListState get contactList => this._contacts;
|
||||
ServerListState get serverList => this._servers;
|
||||
ProfileServerListState get serverList => this._servers;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
|
@ -362,11 +362,21 @@ class ProfileInfoState extends ChangeNotifier {
|
|||
this._downloads[fileKey] = FileDownloadProgress(numChunks, DateTime.now());
|
||||
}
|
||||
|
||||
void downloadUpdate(String fileKey, int progress) {
|
||||
void downloadUpdate(String fileKey, int progress, int numChunks) {
|
||||
if (!downloadActive(fileKey)) {
|
||||
print("error: received progress for unknown download " + fileKey);
|
||||
if (progress < 0) {
|
||||
this._downloads[fileKey] = FileDownloadProgress(numChunks, DateTime.now());
|
||||
this._downloads[fileKey]!.interrupted = true;
|
||||
notifyListeners();
|
||||
} else {
|
||||
print("error: received progress for unknown download " + fileKey);
|
||||
}
|
||||
} else {
|
||||
if (this._downloads[fileKey]!.interrupted) {
|
||||
this._downloads[fileKey]!.interrupted = false;
|
||||
}
|
||||
this._downloads[fileKey]!.chunksDownloaded = progress;
|
||||
this._downloads[fileKey]!.chunksTotal = numChunks;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
@ -394,7 +404,7 @@ class ProfileInfoState extends ChangeNotifier {
|
|||
}
|
||||
|
||||
bool downloadActive(String fileKey) {
|
||||
return this._downloads.containsKey(fileKey);
|
||||
return this._downloads.containsKey(fileKey) && !this._downloads[fileKey]!.interrupted;
|
||||
}
|
||||
|
||||
bool downloadGotManifest(String fileKey) {
|
||||
|
@ -405,10 +415,27 @@ class ProfileInfoState extends ChangeNotifier {
|
|||
return this._downloads.containsKey(fileKey) && this._downloads[fileKey]!.complete;
|
||||
}
|
||||
|
||||
bool downloadInterrupted(String fileKey) {
|
||||
return this._downloads.containsKey(fileKey) && this._downloads[fileKey]!.interrupted;
|
||||
}
|
||||
|
||||
void downloadMarkResumed(String fileKey) {
|
||||
if (this._downloads.containsKey(fileKey)) {
|
||||
this._downloads[fileKey]!.interrupted = false;
|
||||
}
|
||||
}
|
||||
|
||||
double downloadProgress(String fileKey) {
|
||||
return this._downloads.containsKey(fileKey) ? this._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;
|
||||
}
|
||||
}
|
||||
|
||||
String? downloadFinalPath(String fileKey) {
|
||||
return this._downloads.containsKey(fileKey) ? this._downloads[fileKey]!.downloadedTo : null;
|
||||
}
|
||||
|
@ -431,6 +458,7 @@ class FileDownloadProgress {
|
|||
int chunksTotal = 1;
|
||||
bool complete = false;
|
||||
bool gotManifest = false;
|
||||
bool interrupted = false;
|
||||
String? downloadedTo;
|
||||
DateTime? timeStart;
|
||||
DateTime? timeEnd;
|
||||
|
|
|
@ -12,6 +12,7 @@ import '../../model.dart';
|
|||
class FileMessage extends Message {
|
||||
final MessageMetadata metadata;
|
||||
final String content;
|
||||
final RegExp nonHex = RegExp(r'[^a-f0-9]');
|
||||
|
||||
FileMessage(this.metadata, this.content);
|
||||
|
||||
|
@ -20,7 +21,7 @@ class FileMessage extends Message {
|
|||
return ChangeNotifierProvider.value(
|
||||
value: this.metadata,
|
||||
builder: (bcontext, child) {
|
||||
String idx = Provider.of<ContactInfoState>(context).isGroup == true && this.metadata.signature != null ? this.metadata.signature! : this.metadata.messageIndex.toString();
|
||||
String idx = this.metadata.contactHandle + this.metadata.messageIndex.toString();
|
||||
dynamic shareObj = jsonDecode(this.content);
|
||||
if (shareObj == null) {
|
||||
return MessageRow(MalformedBubble());
|
||||
|
@ -30,6 +31,10 @@ class FileMessage extends Message {
|
|||
String nonce = shareObj['n'] as String;
|
||||
int fileSize = shareObj['s'] as int;
|
||||
|
||||
if (!validHash(rootHash, nonce)) {
|
||||
return MessageRow(MalformedBubble());
|
||||
}
|
||||
|
||||
return MessageRow(FileBubble(nameSuggestion, rootHash, nonce, fileSize), key: Provider.of<ContactInfoState>(bcontext).getMessageKey(idx));
|
||||
});
|
||||
}
|
||||
|
@ -47,6 +52,9 @@ class FileMessage extends Message {
|
|||
String rootHash = shareObj['h'] as String;
|
||||
String nonce = shareObj['n'] as String;
|
||||
int fileSize = shareObj['s'] as int;
|
||||
if (!validHash(rootHash, nonce)) {
|
||||
return MessageRow(MalformedBubble());
|
||||
}
|
||||
return FileBubble(
|
||||
nameSuggestion,
|
||||
rootHash,
|
||||
|
@ -61,4 +69,8 @@ class FileMessage extends Message {
|
|||
MessageMetadata getMetadata() {
|
||||
return this.metadata;
|
||||
}
|
||||
|
||||
bool validHash(String hash, String nonce) {
|
||||
return hash.length == 128 && nonce.length == 48 && !hash.contains(nonHex) && !nonce.contains(nonHex);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,8 +21,7 @@ class InviteMessage extends Message {
|
|||
return ChangeNotifierProvider.value(
|
||||
value: this.metadata,
|
||||
builder: (bcontext, child) {
|
||||
String idx = Provider.of<ContactInfoState>(context).isGroup == true && this.metadata.signature != null ? this.metadata.signature! : this.metadata.messageIndex.toString();
|
||||
|
||||
String idx = this.metadata.contactHandle + this.metadata.messageIndex.toString();
|
||||
String inviteTarget;
|
||||
String inviteNick;
|
||||
String invite = this.content;
|
||||
|
|
|
@ -94,7 +94,7 @@ class QuotedMessage extends Message {
|
|||
return ChangeNotifierProvider.value(
|
||||
value: this.metadata,
|
||||
builder: (bcontext, child) {
|
||||
String idx = Provider.of<ContactInfoState>(context).isGroup == true && this.metadata.signature != null ? this.metadata.signature! : this.metadata.messageIndex.toString();
|
||||
String idx = this.metadata.contactHandle + this.metadata.messageIndex.toString();
|
||||
return MessageRow(
|
||||
QuotedMessageBubble(message["body"], quotedMessage.then((LocallyIndexedMessage? localIndex) {
|
||||
if (localIndex != null) {
|
||||
|
|
|
@ -32,7 +32,7 @@ class TextMessage extends Message {
|
|||
return ChangeNotifierProvider.value(
|
||||
value: this.metadata,
|
||||
builder: (bcontext, child) {
|
||||
String idx = Provider.of<ContactInfoState>(context).isGroup == true && this.metadata.signature != null ? this.metadata.signature! : this.metadata.messageIndex.toString();
|
||||
String idx = this.metadata.contactHandle + this.metadata.messageIndex.toString();
|
||||
return MessageRow(MessageBubble(this.content), key: Provider.of<ContactInfoState>(bcontext).getMessageKey(idx));
|
||||
});
|
||||
}
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
class ProfileServerListState extends ChangeNotifier {
|
||||
List<RemoteServerInfoState> _servers = [];
|
||||
|
||||
void replace(Iterable<RemoteServerInfoState> newServers) {
|
||||
_servers.clear();
|
||||
_servers.addAll(newServers);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
RemoteServerInfoState? getServer(String onion) {
|
||||
int idx = _servers.indexWhere((element) => element.onion == onion);
|
||||
return idx >= 0 ? _servers[idx] : null;
|
||||
}
|
||||
|
||||
void updateServerCache(String onion, String status) {
|
||||
int idx = _servers.indexWhere((element) => element.onion == onion);
|
||||
if (idx >= 0) {
|
||||
_servers[idx] = RemoteServerInfoState(onion: onion, status: status);
|
||||
} else {
|
||||
print("Tried to update server cache without a starting state...this is probably an error");
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
List<RemoteServerInfoState> get servers => _servers.sublist(0); //todo: copy?? dont want caller able to bypass changenotifier
|
||||
|
||||
}
|
||||
|
||||
class RemoteServerInfoState extends ChangeNotifier {
|
||||
final String onion;
|
||||
final String status;
|
||||
|
||||
RemoteServerInfoState({required this.onion, required this.status});
|
||||
}
|
|
@ -9,28 +9,72 @@ class ServerListState extends ChangeNotifier {
|
|||
notifyListeners();
|
||||
}
|
||||
|
||||
void clear() {
|
||||
_servers.clear();
|
||||
}
|
||||
|
||||
ServerInfoState? getServer(String onion) {
|
||||
int idx = _servers.indexWhere((element) => element.onion == onion);
|
||||
return idx >= 0 ? _servers[idx] : null;
|
||||
}
|
||||
|
||||
void updateServerCache(String onion, String status) {
|
||||
void add(String onion, String serverBundle, bool running, String description, bool autoStart, bool isEncrypted) {
|
||||
var sis = ServerInfoState(onion: onion, serverBundle: serverBundle, running: running, description: description, autoStart: autoStart, isEncrypted: isEncrypted);
|
||||
int idx = _servers.indexWhere((element) => element.onion == onion);
|
||||
if (idx >= 0) {
|
||||
_servers[idx] = ServerInfoState(onion: onion, status: status);
|
||||
_servers[idx] = sis;
|
||||
} else {
|
||||
print("Tried to update server cache without a starting state...this is probably an error");
|
||||
_servers.add(ServerInfoState(onion: onion,
|
||||
serverBundle: serverBundle,
|
||||
running: running,
|
||||
description: description,
|
||||
autoStart: autoStart,
|
||||
isEncrypted: isEncrypted));
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void updateServer(String onion, String serverBundle, bool running, String description, bool autoStart, bool isEncrypted) {
|
||||
int idx = _servers.indexWhere((element) => element.onion == onion);
|
||||
if (idx >= 0) {
|
||||
_servers[idx] = ServerInfoState(onion: onion, serverBundle: serverBundle, running: running, description: description, autoStart: autoStart, isEncrypted: isEncrypted);
|
||||
} else {
|
||||
print("Tried to update server list without a starting state...this is probably an error");
|
||||
}
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void delete(String onion) {
|
||||
_servers.removeWhere((element) => element.onion == onion);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
List<ServerInfoState> get servers => _servers.sublist(0); //todo: copy?? dont want caller able to bypass changenotifier
|
||||
|
||||
}
|
||||
|
||||
class ServerInfoState extends ChangeNotifier {
|
||||
final String onion;
|
||||
final String status;
|
||||
String onion;
|
||||
String serverBundle;
|
||||
String description;
|
||||
bool running;
|
||||
bool autoStart;
|
||||
bool isEncrypted;
|
||||
|
||||
ServerInfoState({required this.onion, required this.status});
|
||||
ServerInfoState({required this.onion, required this.serverBundle, required this.running, required this.description, required this.autoStart, required this.isEncrypted});
|
||||
|
||||
void setAutostart(bool val) {
|
||||
autoStart = val;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void setRunning(bool val) {
|
||||
running = val;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void setDescription(String val) {
|
||||
description = val;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import 'opaque.dart';
|
|||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
|
||||
const TapirGroupsExperiment = "tapir-groups-experiment";
|
||||
const ServerManagementExperiment = "servers-experiment";
|
||||
const FileSharingExperiment = "filesharing";
|
||||
const ClickableLinksExperiment = "clickable-links";
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ import 'package:cwtch/cwtch_icons_icons.dart';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:cwtch/errorHandler.dart';
|
||||
import 'package:cwtch/models/servers.dart';
|
||||
import 'package:cwtch/models/profileservers.dart';
|
||||
import 'package:cwtch/settings.dart';
|
||||
import 'package:cwtch/widgets/buttontextfield.dart';
|
||||
import 'package:cwtch/widgets/cwtchlabel.dart';
|
||||
|
@ -197,7 +197,7 @@ class _AddContactViewState extends State<AddContactView> {
|
|||
},
|
||||
isExpanded: true, // magic property
|
||||
value: server,
|
||||
items: Provider.of<ProfileInfoState>(context).serverList.servers.map<DropdownMenuItem<String>>((ServerInfoState serverInfo) {
|
||||
items: Provider.of<ProfileInfoState>(context).serverList.servers.map<DropdownMenuItem<String>>((RemoteServerInfoState serverInfo) {
|
||||
return DropdownMenuItem<String>(
|
||||
value: serverInfo.onion,
|
||||
child: Text(
|
||||
|
@ -240,8 +240,8 @@ class _AddContactViewState extends State<AddContactView> {
|
|||
|
||||
/// TODO Manage Servers Tab
|
||||
Widget manageServersTab() {
|
||||
final tiles = Provider.of<ProfileInfoState>(context).serverList.servers.map((ServerInfoState server) {
|
||||
return ChangeNotifierProvider<ServerInfoState>.value(
|
||||
final tiles = Provider.of<ProfileInfoState>(context).serverList.servers.map((RemoteServerInfoState server) {
|
||||
return ChangeNotifierProvider<RemoteServerInfoState>.value(
|
||||
value: server,
|
||||
child: ListTile(
|
||||
title: Text(server.onion),
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:cwtch/cwtch/cwtch.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:cwtch/model.dart';
|
||||
|
@ -106,6 +107,7 @@ class _AddEditProfileViewState extends State<AddEditProfileView> {
|
|||
labelText: AppLocalizations.of(context)!.yourDisplayName,
|
||||
validator: (value) {
|
||||
if (value.isEmpty) {
|
||||
// TODO l10n ize
|
||||
return "Please enter a display name";
|
||||
}
|
||||
return null;
|
||||
|
@ -287,32 +289,19 @@ class _AddEditProfileViewState extends State<AddEditProfileView> {
|
|||
Provider.of<FlwtchState>(context, listen: false).cwtch.CreateProfile(ctrlrNick.value.text, ctrlrPass.value.text);
|
||||
Navigator.of(context).pop();
|
||||
} else {
|
||||
Provider.of<FlwtchState>(context, listen: false).cwtch.CreateProfile(ctrlrNick.value.text, "be gay do crime");
|
||||
Provider.of<FlwtchState>(context, listen: false).cwtch.CreateProfile(ctrlrNick.value.text, DefaultPassword);
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
} else {
|
||||
// Profile Editing
|
||||
if (ctrlrPass.value.text.isEmpty) {
|
||||
// Don't update password, only update name
|
||||
final event = {
|
||||
"EventType": "SetAttribute",
|
||||
"Data": {"Key": "public.name", "Data": ctrlrNick.value.text}
|
||||
};
|
||||
final json = jsonEncode(event);
|
||||
|
||||
Provider.of<FlwtchState>(context, listen: false).cwtch.SendProfileEvent(Provider.of<ProfileInfoState>(context, listen: false).onion, json);
|
||||
Provider.of<FlwtchState>(context, listen: false).cwtch.SetProfileAttribute(Provider.of<ProfileInfoState>(context, listen: false).onion, "profile.name", ctrlrNick.value.text);
|
||||
Navigator.of(context).pop();
|
||||
} else {
|
||||
// At this points passwords have been validated to be the same and not empty
|
||||
// Update both password and name, even if name hasn't been changed...
|
||||
final updateNameEvent = {
|
||||
"EventType": "SetAttribute",
|
||||
"Data": {"Key": "public.name", "Data": ctrlrNick.value.text}
|
||||
};
|
||||
final updateNameEventJson = jsonEncode(updateNameEvent);
|
||||
|
||||
Provider.of<FlwtchState>(context, listen: false).cwtch.SendProfileEvent(Provider.of<ProfileInfoState>(context, listen: false).onion, updateNameEventJson);
|
||||
|
||||
Provider.of<FlwtchState>(context, listen: false).cwtch.SetProfileAttribute(Provider.of<ProfileInfoState>(context, listen: false).onion, "profile.name", ctrlrNick.value.text);
|
||||
final updatePasswordEvent = {
|
||||
"EventType": "ChangePassword",
|
||||
"Data": {"Password": ctrlrOldPass.text, "NewPassword": ctrlrPass.text}
|
||||
|
|
|
@ -0,0 +1,398 @@
|
|||
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';
|
||||
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();
|
||||
|
||||
@override
|
||||
_AddEditServerViewState createState() => _AddEditServerViewState();
|
||||
}
|
||||
|
||||
class _AddEditServerViewState extends State<AddEditServerView> {
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
|
||||
final ctrlrDesc = TextEditingController(text: "");
|
||||
final ctrlrOldPass = TextEditingController(text: "");
|
||||
final ctrlrPass = TextEditingController(text: "");
|
||||
final ctrlrPass2 = TextEditingController(text: "");
|
||||
final ctrlrOnion = TextEditingController(text: "");
|
||||
|
||||
late bool usePassword;
|
||||
//late bool deleted;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
var serverInfoState = Provider.of<ServerInfoState>(context, listen: false);
|
||||
ctrlrOnion.text = serverInfoState.onion;
|
||||
usePassword = serverInfoState.isEncrypted;
|
||||
if (serverInfoState.description.isNotEmpty) {
|
||||
ctrlrDesc.text = serverInfoState.description;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: ctrlrOnion.text.isEmpty ? Text(AppLocalizations.of(context)!.addServerTitle) : Text(AppLocalizations.of(context)!.editServerTitle),
|
||||
),
|
||||
body: _buildSettingsList(),
|
||||
);
|
||||
}
|
||||
|
||||
void _handleSwitchPassword(bool? value) {
|
||||
setState(() {
|
||||
usePassword = value!;
|
||||
});
|
||||
}
|
||||
|
||||
Widget _buildSettingsList() {
|
||||
return Consumer2<ServerInfoState, Settings>(builder: (context, serverInfoState, settings, child) {
|
||||
return LayoutBuilder(builder: (BuildContext context, BoxConstraints viewportConstraints) {
|
||||
return Scrollbar(
|
||||
isAlwaysShown: true,
|
||||
child: SingleChildScrollView(
|
||||
clipBehavior: Clip.antiAlias,
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
minHeight: viewportConstraints.maxHeight,
|
||||
),
|
||||
child: Form(
|
||||
key: _formKey,
|
||||
child: Container(
|
||||
margin: EdgeInsets.fromLTRB(30, 0, 30, 10),
|
||||
padding: EdgeInsets.fromLTRB(20, 0 , 20, 10),
|
||||
child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
|
||||
// Onion
|
||||
Visibility(
|
||||
visible: serverInfoState.onion.isNotEmpty,
|
||||
child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
CwtchLabel(label: AppLocalizations.of(context)!.serverAddress),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
SelectableText(
|
||||
serverInfoState.onion
|
||||
)
|
||||
])),
|
||||
|
||||
// Description
|
||||
Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
CwtchLabel(label: AppLocalizations.of(context)!.serverDescriptionLabel),
|
||||
Text(AppLocalizations.of(context)!.serverDescriptionDescription),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
CwtchTextField(
|
||||
controller: ctrlrDesc,
|
||||
labelText: "Description",
|
||||
autofocus: false,
|
||||
)
|
||||
]),
|
||||
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
|
||||
// Enabled
|
||||
Visibility(
|
||||
visible: serverInfoState.onion.isNotEmpty,
|
||||
child: SwitchListTile(
|
||||
title: Text(AppLocalizations.of(context)!.serverEnabled, style: TextStyle(color: settings.current().mainTextColor())),
|
||||
subtitle: Text(AppLocalizations.of(context)!.serverEnabledDescription),
|
||||
value: serverInfoState.running,
|
||||
onChanged: (bool value) {
|
||||
serverInfoState.setRunning(value);
|
||||
if (value) {
|
||||
Provider.of<FlwtchState>(context, listen: false).cwtch.LaunchServer(serverInfoState.onion);
|
||||
} else {
|
||||
Provider.of<FlwtchState>(context, listen: false).cwtch.StopServer(serverInfoState.onion);
|
||||
}
|
||||
},
|
||||
activeTrackColor: settings.theme.defaultButtonActiveColor(),
|
||||
inactiveTrackColor: settings.theme.defaultButtonDisabledColor(),
|
||||
secondary: Icon(CwtchIcons.negative_heart_24px, color: settings.current().mainTextColor()),
|
||||
)),
|
||||
|
||||
// Auto start
|
||||
SwitchListTile(
|
||||
title: Text(AppLocalizations.of(context)!.serverAutostartLabel, style: TextStyle(color: settings.current().mainTextColor())),
|
||||
subtitle: Text(AppLocalizations.of(context)!.serverAutostartDescription),
|
||||
value: serverInfoState.autoStart,
|
||||
onChanged: (bool value) {
|
||||
serverInfoState.setAutostart(value);
|
||||
|
||||
if (! serverInfoState.onion.isEmpty) {
|
||||
Provider.of<FlwtchState>(context, listen: false).cwtch.SetServerAttribute(serverInfoState.onion, "autostart", value ? "true" : "false");
|
||||
}
|
||||
},
|
||||
activeTrackColor: settings.theme.defaultButtonActiveColor(),
|
||||
inactiveTrackColor: settings.theme.defaultButtonDisabledColor(),
|
||||
secondary: Icon(CwtchIcons.favorite_24dp, color: settings.current().mainTextColor()),
|
||||
),
|
||||
|
||||
|
||||
// ***** Password *****
|
||||
|
||||
// use password toggle
|
||||
Visibility(
|
||||
visible: serverInfoState.onion.isEmpty,
|
||||
child: Column(mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
Checkbox(
|
||||
value: usePassword,
|
||||
fillColor: MaterialStateProperty.all(settings.current().defaultButtonColor()),
|
||||
activeColor: settings.current().defaultButtonActiveColor(),
|
||||
onChanged: _handleSwitchPassword,
|
||||
),
|
||||
Text(
|
||||
AppLocalizations.of(context)!.radioUsePassword,
|
||||
style: TextStyle(color: settings.current().mainTextColor()),
|
||||
),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 24),
|
||||
child: Text(
|
||||
usePassword ? AppLocalizations.of(context)!.encryptedServerDescription : AppLocalizations.of(context)!.plainServerDescription,
|
||||
textAlign: TextAlign.center,
|
||||
)),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
])),
|
||||
|
||||
|
||||
// current password
|
||||
Visibility(
|
||||
visible: serverInfoState.onion.isNotEmpty && serverInfoState.isEncrypted,
|
||||
child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: <Widget>[
|
||||
CwtchLabel(label: AppLocalizations.of(context)!.currentPasswordLabel),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
CwtchPasswordField(
|
||||
controller: ctrlrOldPass,
|
||||
autoFillHints: [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) {
|
||||
return AppLocalizations.of(context)!.passwordErrorEmpty;
|
||||
}
|
||||
if (Provider.of<ErrorHandler>(context).deletedServerError == true) {
|
||||
return AppLocalizations.of(context)!.enterCurrentPasswordForDeleteServer;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
])),
|
||||
|
||||
// new passwords 1 & 2
|
||||
Visibility(
|
||||
// Currently we don't support password change for servers so also gate this on Add server, when ready to support changing password remove the onion.isEmpty check
|
||||
visible: serverInfoState.onion.isEmpty && usePassword,
|
||||
child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||
CwtchLabel(label: AppLocalizations.of(context)!.newPassword),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
CwtchPasswordField(
|
||||
controller: ctrlrPass,
|
||||
validator: (value) {
|
||||
// Password field can be empty when just updating the profile, not on creation
|
||||
if (serverInfoState.onion.isEmpty && value.isEmpty && usePassword) {
|
||||
return AppLocalizations.of(context)!.passwordErrorEmpty;
|
||||
}
|
||||
if (value != ctrlrPass2.value.text) {
|
||||
return AppLocalizations.of(context)!.passwordErrorMatch;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
CwtchLabel(label: AppLocalizations.of(context)!.password2Label),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
CwtchPasswordField(
|
||||
controller: ctrlrPass2,
|
||||
validator: (value) {
|
||||
// Password field can be empty when just updating the profile, not on creation
|
||||
if (serverInfoState.onion.isEmpty && value.isEmpty && usePassword) {
|
||||
return AppLocalizations.of(context)!.passwordErrorEmpty;
|
||||
}
|
||||
if (value != ctrlrPass.value.text) {
|
||||
return AppLocalizations.of(context)!.passwordErrorMatch;
|
||||
}
|
||||
return null;
|
||||
}),
|
||||
]),
|
||||
),
|
||||
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Expanded(
|
||||
child: ElevatedButton(
|
||||
onPressed: serverInfoState.onion.isEmpty ? _createPressed : _savePressed,
|
||||
child: Text(
|
||||
serverInfoState.onion.isEmpty ? AppLocalizations.of(context)!.addServerTitle : AppLocalizations.of(context)!.saveServerButton,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Visibility(
|
||||
visible: serverInfoState.onion.isNotEmpty,
|
||||
child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.end, children: [
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
Tooltip(
|
||||
message: AppLocalizations.of(context)!.enterCurrentPasswordForDeleteServer,
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: () {
|
||||
showAlertDialog(context);
|
||||
},
|
||||
icon: Icon(Icons.delete_forever),
|
||||
label: Text(AppLocalizations.of(context)!.deleteBtn),
|
||||
))
|
||||
]))
|
||||
|
||||
// ***** END Password *****
|
||||
|
||||
]))))));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void _createPressed() {
|
||||
// This will run all the validations in the form including
|
||||
// checking that display name is not empty, and an actual check that the passwords
|
||||
// match (and are provided if the user has requested an encrypted profile).
|
||||
if (_formKey.currentState!.validate()) {
|
||||
if (usePassword) {
|
||||
Provider
|
||||
.of<FlwtchState>(context, listen: false)
|
||||
.cwtch
|
||||
.CreateServer(ctrlrPass.value.text, ctrlrDesc.value.text, Provider.of<ServerInfoState>(context, listen: false).autoStart);
|
||||
} else {
|
||||
Provider
|
||||
.of<FlwtchState>(context, listen: false)
|
||||
.cwtch
|
||||
.CreateServer(DefaultPassword, ctrlrDesc.value.text, Provider.of<ServerInfoState>(context, listen: false).autoStart);
|
||||
}
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
}
|
||||
|
||||
void _savePressed() {
|
||||
|
||||
var server = Provider.of<ServerInfoState>(context, listen: false);
|
||||
|
||||
Provider.of<FlwtchState>(context, listen: false)
|
||||
.cwtch.SetServerAttribute(server.onion, "description", ctrlrDesc.text);
|
||||
server.setDescription(ctrlrDesc.text);
|
||||
|
||||
|
||||
if (_formKey.currentState!.validate()) {
|
||||
// TODO support change password
|
||||
}
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
|
||||
showAlertDialog(BuildContext context) {
|
||||
// set up the buttons
|
||||
Widget cancelButton = ElevatedButton(
|
||||
child: Text(AppLocalizations.of(context)!.cancel),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop(); // dismiss dialog
|
||||
},
|
||||
);
|
||||
Widget continueButton = ElevatedButton(
|
||||
child: Text(AppLocalizations.of(context)!.deleteServerConfirmBtn),
|
||||
onPressed: () {
|
||||
var onion = Provider
|
||||
.of<ServerInfoState>(context, listen: false)
|
||||
.onion;
|
||||
Provider
|
||||
.of<FlwtchState>(context, listen: false)
|
||||
.cwtch
|
||||
.DeleteServer(onion, Provider.of<ServerInfoState>(context, listen: false).isEncrypted ? ctrlrOldPass.value.text : DefaultPassword);
|
||||
Future.delayed(
|
||||
const Duration(milliseconds: 500),
|
||||
() {
|
||||
if (globalErrorHandler.deletedServerSuccess) {
|
||||
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 {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
// set up the AlertDialog
|
||||
AlertDialog alert = AlertDialog(
|
||||
title: Text(AppLocalizations.of(context)!.deleteServerConfirmBtn),
|
||||
actions: [
|
||||
cancelButton,
|
||||
continueButton,
|
||||
],
|
||||
);
|
||||
|
||||
// show the dialog
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return alert;
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
|
@ -5,6 +5,7 @@ import 'package:cwtch/widgets/contactrow.dart';
|
|||
import 'package:cwtch/widgets/profileimage.dart';
|
||||
import 'package:cwtch/widgets/textfield.dart';
|
||||
import 'package:cwtch/widgets/tor_icon.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import '../main.dart';
|
||||
import '../settings.dart';
|
||||
|
@ -103,9 +104,19 @@ class _ContactsViewState extends State<ContactsView> {
|
|||
if (Provider.of<Settings>(context).blockUnknownConnections) {
|
||||
actions.add(Tooltip(message: AppLocalizations.of(context)!.blockUnknownConnectionsEnabledDescription, child: Icon(CwtchIcons.block_unknown)));
|
||||
}
|
||||
actions.add(
|
||||
IconButton(icon: TorIcon(), onPressed: _pushTorStatus),
|
||||
);
|
||||
|
||||
// Copy profile onion
|
||||
actions.add(IconButton(
|
||||
icon: Icon(CwtchIcons.address_copy_2),
|
||||
tooltip: AppLocalizations.of(context)!.copyAddress,
|
||||
onPressed: () {
|
||||
Clipboard.setData(new ClipboardData(text: Provider.of<ProfileInfoState>(context, listen: false).onion));
|
||||
}));
|
||||
|
||||
|
||||
// TODO servers
|
||||
|
||||
// Search contacts
|
||||
actions.add(IconButton(
|
||||
// need both conditions for displaying initial empty textfield and also allowing filters to be cleared if this widget gets lost/reset
|
||||
icon: Icon(showSearchBar || Provider.of<ContactListState>(context).isFiltered ? Icons.search_off : Icons.search),
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'package:cwtch/cwtch_icons_icons.dart';
|
||||
import 'package:cwtch/models/servers.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:cwtch/settings.dart';
|
||||
|
@ -188,6 +190,27 @@ class _GlobalSettingsViewState extends State<GlobalSettingsView> {
|
|||
inactiveTrackColor: settings.theme.defaultButtonDisabledColor(),
|
||||
secondary: Icon(CwtchIcons.enable_groups, color: settings.current().mainTextColor()),
|
||||
),
|
||||
Visibility(
|
||||
visible: !Platform.isAndroid && !Platform.isIOS,
|
||||
child:
|
||||
SwitchListTile(
|
||||
title: Text(AppLocalizations.of(context)!.settingServers, style: TextStyle(color: settings.current().mainTextColor())),
|
||||
subtitle: Text(AppLocalizations.of(context)!.settingServersDescription),
|
||||
value: settings.isExperimentEnabled(ServerManagementExperiment),
|
||||
onChanged: (bool value) {
|
||||
Provider.of<ServerListState>(context, listen: false).clear();
|
||||
if (value) {
|
||||
settings.enableExperiment(ServerManagementExperiment);
|
||||
} else {
|
||||
settings.disableExperiment(ServerManagementExperiment);
|
||||
}
|
||||
// Save Settings...
|
||||
saveSettings(context);
|
||||
},
|
||||
activeTrackColor: settings.theme.defaultButtonActiveColor(),
|
||||
inactiveTrackColor: settings.theme.defaultButtonDisabledColor(),
|
||||
secondary: Icon(CwtchIcons.dns_24px, color: settings.current().mainTextColor()),
|
||||
)),
|
||||
SwitchListTile(
|
||||
title: Text(AppLocalizations.of(context)!.settingFileSharing, style: TextStyle(color: settings.current().mainTextColor())),
|
||||
subtitle: Text(AppLocalizations.of(context)!.descriptionFileSharing),
|
||||
|
|
|
@ -17,6 +17,7 @@ import '../model.dart';
|
|||
import '../torstatus.dart';
|
||||
import 'addeditprofileview.dart';
|
||||
import 'globalsettingsview.dart';
|
||||
import 'serversview.dart';
|
||||
|
||||
class ProfileMgrView extends StatefulWidget {
|
||||
ProfileMgrView();
|
||||
|
@ -56,12 +57,14 @@ class _ProfileMgrViewState extends State<ProfileMgrView> {
|
|||
SizedBox(
|
||||
width: 10,
|
||||
),
|
||||
Expanded(child: Text(AppLocalizations.of(context)!.titleManageProfiles, style: TextStyle(color: settings.current().mainTextColor())))
|
||||
Expanded(child: Text(MediaQuery.of(context).size.width > 600 ?
|
||||
AppLocalizations.of(context)!.titleManageProfiles : AppLocalizations.of(context)!.titleManageProfilesShort,
|
||||
style: TextStyle(color: settings.current().mainTextColor())))
|
||||
]),
|
||||
actions: getActions(),
|
||||
),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
onPressed: _pushAddEditProfile,
|
||||
onPressed: _pushAddProfile,
|
||||
tooltip: AppLocalizations.of(context)!.addNewProfileBtn,
|
||||
child: Icon(
|
||||
Icons.add,
|
||||
|
@ -95,6 +98,11 @@ class _ProfileMgrViewState extends State<ProfileMgrView> {
|
|||
onPressed: _modalUnlockProfiles,
|
||||
));
|
||||
|
||||
// Servers
|
||||
if (Provider.of<Settings>(context).isExperimentEnabled(ServerManagementExperiment) && !Platform.isAndroid && !Platform.isIOS) {
|
||||
actions.add(IconButton(icon: Icon(CwtchIcons.dns_black_24dp), tooltip: AppLocalizations.of(context)!.serversManagerTitleShort, onPressed: _pushServers));
|
||||
}
|
||||
|
||||
// Global Settings
|
||||
actions.add(IconButton(icon: Icon(Icons.settings), tooltip: AppLocalizations.of(context)!.tooltipOpenSettings, onPressed: _pushGlobalSettings));
|
||||
|
||||
|
@ -119,6 +127,18 @@ class _ProfileMgrViewState extends State<ProfileMgrView> {
|
|||
));
|
||||
}
|
||||
|
||||
void _pushServers() {
|
||||
Navigator.of(context).push(MaterialPageRoute<void>(
|
||||
settings: RouteSettings(name: "servers"),
|
||||
builder: (BuildContext context) {
|
||||
return MultiProvider(
|
||||
providers: [Provider.value(value: Provider.of<FlwtchState>(context))],
|
||||
child: ServersView(),
|
||||
);
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
void _pushTorStatus() {
|
||||
Navigator.of(context).push(MaterialPageRoute<void>(
|
||||
builder: (BuildContext context) {
|
||||
|
@ -130,7 +150,7 @@ class _ProfileMgrViewState extends State<ProfileMgrView> {
|
|||
));
|
||||
}
|
||||
|
||||
void _pushAddEditProfile({onion: ""}) {
|
||||
void _pushAddProfile({onion: ""}) {
|
||||
Navigator.of(context).push(MaterialPageRoute<void>(
|
||||
builder: (BuildContext context) {
|
||||
return MultiProvider(
|
||||
|
@ -216,9 +236,9 @@ class _ProfileMgrViewState extends State<ProfileMgrView> {
|
|||
).toList();
|
||||
|
||||
if (tiles.isEmpty) {
|
||||
return const Center(
|
||||
child: const Text(
|
||||
"Please create or unlock a profile to begin!",
|
||||
return Center(
|
||||
child: Text(
|
||||
AppLocalizations.of(context)!.unlockProfileTip,
|
||||
textAlign: TextAlign.center,
|
||||
));
|
||||
}
|
||||
|
|
|
@ -0,0 +1,152 @@
|
|||
import 'package:cwtch/models/servers.dart';
|
||||
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';
|
||||
|
||||
import '../cwtch_icons_icons.dart';
|
||||
import '../main.dart';
|
||||
import '../settings.dart';
|
||||
|
||||
///
|
||||
class ServersView extends StatefulWidget {
|
||||
@override
|
||||
_ServersView createState() => _ServersView();
|
||||
}
|
||||
|
||||
class _ServersView extends State<ServersView> {
|
||||
final ctrlrPassword = TextEditingController();
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
ctrlrPassword.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text( MediaQuery.of(context).size.width > 600 ? AppLocalizations.of(context)!.serversManagerTitleLong : AppLocalizations.of(context)!.serversManagerTitleShort),
|
||||
actions: getActions(),
|
||||
),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
onPressed: _pushAddServer,
|
||||
tooltip: AppLocalizations.of(context)!.addServerTooltip,
|
||||
child: Icon(
|
||||
Icons.add,
|
||||
semanticLabel: AppLocalizations.of(context)!.addServerTooltip,
|
||||
),
|
||||
),
|
||||
body: Consumer<ServerListState>(
|
||||
builder: (context, svrs, child) {
|
||||
final tiles = svrs.servers.map((ServerInfoState server) {
|
||||
return ChangeNotifierProvider<ServerInfoState>.value(
|
||||
value: server,
|
||||
builder: (context, child) => RepaintBoundary(child: ServerRow()),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
final divided = ListTile.divideTiles(
|
||||
context: context,
|
||||
tiles: tiles,
|
||||
).toList();
|
||||
|
||||
if (tiles.isEmpty) {
|
||||
return Center(
|
||||
child: Text(
|
||||
AppLocalizations.of(context)!.unlockServerTip,
|
||||
textAlign: TextAlign.center,
|
||||
));
|
||||
}
|
||||
|
||||
return ListView(children: divided);
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
List<Widget> getActions() {
|
||||
List<Widget> actions = new List<Widget>.empty(growable: true);
|
||||
|
||||
// Unlock Profiles
|
||||
actions.add(IconButton(
|
||||
icon: 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,
|
||||
));
|
||||
|
||||
return actions;
|
||||
}
|
||||
|
||||
void _modalUnlockServers() {
|
||||
showModalBottomSheet<void>(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
builder: (BuildContext context) {
|
||||
return Padding(
|
||||
padding: MediaQuery.of(context).viewInsets,
|
||||
child: RepaintBoundary(
|
||||
child: Container(
|
||||
height: 200, // bespoke value courtesy of the [TextField] docs
|
||||
child: Center(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(10.0),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
Text(AppLocalizations.of(context)!.enterServerPassword),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
CwtchPasswordField(
|
||||
autofocus: true,
|
||||
controller: ctrlrPassword,
|
||||
action: unlock,
|
||||
validator: (value) {},
|
||||
),
|
||||
SizedBox(
|
||||
height: 20,
|
||||
),
|
||||
Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [
|
||||
Spacer(),
|
||||
Expanded(
|
||||
child: ElevatedButton(
|
||||
child: Text(AppLocalizations.of(context)!.unlock, semanticsLabel: AppLocalizations.of(context)!.unlock),
|
||||
onPressed: () {
|
||||
unlock(ctrlrPassword.value.text);
|
||||
},
|
||||
)),
|
||||
Spacer()
|
||||
]),
|
||||
],
|
||||
))),
|
||||
)));
|
||||
});
|
||||
}
|
||||
|
||||
void unlock(String password) {
|
||||
Provider.of<FlwtchState>(context, listen: false).cwtch.LoadServers(password);
|
||||
ctrlrPassword.text = "";
|
||||
Navigator.pop(context);
|
||||
}
|
||||
|
||||
void _pushAddServer() {
|
||||
Navigator.of(context).push(MaterialPageRoute<void>(
|
||||
builder: (BuildContext context) {
|
||||
return MultiProvider(
|
||||
providers: [ChangeNotifierProvider<ServerInfoState>(
|
||||
create: (_) => ServerInfoState(onion: "", serverBundle: "", description: "", autoStart: true, running: false, isEncrypted: true),
|
||||
)],
|
||||
child: AddEditServerView(),
|
||||
);
|
||||
},
|
||||
));
|
||||
}
|
||||
}
|
|
@ -33,6 +33,11 @@ class FileBubble extends StatefulWidget {
|
|||
}
|
||||
|
||||
class FileBubbleState extends State<FileBubble> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var fromMe = Provider.of<MessageMetadata>(context).senderHandle == Provider.of<ProfileInfoState>(context).onion;
|
||||
|
@ -71,7 +76,7 @@ class FileBubbleState extends State<FileBubble> {
|
|||
} else if (Provider.of<ProfileInfoState>(context).downloadComplete(widget.fileKey())) {
|
||||
// in this case, whatever marked download.complete would have also set the path
|
||||
var path = Provider.of<ProfileInfoState>(context).downloadFinalPath(widget.fileKey())!;
|
||||
wdgDecorations = Text('Saved to: ' + path + '\u202F');
|
||||
wdgDecorations = Text(AppLocalizations.of(context)!.fileSavedTo + ': ' + path + '\u202F');
|
||||
} else if (Provider.of<ProfileInfoState>(context).downloadActive(widget.fileKey())) {
|
||||
if (!Provider.of<ProfileInfoState>(context).downloadGotManifest(widget.fileKey())) {
|
||||
wdgDecorations = Text(AppLocalizations.of(context)!.retrievingManifestMessage + '\u202F');
|
||||
|
@ -84,12 +89,15 @@ class FileBubbleState extends State<FileBubble> {
|
|||
} else if (flagStarted) {
|
||||
// in this case, the download was done in a previous application launch,
|
||||
// so we probably have to request an info lookup
|
||||
var path = Provider.of<ProfileInfoState>(context).downloadFinalPath(widget.fileKey());
|
||||
if (path == null) {
|
||||
wdgDecorations = Text('Checking download status...' + '\u202F');
|
||||
if (!Provider.of<ProfileInfoState>(context).downloadInterrupted(widget.fileKey()) ) {
|
||||
wdgDecorations = Text(AppLocalizations.of(context)!.fileCheckingStatus + '...' + '\u202F');
|
||||
Provider.of<FlwtchState>(context, listen: false).cwtch.CheckDownloadStatus(Provider.of<ProfileInfoState>(context, listen: false).onion, widget.fileKey());
|
||||
} else {
|
||||
wdgDecorations = Text('Saved to: ' + path + '\u202F');
|
||||
var path = Provider.of<ProfileInfoState>(context).downloadFinalPath(widget.fileKey()) ?? "";
|
||||
wdgDecorations = Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children:[Text(AppLocalizations.of(context)!.fileInterrupted + ': ' + path + '\u202F'),ElevatedButton(onPressed: _btnResume, child: Text(AppLocalizations.of(context)!.verfiyResumeButton))]
|
||||
);
|
||||
}
|
||||
} else {
|
||||
wdgDecorations = Center(
|
||||
|
@ -167,6 +175,13 @@ class FileBubbleState extends State<FileBubble> {
|
|||
}
|
||||
}
|
||||
|
||||
void _btnResume() async {
|
||||
var profileOnion = Provider.of<ProfileInfoState>(context, listen: false).onion;
|
||||
var handle = Provider.of<MessageMetadata>(context, listen: false).senderHandle;
|
||||
Provider.of<ProfileInfoState>(context, listen: false).downloadMarkResumed(widget.fileKey());
|
||||
Provider.of<FlwtchState>(context, listen: false).cwtch.VerifyOrResumeDownload(profileOnion, handle, widget.fileKey());
|
||||
}
|
||||
|
||||
// Construct an file chrome for the sender
|
||||
Widget senderFileChrome(String chrome, String fileName, String rootHash, int fileSize) {
|
||||
return ListTile(
|
||||
|
|
|
@ -7,6 +7,7 @@ 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 '../model.dart';
|
||||
import '../settings.dart';
|
||||
|
@ -61,7 +62,7 @@ class _ProfileRowState extends State<ProfileRow> {
|
|||
tooltip: AppLocalizations.of(context)!.editProfile + " " + profile.nickname,
|
||||
icon: Icon(Icons.create, color: Provider.of<Settings>(context).current().mainTextColor()),
|
||||
onPressed: () {
|
||||
_pushAddEditProfile(onion: profile.onion, displayName: profile.nickname, profileImage: profile.imagePath, encrypted: profile.isEncrypted);
|
||||
_pushEditProfile(onion: profile.onion, displayName: profile.nickname, profileImage: profile.imagePath, encrypted: profile.isEncrypted);
|
||||
},
|
||||
)
|
||||
],
|
||||
|
@ -100,7 +101,8 @@ class _ProfileRowState extends State<ProfileRow> {
|
|||
);
|
||||
}
|
||||
|
||||
void _pushAddEditProfile({onion: "", displayName: "", profileImage: "", encrypted: true}) {
|
||||
void _pushEditProfile({onion: "", displayName: "", profileImage: "", encrypted: true}) {
|
||||
Provider.of<ErrorHandler>(context).reset();
|
||||
Navigator.of(context).push(MaterialPageRoute<void>(
|
||||
builder: (BuildContext context) {
|
||||
return MultiProvider(
|
||||
|
|
|
@ -0,0 +1,97 @@
|
|||
import 'package:cwtch/main.dart';
|
||||
import 'package:cwtch/models/servers.dart';
|
||||
import 'package:cwtch/views/addeditservers.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 '../model.dart';
|
||||
import '../settings.dart';
|
||||
|
||||
class ServerRow extends StatefulWidget {
|
||||
@override
|
||||
_ServerRowState createState() => _ServerRowState();
|
||||
}
|
||||
|
||||
class _ServerRowState extends State<ServerRow> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var server = Provider.of<ServerInfoState>(context);
|
||||
return Card(clipBehavior: Clip.antiAlias,
|
||||
margin: EdgeInsets.all(0.0),
|
||||
child: InkWell(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(6.0), //border size
|
||||
child: Icon(CwtchIcons.dns_24px,
|
||||
color: server.running ? Provider.of<Settings>(context).theme.portraitOnlineBorderColor() : Provider.of<Settings>(context).theme.portraitOfflineBorderColor(),
|
||||
size: 64)
|
||||
|
||||
),
|
||||
Expanded(
|
||||
child: Column(
|
||||
children: [
|
||||
Text(
|
||||
server.description,
|
||||
semanticsLabel: server.description,
|
||||
style: Provider.of<FlwtchState>(context).biggerFont.apply(color: server.running ? Provider.of<Settings>(context).theme.portraitOnlineBorderColor() : Provider.of<Settings>(context).theme.portraitOfflineBorderColor()),
|
||||
softWrap: true,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
Visibility(
|
||||
visible: !Provider.of<Settings>(context).streamerMode,
|
||||
child: ExcludeSemantics(
|
||||
child: Text(
|
||||
server.onion,
|
||||
softWrap: true,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(color: server.running ? Provider.of<Settings>(context).theme.portraitOnlineBorderColor() : Provider.of<Settings>(context).theme.portraitOfflineBorderColor()),
|
||||
)))
|
||||
],
|
||||
)),
|
||||
|
||||
// Copy server button
|
||||
IconButton(
|
||||
enableFeedback: true,
|
||||
tooltip: AppLocalizations.of(context)!.copyServerKeys,
|
||||
icon: Icon(CwtchIcons.address_copy_2, color: Provider.of<Settings>(context).current().mainTextColor()),
|
||||
onPressed: () {
|
||||
Clipboard.setData(new ClipboardData(text: server.serverBundle));
|
||||
},
|
||||
),
|
||||
|
||||
// Edit button
|
||||
IconButton(
|
||||
enableFeedback: true,
|
||||
tooltip: AppLocalizations.of(context)!.editServerTitle,
|
||||
icon: Icon(Icons.create, color: Provider.of<Settings>(context).current().mainTextColor()),
|
||||
onPressed: () {
|
||||
_pushEditServer(server);
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
])));
|
||||
}
|
||||
|
||||
void _pushEditServer(ServerInfoState server ) {
|
||||
Provider.of<ErrorHandler>(context).reset();
|
||||
Navigator.of(context).push(MaterialPageRoute<void>(
|
||||
settings: RouteSettings(name: "serveraddedit"),
|
||||
builder: (BuildContext context) {
|
||||
return MultiProvider(
|
||||
providers: [ChangeNotifierProvider<ServerInfoState>(
|
||||
create: (_) => server,
|
||||
)],
|
||||
child: AddEditServerView(),
|
||||
);
|
||||
},
|
||||
));
|
||||
}
|
||||
}
|
|
@ -3,13 +3,14 @@
|
|||
# Run from SRCROOT
|
||||
|
||||
cp libCwtch.dylib build/macos/Build/Products/Release/Cwtch.app/Contents/Frameworks/
|
||||
cp -r /Applications/Tor\ Browser.app/Contents/MacOS/Tor build/macos/Build/Products/Release/Cwtch.app/Contents/MacOS/
|
||||
cp -r macos/Tor build/macos/Build/Products/Release/Cwtch.app/Contents/MacOS/
|
||||
|
||||
rm Cwtch.dmg
|
||||
rm -r macos_dmg
|
||||
mkdir macos_dmg
|
||||
cp -r "build/macos/Build/Products/Release/Cwtch.app" macos_dmg/
|
||||
|
||||
# https://github.com/create-dmg/create-dmg
|
||||
create-dmg \
|
||||
--volname "Cwtch" \
|
||||
--volicon "macos/cwtch.icns" \
|
||||
|
|
|
@ -15,7 +15,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
|||
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
|
||||
# Read more about iOS versioning at
|
||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||
version: 1.3.0+21
|
||||
version: 1.4.0+22
|
||||
|
||||
environment:
|
||||
sdk: ">=2.12.0 <3.0.0"
|
||||
|
|
Loading…
Reference in New Issue