Merge pull request 'Import and Export Profile' (#397) from import_export into trunk
continuous-integration/drone/push Build is passing
Details
continuous-integration/drone/push Build is passing
Details
Reviewed-on: #397
This commit is contained in:
commit
d8e19de5b1
|
@ -1 +1 @@
|
||||||
2022-03-10-17-32-v1.6.0-6-gc2874db
|
2022-03-10-19-05-v1.6.0-9-g5b34715
|
|
@ -1 +1 @@
|
||||||
2022-03-10-22-49-v1.6.0-6-gc2874db
|
2022-03-11-00-06-v1.6.0-9-g5b34715
|
|
@ -46,7 +46,7 @@
|
||||||
<!--Needed to run in background (lol)-->
|
<!--Needed to run in background (lol)-->
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
|
|
||||||
<!--Meeded to check if activity is foregrounded or if messages from the service should be queued-->
|
<!--Needed to check if activity is foregrounded or if messages from the service should be queued-->
|
||||||
<uses-permission android:name="android.permission.GET_TASKS" />
|
<uses-permission android:name="android.permission.GET_TASKS" />
|
||||||
|
|
||||||
<queries>
|
<queries>
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package im.cwtch.flwtch
|
package im.cwtch.flwtch
|
||||||
|
|
||||||
import android.app.*
|
import android.app.*
|
||||||
|
import android.os.Environment
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.graphics.BitmapFactory
|
import android.graphics.BitmapFactory
|
||||||
|
@ -428,6 +429,17 @@ class FlwtchWorker(context: Context, parameters: WorkerParameters) :
|
||||||
notificationConversationInfo = (a.get("notificationConversationInfo") as? String)
|
notificationConversationInfo = (a.get("notificationConversationInfo") as? String)
|
||||||
?: "New Message From "
|
?: "New Message From "
|
||||||
}
|
}
|
||||||
|
"ExportProfile" -> {
|
||||||
|
val profileOnion = (a.get("ProfileOnion") as? String) ?: ""
|
||||||
|
val file = StringBuilder().append(this.applicationContext.cacheDir).append("/").append((a.get("file") as? String) ?: "").toString()
|
||||||
|
Log.i("FlwtchWorker", "constructing exported file " + file);
|
||||||
|
Cwtch.exportProfile(profileOnion,file)
|
||||||
|
}
|
||||||
|
"ImportProfile" -> {
|
||||||
|
val file = (a.get("file") as? String) ?: ""
|
||||||
|
val pass = (a.get("pass") as? String) ?: ""
|
||||||
|
return Result.success(Data.Builder().putString("result", Cwtch.importProfile(file, pass)).build());
|
||||||
|
}
|
||||||
else -> {
|
else -> {
|
||||||
Log.i(TAG, "unknown command: " + method);
|
Log.i(TAG, "unknown command: " + method);
|
||||||
return Result.failure()
|
return Result.failure()
|
||||||
|
|
|
@ -62,6 +62,7 @@ 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 val PREVIEW_EXPORT_REQUEST_CODE = 235
|
||||||
|
private val PROFILE_EXPORT_REQUEST_CODE = 236
|
||||||
private var dlToProfile = ""
|
private var dlToProfile = ""
|
||||||
private var dlToHandle = ""
|
private var dlToHandle = ""
|
||||||
private var dlToFileKey = ""
|
private var dlToFileKey = ""
|
||||||
|
@ -110,8 +111,6 @@ class MainActivity: FlutterActivity() {
|
||||||
)), ErrorLogResult(""));//placeholder; this Result is never actually invoked
|
)), ErrorLogResult(""));//placeholder; this Result is never actually invoked
|
||||||
} else if (requestCode == PREVIEW_EXPORT_REQUEST_CODE) {
|
} else if (requestCode == PREVIEW_EXPORT_REQUEST_CODE) {
|
||||||
val targetPath = intent!!.getData().toString()
|
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 sourcePath = Paths.get(this.exportFromPath);
|
||||||
val targetUri = Uri.parse(targetPath);
|
val targetUri = Uri.parse(targetPath);
|
||||||
val os = this.applicationContext.getContentResolver().openOutputStream(targetUri);
|
val os = this.applicationContext.getContentResolver().openOutputStream(targetUri);
|
||||||
|
@ -122,6 +121,20 @@ class MainActivity: FlutterActivity() {
|
||||||
os?.close();
|
os?.close();
|
||||||
//Files.delete(sourcePath);
|
//Files.delete(sourcePath);
|
||||||
}
|
}
|
||||||
|
} else if (requestCode == PROFILE_EXPORT_REQUEST_CODE ) {
|
||||||
|
val targetPath = intent!!.getData().toString()
|
||||||
|
val srcFile = StringBuilder().append(this.applicationContext.cacheDir).append("/").append(this.exportFromPath).toString();
|
||||||
|
Log.i("MainActivity:PREVIEW_EXPORT", "exporting previewed file " + srcFile);
|
||||||
|
val sourcePath = Paths.get(srcFile);
|
||||||
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -211,6 +224,14 @@ class MainActivity: FlutterActivity() {
|
||||||
}
|
}
|
||||||
startActivityForResult(intent, PREVIEW_EXPORT_REQUEST_CODE)
|
startActivityForResult(intent, PREVIEW_EXPORT_REQUEST_CODE)
|
||||||
return
|
return
|
||||||
|
} else if (call.method == "ExportProfile") {
|
||||||
|
this.exportFromPath = argmap["file"] ?: ""
|
||||||
|
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
|
||||||
|
addCategory(Intent.CATEGORY_OPENABLE)
|
||||||
|
type = "application/gzip"
|
||||||
|
putExtra(Intent.EXTRA_TITLE, argmap["file"])
|
||||||
|
}
|
||||||
|
startActivityForResult(intent, PROFILE_EXPORT_REQUEST_CODE)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ...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)
|
||||||
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
import 'package:cwtch/widgets/passwordfield.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
|
||||||
|
void showPasswordDialog(BuildContext context, String title, String action, Function(String) onEntered) {
|
||||||
|
TextEditingController passwordController = TextEditingController();
|
||||||
|
CwtchPasswordField passwordField = CwtchPasswordField(
|
||||||
|
controller: passwordController,
|
||||||
|
validator: (passsword) {
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 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(action),
|
||||||
|
onPressed: () {
|
||||||
|
onEntered(passwordController.value.text);
|
||||||
|
});
|
||||||
|
|
||||||
|
// set up the AlertDialog
|
||||||
|
AlertDialog alert = AlertDialog(
|
||||||
|
title: Text(title),
|
||||||
|
content: passwordField,
|
||||||
|
actions: [
|
||||||
|
cancelButton,
|
||||||
|
continueButton,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
// show the dialog
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return alert;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
|
@ -28,3 +28,17 @@ void showFilePicker(BuildContext ctx, int maxBytes, Function(File) onSuccess, Fu
|
||||||
onCancel();
|
onCancel();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<String?> showCreateFilePicker(BuildContext ctx) async {
|
||||||
|
// only allow one file picker at a time
|
||||||
|
// note: ideally we would destroy file picker when leaving a conversation
|
||||||
|
// but we don't currently have that option.
|
||||||
|
// we need to store AppState in a variable because ctx might be destroyed
|
||||||
|
// while awaiting for pickFiles.
|
||||||
|
var appstate = Provider.of<AppState>(ctx, listen: false);
|
||||||
|
appstate.disableFilePicker = true;
|
||||||
|
// currently lockParentWindow only works on Windows...
|
||||||
|
String? result = await FilePicker.platform.saveFile(lockParentWindow: true);
|
||||||
|
appstate.disableFilePicker = false;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
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.
|
// 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
|
// Details: https://docs.openprivacy.ca/cwtch-security-handbook/profile_encryption_and_storage.html
|
||||||
const DefaultPassword = "be gay do crime";
|
const DefaultPassword = "be gay do crime";
|
||||||
|
@ -19,6 +17,11 @@ abstract class Cwtch {
|
||||||
// ignore: non_constant_identifier_names
|
// ignore: non_constant_identifier_names
|
||||||
void ChangePassword(String profile, String pass, String newpass, String newpassAgain);
|
void ChangePassword(String profile, String pass, String newpass, String newpassAgain);
|
||||||
|
|
||||||
|
// ignore: non_constant_identifier_names
|
||||||
|
void ExportProfile(String profile, String file);
|
||||||
|
// ignore: non_constant_identifier_names
|
||||||
|
Future<dynamic> ImportProfile(String file, String pass);
|
||||||
|
|
||||||
// ignore: non_constant_identifier_names
|
// ignore: non_constant_identifier_names
|
||||||
void ResetTor();
|
void ResetTor();
|
||||||
|
|
||||||
|
|
|
@ -52,6 +52,9 @@ typedef StringFn = void Function(Pointer<Utf8> dir, int);
|
||||||
typedef string_string_to_void_function = Void Function(Pointer<Utf8> str, Int32 length, Pointer<Utf8> str2, Int32 length2);
|
typedef string_string_to_void_function = Void Function(Pointer<Utf8> str, Int32 length, Pointer<Utf8> str2, Int32 length2);
|
||||||
typedef StringStringFn = void Function(Pointer<Utf8>, int, Pointer<Utf8>, int);
|
typedef StringStringFn = void Function(Pointer<Utf8>, int, Pointer<Utf8>, int);
|
||||||
|
|
||||||
|
typedef string_string_to_string_function = Pointer<Utf8> Function(Pointer<Utf8> str, Int32 length, Pointer<Utf8> str2, Int32 length2);
|
||||||
|
typedef StringFromStringStringFn = Pointer<Utf8> Function(Pointer<Utf8>, int, Pointer<Utf8>, int);
|
||||||
|
|
||||||
typedef string_int_to_void_function = Void Function(Pointer<Utf8> str, Int32 length, Int32 handle);
|
typedef string_int_to_void_function = Void Function(Pointer<Utf8> str, Int32 length, Int32 handle);
|
||||||
typedef VoidFromStringIntFn = void Function(Pointer<Utf8>, int, int);
|
typedef VoidFromStringIntFn = void Function(Pointer<Utf8>, int, int);
|
||||||
|
|
||||||
|
@ -756,4 +759,33 @@ class CwtchFfi implements Cwtch {
|
||||||
cwtchNotifier.l10nInit(notificationSimple, notificationConversationInfo);
|
cwtchNotifier.l10nInit(notificationSimple, notificationConversationInfo);
|
||||||
_isL10nInit = true;
|
_isL10nInit = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
// ignore: non_constant_identifier_names
|
||||||
|
void ExportProfile(String profile, String file) {
|
||||||
|
final utf8profile = profile.toNativeUtf8();
|
||||||
|
final utf8file = file.toNativeUtf8();
|
||||||
|
var exportProfileC = library.lookup<NativeFunction<void_from_string_string_function>>("c_ExportProfile");
|
||||||
|
// ignore: non_constant_identifier_names
|
||||||
|
final ExportProfileFn = exportProfileC.asFunction<VoidFromStringStringFn>();
|
||||||
|
ExportProfileFn(utf8profile, utf8profile.length, utf8file, utf8file.length);
|
||||||
|
malloc.free(utf8profile);
|
||||||
|
malloc.free(utf8file);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
// ignore: non_constant_identifier_names
|
||||||
|
Future<String> ImportProfile(String file, String pass) async {
|
||||||
|
final utf8pass = pass.toNativeUtf8();
|
||||||
|
final utf8file = file.toNativeUtf8();
|
||||||
|
var exportProfileC = library.lookup<NativeFunction<string_string_to_string_function>>("c_ImportProfile");
|
||||||
|
// ignore: non_constant_identifier_names
|
||||||
|
final ExportProfileFn = exportProfileC.asFunction<StringFromStringStringFn>();
|
||||||
|
Pointer<Utf8> result = ExportProfileFn(utf8file, utf8file.length, utf8pass, utf8pass.length);
|
||||||
|
String importResult = result.toDartString();
|
||||||
|
_UnsafeFreePointerAnyUseOfThisFunctionMustBeDoubleApproved(result);
|
||||||
|
malloc.free(utf8pass);
|
||||||
|
malloc.free(utf8file);
|
||||||
|
return importResult;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -308,4 +308,16 @@ class CwtchGomobile implements Cwtch {
|
||||||
cwtchPlatform.invokeMethod("L10nInit", {"notificationSimple": notificationSimple, "notificationConversationInfo": notificationConversationInfo});
|
cwtchPlatform.invokeMethod("L10nInit", {"notificationSimple": notificationSimple, "notificationConversationInfo": notificationConversationInfo});
|
||||||
_isL10nInit = true;
|
_isL10nInit = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
// ignore: non_constant_identifier_names
|
||||||
|
void ExportProfile(String profile, String file) {
|
||||||
|
cwtchPlatform.invokeMethod("ExportProfile", {"ProfileOnion": profile, "file": file});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
// ignore: non_constant_identifier_names
|
||||||
|
Future<dynamic> ImportProfile(String file, String pass) {
|
||||||
|
return cwtchPlatform.invokeMethod("ImportProfile", {"file": file, "pass": pass});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:cwtch/config.dart';
|
import 'package:cwtch/config.dart';
|
||||||
|
import 'package:cwtch/controllers/filesharing.dart';
|
||||||
import 'package:cwtch/cwtch/cwtch.dart';
|
import 'package:cwtch/cwtch/cwtch.dart';
|
||||||
import 'package:cwtch/models/appstate.dart';
|
import 'package:cwtch/models/appstate.dart';
|
||||||
import 'package:cwtch/models/profile.dart';
|
import 'package:cwtch/models/profile.dart';
|
||||||
|
@ -286,6 +287,34 @@ class _AddEditProfileViewState extends State<AddEditProfileView> {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
Visibility(
|
||||||
|
visible: Provider.of<ProfileInfoState>(context, listen: false).onion.isNotEmpty,
|
||||||
|
child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.end, children: [
|
||||||
|
SizedBox(
|
||||||
|
height: 20,
|
||||||
|
),
|
||||||
|
Tooltip(
|
||||||
|
message: AppLocalizations.of(context)!.exportProfileTooltip,
|
||||||
|
child: ElevatedButton.icon(
|
||||||
|
onPressed: () {
|
||||||
|
if (Platform.isAndroid) {
|
||||||
|
Provider.of<FlwtchState>(context, listen: false).cwtch.ExportProfile(ctrlrOnion.value.text, ctrlrOnion.value.text + ".tar.gz");
|
||||||
|
final snackBar = SnackBar(content: Text(AppLocalizations.of(context)!.fileSavedTo + " " + ctrlrOnion.value.text + ".tar.gz"));
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
||||||
|
} else {
|
||||||
|
showCreateFilePicker(context).then((name) {
|
||||||
|
if (name != null) {
|
||||||
|
Provider.of<FlwtchState>(context, listen: false).cwtch.ExportProfile(ctrlrOnion.value.text, name);
|
||||||
|
final snackBar = SnackBar(content: Text(AppLocalizations.of(context)!.fileSavedTo + " " + name));
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
icon: Icon(Icons.import_export),
|
||||||
|
label: Text(AppLocalizations.of(context)!.exportProfile),
|
||||||
|
))
|
||||||
|
])),
|
||||||
Visibility(
|
Visibility(
|
||||||
visible: Provider.of<ProfileInfoState>(context, listen: false).onion.isNotEmpty,
|
visible: Provider.of<ProfileInfoState>(context, listen: false).onion.isNotEmpty,
|
||||||
child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.end, children: [
|
child: Column(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.end, children: [
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:cwtch/constants.dart';
|
||||||
|
import 'package:cwtch/controllers/enter_password.dart';
|
||||||
|
import 'package:cwtch/controllers/filesharing.dart';
|
||||||
import 'package:cwtch/cwtch_icons_icons.dart';
|
import 'package:cwtch/cwtch_icons_icons.dart';
|
||||||
import 'package:cwtch/models/appstate.dart';
|
import 'package:cwtch/models/appstate.dart';
|
||||||
import 'package:cwtch/models/profile.dart';
|
import 'package:cwtch/models/profile.dart';
|
||||||
|
@ -67,7 +70,7 @@ class _ProfileMgrViewState extends State<ProfileMgrView> {
|
||||||
actions: getActions(),
|
actions: getActions(),
|
||||||
),
|
),
|
||||||
floatingActionButton: FloatingActionButton(
|
floatingActionButton: FloatingActionButton(
|
||||||
onPressed: _pushAddProfile,
|
onPressed: _modalAddImportProfiles,
|
||||||
tooltip: AppLocalizations.of(context)!.addNewProfileBtn,
|
tooltip: AppLocalizations.of(context)!.addNewProfileBtn,
|
||||||
child: Icon(
|
child: Icon(
|
||||||
Icons.add,
|
Icons.add,
|
||||||
|
@ -159,8 +162,9 @@ class _ProfileMgrViewState extends State<ProfileMgrView> {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
void _pushAddProfile({onion: ""}) {
|
void _pushAddProfile(bcontext, {onion: ""}) {
|
||||||
Navigator.of(context).push(MaterialPageRoute<void>(
|
Navigator.popUntil(bcontext, (route) => route.isFirst);
|
||||||
|
Navigator.of(bcontext).push(MaterialPageRoute<void>(
|
||||||
builder: (BuildContext context) {
|
builder: (BuildContext context) {
|
||||||
return MultiProvider(
|
return MultiProvider(
|
||||||
providers: [
|
providers: [
|
||||||
|
@ -174,6 +178,70 @@ class _ProfileMgrViewState extends State<ProfileMgrView> {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _modalAddImportProfiles() {
|
||||||
|
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>[
|
||||||
|
Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [
|
||||||
|
Spacer(),
|
||||||
|
Expanded(
|
||||||
|
child: ElevatedButton(
|
||||||
|
child: Text(AppLocalizations.of(context)!.addProfileTitle, semanticsLabel: AppLocalizations.of(context)!.addProfileTitle),
|
||||||
|
onPressed: () {
|
||||||
|
_pushAddProfile(context);
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
Spacer()
|
||||||
|
]),
|
||||||
|
SizedBox(
|
||||||
|
height: 20,
|
||||||
|
),
|
||||||
|
Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [
|
||||||
|
Spacer(),
|
||||||
|
Expanded(
|
||||||
|
child: Tooltip(
|
||||||
|
message: AppLocalizations.of(context)!.importProfileTooltip,
|
||||||
|
child: ElevatedButton(
|
||||||
|
child: Text(AppLocalizations.of(context)!.importProfile, semanticsLabel: AppLocalizations.of(context)!.importProfile),
|
||||||
|
onPressed: () {
|
||||||
|
// 10GB profiles should be enough for anyone?
|
||||||
|
showFilePicker(context, MaxGeneralFileSharingSize, (file) {
|
||||||
|
showPasswordDialog(context, AppLocalizations.of(context)!.importProfile, AppLocalizations.of(context)!.importProfile, (password) {
|
||||||
|
Navigator.popUntil(context, (route) => route.isFirst);
|
||||||
|
Provider.of<FlwtchState>(context, listen: false).cwtch.ImportProfile(file.path, password).then((value) {
|
||||||
|
if (value == "") {
|
||||||
|
final snackBar = SnackBar(content: Text(AppLocalizations.of(context)!.successfullyImportedProfile.replaceFirst("%profile", file.path)));
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
||||||
|
} else {
|
||||||
|
final snackBar = SnackBar(content: Text(AppLocalizations.of(context)!.failedToImportProfile));
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}, () {}, () {});
|
||||||
|
},
|
||||||
|
))),
|
||||||
|
Spacer()
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
))),
|
||||||
|
)));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
void _modalUnlockProfiles() {
|
void _modalUnlockProfiles() {
|
||||||
showModalBottomSheet<void>(
|
showModalBottomSheet<void>(
|
||||||
context: context,
|
context: context,
|
||||||
|
|
|
@ -46,10 +46,10 @@ class FileBubbleState extends State<FileBubble> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var fromMe = Provider.of<MessageMetadata>(context, listen: false).senderHandle == Provider.of<ProfileInfoState>(context).onion;
|
var fromMe = Provider.of<MessageMetadata>(context).senderHandle == Provider.of<ProfileInfoState>(context).onion;
|
||||||
var flagStarted = Provider.of<MessageMetadata>(context).attributes["file-downloaded"] == "true";
|
var flagStarted = Provider.of<MessageMetadata>(context).attributes["file-downloaded"] == "true";
|
||||||
var borderRadiousEh = 15.0;
|
var borderRadiousEh = 15.0;
|
||||||
var showFileSharing = Provider.of<Settings>(context, listen: false).isExperimentEnabled(FileSharingExperiment);
|
var showFileSharing = Provider.of<Settings>(context).isExperimentEnabled(FileSharingExperiment);
|
||||||
DateTime messageDate = Provider.of<MessageMetadata>(context).timestamp;
|
DateTime messageDate = Provider.of<MessageMetadata>(context).timestamp;
|
||||||
|
|
||||||
var metadata = Provider.of<MessageMetadata>(context);
|
var metadata = Provider.of<MessageMetadata>(context);
|
||||||
|
|
Loading…
Reference in New Issue