Merge pull request 'image previews' (#267) from ipreview into trunk
continuous-integration/drone/push Build is pending
Details
continuous-integration/drone/push Build is pending
Details
Reviewed-on: #267 Reviewed-by: Sarah Jamie Lewis <sarah@openprivacy.ca>
This commit is contained in:
commit
d13ded8f50
20
.drone.yml
20
.drone.yml
|
@ -8,7 +8,7 @@ clone:
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: clone
|
- name: clone
|
||||||
image: cirrusci/flutter:2.5.3
|
image: cirrusci/flutter:2.8.0
|
||||||
environment:
|
environment:
|
||||||
buildbot_key_b64:
|
buildbot_key_b64:
|
||||||
from_secret: buildbot_key_b64
|
from_secret: buildbot_key_b64
|
||||||
|
@ -24,7 +24,7 @@ steps:
|
||||||
- git checkout $DRONE_COMMIT
|
- git checkout $DRONE_COMMIT
|
||||||
|
|
||||||
- name: fetch
|
- name: fetch
|
||||||
image: cirrusci/flutter:2.5.3
|
image: cirrusci/flutter:2.8.0
|
||||||
volumes:
|
volumes:
|
||||||
- name: deps
|
- name: deps
|
||||||
path: /root/.pub-cache
|
path: /root/.pub-cache
|
||||||
|
@ -47,7 +47,7 @@ steps:
|
||||||
# #Todo: fix all the lint errors and add `-set_exit_status` above to enforce linting
|
# #Todo: fix all the lint errors and add `-set_exit_status` above to enforce linting
|
||||||
|
|
||||||
- name: build-linux
|
- name: build-linux
|
||||||
image: openpriv/flutter-desktop:linux-fstable-2.5.3
|
image: openpriv/flutter-desktop:linux-fstable-2.8.0
|
||||||
volumes:
|
volumes:
|
||||||
- name: deps
|
- name: deps
|
||||||
path: /root/.pub-cache
|
path: /root/.pub-cache
|
||||||
|
@ -61,7 +61,7 @@ steps:
|
||||||
- rm -r cwtch
|
- rm -r cwtch
|
||||||
|
|
||||||
- name: test-build-android
|
- name: test-build-android
|
||||||
image: cirrusci/flutter:2.5.3
|
image: cirrusci/flutter:2.8.0
|
||||||
when:
|
when:
|
||||||
event: pull_request
|
event: pull_request
|
||||||
volumes:
|
volumes:
|
||||||
|
@ -71,7 +71,7 @@ steps:
|
||||||
- flutter build apk --debug
|
- flutter build apk --debug
|
||||||
|
|
||||||
- name: build-android
|
- name: build-android
|
||||||
image: cirrusci/flutter:2.5.3
|
image: cirrusci/flutter:2.8.0
|
||||||
when:
|
when:
|
||||||
event: push
|
event: push
|
||||||
environment:
|
environment:
|
||||||
|
@ -95,7 +95,7 @@ steps:
|
||||||
#- cp build/app/outputs/flutter-apk/app-debug.apk deploy/android
|
#- cp build/app/outputs/flutter-apk/app-debug.apk deploy/android
|
||||||
|
|
||||||
- name: widget-tests
|
- name: widget-tests
|
||||||
image: cirrusci/flutter:2.5.3
|
image: cirrusci/flutter:2.8.0
|
||||||
volumes:
|
volumes:
|
||||||
- name: deps
|
- name: deps
|
||||||
path: /root/.pub-cache
|
path: /root/.pub-cache
|
||||||
|
@ -174,7 +174,7 @@ clone:
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: clone
|
- name: clone
|
||||||
image: openpriv/flutter-desktop:windows-sdk30-fstable2.5.3
|
image: openpriv/flutter-desktop:windows-sdk30-fstable-2.8.1
|
||||||
environment:
|
environment:
|
||||||
buildbot_key_b64:
|
buildbot_key_b64:
|
||||||
from_secret: buildbot_key_b64
|
from_secret: buildbot_key_b64
|
||||||
|
@ -192,7 +192,7 @@ steps:
|
||||||
- git checkout $Env:DRONE_COMMIT
|
- git checkout $Env:DRONE_COMMIT
|
||||||
|
|
||||||
- name: fetch
|
- name: fetch
|
||||||
image: openpriv/flutter-desktop:windows-sdk30-fstable2.5.3
|
image: openpriv/flutter-desktop:windows-sdk30-fstable-2.8.1
|
||||||
commands:
|
commands:
|
||||||
- powershell -command "Invoke-WebRequest -Uri https://git.openprivacy.ca/openprivacy/buildfiles/raw/branch/master/tor/tor-win64-0.4.6.5.zip -OutFile tor.zip"
|
- powershell -command "Invoke-WebRequest -Uri https://git.openprivacy.ca/openprivacy/buildfiles/raw/branch/master/tor/tor-win64-0.4.6.5.zip -OutFile tor.zip"
|
||||||
- powershell -command "if ((Get-FileHash tor.zip -Algorithm sha512).Hash -ne '7917561a7a063440a1ddfa9cb544ab9ffd09de84cea3dd66e3cc9cd349dd9f85b74a522ec390d7a974bc19b424c4d53af60e57bbc47e763d13cab6a203c4592f' ) { Write-Error 'tor.zip sha512sum mismatch' }"
|
- powershell -command "if ((Get-FileHash tor.zip -Algorithm sha512).Hash -ne '7917561a7a063440a1ddfa9cb544ab9ffd09de84cea3dd66e3cc9cd349dd9f85b74a522ec390d7a974bc19b424c4d53af60e57bbc47e763d13cab6a203c4592f' ) { Write-Error 'tor.zip sha512sum mismatch' }"
|
||||||
|
@ -201,7 +201,7 @@ steps:
|
||||||
- .\fetch-libcwtch-go.ps1
|
- .\fetch-libcwtch-go.ps1
|
||||||
|
|
||||||
- name: build-windows
|
- name: build-windows
|
||||||
image: openpriv/flutter-desktop:windows-sdk30-fstable2.5.3
|
image: openpriv/flutter-desktop:windows-sdk30-fstable-2.8.1
|
||||||
commands:
|
commands:
|
||||||
- flutter pub get
|
- flutter pub get
|
||||||
- $Env:version += type .\VERSION
|
- $Env:version += type .\VERSION
|
||||||
|
@ -257,7 +257,7 @@ steps:
|
||||||
- move *.sha512 deploy\$Env:builddir
|
- move *.sha512 deploy\$Env:builddir
|
||||||
|
|
||||||
- name: deploy-windows
|
- name: deploy-windows
|
||||||
image: openpriv/flutter-desktop:windows-sdk30-fstable2.5.3
|
image: openpriv/flutter-desktop:windows-sdk30-fstable-2.8.1
|
||||||
when:
|
when:
|
||||||
event: push
|
event: push
|
||||||
status: [ success ]
|
status: [ success ]
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
2021-12-11-02-00-v1.5.0-9-gaa102bd
|
2021-12-18-20-32-v1.5.1
|
|
@ -1 +1 @@
|
||||||
2021-12-11-07-00-v1.5.0-9-gaa102bd
|
2021-12-19-01-32-v1.5.1
|
|
@ -140,7 +140,7 @@ class FlwtchWorker(context: Context, parameters: WorkerParameters) :
|
||||||
val data = JSONObject(evt.Data);
|
val data = JSONObject(evt.Data);
|
||||||
val tempFile = data.getString("TempFile");
|
val tempFile = data.getString("TempFile");
|
||||||
val fileKey = data.getString("FileKey");
|
val fileKey = data.getString("FileKey");
|
||||||
if (tempFile != "") {
|
if (tempFile != "" && tempFile != data.getString("FilePath")) {
|
||||||
val filePath = data.getString("FilePath");
|
val filePath = data.getString("FilePath");
|
||||||
Log.i("FlwtchWorker", "moving "+tempFile+" to "+filePath);
|
Log.i("FlwtchWorker", "moving "+tempFile+" to "+filePath);
|
||||||
val sourcePath = Paths.get(tempFile);
|
val sourcePath = Paths.get(tempFile);
|
||||||
|
|
|
@ -22,6 +22,10 @@ import io.flutter.plugin.common.ErrorLogResult
|
||||||
|
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
import java.io.File
|
||||||
|
import java.nio.file.Files
|
||||||
|
import java.nio.file.Paths
|
||||||
|
import java.nio.file.StandardCopyOption
|
||||||
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.provider.DocumentsContract
|
import android.provider.DocumentsContract
|
||||||
|
@ -57,9 +61,11 @@ class MainActivity: FlutterActivity() {
|
||||||
|
|
||||||
// "Download to..." prompt extra arguments
|
// "Download to..." prompt extra arguments
|
||||||
private val FILEPICKER_REQUEST_CODE = 234
|
private val FILEPICKER_REQUEST_CODE = 234
|
||||||
|
private val PREVIEW_EXPORT_REQUEST_CODE = 235
|
||||||
private var dlToProfile = ""
|
private var dlToProfile = ""
|
||||||
private var dlToHandle = ""
|
private var dlToHandle = ""
|
||||||
private var dlToFileKey = ""
|
private var dlToFileKey = ""
|
||||||
|
private var exportFromPath = ""
|
||||||
|
|
||||||
// handles clicks received from outside the app (ie, notifications)
|
// handles clicks received from outside the app (ie, notifications)
|
||||||
override fun onNewIntent(intent: Intent) {
|
override fun onNewIntent(intent: Intent) {
|
||||||
|
@ -102,6 +108,20 @@ class MainActivity: FlutterActivity() {
|
||||||
"manifestpath" to manifestPath,
|
"manifestpath" to manifestPath,
|
||||||
"filekey" to this.dlToFileKey
|
"filekey" to this.dlToFileKey
|
||||||
)), ErrorLogResult(""));//placeholder; this Result is never actually invoked
|
)), ErrorLogResult(""));//placeholder; this Result is never actually invoked
|
||||||
|
} else if (requestCode == PREVIEW_EXPORT_REQUEST_CODE) {
|
||||||
|
val targetPath = intent!!.getData().toString()
|
||||||
|
var srcFile = File(this.exportFromPath)
|
||||||
|
Log.i("MainActivity:PREVIEW_EXPORT", "exporting previewed file")
|
||||||
|
val sourcePath = Paths.get(this.exportFromPath);
|
||||||
|
val targetUri = Uri.parse(targetPath);
|
||||||
|
val os = this.applicationContext.getContentResolver().openOutputStream(targetUri);
|
||||||
|
val bytesWritten = Files.copy(sourcePath, os);
|
||||||
|
Log.d("MainActivity:PREVIEW_EXPORT", "copied " + bytesWritten.toString() + " bytes");
|
||||||
|
if (bytesWritten != 0L) {
|
||||||
|
os?.flush();
|
||||||
|
os?.close();
|
||||||
|
//Files.delete(sourcePath);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -170,6 +190,26 @@ class MainActivity: FlutterActivity() {
|
||||||
}
|
}
|
||||||
startActivityForResult(intent, FILEPICKER_REQUEST_CODE)
|
startActivityForResult(intent, FILEPICKER_REQUEST_CODE)
|
||||||
return
|
return
|
||||||
|
} else if (call.method == "ExportPreviewedFile") {
|
||||||
|
this.exportFromPath = argmap["Path"] ?: ""
|
||||||
|
val suggestion = argmap["FileName"] ?: "filename.ext"
|
||||||
|
var imgType = "jpeg"
|
||||||
|
if (suggestion.endsWith("png")) {
|
||||||
|
imgType = "png"
|
||||||
|
} else if (suggestion.endsWith("webp")) {
|
||||||
|
imgType = "webp"
|
||||||
|
} else if (suggestion.endsWith("bmp")) {
|
||||||
|
imgType = "bmp"
|
||||||
|
} else if (suggestion.endsWith("gif")) {
|
||||||
|
imgType = "gif"
|
||||||
|
}
|
||||||
|
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
|
||||||
|
addCategory(Intent.CATEGORY_OPENABLE)
|
||||||
|
type = "image/" + imgType
|
||||||
|
putExtra(Intent.EXTRA_TITLE, suggestion)
|
||||||
|
}
|
||||||
|
startActivityForResult(intent, PREVIEW_EXPORT_REQUEST_CODE)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// ...otherwise fallthru to a normal ffi method call (and return the result using the result callback)
|
// ...otherwise fallthru to a normal ffi method call (and return the result using the result callback)
|
||||||
|
|
|
@ -15,7 +15,9 @@ abstract class Cwtch {
|
||||||
// ignore: non_constant_identifier_names
|
// ignore: non_constant_identifier_names
|
||||||
void LoadProfiles(String pass);
|
void LoadProfiles(String pass);
|
||||||
// ignore: non_constant_identifier_names
|
// ignore: non_constant_identifier_names
|
||||||
void DeleteProfile(String onion, String pass);
|
void DeleteProfile(String profile, String pass);
|
||||||
|
// ignore: non_constant_identifier_names
|
||||||
|
void ChangePassword(String profile, String pass, String newpass, String newpassAgain);
|
||||||
|
|
||||||
// ignore: non_constant_identifier_names
|
// ignore: non_constant_identifier_names
|
||||||
void ResetTor();
|
void ResetTor();
|
||||||
|
@ -49,12 +51,16 @@ abstract class Cwtch {
|
||||||
void ShareFile(String profile, int handle, String filepath);
|
void ShareFile(String profile, int handle, String filepath);
|
||||||
// ignore: non_constant_identifier_names
|
// ignore: non_constant_identifier_names
|
||||||
void DownloadFile(String profile, int handle, String filepath, String manifestpath, String filekey);
|
void DownloadFile(String profile, int handle, String filepath, String manifestpath, String filekey);
|
||||||
|
// android-only
|
||||||
// ignore: non_constant_identifier_names
|
// ignore: non_constant_identifier_names
|
||||||
void CreateDownloadableFile(String profile, int handle, String filenameSuggestion, String filekey);
|
void CreateDownloadableFile(String profile, int handle, String filenameSuggestion, String filekey);
|
||||||
// ignore: non_constant_identifier_names
|
// ignore: non_constant_identifier_names
|
||||||
void CheckDownloadStatus(String profile, String fileKey);
|
void CheckDownloadStatus(String profile, String fileKey);
|
||||||
// ignore: non_constant_identifier_names
|
// ignore: non_constant_identifier_names
|
||||||
void VerifyOrResumeDownload(String profile, int handle, String filekey);
|
void VerifyOrResumeDownload(String profile, int handle, String filekey);
|
||||||
|
// android-only
|
||||||
|
// ignore: non_constant_identifier_names
|
||||||
|
void ExportPreviewedFile(String sourceFile, String suggestion);
|
||||||
|
|
||||||
// ignore: non_constant_identifier_names
|
// ignore: non_constant_identifier_names
|
||||||
void ArchiveConversation(String profile, int handle);
|
void ArchiveConversation(String profile, int handle);
|
||||||
|
@ -94,5 +100,8 @@ abstract class Cwtch {
|
||||||
// ignore: non_constant_identifier_names
|
// ignore: non_constant_identifier_names
|
||||||
void Shutdown();
|
void Shutdown();
|
||||||
|
|
||||||
|
// non-ffi
|
||||||
|
String defaultDownloadPath();
|
||||||
|
|
||||||
void dispose();
|
void dispose();
|
||||||
}
|
}
|
||||||
|
|
|
@ -141,6 +141,7 @@ class CwtchNotifier {
|
||||||
var timestamp = DateTime.tryParse(data['TimestampReceived'])!;
|
var timestamp = DateTime.tryParse(data['TimestampReceived'])!;
|
||||||
var senderHandle = data['RemotePeer'];
|
var senderHandle = data['RemotePeer'];
|
||||||
var senderImage = data['Picture'];
|
var senderImage = data['Picture'];
|
||||||
|
var isAuto = data['Auto'] == "true";
|
||||||
|
|
||||||
// We might not have received a contact created for this contact yet...
|
// We might not have received a contact created for this contact yet...
|
||||||
// In that case the **next** event we receive will actually update these values...
|
// In that case the **next** event we receive will actually update these values...
|
||||||
|
@ -151,7 +152,7 @@ class CwtchNotifier {
|
||||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(identifier)!.newMarker++;
|
profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(identifier)!.newMarker++;
|
||||||
}
|
}
|
||||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.updateLastMessageTime(identifier, DateTime.now());
|
profileCN.getProfile(data["ProfileOnion"])?.contactList.updateLastMessageTime(identifier, DateTime.now());
|
||||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(identifier)!.updateMessageCache(identifier, messageID, timestamp, senderHandle, senderImage, data["Data"]);
|
profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(identifier)!.updateMessageCache(identifier, messageID, timestamp, senderHandle, senderImage, isAuto, data["Data"]);
|
||||||
|
|
||||||
// We only ever see messages from authenticated peers.
|
// We only ever see messages from authenticated peers.
|
||||||
// If the contact is marked as offline then override this - can happen when the contact is removed from the front
|
// If the contact is marked as offline then override this - can happen when the contact is removed from the front
|
||||||
|
@ -197,10 +198,11 @@ class CwtchNotifier {
|
||||||
var senderImage = data['Picture'];
|
var senderImage = data['Picture'];
|
||||||
var timestampSent = DateTime.tryParse(data['TimestampSent'])!;
|
var timestampSent = DateTime.tryParse(data['TimestampSent'])!;
|
||||||
var currentTotal = profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(identifier)!.totalMessages;
|
var currentTotal = profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(identifier)!.totalMessages;
|
||||||
|
var isAuto = data['Auto'] == "true";
|
||||||
|
|
||||||
// Only bother to do anything if we know about the group and the provided index is greater than our current total...
|
// Only bother to do anything if we know about the group and the provided index is greater than our current total...
|
||||||
if (currentTotal != null && idx >= currentTotal) {
|
if (currentTotal != null && idx >= currentTotal) {
|
||||||
profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(identifier)!.updateMessageCache(identifier, idx, timestampSent, senderHandle, senderImage, data["Data"]);
|
profileCN.getProfile(data["ProfileOnion"])?.contactList.getContact(identifier)!.updateMessageCache(identifier, idx, timestampSent, senderHandle, senderImage, isAuto, data["Data"]);
|
||||||
|
|
||||||
//if not currently open
|
//if not currently open
|
||||||
if (appState.selectedProfile != data["ProfileOnion"] || appState.selectedConversation != identifier) {
|
if (appState.selectedProfile != data["ProfileOnion"] || appState.selectedConversation != identifier) {
|
||||||
|
@ -228,7 +230,10 @@ class CwtchNotifier {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "SendMessageToPeerError":
|
case "SendMessageToPeerError":
|
||||||
// Ignore
|
// Ignore dealt with by IndexedFailure
|
||||||
|
break;
|
||||||
|
case "SendMessageToGroupError":
|
||||||
|
// Ignore dealt with by IndexedFailure
|
||||||
break;
|
break;
|
||||||
case "IndexedFailure":
|
case "IndexedFailure":
|
||||||
var contact = profileCN.getProfile(data["ProfileOnion"])?.contactList.findContact(data["RemotePeer"]);
|
var contact = profileCN.getProfile(data["ProfileOnion"])?.contactList.findContact(data["RemotePeer"]);
|
||||||
|
@ -339,6 +344,11 @@ class CwtchNotifier {
|
||||||
EnvironmentConfig.debugLog("unhandled ret val event: ${data['Path']}");
|
EnvironmentConfig.debugLog("unhandled ret val event: ${data['Path']}");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case "ManifestSizeReceived":
|
||||||
|
if (!profileCN.getProfile(data["ProfileOnion"])!.downloadActive(data["FileKey"])) {
|
||||||
|
profileCN.getProfile(data["ProfileOnion"])?.downloadUpdate(data["FileKey"], 0, 1);
|
||||||
|
}
|
||||||
|
break;
|
||||||
case "ManifestSaved":
|
case "ManifestSaved":
|
||||||
profileCN.getProfile(data["ProfileOnion"])?.downloadMarkManifest(data["FileKey"]);
|
profileCN.getProfile(data["ProfileOnion"])?.downloadMarkManifest(data["FileKey"]);
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -115,13 +115,13 @@ class CwtchFfi implements Cwtch {
|
||||||
}
|
}
|
||||||
|
|
||||||
CwtchFfi(CwtchNotifier _cwtchNotifier) {
|
CwtchFfi(CwtchNotifier _cwtchNotifier) {
|
||||||
String library_path = getLibraryPath();
|
String libraryPath = getLibraryPath();
|
||||||
if (library_path == UNSUPPORTED_OS) {
|
if (libraryPath == UNSUPPORTED_OS) {
|
||||||
print("OS ${Platform.operatingSystem} not supported by cwtch/ffi");
|
print("OS ${Platform.operatingSystem} not supported by cwtch/ffi");
|
||||||
// emergency, ideally the app stays on splash and just posts the error till user closes
|
// emergency, ideally the app stays on splash and just posts the error till user closes
|
||||||
exit(0);
|
exit(0);
|
||||||
}
|
}
|
||||||
library = DynamicLibrary.open(library_path);
|
library = DynamicLibrary.open(libraryPath);
|
||||||
cwtchNotifier = _cwtchNotifier;
|
cwtchNotifier = _cwtchNotifier;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -402,6 +402,11 @@ class CwtchFfi implements Cwtch {
|
||||||
// android only - do nothing
|
// android only - do nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ignore: non_constant_identifier_names
|
||||||
|
void ExportPreviewedFile(String sourceFile, String suggestion) {
|
||||||
|
// android only - do nothing
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
// ignore: non_constant_identifier_names
|
// ignore: non_constant_identifier_names
|
||||||
void CheckDownloadStatus(String profileOnion, String fileKey) {
|
void CheckDownloadStatus(String profileOnion, String fileKey) {
|
||||||
|
@ -693,6 +698,13 @@ class CwtchFfi implements Cwtch {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
String defaultDownloadPath() {
|
||||||
|
Map<String, String> envVars = Platform.environment;
|
||||||
|
return path.join(envVars[Platform.isWindows ? 'UserProfile' : 'HOME']!, "Downloads");
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
// ignore: non_constant_identifier_names
|
||||||
Future<String> GetMessageByID(String profile, int handle, int index) async {
|
Future<String> GetMessageByID(String profile, int handle, int index) async {
|
||||||
var getMessageC = library.lookup<NativeFunction<get_json_blob_from_str_int_int_function>>("c_GetMessageByID");
|
var getMessageC = library.lookup<NativeFunction<get_json_blob_from_str_int_int_function>>("c_GetMessageByID");
|
||||||
// ignore: non_constant_identifier_names
|
// ignore: non_constant_identifier_names
|
||||||
|
@ -704,4 +716,21 @@ class CwtchFfi implements Cwtch {
|
||||||
malloc.free(utf8profile);
|
malloc.free(utf8profile);
|
||||||
return jsonMessage;
|
return jsonMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
// ignore: non_constant_identifier_names
|
||||||
|
void ChangePassword(String profile, String pass, String newpass, String newpassAgain) {
|
||||||
|
var changePasswordC = library.lookup<NativeFunction<void_from_string_string_string_string_function>>("c_ChangePassword");
|
||||||
|
// ignore: non_constant_identifier_names
|
||||||
|
final ChangePasswordFn = changePasswordC.asFunction<VoidFromStringStringStringStringFn>();
|
||||||
|
final utf8profile = profile.toNativeUtf8();
|
||||||
|
final utf8pass = pass.toNativeUtf8();
|
||||||
|
final utf8newpass = newpass.toNativeUtf8();
|
||||||
|
final utf8newpasssagain = newpassAgain.toNativeUtf8();
|
||||||
|
ChangePasswordFn(utf8profile, utf8profile.length, utf8pass, utf8pass.length, utf8newpass, utf8newpass.length, utf8newpasssagain, utf8newpasssagain.length);
|
||||||
|
malloc.free(utf8profile);
|
||||||
|
malloc.free(utf8pass);
|
||||||
|
malloc.free(utf8newpass);
|
||||||
|
malloc.free(utf8newpasssagain);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,6 +28,7 @@ class CwtchGomobile implements Cwtch {
|
||||||
|
|
||||||
late Future<dynamic> androidLibraryDir;
|
late Future<dynamic> androidLibraryDir;
|
||||||
late Future<dynamic> androidHomeDirectory;
|
late Future<dynamic> androidHomeDirectory;
|
||||||
|
String androidHomeDirectoryStr = "";
|
||||||
late CwtchNotifier cwtchNotifier;
|
late CwtchNotifier cwtchNotifier;
|
||||||
|
|
||||||
CwtchGomobile(CwtchNotifier _cwtchNotifier) {
|
CwtchGomobile(CwtchNotifier _cwtchNotifier) {
|
||||||
|
@ -44,7 +45,8 @@ class CwtchGomobile implements Cwtch {
|
||||||
// ignore: non_constant_identifier_names
|
// ignore: non_constant_identifier_names
|
||||||
Future<void> Start() async {
|
Future<void> Start() async {
|
||||||
print("gomobile.dart: Start()...");
|
print("gomobile.dart: Start()...");
|
||||||
var cwtchDir = path.join((await androidHomeDirectory).path, ".cwtch");
|
androidHomeDirectoryStr = (await androidHomeDirectory).path;
|
||||||
|
var cwtchDir = path.join(androidHomeDirectoryStr, ".cwtch");
|
||||||
if (EnvironmentConfig.BUILD_VER == dev_version) {
|
if (EnvironmentConfig.BUILD_VER == dev_version) {
|
||||||
cwtchDir = path.join(cwtchDir, "dev");
|
cwtchDir = path.join(cwtchDir, "dev");
|
||||||
}
|
}
|
||||||
|
@ -147,6 +149,14 @@ class CwtchGomobile implements Cwtch {
|
||||||
cwtchPlatform.invokeMethod("CreateDownloadableFile", {"ProfileOnion": profileOnion, "conversation": conversation, "filename": filenameSuggestion, "filekey": filekey});
|
cwtchPlatform.invokeMethod("CreateDownloadableFile", {"ProfileOnion": profileOnion, "conversation": conversation, "filename": filenameSuggestion, "filekey": filekey});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ignore: non_constant_identifier_names
|
||||||
|
void ExportPreviewedFile(String sourceFile, String suggestion) {
|
||||||
|
cwtchPlatform.invokeMethod("ExportPreviewedFile", {
|
||||||
|
"Path": sourceFile,
|
||||||
|
"FileName": suggestion,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
// ignore: non_constant_identifier_names
|
// ignore: non_constant_identifier_names
|
||||||
void CheckDownloadStatus(String profileOnion, String fileKey) {
|
void CheckDownloadStatus(String profileOnion, String fileKey) {
|
||||||
|
@ -269,4 +279,14 @@ class CwtchGomobile implements Cwtch {
|
||||||
void SetMessageAttribute(String profile, int conversation, int channel, int message, String key, String val) {
|
void SetMessageAttribute(String profile, int conversation, int channel, int message, String key, String val) {
|
||||||
cwtchPlatform.invokeMethod("SetMessageAttribute", {"ProfileOnion": profile, "conversation": conversation, "Channel": channel, "Message": message, "Key": key, "Val": val});
|
cwtchPlatform.invokeMethod("SetMessageAttribute", {"ProfileOnion": profile, "conversation": conversation, "Channel": channel, "Message": message, "Key": key, "Val": val});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String defaultDownloadPath() {
|
||||||
|
return this.androidHomeDirectoryStr;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void ChangePassword(String profile, String pass, String newpass, String newpassAgain) {
|
||||||
|
cwtchPlatform.invokeMethod("ChangePassword", {"ProfileOnion": profile, "OldPass": pass, "NewPass": newpass, "NewPassAgain": newpassAgain});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,12 +6,17 @@ class ErrorHandler extends ChangeNotifier {
|
||||||
|
|
||||||
// Add Contact Specific Errors...
|
// Add Contact Specific Errors...
|
||||||
static const String addContactErrorPrefix = "addcontact";
|
static const String addContactErrorPrefix = "addcontact";
|
||||||
|
static const String changePasswordErrorPrefix = "changepassword";
|
||||||
static const String invalidImportStringErrorType = "invalid_import_string";
|
static const String invalidImportStringErrorType = "invalid_import_string";
|
||||||
static const String contactAlreadyExistsErrorType = "contact_already_exists";
|
static const String contactAlreadyExistsErrorType = "contact_already_exists";
|
||||||
bool invalidImportStringError = false;
|
bool invalidImportStringError = false;
|
||||||
bool contactAlreadyExistsError = false;
|
bool contactAlreadyExistsError = false;
|
||||||
bool explicitAddContactSuccess = false;
|
bool explicitAddContactSuccess = false;
|
||||||
|
|
||||||
|
// ChangePassword
|
||||||
|
bool changePasswordError = false;
|
||||||
|
bool explicitChangePasswordSuccess = false;
|
||||||
|
|
||||||
// Import Bundle Specific Errors
|
// Import Bundle Specific Errors
|
||||||
static const String importBundleErrorPrefix = "importBundle";
|
static const String importBundleErrorPrefix = "importBundle";
|
||||||
bool importBundleError = false;
|
bool importBundleError = false;
|
||||||
|
@ -39,6 +44,9 @@ class ErrorHandler extends ChangeNotifier {
|
||||||
deletedServerError = false;
|
deletedServerError = false;
|
||||||
deletedServerSuccess = false;
|
deletedServerSuccess = false;
|
||||||
|
|
||||||
|
changePasswordError = false;
|
||||||
|
explicitChangePasswordSuccess = false;
|
||||||
|
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,6 +66,9 @@ class ErrorHandler extends ChangeNotifier {
|
||||||
case deleteProfileErrorPrefix:
|
case deleteProfileErrorPrefix:
|
||||||
handleDeleteProfileError(errorType);
|
handleDeleteProfileError(errorType);
|
||||||
break;
|
break;
|
||||||
|
case changePasswordErrorPrefix:
|
||||||
|
handleChangePasswordError(errorType);
|
||||||
|
break;
|
||||||
case deletedServerErrorPrefix:
|
case deletedServerErrorPrefix:
|
||||||
handleDeletedServerError(errorType);
|
handleDeletedServerError(errorType);
|
||||||
}
|
}
|
||||||
|
@ -115,6 +126,21 @@ class ErrorHandler extends ChangeNotifier {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleChangePasswordError(String errorType) {
|
||||||
|
// Reset add contact errors
|
||||||
|
changePasswordError = false;
|
||||||
|
explicitChangePasswordSuccess = false;
|
||||||
|
|
||||||
|
switch (errorType) {
|
||||||
|
case successErrorType:
|
||||||
|
explicitChangePasswordSuccess = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
changePasswordError = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
handleDeletedServerError(String errorType) {
|
handleDeletedServerError(String errorType) {
|
||||||
// reset
|
// reset
|
||||||
deletedServerError = false;
|
deletedServerError = false;
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
{
|
{
|
||||||
"@@locale": "de",
|
"@@locale": "de",
|
||||||
"@@last_modified": "2021-12-17T23:48:01+01:00",
|
"@@last_modified": "2021-12-19T02:59:05+01:00",
|
||||||
|
"msgAddToAccept": "Add this account to your contacts in order to accept this file.",
|
||||||
|
"btnSendFile": "Send File",
|
||||||
|
"msgConfirmSend": "Are you sure you want to send",
|
||||||
|
"msgFileTooBig": "File size cannot exceed 10 GB",
|
||||||
"storageMigrationModalMessage": "Migrating profiles to new storage format. This could take a few minutes...",
|
"storageMigrationModalMessage": "Migrating profiles to new storage format. This could take a few minutes...",
|
||||||
"loadingCwtch": "Loading Cwtch...",
|
"loadingCwtch": "Loading Cwtch...",
|
||||||
"themeColorLabel": "Color Theme",
|
"themeColorLabel": "Color Theme",
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
{
|
{
|
||||||
"@@locale": "en",
|
"@@locale": "en",
|
||||||
"@@last_modified": "2021-12-17T23:48:01+01:00",
|
"@@last_modified": "2021-12-19T02:59:05+01:00",
|
||||||
|
"msgAddToAccept": "Add this account to your contacts in order to accept this file.",
|
||||||
|
"btnSendFile": "Send File",
|
||||||
|
"msgConfirmSend": "Are you sure you want to send",
|
||||||
|
"msgFileTooBig": "File size cannot exceed 10 GB",
|
||||||
"storageMigrationModalMessage": "Migrating profiles to new storage format. This could take a few minutes...",
|
"storageMigrationModalMessage": "Migrating profiles to new storage format. This could take a few minutes...",
|
||||||
"loadingCwtch": "Loading Cwtch...",
|
"loadingCwtch": "Loading Cwtch...",
|
||||||
"themeColorLabel": "Color Theme",
|
"themeColorLabel": "Color Theme",
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
{
|
{
|
||||||
"@@locale": "es",
|
"@@locale": "es",
|
||||||
"@@last_modified": "2021-12-17T23:48:01+01:00",
|
"@@last_modified": "2021-12-19T02:59:05+01:00",
|
||||||
|
"msgAddToAccept": "Add this account to your contacts in order to accept this file.",
|
||||||
|
"btnSendFile": "Send File",
|
||||||
|
"msgConfirmSend": "Are you sure you want to send",
|
||||||
|
"msgFileTooBig": "File size cannot exceed 10 GB",
|
||||||
"storageMigrationModalMessage": "Migrating profiles to new storage format. This could take a few minutes...",
|
"storageMigrationModalMessage": "Migrating profiles to new storage format. This could take a few minutes...",
|
||||||
"loadingCwtch": "Loading Cwtch...",
|
"loadingCwtch": "Loading Cwtch...",
|
||||||
"themeColorLabel": "Color Theme",
|
"themeColorLabel": "Color Theme",
|
||||||
|
|
|
@ -1,8 +1,12 @@
|
||||||
{
|
{
|
||||||
"@@locale": "fr",
|
"@@locale": "fr",
|
||||||
"@@last_modified": "2021-12-17T23:48:01+01:00",
|
"@@last_modified": "2021-12-19T02:59:05+01:00",
|
||||||
"storageMigrationModalMessage": "Migrating profiles to new storage format. This could take a few minutes...",
|
"msgAddToAccept": "Add this account to your contacts in order to accept this file.",
|
||||||
"loadingCwtch": "Loading Cwtch...",
|
"btnSendFile": "Send File",
|
||||||
|
"msgConfirmSend": "Are you sure you want to send",
|
||||||
|
"msgFileTooBig": "File size cannot exceed 10 GB",
|
||||||
|
"storageMigrationModalMessage": "Migration des profils vers un nouveau format de stockage. Cela peut prendre quelques minutes...",
|
||||||
|
"loadingCwtch": "Chargement de Cwtch...",
|
||||||
"experimentClickableLinksDescription": "L'expérience des liens cliquables vous permet de cliquer sur les URLs partagés dans les messages",
|
"experimentClickableLinksDescription": "L'expérience des liens cliquables vous permet de cliquer sur les URLs partagés dans les messages",
|
||||||
"themeNameWitch": "Sorcière",
|
"themeNameWitch": "Sorcière",
|
||||||
"themeNameVampire": "Vampire",
|
"themeNameVampire": "Vampire",
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
{
|
{
|
||||||
"@@locale": "it",
|
"@@locale": "it",
|
||||||
"@@last_modified": "2021-12-17T23:48:01+01:00",
|
"@@last_modified": "2021-12-19T02:59:05+01:00",
|
||||||
|
"msgAddToAccept": "Add this account to your contacts in order to accept this file.",
|
||||||
|
"btnSendFile": "Send File",
|
||||||
|
"msgConfirmSend": "Are you sure you want to send",
|
||||||
|
"msgFileTooBig": "File size cannot exceed 10 GB",
|
||||||
"storageMigrationModalMessage": "Migrating profiles to new storage format. This could take a few minutes...",
|
"storageMigrationModalMessage": "Migrating profiles to new storage format. This could take a few minutes...",
|
||||||
"loadingCwtch": "Loading Cwtch...",
|
"loadingCwtch": "Loading Cwtch...",
|
||||||
"themeColorLabel": "Color Theme",
|
"themeColorLabel": "Color Theme",
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
{
|
{
|
||||||
"@@locale": "pl",
|
"@@locale": "pl",
|
||||||
"@@last_modified": "2021-12-17T23:48:01+01:00",
|
"@@last_modified": "2021-12-19T02:59:05+01:00",
|
||||||
|
"msgAddToAccept": "Add this account to your contacts in order to accept this file.",
|
||||||
|
"btnSendFile": "Send File",
|
||||||
|
"msgConfirmSend": "Are you sure you want to send",
|
||||||
|
"msgFileTooBig": "File size cannot exceed 10 GB",
|
||||||
"storageMigrationModalMessage": "Migrating profiles to new storage format. This could take a few minutes...",
|
"storageMigrationModalMessage": "Migrating profiles to new storage format. This could take a few minutes...",
|
||||||
"loadingCwtch": "Loading Cwtch...",
|
"loadingCwtch": "Loading Cwtch...",
|
||||||
"themeColorLabel": "Color Theme",
|
"themeColorLabel": "Color Theme",
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
{
|
{
|
||||||
"@@locale": "pt",
|
"@@locale": "pt",
|
||||||
"@@last_modified": "2021-12-17T23:48:01+01:00",
|
"@@last_modified": "2021-12-19T02:59:05+01:00",
|
||||||
|
"msgAddToAccept": "Add this account to your contacts in order to accept this file.",
|
||||||
|
"btnSendFile": "Send File",
|
||||||
|
"msgConfirmSend": "Are you sure you want to send",
|
||||||
|
"msgFileTooBig": "File size cannot exceed 10 GB",
|
||||||
"storageMigrationModalMessage": "Migrating profiles to new storage format. This could take a few minutes...",
|
"storageMigrationModalMessage": "Migrating profiles to new storage format. This could take a few minutes...",
|
||||||
"loadingCwtch": "Loading Cwtch...",
|
"loadingCwtch": "Loading Cwtch...",
|
||||||
"themeColorLabel": "Color Theme",
|
"themeColorLabel": "Color Theme",
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
{
|
{
|
||||||
"@@locale": "ru",
|
"@@locale": "ru",
|
||||||
"@@last_modified": "2021-12-17T23:48:01+01:00",
|
"@@last_modified": "2021-12-19T02:59:05+01:00",
|
||||||
|
"msgAddToAccept": "Add this account to your contacts in order to accept this file.",
|
||||||
|
"btnSendFile": "Send File",
|
||||||
|
"msgConfirmSend": "Are you sure you want to send",
|
||||||
|
"msgFileTooBig": "File size cannot exceed 10 GB",
|
||||||
"storageMigrationModalMessage": "Migrating profiles to new storage format. This could take a few minutes...",
|
"storageMigrationModalMessage": "Migrating profiles to new storage format. This could take a few minutes...",
|
||||||
"loadingCwtch": "Loading Cwtch...",
|
"loadingCwtch": "Loading Cwtch...",
|
||||||
"themeColorLabel": "Тема",
|
"themeColorLabel": "Тема",
|
||||||
|
|
|
@ -414,12 +414,9 @@ class ProfileInfoState extends ChangeNotifier {
|
||||||
|
|
||||||
void downloadUpdate(String fileKey, int progress, int numChunks) {
|
void downloadUpdate(String fileKey, int progress, int numChunks) {
|
||||||
if (!downloadActive(fileKey)) {
|
if (!downloadActive(fileKey)) {
|
||||||
|
this._downloads[fileKey] = FileDownloadProgress(numChunks, DateTime.now());
|
||||||
if (progress < 0) {
|
if (progress < 0) {
|
||||||
this._downloads[fileKey] = FileDownloadProgress(numChunks, DateTime.now());
|
|
||||||
this._downloads[fileKey]!.interrupted = true;
|
this._downloads[fileKey]!.interrupted = true;
|
||||||
notifyListeners();
|
|
||||||
} else {
|
|
||||||
print("error: received progress for unknown download " + fileKey);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (this._downloads[fileKey]!.interrupted) {
|
if (this._downloads[fileKey]!.interrupted) {
|
||||||
|
@ -427,17 +424,16 @@ class ProfileInfoState extends ChangeNotifier {
|
||||||
}
|
}
|
||||||
this._downloads[fileKey]!.chunksDownloaded = progress;
|
this._downloads[fileKey]!.chunksDownloaded = progress;
|
||||||
this._downloads[fileKey]!.chunksTotal = numChunks;
|
this._downloads[fileKey]!.chunksTotal = numChunks;
|
||||||
notifyListeners();
|
|
||||||
}
|
}
|
||||||
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
void downloadMarkManifest(String fileKey) {
|
void downloadMarkManifest(String fileKey) {
|
||||||
if (!downloadActive(fileKey)) {
|
if (!downloadActive(fileKey)) {
|
||||||
print("error: received download completion notice for unknown download " + fileKey);
|
this._downloads[fileKey] = FileDownloadProgress(1, DateTime.now());
|
||||||
} else {
|
|
||||||
this._downloads[fileKey]!.gotManifest = true;
|
|
||||||
notifyListeners();
|
|
||||||
}
|
}
|
||||||
|
this._downloads[fileKey]!.gotManifest = true;
|
||||||
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
void downloadMarkFinished(String fileKey, String finalPath) {
|
void downloadMarkFinished(String fileKey, String finalPath) {
|
||||||
|
@ -728,8 +724,8 @@ class ContactInfoState extends ChangeNotifier {
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
void updateMessageCache(int conversation, int messageID, DateTime timestamp, String senderHandle, String senderImage, String data) {
|
void updateMessageCache(int conversation, int messageID, DateTime timestamp, String senderHandle, String senderImage, bool isAuto, String data) {
|
||||||
this.messageCache.insert(0, MessageCache(MessageMetadata(profileOnion, conversation, messageID, timestamp, senderHandle, senderImage, "", {}, false, false), data));
|
this.messageCache.insert(0, MessageCache(MessageMetadata(profileOnion, conversation, messageID, timestamp, senderHandle, senderImage, "", {}, false, false, isAuto), data));
|
||||||
this.totalMessages += 1;
|
this.totalMessages += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -58,11 +58,15 @@ Message compileOverlay(MessageMetadata metadata, String messageData) {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Message> messageHandler(BuildContext context, String profileOnion, int conversationIdentifier, int index, {bool byID = false}) {
|
Future<Message> messageHandler(BuildContext context, String profileOnion, int conversationIdentifier, int index, {bool byID = false}) {
|
||||||
var cache = Provider.of<ProfileInfoState>(context).contactList.getContact(conversationIdentifier)?.messageCache;
|
try {
|
||||||
if (cache != null && cache.length > index) {
|
var cache = Provider.of<ProfileInfoState>(context, listen: false).contactList.getContact(conversationIdentifier)?.messageCache;
|
||||||
if (cache[index] != null) {
|
if (cache != null && cache.length > index) {
|
||||||
return Future.value(compileOverlay(cache[index]!.metadata, cache[index]!.wrapper));
|
if (cache[index] != null) {
|
||||||
|
return Future.value(compileOverlay(cache[index]!.metadata, cache[index]!.wrapper));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// provider check failed...make an expensive call...
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -75,7 +79,7 @@ Future<Message> messageHandler(BuildContext context, String profileOnion, int co
|
||||||
}
|
}
|
||||||
|
|
||||||
return rawMessageEnvelopeFuture.then((dynamic rawMessageEnvelope) {
|
return rawMessageEnvelopeFuture.then((dynamic rawMessageEnvelope) {
|
||||||
var metadata = MessageMetadata(profileOnion, conversationIdentifier, index, DateTime.now(), "", "", "", <String, String>{}, false, true);
|
var metadata = MessageMetadata(profileOnion, conversationIdentifier, index, DateTime.now(), "", "", "", <String, String>{}, false, true, false);
|
||||||
try {
|
try {
|
||||||
dynamic messageWrapper = jsonDecode(rawMessageEnvelope);
|
dynamic messageWrapper = jsonDecode(rawMessageEnvelope);
|
||||||
// There are 2 conditions in which this error condition can be met:
|
// There are 2 conditions in which this error condition can be met:
|
||||||
|
@ -103,7 +107,7 @@ Future<Message> messageHandler(BuildContext context, String profileOnion, int co
|
||||||
var ackd = messageWrapper['Acknowledged'];
|
var ackd = messageWrapper['Acknowledged'];
|
||||||
var error = messageWrapper['Error'] != null;
|
var error = messageWrapper['Error'] != null;
|
||||||
var signature = messageWrapper['Signature'];
|
var signature = messageWrapper['Signature'];
|
||||||
metadata = MessageMetadata(profileOnion, conversationIdentifier, messageID, timestamp, senderHandle, senderImage, signature, attributes, ackd, error);
|
metadata = MessageMetadata(profileOnion, conversationIdentifier, messageID, timestamp, senderHandle, senderImage, signature, attributes, ackd, error, false);
|
||||||
|
|
||||||
return compileOverlay(metadata, messageWrapper['Message']);
|
return compileOverlay(metadata, messageWrapper['Message']);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -112,7 +116,7 @@ Future<Message> messageHandler(BuildContext context, String profileOnion, int co
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return Future.value(MalformedMessage(MessageMetadata(profileOnion, conversationIdentifier, -1, DateTime.now(), "", "", "", <String, String>{}, false, true)));
|
return Future.value(MalformedMessage(MessageMetadata(profileOnion, conversationIdentifier, -1, DateTime.now(), "", "", "", <String, String>{}, false, true, false)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -128,6 +132,7 @@ class MessageMetadata extends ChangeNotifier {
|
||||||
final dynamic _attributes;
|
final dynamic _attributes;
|
||||||
bool _ackd;
|
bool _ackd;
|
||||||
bool _error;
|
bool _error;
|
||||||
|
final bool isAuto;
|
||||||
|
|
||||||
final String? signature;
|
final String? signature;
|
||||||
|
|
||||||
|
@ -145,5 +150,6 @@ class MessageMetadata extends ChangeNotifier {
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
MessageMetadata(this.profileOnion, this.conversationIdentifier, this.messageID, this.timestamp, this.senderHandle, this.senderImage, this.signature, this._attributes, this._ackd, this._error);
|
MessageMetadata(
|
||||||
|
this.profileOnion, this.conversationIdentifier, this.messageID, this.timestamp, this.senderHandle, this.senderImage, this.signature, this._attributes, this._ackd, this._error, this.isAuto);
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,6 @@ class FileMessage extends Message {
|
||||||
@override
|
@override
|
||||||
Widget getWidget(BuildContext context, Key key) {
|
Widget getWidget(BuildContext context, Key key) {
|
||||||
return ChangeNotifierProvider.value(
|
return ChangeNotifierProvider.value(
|
||||||
key: key,
|
|
||||||
value: this.metadata,
|
value: this.metadata,
|
||||||
builder: (bcontext, child) {
|
builder: (bcontext, child) {
|
||||||
dynamic shareObj = jsonDecode(this.content);
|
dynamic shareObj = jsonDecode(this.content);
|
||||||
|
@ -35,7 +34,7 @@ class FileMessage extends Message {
|
||||||
return MessageRow(MalformedBubble());
|
return MessageRow(MalformedBubble());
|
||||||
}
|
}
|
||||||
|
|
||||||
return MessageRow(FileBubble(nameSuggestion, rootHash, nonce, fileSize), key: key);
|
return MessageRow(FileBubble(nameSuggestion, rootHash, nonce, fileSize, isAuto: metadata.isAuto), key: key);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,13 +54,16 @@ class FileMessage extends Message {
|
||||||
if (!validHash(rootHash, nonce)) {
|
if (!validHash(rootHash, nonce)) {
|
||||||
return MessageRow(MalformedBubble());
|
return MessageRow(MalformedBubble());
|
||||||
}
|
}
|
||||||
return FileBubble(
|
return Container(
|
||||||
nameSuggestion,
|
alignment: Alignment.center,
|
||||||
rootHash,
|
child: FileBubble(
|
||||||
nonce,
|
nameSuggestion,
|
||||||
fileSize,
|
rootHash,
|
||||||
interactive: false,
|
nonce,
|
||||||
);
|
fileSize,
|
||||||
|
isAuto: metadata.isAuto,
|
||||||
|
interactive: false,
|
||||||
|
));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,6 @@ class InviteMessage extends Message {
|
||||||
@override
|
@override
|
||||||
Widget getWidget(BuildContext context, Key key) {
|
Widget getWidget(BuildContext context, Key key) {
|
||||||
return ChangeNotifierProvider.value(
|
return ChangeNotifierProvider.value(
|
||||||
key: key,
|
|
||||||
value: this.metadata,
|
value: this.metadata,
|
||||||
builder: (bcontext, child) {
|
builder: (bcontext, child) {
|
||||||
String inviteTarget;
|
String inviteTarget;
|
||||||
|
@ -37,7 +36,7 @@ class InviteMessage extends Message {
|
||||||
inviteTarget = jsonObj['GroupID'];
|
inviteTarget = jsonObj['GroupID'];
|
||||||
inviteNick = jsonObj['GroupName'];
|
inviteNick = jsonObj['GroupName'];
|
||||||
} else {
|
} else {
|
||||||
return MessageRow(MalformedBubble(), key: key);
|
return MessageRow(MalformedBubble());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return MessageRow(InvitationBubble(overlay, inviteTarget, inviteNick, invite), key: key);
|
return MessageRow(InvitationBubble(overlay, inviteTarget, inviteNick, invite), key: key);
|
||||||
|
|
|
@ -2,6 +2,7 @@ import 'dart:convert';
|
||||||
|
|
||||||
import 'package:cwtch/models/message.dart';
|
import 'package:cwtch/models/message.dart';
|
||||||
import 'package:cwtch/models/messages/malformedmessage.dart';
|
import 'package:cwtch/models/messages/malformedmessage.dart';
|
||||||
|
import 'package:cwtch/widgets/malformedbubble.dart';
|
||||||
import 'package:cwtch/widgets/messagerow.dart';
|
import 'package:cwtch/widgets/messagerow.dart';
|
||||||
import 'package:cwtch/widgets/quotedmessage.dart';
|
import 'package:cwtch/widgets/quotedmessage.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
|
@ -51,7 +52,7 @@ class QuotedMessage extends Message {
|
||||||
dynamic message = jsonDecode(this.content);
|
dynamic message = jsonDecode(this.content);
|
||||||
return Text(message["body"]);
|
return Text(message["body"]);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return MalformedMessage(this.metadata).getWidget(context, Key("malformed"));
|
return MalformedBubble();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -67,7 +68,7 @@ class QuotedMessage extends Message {
|
||||||
dynamic message = jsonDecode(this.content);
|
dynamic message = jsonDecode(this.content);
|
||||||
|
|
||||||
if (message["body"] == null || message["quotedHash"] == null) {
|
if (message["body"] == null || message["quotedHash"] == null) {
|
||||||
return MalformedMessage(this.metadata).getWidget(context, key);
|
return MalformedBubble();
|
||||||
}
|
}
|
||||||
|
|
||||||
var quotedMessagePotentials = Provider.of<FlwtchState>(context).cwtch.GetMessageByContentHash(metadata.profileOnion, metadata.conversationIdentifier, message["quotedHash"]);
|
var quotedMessagePotentials = Provider.of<FlwtchState>(context).cwtch.GetMessageByContentHash(metadata.profileOnion, metadata.conversationIdentifier, message["quotedHash"]);
|
||||||
|
@ -94,14 +95,14 @@ class QuotedMessage extends Message {
|
||||||
return MessageRow(
|
return MessageRow(
|
||||||
QuotedMessageBubble(message["body"], quotedMessage.then((LocallyIndexedMessage? localIndex) {
|
QuotedMessageBubble(message["body"], quotedMessage.then((LocallyIndexedMessage? localIndex) {
|
||||||
if (localIndex != null) {
|
if (localIndex != null) {
|
||||||
return messageHandler(context, metadata.profileOnion, metadata.conversationIdentifier, localIndex.index);
|
return messageHandler(bcontext, metadata.profileOnion, metadata.conversationIdentifier, localIndex.index);
|
||||||
}
|
}
|
||||||
return MalformedMessage(this.metadata);
|
return MalformedMessage(this.metadata);
|
||||||
})),
|
})),
|
||||||
key: key);
|
key: key);
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return MalformedMessage(this.metadata).getWidget(context, key);
|
return MalformedBubble();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
const TapirGroupsExperiment = "tapir-groups-experiment";
|
const TapirGroupsExperiment = "tapir-groups-experiment";
|
||||||
const ServerManagementExperiment = "servers-experiment";
|
const ServerManagementExperiment = "servers-experiment";
|
||||||
const FileSharingExperiment = "filesharing";
|
const FileSharingExperiment = "filesharing";
|
||||||
|
const ImagePreviewsExperiment = "filesharing-images";
|
||||||
const ClickableLinksExperiment = "clickable-links";
|
const ClickableLinksExperiment = "clickable-links";
|
||||||
|
|
||||||
enum DualpaneMode {
|
enum DualpaneMode {
|
||||||
|
@ -36,6 +37,7 @@ class Settings extends ChangeNotifier {
|
||||||
|
|
||||||
bool blockUnknownConnections = false;
|
bool blockUnknownConnections = false;
|
||||||
bool streamerMode = false;
|
bool streamerMode = false;
|
||||||
|
String _downloadPath = "";
|
||||||
|
|
||||||
void setTheme(String themeId, String mode) {
|
void setTheme(String themeId, String mode) {
|
||||||
theme = getTheme(themeId, mode);
|
theme = getTheme(themeId, mode);
|
||||||
|
@ -81,6 +83,9 @@ class Settings extends ChangeNotifier {
|
||||||
_uiColumnModePortrait = uiColumnModeFromString(settings["UIColumnModePortrait"]);
|
_uiColumnModePortrait = uiColumnModeFromString(settings["UIColumnModePortrait"]);
|
||||||
_uiColumnModeLandscape = uiColumnModeFromString(settings["UIColumnModeLandscape"]);
|
_uiColumnModeLandscape = uiColumnModeFromString(settings["UIColumnModeLandscape"]);
|
||||||
|
|
||||||
|
// auto-download folder
|
||||||
|
_downloadPath = settings["DownloadPath"] ?? "";
|
||||||
|
|
||||||
// Push the experimental settings to Consumers of Settings
|
// Push the experimental settings to Consumers of Settings
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
@ -213,6 +218,26 @@ class Settings extends ChangeNotifier {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// checks experiment settings and file extension for image previews
|
||||||
|
// (ignores file size; if the user manually accepts the file, assume it's okay to preview)
|
||||||
|
bool shouldPreview(String path) {
|
||||||
|
var lpath = path.toLowerCase();
|
||||||
|
return isExperimentEnabled(ImagePreviewsExperiment) && (
|
||||||
|
lpath.endsWith(".jpg") ||
|
||||||
|
lpath.endsWith(".jpeg") ||
|
||||||
|
lpath.endsWith(".png") ||
|
||||||
|
lpath.endsWith(".gif") ||
|
||||||
|
lpath.endsWith(".webp") ||
|
||||||
|
lpath.endsWith(".bmp")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
String get downloadPath => _downloadPath;
|
||||||
|
set downloadPath(String newval) {
|
||||||
|
_downloadPath = newval;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
/// Construct a default settings object.
|
/// Construct a default settings object.
|
||||||
Settings(this.locale, this.theme);
|
Settings(this.locale, this.theme);
|
||||||
|
|
||||||
|
@ -232,6 +257,7 @@ class Settings extends ChangeNotifier {
|
||||||
"FirstTime": false,
|
"FirstTime": false,
|
||||||
"UIColumnModePortrait": uiColumnModePortrait.toString(),
|
"UIColumnModePortrait": uiColumnModePortrait.toString(),
|
||||||
"UIColumnModeLandscape": uiColumnModeLandscape.toString(),
|
"UIColumnModeLandscape": uiColumnModeLandscape.toString(),
|
||||||
|
"DownloadPath": _downloadPath,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
import 'dart:io';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:cwtch/config.dart';
|
||||||
import 'package:cwtch/cwtch/cwtch.dart';
|
import 'package:cwtch/cwtch/cwtch.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
@ -278,7 +280,7 @@ class _AddEditProfileViewState extends State<AddEditProfileView> {
|
||||||
// TODO Toast
|
// TODO Toast
|
||||||
}
|
}
|
||||||
|
|
||||||
void _createPressed() {
|
void _createPressed() async {
|
||||||
// This will run all the validations in the form including
|
// This will run all the validations in the form including
|
||||||
// checking that display name is not empty, and an actual check that the passwords
|
// 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).
|
// match (and are provided if the user has requested an encrypted profile).
|
||||||
|
@ -301,17 +303,32 @@ class _AddEditProfileViewState extends State<AddEditProfileView> {
|
||||||
} else {
|
} else {
|
||||||
// At this points passwords have been validated to be the same and not empty
|
// 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...
|
// Update both password and name, even if name hasn't been changed...
|
||||||
|
var profile = Provider.of<ProfileInfoState>(context, listen: false).onion;
|
||||||
Provider.of<ProfileInfoState>(context, listen: false).nickname = ctrlrNick.value.text;
|
Provider.of<ProfileInfoState>(context, listen: false).nickname = ctrlrNick.value.text;
|
||||||
Provider.of<FlwtchState>(context, listen: false).cwtch.SetProfileAttribute(Provider.of<ProfileInfoState>(context, listen: false).onion, "profile.name", ctrlrNick.value.text);
|
Provider.of<FlwtchState>(context, listen: false).cwtch.SetProfileAttribute(profile, "profile.name", ctrlrNick.value.text);
|
||||||
final updatePasswordEvent = {
|
Provider.of<FlwtchState>(context, listen: false).cwtch.ChangePassword(profile, ctrlrOldPass.text, ctrlrPass.text, ctrlrPass2.text);
|
||||||
"EventType": "ChangePassword",
|
|
||||||
"Data": {"Password": ctrlrOldPass.text, "NewPassword": ctrlrPass.text}
|
|
||||||
};
|
|
||||||
final updatePasswordEventJson = jsonEncode(updatePasswordEvent);
|
|
||||||
|
|
||||||
Provider.of<FlwtchState>(context, listen: false).cwtch.SendProfileEvent(Provider.of<ProfileInfoState>(context, listen: false).onion, updatePasswordEventJson);
|
EnvironmentConfig.debugLog("waiting for change password response");
|
||||||
|
Future.delayed(const Duration(milliseconds: 500), () {
|
||||||
Navigator.of(context).pop();
|
if (globalErrorHandler.changePasswordError) {
|
||||||
|
// TODO: This isn't ideal, but because onChange can be fired during this future check
|
||||||
|
// and because the context can change after being popped we have this kind of double assertion...
|
||||||
|
// There is probably a better pattern to handle this...
|
||||||
|
if (AppLocalizations.of(context) != null) {
|
||||||
|
final snackBar = SnackBar(content: Text(AppLocalizations.of(context)!.passwordChangeError));
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
||||||
|
Navigator.pop(context);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).whenComplete(() {
|
||||||
|
if (globalErrorHandler.explicitChangePasswordSuccess) {
|
||||||
|
final snackBar = SnackBar(content: Text(AppLocalizations.of(context)!.newPassword));
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
||||||
|
Navigator.pop(context);
|
||||||
|
return; // otherwise round and round we go...
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'package:cwtch/cwtch_icons_icons.dart';
|
import 'package:cwtch/cwtch_icons_icons.dart';
|
||||||
import 'package:cwtch/models/servers.dart';
|
import 'package:cwtch/models/servers.dart';
|
||||||
|
import 'package:cwtch/widgets/folderpicker.dart';
|
||||||
import 'package:cwtch/themes/cwtch.dart';
|
import 'package:cwtch/themes/cwtch.dart';
|
||||||
import 'package:cwtch/themes/ghost.dart';
|
import 'package:cwtch/themes/ghost.dart';
|
||||||
import 'package:cwtch/themes/mermaid.dart';
|
import 'package:cwtch/themes/mermaid.dart';
|
||||||
|
@ -254,21 +255,54 @@ class _GlobalSettingsViewState extends State<GlobalSettingsView> {
|
||||||
inactiveTrackColor: settings.theme.defaultButtonDisabledColor,
|
inactiveTrackColor: settings.theme.defaultButtonDisabledColor,
|
||||||
secondary: Icon(Icons.attach_file, color: settings.current().mainTextColor),
|
secondary: Icon(Icons.attach_file, color: settings.current().mainTextColor),
|
||||||
),
|
),
|
||||||
SwitchListTile(
|
Visibility(
|
||||||
title: Text(AppLocalizations.of(context)!.enableExperimentClickableLinks, style: TextStyle(color: settings.current().mainTextColor)),
|
visible: settings.isExperimentEnabled(FileSharingExperiment),
|
||||||
subtitle: Text(AppLocalizations.of(context)!.experimentClickableLinksDescription),
|
child: Column(children: [
|
||||||
value: settings.isExperimentEnabled(ClickableLinksExperiment),
|
SwitchListTile(
|
||||||
onChanged: (bool value) {
|
title: Text(AppLocalizations.of(context)!.settingImagePreviews, style: TextStyle(color: settings.current().mainTextColor)),
|
||||||
if (value) {
|
subtitle: Text(AppLocalizations.of(context)!.settingImagePreviewsDescription),
|
||||||
settings.enableExperiment(ClickableLinksExperiment);
|
value: settings.isExperimentEnabled(ImagePreviewsExperiment),
|
||||||
} else {
|
onChanged: (bool value) {
|
||||||
settings.disableExperiment(ClickableLinksExperiment);
|
if (value) {
|
||||||
}
|
settings.enableExperiment(ImagePreviewsExperiment);
|
||||||
saveSettings(context);
|
settings.downloadPath = Provider.of<FlwtchState>(context, listen: false).cwtch.defaultDownloadPath();
|
||||||
},
|
} else {
|
||||||
activeTrackColor: settings.theme.defaultButtonColor,
|
settings.disableExperiment(ImagePreviewsExperiment);
|
||||||
inactiveTrackColor: settings.theme.defaultButtonDisabledColor,
|
}
|
||||||
secondary: Icon(Icons.link, color: settings.current().mainTextColor),
|
saveSettings(context);
|
||||||
|
},
|
||||||
|
activeTrackColor: settings.theme.defaultButtonActiveColor,
|
||||||
|
inactiveTrackColor: settings.theme.defaultButtonDisabledColor,
|
||||||
|
secondary: Icon(Icons.attach_file, color: settings.current().mainTextColor),
|
||||||
|
),
|
||||||
|
Visibility(
|
||||||
|
visible: settings.isExperimentEnabled(ImagePreviewsExperiment) && !Platform.isAndroid,
|
||||||
|
child: CwtchFolderPicker(
|
||||||
|
label: AppLocalizations.of(context)!.settingDownloadFolder,
|
||||||
|
initialValue: settings.downloadPath,
|
||||||
|
onSave: (newVal) {
|
||||||
|
settings.downloadPath = newVal;
|
||||||
|
saveSettings(context);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SwitchListTile(
|
||||||
|
title: Text(AppLocalizations.of(context)!.enableExperimentClickableLinks, style: TextStyle(color: settings.current().mainTextColor)),
|
||||||
|
subtitle: Text(AppLocalizations.of(context)!.experimentClickableLinksDescription),
|
||||||
|
value: settings.isExperimentEnabled(ClickableLinksExperiment),
|
||||||
|
onChanged: (bool value) {
|
||||||
|
if (value) {
|
||||||
|
settings.enableExperiment(ClickableLinksExperiment);
|
||||||
|
} else {
|
||||||
|
settings.disableExperiment(ClickableLinksExperiment);
|
||||||
|
}
|
||||||
|
saveSettings(context);
|
||||||
|
},
|
||||||
|
activeTrackColor: settings.theme.defaultButtonActiveColor,
|
||||||
|
inactiveTrackColor: settings.theme.defaultButtonDisabledColor,
|
||||||
|
secondary: Icon(Icons.link, color: settings.current().mainTextColor),
|
||||||
|
),
|
||||||
|
]),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)),
|
)),
|
||||||
|
|
|
@ -5,6 +5,7 @@ import 'package:cwtch/config.dart';
|
||||||
import 'package:cwtch/cwtch_icons_icons.dart';
|
import 'package:cwtch/cwtch_icons_icons.dart';
|
||||||
import 'package:cwtch/models/message.dart';
|
import 'package:cwtch/models/message.dart';
|
||||||
import 'package:cwtch/models/messages/quotedmessage.dart';
|
import 'package:cwtch/models/messages/quotedmessage.dart';
|
||||||
|
import 'package:cwtch/widgets/malformedbubble.dart';
|
||||||
import 'package:cwtch/widgets/messageloadingbubble.dart';
|
import 'package:cwtch/widgets/messageloadingbubble.dart';
|
||||||
import 'package:cwtch/widgets/profileimage.dart';
|
import 'package:cwtch/widgets/profileimage.dart';
|
||||||
|
|
||||||
|
@ -37,6 +38,7 @@ class _MessageViewState extends State<MessageView> {
|
||||||
int selectedContact = -1;
|
int selectedContact = -1;
|
||||||
ItemPositionsListener scrollListener = ItemPositionsListener.create();
|
ItemPositionsListener scrollListener = ItemPositionsListener.create();
|
||||||
ItemScrollController scrollController = ItemScrollController();
|
ItemScrollController scrollController = ItemScrollController();
|
||||||
|
File? imagePreview;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
@ -85,7 +87,7 @@ class _MessageViewState extends State<MessageView> {
|
||||||
appBarButtons.add(IconButton(
|
appBarButtons.add(IconButton(
|
||||||
icon: Icon(Icons.attach_file, size: 24),
|
icon: Icon(Icons.attach_file, size: 24),
|
||||||
tooltip: AppLocalizations.of(context)!.tooltipSendFile,
|
tooltip: AppLocalizations.of(context)!.tooltipSendFile,
|
||||||
onPressed: _showFilePicker,
|
onPressed: (){_showFilePicker(context);},
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
appBarButtons.add(IconButton(
|
appBarButtons.add(IconButton(
|
||||||
|
@ -355,7 +357,7 @@ class _MessageViewState extends State<MessageView> {
|
||||||
return contact.onion != Provider.of<ContactInfoState>(context).onion;
|
return contact.onion != Provider.of<ContactInfoState>(context).onion;
|
||||||
}, onChanged: (newVal) {
|
}, onChanged: (newVal) {
|
||||||
setState(() {
|
setState(() {
|
||||||
this.selectedContact = Provider.of<ProfileInfoState>(context).contactList.findContact(newVal)!.identifier;
|
this.selectedContact = Provider.of<ProfileInfoState>(context, listen: false).contactList.findContact(newVal)!.identifier;
|
||||||
});
|
});
|
||||||
})),
|
})),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
|
@ -376,7 +378,8 @@ class _MessageViewState extends State<MessageView> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void _showFilePicker() async {
|
void _showFilePicker(BuildContext ctx) async {
|
||||||
|
imagePreview = null;
|
||||||
FilePickerResult? result = await FilePicker.platform.pickFiles();
|
FilePickerResult? result = await FilePicker.platform.pickFiles();
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
File file = File(result.files.first.path);
|
File file = File(result.files.first.path);
|
||||||
|
@ -384,11 +387,73 @@ class _MessageViewState extends State<MessageView> {
|
||||||
// a manifest (see : https://git.openprivacy.ca/cwtch.im/cwtch/src/branch/master/protocol/files/manifest.go#L25)
|
// a manifest (see : https://git.openprivacy.ca/cwtch.im/cwtch/src/branch/master/protocol/files/manifest.go#L25)
|
||||||
if (file.lengthSync() <= 10737418240) {
|
if (file.lengthSync() <= 10737418240) {
|
||||||
print("Sending " + file.path);
|
print("Sending " + file.path);
|
||||||
_sendFile(file.path);
|
_confirmFileSend(ctx, file.path);
|
||||||
} else {
|
} else {
|
||||||
print("file size cannot exceed 10 gigabytes");
|
final snackBar = SnackBar(
|
||||||
//todo: toast error
|
content: Text(AppLocalizations.of(context)!.msgFileTooBig),
|
||||||
|
duration: Duration(seconds: 4),
|
||||||
|
);
|
||||||
|
ScaffoldMessenger.of(ctx).showSnackBar(snackBar);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _confirmFileSend(BuildContext ctx, String path) async {
|
||||||
|
showModalBottomSheet<void>(
|
||||||
|
context: ctx,
|
||||||
|
builder: (BuildContext bcontext) {
|
||||||
|
var showPreview = false;
|
||||||
|
if (Provider.of<Settings>(context, listen: false).shouldPreview(path)) {
|
||||||
|
showPreview = true;
|
||||||
|
if (imagePreview == null) {
|
||||||
|
imagePreview = new File(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Container(
|
||||||
|
height: 300, // 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)!.msgConfirmSend + " $path?"),
|
||||||
|
SizedBox(
|
||||||
|
height: 20,
|
||||||
|
),
|
||||||
|
Visibility(visible: showPreview, child: showPreview ? Image.file(
|
||||||
|
imagePreview!,
|
||||||
|
cacheHeight: 150, // limit the amount of space the image can decode too, we keep this high-ish to allow quality previews...
|
||||||
|
filterQuality: FilterQuality.medium,
|
||||||
|
fit: BoxFit.fill,
|
||||||
|
alignment: Alignment.center,
|
||||||
|
height: 150,
|
||||||
|
isAntiAlias: false,
|
||||||
|
errorBuilder: (context, error, stackTrace) {
|
||||||
|
return MalformedBubble();
|
||||||
|
},
|
||||||
|
) : Container()),
|
||||||
|
Visibility(visible: showPreview, child: SizedBox(height: 10,)),
|
||||||
|
Row(mainAxisAlignment: MainAxisAlignment.center, children: [
|
||||||
|
ElevatedButton(
|
||||||
|
child: Text(AppLocalizations.of(context)!.cancel, semanticsLabel: AppLocalizations.of(context)!.cancel),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.pop(bcontext);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
SizedBox(width: 20,),
|
||||||
|
ElevatedButton(
|
||||||
|
child: Text(AppLocalizations.of(context)!.btnSendFile, semanticsLabel: AppLocalizations.of(context)!.btnSendFile),
|
||||||
|
onPressed: () {
|
||||||
|
_sendFile(path);
|
||||||
|
Navigator.pop(bcontext);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
)),
|
||||||
|
));
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ import 'dart:io';
|
||||||
|
|
||||||
import 'package:cwtch/config.dart';
|
import 'package:cwtch/config.dart';
|
||||||
import 'package:cwtch/models/message.dart';
|
import 'package:cwtch/models/message.dart';
|
||||||
|
import 'package:cwtch/widgets/malformedbubble.dart';
|
||||||
import 'package:file_picker_desktop/file_picker_desktop.dart';
|
import 'package:file_picker_desktop/file_picker_desktop.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
@ -22,8 +23,9 @@ class FileBubble extends StatefulWidget {
|
||||||
final String nonce;
|
final String nonce;
|
||||||
final int fileSize;
|
final int fileSize;
|
||||||
final bool interactive;
|
final bool interactive;
|
||||||
|
final bool isAuto;
|
||||||
|
|
||||||
FileBubble(this.nameSuggestion, this.rootHash, this.nonce, this.fileSize, {this.interactive = true});
|
FileBubble(this.nameSuggestion, this.rootHash, this.nonce, this.fileSize, {this.isAuto = false, this.interactive = true});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
FileBubbleState createState() => FileBubbleState();
|
FileBubbleState createState() => FileBubbleState();
|
||||||
|
@ -34,6 +36,8 @@ class FileBubble extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class FileBubbleState extends State<FileBubble> {
|
class FileBubbleState extends State<FileBubble> {
|
||||||
|
File? myFile;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
@ -46,99 +50,148 @@ class FileBubbleState extends State<FileBubble> {
|
||||||
var borderRadiousEh = 15.0;
|
var borderRadiousEh = 15.0;
|
||||||
var showFileSharing = Provider.of<Settings>(context).isExperimentEnabled(FileSharingExperiment);
|
var showFileSharing = Provider.of<Settings>(context).isExperimentEnabled(FileSharingExperiment);
|
||||||
var prettyDate = DateFormat.yMd(Platform.localeName).add_jm().format(Provider.of<MessageMetadata>(context).timestamp);
|
var prettyDate = DateFormat.yMd(Platform.localeName).add_jm().format(Provider.of<MessageMetadata>(context).timestamp);
|
||||||
|
var downloadComplete = Provider.of<ProfileInfoState>(context).downloadComplete(widget.fileKey());
|
||||||
|
var downloadInterrupted = Provider.of<ProfileInfoState>(context).downloadInterrupted(widget.fileKey());
|
||||||
|
|
||||||
|
if (flagStarted && !downloadInterrupted) {
|
||||||
|
Provider.of<FlwtchState>(context, listen: false).cwtch.CheckDownloadStatus(Provider.of<ProfileInfoState>(context, listen: false).onion, widget.fileKey());
|
||||||
|
}
|
||||||
|
|
||||||
|
var path = Provider.of<ProfileInfoState>(context).downloadFinalPath(widget.fileKey());
|
||||||
|
if (downloadComplete) {
|
||||||
|
var lpath = path!.toLowerCase();
|
||||||
|
if (lpath.endsWith(".jpg") || lpath.endsWith(".jpeg") || lpath.endsWith(".png") || lpath.endsWith(".gif") || lpath.endsWith(".webp") || lpath.endsWith(".bmp")) {
|
||||||
|
if (myFile == null) {
|
||||||
|
setState(() {
|
||||||
|
myFile = new File(path);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var downloadActive = Provider.of<ProfileInfoState>(context).downloadActive(widget.fileKey());
|
||||||
|
var downloadGotManifest = Provider.of<ProfileInfoState>(context).downloadGotManifest(widget.fileKey());
|
||||||
|
|
||||||
// If the sender is not us, then we want to give them a nickname...
|
// If the sender is not us, then we want to give them a nickname...
|
||||||
var senderDisplayStr = "";
|
var senderDisplayStr = "";
|
||||||
|
var senderIsContact = false;
|
||||||
if (!fromMe) {
|
if (!fromMe) {
|
||||||
ContactInfoState? contact = Provider.of<ProfileInfoState>(context).contactList.findContact(Provider.of<MessageMetadata>(context).senderHandle);
|
ContactInfoState? contact = Provider.of<ProfileInfoState>(context).contactList.findContact(Provider.of<MessageMetadata>(context).senderHandle);
|
||||||
if (contact != null) {
|
if (contact != null) {
|
||||||
senderDisplayStr = contact.nickname;
|
senderDisplayStr = contact.nickname;
|
||||||
|
senderIsContact = true;
|
||||||
} else {
|
} else {
|
||||||
senderDisplayStr = Provider.of<MessageMetadata>(context).senderHandle;
|
senderDisplayStr = Provider.of<MessageMetadata>(context).senderHandle;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return LayoutBuilder(builder: (bcontext, constraints) {
|
||||||
|
var wdgSender = Visibility(
|
||||||
|
visible: widget.interactive,
|
||||||
|
child: SelectableText(senderDisplayStr + '\u202F',
|
||||||
|
style: TextStyle(fontSize: 9.0, color: fromMe ? Provider.of<Settings>(context).theme.messageFromMeTextColor : Provider.of<Settings>(context).theme.messageFromOtherTextColor)));
|
||||||
|
var isPreview = false;
|
||||||
|
var wdgMessage = !showFileSharing
|
||||||
|
? Text(AppLocalizations.of(context)!.messageEnableFileSharing)
|
||||||
|
: fromMe
|
||||||
|
? senderFileChrome(AppLocalizations.of(context)!.messageFileSent, widget.nameSuggestion, widget.rootHash, widget.fileSize)
|
||||||
|
: (fileChrome(AppLocalizations.of(context)!.messageFileOffered + ":", widget.nameSuggestion, widget.rootHash, widget.fileSize,
|
||||||
|
Provider.of<ProfileInfoState>(context).downloadSpeed(widget.fileKey())));
|
||||||
|
Widget wdgDecorations;
|
||||||
|
|
||||||
var wdgSender = Center(
|
if (!showFileSharing) {
|
||||||
widthFactor: 1,
|
wdgDecorations = Text('\u202F');
|
||||||
child: SelectableText(senderDisplayStr + '\u202F',
|
} else if (fromMe) {
|
||||||
style: TextStyle(fontSize: 9.0, color: fromMe ? Provider.of<Settings>(context).theme.messageFromMeTextColor : Provider.of<Settings>(context).theme.messageFromOtherTextColor)));
|
wdgDecorations = Visibility(
|
||||||
|
visible: widget.interactive,
|
||||||
var wdgMessage = !showFileSharing
|
child: MessageBubbleDecoration(ackd: Provider.of<MessageMetadata>(context).ackd, errored: Provider.of<MessageMetadata>(context).error, fromMe: fromMe, prettyDate: prettyDate));
|
||||||
? Text(AppLocalizations.of(context)!.messageEnableFileSharing)
|
} else if (downloadComplete) {
|
||||||
: fromMe
|
// in this case, whatever marked download.complete would have also set the path
|
||||||
? senderFileChrome(AppLocalizations.of(context)!.messageFileSent, widget.nameSuggestion, widget.rootHash, widget.fileSize)
|
if (Provider.of<Settings>(context).shouldPreview(path!)) {
|
||||||
: (fileChrome(AppLocalizations.of(context)!.messageFileOffered + ":", widget.nameSuggestion, widget.rootHash, widget.fileSize,
|
isPreview = true;
|
||||||
Provider.of<ProfileInfoState>(context).downloadSpeed(widget.fileKey())));
|
wdgDecorations = Center(
|
||||||
Widget wdgDecorations;
|
child: GestureDetector(
|
||||||
if (!showFileSharing) {
|
child: Padding(
|
||||||
wdgDecorations = Text('\u202F');
|
padding: EdgeInsets.all(1.0),
|
||||||
} else if (fromMe) {
|
child: Image.file(
|
||||||
wdgDecorations = MessageBubbleDecoration(ackd: Provider.of<MessageMetadata>(context).ackd, errored: Provider.of<MessageMetadata>(context).error, fromMe: fromMe, prettyDate: prettyDate);
|
myFile!,
|
||||||
} else if (Provider.of<ProfileInfoState>(context).downloadComplete(widget.fileKey())) {
|
cacheWidth: 2048, // limit the amount of space the image can decode too, we keep this high-ish to allow quality previews...
|
||||||
// in this case, whatever marked download.complete would have also set the path
|
filterQuality: FilterQuality.medium,
|
||||||
var path = Provider.of<ProfileInfoState>(context).downloadFinalPath(widget.fileKey())!;
|
fit: BoxFit.scaleDown,
|
||||||
wdgDecorations = Text(AppLocalizations.of(context)!.fileSavedTo + ': ' + path + '\u202F');
|
alignment: Alignment.center,
|
||||||
} else if (Provider.of<ProfileInfoState>(context).downloadActive(widget.fileKey())) {
|
height: MediaQuery.of(bcontext).size.height * 0.30,
|
||||||
if (!Provider.of<ProfileInfoState>(context).downloadGotManifest(widget.fileKey())) {
|
isAntiAlias: false,
|
||||||
wdgDecorations = Text(AppLocalizations.of(context)!.retrievingManifestMessage + '\u202F');
|
errorBuilder: (context, error, stackTrace) {
|
||||||
|
return MalformedBubble();
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
onTap: () {
|
||||||
|
pop(bcontext, myFile!, wdgMessage);
|
||||||
|
},
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
wdgDecorations = Visibility(visible: widget.interactive, child: Text(AppLocalizations.of(context)!.fileSavedTo + ': ' + path + '\u202F'));
|
||||||
|
}
|
||||||
|
} else if (downloadActive) {
|
||||||
|
if (!downloadGotManifest) {
|
||||||
|
wdgDecorations = Visibility(visible: widget.interactive, child: Text(AppLocalizations.of(context)!.retrievingManifestMessage + '\u202F'));
|
||||||
|
} else {
|
||||||
|
wdgDecorations = Visibility(
|
||||||
|
visible: widget.interactive,
|
||||||
|
child: LinearProgressIndicator(
|
||||||
|
value: Provider.of<ProfileInfoState>(context).downloadProgress(widget.fileKey()),
|
||||||
|
color: Provider.of<Settings>(context).theme.defaultButtonActiveColor,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
} else if (flagStarted) {
|
||||||
|
// in this case, the download was done in a previous application launch,
|
||||||
|
// so we probably have to request an info lookup
|
||||||
|
if (!downloadInterrupted) {
|
||||||
|
wdgDecorations = Text(
|
||||||
|
AppLocalizations.of(context)!.fileCheckingStatus + '...' +
|
||||||
|
'\u202F');
|
||||||
|
} else {
|
||||||
|
var path = Provider.of<ProfileInfoState>(context).downloadFinalPath(widget.fileKey()) ?? "";
|
||||||
|
wdgDecorations = Visibility(
|
||||||
|
visible: widget.interactive,
|
||||||
|
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||||
|
Text(AppLocalizations.of(context)!.fileInterrupted + ': ' + path + '\u202F'),
|
||||||
|
ElevatedButton(onPressed: _btnResume, child: Text(AppLocalizations.of(context)!.verfiyResumeButton))
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
} else if (!senderIsContact) {
|
||||||
|
wdgDecorations = Text(AppLocalizations.of(context)!.msgAddToAccept);
|
||||||
|
} else if (!widget.isAuto) {
|
||||||
|
wdgDecorations = Visibility(
|
||||||
|
visible: widget.interactive,
|
||||||
|
child: Center(
|
||||||
|
widthFactor: 1,
|
||||||
|
child: Wrap(children: [
|
||||||
|
Padding(padding: EdgeInsets.all(5), child: ElevatedButton(child: Text(AppLocalizations.of(context)!.downloadFileButton + '\u202F'), onPressed: _btnAccept)),
|
||||||
|
])));
|
||||||
} else {
|
} else {
|
||||||
wdgDecorations = LinearProgressIndicator(
|
wdgDecorations = Container();
|
||||||
value: Provider.of<ProfileInfoState>(context).downloadProgress(widget.fileKey()),
|
|
||||||
color: Provider.of<Settings>(context).theme.defaultButtonActiveColor,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
} else if (flagStarted) {
|
|
||||||
// in this case, the download was done in a previous application launch,
|
|
||||||
// so we probably have to request an info lookup
|
|
||||||
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 {
|
|
||||||
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(
|
|
||||||
widthFactor: 1,
|
|
||||||
child: Wrap(children: [
|
|
||||||
Padding(padding: EdgeInsets.all(5), child: ElevatedButton(child: Text(AppLocalizations.of(context)!.downloadFileButton + '\u202F'), onPressed: _btnAccept)),
|
|
||||||
]));
|
|
||||||
}
|
|
||||||
|
|
||||||
return LayoutBuilder(builder: (context, constraints) {
|
return Container(
|
||||||
//print(constraints.toString()+", "+constraints.maxWidth.toString());
|
constraints: constraints,
|
||||||
return Center(
|
decoration: BoxDecoration(
|
||||||
widthFactor: 1.0,
|
color: fromMe ? Provider.of<Settings>(context).theme.messageFromMeBackgroundColor : Provider.of<Settings>(context).theme.messageFromOtherBackgroundColor,
|
||||||
child: Container(
|
border: Border.all(color: fromMe ? Provider.of<Settings>(context).theme.messageFromMeBackgroundColor : Provider.of<Settings>(context).theme.messageFromOtherBackgroundColor, width: 1),
|
||||||
decoration: BoxDecoration(
|
borderRadius: BorderRadius.only(
|
||||||
color: fromMe ? Provider.of<Settings>(context).theme.messageFromMeBackgroundColor : Provider.of<Settings>(context).theme.messageFromOtherBackgroundColor,
|
topLeft: Radius.circular(borderRadiousEh),
|
||||||
border: Border.all(color: fromMe ? Provider.of<Settings>(context).theme.messageFromMeBackgroundColor : Provider.of<Settings>(context).theme.messageFromOtherBackgroundColor, width: 1),
|
topRight: Radius.circular(borderRadiousEh),
|
||||||
borderRadius: BorderRadius.only(
|
bottomLeft: fromMe ? Radius.circular(borderRadiousEh) : Radius.zero,
|
||||||
topLeft: Radius.circular(borderRadiousEh),
|
bottomRight: fromMe ? Radius.zero : Radius.circular(borderRadiousEh),
|
||||||
topRight: Radius.circular(borderRadiousEh),
|
),
|
||||||
bottomLeft: fromMe ? Radius.circular(borderRadiousEh) : Radius.zero,
|
),
|
||||||
bottomRight: fromMe ? Radius.zero : Radius.circular(borderRadiousEh),
|
child: Padding(
|
||||||
),
|
padding: EdgeInsets.all(9.0),
|
||||||
),
|
child: Column(
|
||||||
child: Center(
|
crossAxisAlignment: fromMe ? CrossAxisAlignment.end : CrossAxisAlignment.start,
|
||||||
widthFactor: 1.0,
|
mainAxisAlignment: fromMe ? MainAxisAlignment.end : MainAxisAlignment.start,
|
||||||
child: Padding(
|
mainAxisSize: MainAxisSize.min,
|
||||||
padding: EdgeInsets.all(9.0),
|
children: fromMe ? [wdgMessage, Visibility(visible: widget.interactive, child: wdgDecorations)] : [wdgSender, isPreview ? Container() : wdgMessage, wdgDecorations]),
|
||||||
child: Wrap(alignment: WrapAlignment.start, children: [
|
));
|
||||||
Center(
|
|
||||||
widthFactor: 1.0,
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: fromMe ? CrossAxisAlignment.end : CrossAxisAlignment.start,
|
|
||||||
mainAxisAlignment: fromMe ? MainAxisAlignment.end : MainAxisAlignment.start,
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: fromMe
|
|
||||||
? [wdgMessage, Visibility(visible: widget.interactive, child: wdgDecorations)]
|
|
||||||
: [wdgSender, wdgMessage, Visibility(visible: widget.interactive, child: wdgDecorations)]),
|
|
||||||
)
|
|
||||||
])))));
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -169,7 +222,7 @@ class FileBubbleState extends State<FileBubble> {
|
||||||
Provider.of<ProfileInfoState>(context, listen: false).downloadInit(widget.fileKey(), (widget.fileSize / 4096).ceil());
|
Provider.of<ProfileInfoState>(context, listen: false).downloadInit(widget.fileKey(), (widget.fileSize / 4096).ceil());
|
||||||
Provider.of<FlwtchState>(context, listen: false).cwtch.SetMessageAttribute(profileOnion, conversation, 0, idx, "file-downloaded", "true");
|
Provider.of<FlwtchState>(context, listen: false).cwtch.SetMessageAttribute(profileOnion, conversation, 0, idx, "file-downloaded", "true");
|
||||||
//Provider.of<MessageMetadata>(context, listen: false).flags |= 0x02;
|
//Provider.of<MessageMetadata>(context, listen: false).flags |= 0x02;
|
||||||
ContactInfoState? contact = Provider.of<ProfileInfoState>(context).contactList.findContact(Provider.of<MessageMetadata>(context).senderHandle);
|
ContactInfoState? contact = Provider.of<ProfileInfoState>(context, listen: false).contactList.findContact(Provider.of<MessageMetadata>(context).senderHandle);
|
||||||
if (contact != null) {
|
if (contact != null) {
|
||||||
Provider.of<FlwtchState>(context, listen: false).cwtch.DownloadFile(profileOnion, contact.identifier, file.path, manifestPath, widget.fileKey());
|
Provider.of<FlwtchState>(context, listen: false).cwtch.DownloadFile(profileOnion, contact.identifier, file.path, manifestPath, widget.fileKey());
|
||||||
}
|
}
|
||||||
|
@ -294,4 +347,32 @@ class FileBubbleState extends State<FileBubble> {
|
||||||
)),
|
)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void pop(context, File myFile, Widget meta) async {
|
||||||
|
await showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (_) => Dialog(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: Container(
|
||||||
|
padding: EdgeInsets.all(10),
|
||||||
|
child: Column(children: [
|
||||||
|
meta,
|
||||||
|
Image.file(
|
||||||
|
myFile,
|
||||||
|
cacheWidth: (MediaQuery.of(context).size.width * 0.6).floor(),
|
||||||
|
width: (MediaQuery.of(context).size.width * 0.6),
|
||||||
|
height: (MediaQuery.of(context).size.height * 0.6),
|
||||||
|
fit: BoxFit.scaleDown,
|
||||||
|
),
|
||||||
|
Visibility(visible: !Platform.isAndroid, child: Text(myFile.path, textAlign: TextAlign.center)),
|
||||||
|
Visibility(visible: Platform.isAndroid, child: IconButton(icon: Icon(Icons.arrow_downward), onPressed: androidExport)),
|
||||||
|
]),
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
void androidExport() async {
|
||||||
|
if (myFile != null) {
|
||||||
|
Provider.of<FlwtchState>(context, listen: false).cwtch.ExportPreviewedFile(myFile!.path, widget.nameSuggestion);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,67 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:file_picker_desktop/file_picker_desktop.dart';
|
||||||
|
import 'buttontextfield.dart';
|
||||||
|
import 'cwtchlabel.dart';
|
||||||
|
|
||||||
|
class CwtchFolderPicker extends StatefulWidget {
|
||||||
|
final String label;
|
||||||
|
final String initialValue;
|
||||||
|
final Function(String)? onSave;
|
||||||
|
const CwtchFolderPicker({Key? key, this.label = "", this.initialValue = "", this.onSave}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_CwtchFolderPickerState createState() => _CwtchFolderPickerState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _CwtchFolderPickerState extends State<CwtchFolderPicker> {
|
||||||
|
final TextEditingController ctrlrVal = TextEditingController();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
ctrlrVal.text = widget.initialValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
margin: EdgeInsets.all(10),
|
||||||
|
padding: EdgeInsets.all(2),
|
||||||
|
child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||||
|
CwtchLabel(label: widget.label),
|
||||||
|
SizedBox(
|
||||||
|
height: 20,
|
||||||
|
),
|
||||||
|
CwtchButtonTextField(
|
||||||
|
controller: ctrlrVal,
|
||||||
|
readonly: Platform.isAndroid,
|
||||||
|
onPressed: () async {
|
||||||
|
if (Platform.isAndroid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
var selectedDirectory = await getDirectoryPath();
|
||||||
|
if (selectedDirectory != null) {
|
||||||
|
//File directory = File(selectedDirectory);
|
||||||
|
selectedDirectory += "/";
|
||||||
|
ctrlrVal.text = selectedDirectory;
|
||||||
|
if (widget.onSave != null) {
|
||||||
|
widget.onSave!(selectedDirectory);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// User canceled the picker
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
icon: Icon(Icons.folder),
|
||||||
|
tooltip: "Browse", //todo: l18n
|
||||||
|
)
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
}
|
|
@ -102,7 +102,7 @@ class _ProfileRowState extends State<ProfileRow> {
|
||||||
}
|
}
|
||||||
|
|
||||||
void _pushEditProfile({onion: "", displayName: "", profileImage: "", encrypted: true}) {
|
void _pushEditProfile({onion: "", displayName: "", profileImage: "", encrypted: true}) {
|
||||||
Provider.of<ErrorHandler>(context).reset();
|
Provider.of<ErrorHandler>(context, listen: false).reset();
|
||||||
Navigator.of(context).push(MaterialPageRoute<void>(
|
Navigator.of(context).push(MaterialPageRoute<void>(
|
||||||
builder: (BuildContext context) {
|
builder: (BuildContext context) {
|
||||||
return MultiProvider(
|
return MultiProvider(
|
||||||
|
|
|
@ -417,7 +417,7 @@ packages:
|
||||||
name: test_api
|
name: test_api
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.4.7"
|
version: "0.4.3"
|
||||||
typed_data:
|
typed_data:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
Loading…
Reference in New Issue