wip: filesharing ui dev
This commit is contained in:
parent
4eed72ded3
commit
cb3c161277
|
@ -33,7 +33,7 @@ if (keystorePropertiesFile.exists()) {
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 29
|
compileSdkVersion 30
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
main.java.srcDirs += 'src/main/kotlin'
|
main.java.srcDirs += 'src/main/kotlin'
|
||||||
|
@ -48,7 +48,7 @@ android {
|
||||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||||
applicationId "im.cwtch.flwtch"
|
applicationId "im.cwtch.flwtch"
|
||||||
minSdkVersion 16
|
minSdkVersion 16
|
||||||
targetSdkVersion 29
|
targetSdkVersion 30
|
||||||
versionCode flutterVersionCode.toInteger()
|
versionCode flutterVersionCode.toInteger()
|
||||||
versionName flutterVersionName
|
versionName flutterVersionName
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
## This file must *NOT* be checked into Version Control Systems,
|
||||||
|
# as it contains information specific to your local configuration.
|
||||||
|
#
|
||||||
|
# Location of the SDK. This is only used by Gradle.
|
||||||
|
# For customization when using a Version Control System, please read the
|
||||||
|
# header note.
|
||||||
|
#Fri Jul 02 15:08:54 PDT 2021
|
||||||
|
sdk.dir=/home/erinn/Android/Sdk
|
|
@ -9,7 +9,8 @@
|
||||||
android:name="io.flutter.app.FlutterApplication"
|
android:name="io.flutter.app.FlutterApplication"
|
||||||
android:label="Cwtch"
|
android:label="Cwtch"
|
||||||
android:extractNativeLibs="true"
|
android:extractNativeLibs="true"
|
||||||
android:icon="@mipmap/knott">
|
android:icon="@mipmap/knott"
|
||||||
|
android:requestLegacyExternalStorage="true">
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:launchMode="singleTop"
|
android:launchMode="singleTop"
|
||||||
|
@ -48,4 +49,10 @@
|
||||||
|
|
||||||
<!--Meeded to check if activity is foregrounded or if messages from the service should be queued-->
|
<!--Meeded 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" />
|
||||||
|
|
||||||
|
<!--Needed to download files-->
|
||||||
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||||
|
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
|
||||||
|
<uses-permission android:name="android.permission.PERMISSIONS_STORAGE" />
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|
|
@ -15,6 +15,10 @@ import cwtch.Cwtch
|
||||||
import io.flutter.FlutterInjector
|
import io.flutter.FlutterInjector
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
|
|
||||||
|
import java.nio.file.Files
|
||||||
|
import java.nio.file.Paths
|
||||||
|
import java.nio.file.StandardCopyOption
|
||||||
|
import android.net.Uri
|
||||||
|
|
||||||
class FlwtchWorker(context: Context, parameters: WorkerParameters) :
|
class FlwtchWorker(context: Context, parameters: WorkerParameters) :
|
||||||
CoroutineWorker(context, parameters) {
|
CoroutineWorker(context, parameters) {
|
||||||
|
@ -93,6 +97,24 @@ class FlwtchWorker(context: Context, parameters: WorkerParameters) :
|
||||||
.build()
|
.build()
|
||||||
notificationManager.notify(getNotificationID(data.getString("ProfileOnion"), handle), newNotification)
|
notificationManager.notify(getNotificationID(data.getString("ProfileOnion"), handle), newNotification)
|
||||||
}
|
}
|
||||||
|
} else if (evt.EventType == "FileDownloaded") {
|
||||||
|
Log.i("FlwtchWorker", "file downloaded!");
|
||||||
|
val data = JSONObject(evt.Data);
|
||||||
|
val tempFile = data.getString("TempFile");
|
||||||
|
if (tempFile != "") {
|
||||||
|
val filePath = data.getString("FilePath");
|
||||||
|
Log.i("FlwtchWorker", "moving "+tempFile+" to "+filePath);
|
||||||
|
val sourcePath = Paths.get(tempFile);
|
||||||
|
val targetUri = Uri.parse(filePath);
|
||||||
|
val os = this.applicationContext.getContentResolver().openOutputStream(targetUri);
|
||||||
|
val bytesWritten = Files.copy(sourcePath, os);
|
||||||
|
Log.i("FlwtchWorker", "copied " + bytesWritten.toString() + " bytes");
|
||||||
|
if (bytesWritten != 0L) {
|
||||||
|
os?.flush();
|
||||||
|
os?.close();
|
||||||
|
Files.delete(sourcePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Intent().also { intent ->
|
Intent().also { intent ->
|
||||||
|
@ -157,6 +179,21 @@ class FlwtchWorker(context: Context, parameters: WorkerParameters) :
|
||||||
val target = (a.get("target") as? String) ?: ""
|
val target = (a.get("target") as? String) ?: ""
|
||||||
Cwtch.sendInvitation(profile, handle, target)
|
Cwtch.sendInvitation(profile, handle, target)
|
||||||
}
|
}
|
||||||
|
"ShareFile" -> {
|
||||||
|
val profile = (a.get("ProfileOnion") as? String) ?: ""
|
||||||
|
val handle = (a.get("handle") as? String) ?: ""
|
||||||
|
val filepath = (a.get("filepath") as? String) ?: ""
|
||||||
|
Cwtch.shareFile(profile, handle, filepath)
|
||||||
|
}
|
||||||
|
"DownloadFile" -> {
|
||||||
|
val profile = (a.get("ProfileOnion") as? String) ?: ""
|
||||||
|
val handle = (a.get("handle") as? String) ?: ""
|
||||||
|
val filepath = (a.get("filepath") as? String) ?: ""
|
||||||
|
val manifestpath = (a.get("manifestpath") as? String) ?: ""
|
||||||
|
val filekey = (a.get("filekey") as? String) ?: ""
|
||||||
|
Log.i("FlwtchWorker::DownloadFile", "DownloadFile("+filepath+", "+manifestpath+")")
|
||||||
|
Cwtch.downloadFile(profile, handle, filepath, manifestpath, filekey)
|
||||||
|
}
|
||||||
"SendProfileEvent" -> {
|
"SendProfileEvent" -> {
|
||||||
val onion = (a.get("onion") as? String) ?: ""
|
val onion = (a.get("onion") as? String) ?: ""
|
||||||
val jsonEvent = (a.get("jsonEvent") as? String) ?: ""
|
val jsonEvent = (a.get("jsonEvent") as? String) ?: ""
|
||||||
|
|
|
@ -12,17 +12,25 @@ import android.view.Window
|
||||||
import androidx.lifecycle.Observer
|
import androidx.lifecycle.Observer
|
||||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager
|
import androidx.localbroadcastmanager.content.LocalBroadcastManager
|
||||||
import androidx.work.*
|
import androidx.work.*
|
||||||
|
|
||||||
import io.flutter.embedding.android.SplashScreen
|
import io.flutter.embedding.android.SplashScreen
|
||||||
import io.flutter.embedding.android.FlutterActivity
|
import io.flutter.embedding.android.FlutterActivity
|
||||||
import io.flutter.embedding.engine.FlutterEngine
|
import io.flutter.embedding.engine.FlutterEngine
|
||||||
import io.flutter.plugin.common.MethodChannel
|
import io.flutter.plugin.common.MethodChannel
|
||||||
import io.flutter.plugin.common.MethodCall
|
import io.flutter.plugin.common.MethodCall
|
||||||
import io.flutter.plugin.common.MethodChannel.Result
|
import io.flutter.plugin.common.MethodChannel.Result
|
||||||
|
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 android.net.Uri
|
||||||
|
import android.provider.DocumentsContract
|
||||||
|
import android.content.ContentUris
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.Environment
|
||||||
|
import android.database.Cursor
|
||||||
|
import android.provider.MediaStore
|
||||||
|
|
||||||
class MainActivity: FlutterActivity() {
|
class MainActivity: FlutterActivity() {
|
||||||
override fun provideSplashScreen(): SplashScreen? = SplashView()
|
override fun provideSplashScreen(): SplashScreen? = SplashView()
|
||||||
|
|
||||||
|
@ -47,6 +55,11 @@ class MainActivity: FlutterActivity() {
|
||||||
private var notificationClickChannel: MethodChannel? = null
|
private var notificationClickChannel: MethodChannel? = null
|
||||||
private var shutdownClickChannel: MethodChannel? = null
|
private var shutdownClickChannel: MethodChannel? = null
|
||||||
|
|
||||||
|
// "Download to..." prompt extra arguments
|
||||||
|
private var dlToProfile = ""
|
||||||
|
private var dlToHandle = ""
|
||||||
|
private var dlToFileKey = ""
|
||||||
|
|
||||||
override fun onNewIntent(intent: Intent) {
|
override fun onNewIntent(intent: Intent) {
|
||||||
super.onNewIntent(intent)
|
super.onNewIntent(intent)
|
||||||
if (notificationClickChannel == null || intent.extras == null) return
|
if (notificationClickChannel == null || intent.extras == null) return
|
||||||
|
@ -68,6 +81,25 @@ class MainActivity: FlutterActivity() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onActivityResult(requestCode: Int, result: Int, intent: Intent?) {
|
||||||
|
if (intent == null || intent!!.getData() == null) {
|
||||||
|
Log.i("MainActivity:onActivityResult", "user canceled activity");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
val filePath = intent!!.getData().toString();
|
||||||
|
val manifestPath = StringBuilder().append(this.applicationContext.cacheDir).append("/").append(this.dlToFileKey).toString();
|
||||||
|
Log.i("onActivityResult", "got download path: " + filePath);
|
||||||
|
Log.i("onActivityResult", "got manifest path: " + manifestPath);
|
||||||
|
handleCwtch(MethodCall("DownloadFile", mapOf(
|
||||||
|
"ProfileOnion" to this.dlToProfile,
|
||||||
|
"handle" to this.dlToHandle,
|
||||||
|
"filepath" to filePath,
|
||||||
|
"manifestpath" to manifestPath,
|
||||||
|
"filekey" to this.dlToFileKey
|
||||||
|
)), ErrorLogResult(""));//placeholder; result is never actually invoked
|
||||||
|
}
|
||||||
|
|
||||||
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
|
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
|
||||||
super.configureFlutterEngine(flutterEngine)
|
super.configureFlutterEngine(flutterEngine)
|
||||||
// Note: this methods are invoked on the main thread.
|
// Note: this methods are invoked on the main thread.
|
||||||
|
@ -125,6 +157,18 @@ class MainActivity: FlutterActivity() {
|
||||||
val workRequest = PeriodicWorkRequestBuilder<FlwtchWorker>(15, TimeUnit.MINUTES).setInputData(data).addTag(WORKER_TAG).addTag(uniqueTag).build()
|
val workRequest = PeriodicWorkRequestBuilder<FlwtchWorker>(15, TimeUnit.MINUTES).setInputData(data).addTag(WORKER_TAG).addTag(uniqueTag).build()
|
||||||
WorkManager.getInstance(this).enqueueUniquePeriodicWork("req_$uniqueTag", ExistingPeriodicWorkPolicy.REPLACE, workRequest)
|
WorkManager.getInstance(this).enqueueUniquePeriodicWork("req_$uniqueTag", ExistingPeriodicWorkPolicy.REPLACE, workRequest)
|
||||||
return
|
return
|
||||||
|
} else if (call.method == "CreateDownloadableFile") {
|
||||||
|
this.dlToProfile = argmap["ProfileOnion"] ?: ""
|
||||||
|
this.dlToHandle = argmap["handle"] ?: ""
|
||||||
|
val suggestedName = argmap["filename"] ?: "filename.ext"
|
||||||
|
this.dlToFileKey = argmap["filekey"] ?: ""
|
||||||
|
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
|
||||||
|
addCategory(Intent.CATEGORY_OPENABLE)
|
||||||
|
type = "application/octet-stream"
|
||||||
|
putExtra(Intent.EXTRA_TITLE, suggestedName)
|
||||||
|
}
|
||||||
|
startActivityForResult(intent, 1)
|
||||||
|
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)
|
||||||
|
@ -178,20 +222,6 @@ class MainActivity: FlutterActivity() {
|
||||||
WorkManager.getInstance(this).pruneWork()
|
WorkManager.getInstance(this).pruneWork()
|
||||||
}
|
}
|
||||||
|
|
||||||
// source: https://web.archive.org/web/20210203022531/https://stackoverflow.com/questions/41928803/how-to-parse-json-in-kotlin/50468095
|
|
||||||
// for reference:
|
|
||||||
//
|
|
||||||
// class Response(json: String) : JSONObject(json) {
|
|
||||||
// val type: String? = this.optString("type")
|
|
||||||
// val data = this.optJSONArray("data")
|
|
||||||
// ?.let { 0.until(it.length()).map { i -> it.optJSONObject(i) } } // returns an array of JSONObject
|
|
||||||
// ?.map { Foo(it.toString()) } // transforms each JSONObject of the array into Foo
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// class Foo(json: String) : JSONObject(json) {
|
|
||||||
// val id = this.optInt("id")
|
|
||||||
// val title: String? = this.optString("title")
|
|
||||||
// }
|
|
||||||
class AppbusEvent(json: String) : JSONObject(json) {
|
class AppbusEvent(json: String) : JSONObject(json) {
|
||||||
val EventType = this.optString("EventType")
|
val EventType = this.optString("EventType")
|
||||||
val EventID = this.optString("EventID")
|
val EventID = this.optString("EventID")
|
||||||
|
|
|
@ -6,7 +6,7 @@ buildscript {
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:3.5.0'
|
classpath 'com.android.tools.build:gradle:3.5.4'
|
||||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -360,7 +360,7 @@ class ProfileInfoState extends ChangeNotifier {
|
||||||
}
|
}
|
||||||
|
|
||||||
void downloadInit(String fileKey, int numChunks) {
|
void downloadInit(String fileKey, int numChunks) {
|
||||||
this._downloads[fileKey] = FileDownloadProgress(numChunks);
|
this._downloads[fileKey] = FileDownloadProgress(numChunks, DateTime.now());
|
||||||
}
|
}
|
||||||
|
|
||||||
void downloadUpdate(String fileKey, int progress) {
|
void downloadUpdate(String fileKey, int progress) {
|
||||||
|
@ -385,6 +385,7 @@ class ProfileInfoState extends ChangeNotifier {
|
||||||
if (!downloadActive(fileKey)) {
|
if (!downloadActive(fileKey)) {
|
||||||
print("error: received download completion notice for unknown download "+fileKey);
|
print("error: received download completion notice for unknown download "+fileKey);
|
||||||
} else {
|
} else {
|
||||||
|
this._downloads[fileKey]!.timeEnd = DateTime.now();
|
||||||
this._downloads[fileKey]!.complete = true;
|
this._downloads[fileKey]!.complete = true;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
@ -405,6 +406,18 @@ class ProfileInfoState extends ChangeNotifier {
|
||||||
double downloadProgress(String fileKey) {
|
double downloadProgress(String fileKey) {
|
||||||
return this._downloads.containsKey(fileKey) ? this._downloads[fileKey]!.progress() : 0.0;
|
return this._downloads.containsKey(fileKey) ? this._downloads[fileKey]!.progress() : 0.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String downloadSpeed(String fileKey) {
|
||||||
|
if (!downloadActive(fileKey)) {
|
||||||
|
return "0 B/s";
|
||||||
|
}
|
||||||
|
var bytes = this._downloads[fileKey]!.chunksDownloaded * 4096;
|
||||||
|
var seconds = (this._downloads[fileKey]!.timeEnd ?? DateTime.now()).difference(this._downloads[fileKey]!.timeStart!).inSeconds;
|
||||||
|
if (seconds == 0) {
|
||||||
|
return "0 B/s";
|
||||||
|
}
|
||||||
|
return prettyBytes((bytes / seconds).round()) + "/s";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class FileDownloadProgress {
|
class FileDownloadProgress {
|
||||||
|
@ -412,13 +425,27 @@ class FileDownloadProgress {
|
||||||
int chunksTotal = 1;
|
int chunksTotal = 1;
|
||||||
bool complete = false;
|
bool complete = false;
|
||||||
bool gotManifest = false;
|
bool gotManifest = false;
|
||||||
|
DateTime? timeStart;
|
||||||
|
DateTime? timeEnd;
|
||||||
|
|
||||||
FileDownloadProgress(this.chunksTotal);
|
FileDownloadProgress(this.chunksTotal, this.timeStart);
|
||||||
double progress() {
|
double progress() {
|
||||||
return 1.0 * chunksDownloaded / chunksTotal;
|
return 1.0 * chunksDownloaded / chunksTotal;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String prettyBytes(int bytes) {
|
||||||
|
if (bytes > 1000000000) {
|
||||||
|
return (1.0 * bytes / 1000000000).toStringAsFixed(1) + " GB";
|
||||||
|
} else if (bytes > 1000000) {
|
||||||
|
return (1.0 * bytes / 1000000).toStringAsFixed(1) + " MB";
|
||||||
|
} else if (bytes > 1000) {
|
||||||
|
return (1.0 * bytes / 1000).toStringAsFixed(1) + " kB";
|
||||||
|
} else {
|
||||||
|
return bytes.toString() + " B";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
enum ContactAuthorization { unknown, approved, blocked }
|
enum ContactAuthorization { unknown, approved, blocked }
|
||||||
|
|
||||||
ContactAuthorization stringToContactAuthorization(String authStr) {
|
ContactAuthorization stringToContactAuthorization(String authStr) {
|
||||||
|
|
|
@ -189,7 +189,7 @@ class _GlobalSettingsViewState extends State<GlobalSettingsView> {
|
||||||
secondary: Icon(CwtchIcons.enable_groups, color: settings.current().mainTextColor()),
|
secondary: Icon(CwtchIcons.enable_groups, color: settings.current().mainTextColor()),
|
||||||
),
|
),
|
||||||
SwitchListTile(
|
SwitchListTile(
|
||||||
title: Text(AppLocalizations.of(context)!.labelFileSharing, style: TextStyle(color: settings.current().mainTextColor())),
|
title: Text(AppLocalizations.of(context)!.settingFileSharing, style: TextStyle(color: settings.current().mainTextColor())),
|
||||||
subtitle: Text(AppLocalizations.of(context)!.descriptionFileSharing),
|
subtitle: Text(AppLocalizations.of(context)!.descriptionFileSharing),
|
||||||
value: settings.isExperimentEnabled(FileSharingExperiment),
|
value: settings.isExperimentEnabled(FileSharingExperiment),
|
||||||
onChanged: (bool value) {
|
onChanged: (bool value) {
|
||||||
|
|
|
@ -40,6 +40,9 @@ class _MessageViewState extends State<MessageView> {
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
scrollListener.itemPositions.addListener(() {
|
scrollListener.itemPositions.addListener(() {
|
||||||
|
if (scrollListener.itemPositions.value.length == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
var first = scrollListener.itemPositions.value.first.index;
|
var first = scrollListener.itemPositions.value.first.index;
|
||||||
var last = scrollListener.itemPositions.value.last.index;
|
var last = scrollListener.itemPositions.value.last.index;
|
||||||
// sometimes these go hi->lo and sometimes they go lo->hi because [who tf knows]
|
// sometimes these go hi->lo and sometimes they go lo->hi because [who tf knows]
|
||||||
|
|
|
@ -63,9 +63,9 @@ class FileBubbleState extends State<FileBubble> {
|
||||||
var wdgMessage = !showFileSharing
|
var wdgMessage = !showFileSharing
|
||||||
? Text(AppLocalizations.of(context)!.messageEnableFileSharing)
|
? Text(AppLocalizations.of(context)!.messageEnableFileSharing)
|
||||||
: fromMe
|
: fromMe
|
||||||
? senderInviteChrome(
|
? senderFileChrome(
|
||||||
AppLocalizations.of(context)!.messageFileSent, widget.nameSuggestion, widget.rootHash, widget.fileSize)
|
AppLocalizations.of(context)!.messageFileSent, widget.nameSuggestion, widget.rootHash, widget.fileSize)
|
||||||
: (inviteChrome(AppLocalizations.of(context)!.messageFileOffered + ":", 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;
|
Widget wdgDecorations;
|
||||||
if (!showFileSharing) {
|
if (!showFileSharing) {
|
||||||
|
@ -147,9 +147,8 @@ class FileBubbleState extends State<FileBubble> {
|
||||||
file = File(selectedFileName);
|
file = File(selectedFileName);
|
||||||
print("saving to " + file.path);
|
print("saving to " + file.path);
|
||||||
var manifestPath = file.path + ".manifest";
|
var manifestPath = file.path + ".manifest";
|
||||||
setState(() {
|
Provider.of<ProfileInfoState>(context, listen: false).downloadInit(widget.fileKey(), (widget.fileSize / 4096).ceil());
|
||||||
Provider.of<FlwtchState>(context, listen: false).cwtch.DownloadFile(profileOnion, handle, file!.path, manifestPath, widget.fileKey());
|
Provider.of<FlwtchState>(context, listen: false).cwtch.DownloadFile(profileOnion, handle, file.path, manifestPath, widget.fileKey());
|
||||||
});
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print(e);
|
print(e);
|
||||||
|
@ -158,8 +157,8 @@ class FileBubbleState extends State<FileBubble> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Construct an invite chrome for the sender
|
// Construct an invite chrome for the sender
|
||||||
Widget senderInviteChrome(String chrome, String fileName, String rootHash, int fileSize) {
|
Widget senderFileChrome(String chrome, String fileName, String rootHash, int fileSize) {
|
||||||
return Wrap(children: [
|
return Wrap(direction: Axis.vertical,children: [
|
||||||
SelectableText(
|
SelectableText(
|
||||||
chrome + '\u202F',
|
chrome + '\u202F',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
|
@ -179,7 +178,7 @@ class FileBubbleState extends State<FileBubble> {
|
||||||
textWidthBasis: TextWidthBasis.longestLine,
|
textWidthBasis: TextWidthBasis.longestLine,
|
||||||
),
|
),
|
||||||
SelectableText(
|
SelectableText(
|
||||||
fileSize.toString() + 'B\u202F',
|
prettyBytes(fileSize) + '\u202F',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Provider.of<Settings>(context).theme.messageFromMeTextColor(),
|
color: Provider.of<Settings>(context).theme.messageFromMeTextColor(),
|
||||||
),
|
),
|
||||||
|
@ -200,7 +199,7 @@ class FileBubbleState extends State<FileBubble> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Construct an invite chrome
|
// Construct an invite chrome
|
||||||
Widget inviteChrome(String chrome, String fileName, String rootHash, int fileSize) {
|
Widget fileChrome(String chrome, String fileName, String rootHash, int fileSize, String speed) {
|
||||||
var prettyHash = rootHash;
|
var prettyHash = rootHash;
|
||||||
if (rootHash.length == 128) {
|
if (rootHash.length == 128) {
|
||||||
prettyHash = rootHash.substring(0, 32) + '\n' +
|
prettyHash = rootHash.substring(0, 32) + '\n' +
|
||||||
|
@ -230,7 +229,7 @@ class FileBubbleState extends State<FileBubble> {
|
||||||
textWidthBasis: TextWidthBasis.longestLine,
|
textWidthBasis: TextWidthBasis.longestLine,
|
||||||
),
|
),
|
||||||
SelectableText(
|
SelectableText(
|
||||||
AppLocalizations.of(context)!.labelFilesize + ': ' + fileSize.toString() + 'B\u202F',
|
AppLocalizations.of(context)!.labelFilesize + ': ' + prettyBytes(fileSize) + '\u202F',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Provider.of<Settings>(context).theme.messageFromMeTextColor(),
|
color: Provider.of<Settings>(context).theme.messageFromMeTextColor(),
|
||||||
),
|
),
|
||||||
|
@ -247,6 +246,15 @@ class FileBubbleState extends State<FileBubble> {
|
||||||
maxLines: 4,
|
maxLines: 4,
|
||||||
textWidthBasis: TextWidthBasis.longestLine,
|
textWidthBasis: TextWidthBasis.longestLine,
|
||||||
),
|
),
|
||||||
|
SelectableText(
|
||||||
|
speed + '\u202F',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Provider.of<Settings>(context).theme.messageFromMeTextColor(),
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.left,
|
||||||
|
maxLines: 1,
|
||||||
|
textWidthBasis: TextWidthBasis.longestLine,
|
||||||
|
),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -163,7 +163,7 @@ class MessageRowState extends State<MessageRow> with SingleTickerProviderStateMi
|
||||||
// For desktop...
|
// For desktop...
|
||||||
onHover: (event) {
|
onHover: (event) {
|
||||||
setState(() {
|
setState(() {
|
||||||
Provider.of<AppState>(context, listen: false).hoveredIndex = Provider.of<MessageMetadata>(context).messageIndex;
|
Provider.of<AppState>(context, listen: false).hoveredIndex = Provider.of<MessageMetadata>(context, listen: false).messageIndex;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
onExit: (event) {
|
onExit: (event) {
|
||||||
|
|
29
pubspec.lock
29
pubspec.lock
|
@ -21,7 +21,7 @@ packages:
|
||||||
name: async
|
name: async
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.8.1"
|
version: "2.8.2"
|
||||||
boolean_selector:
|
boolean_selector:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -106,6 +106,20 @@ packages:
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.1.2"
|
version: "6.1.2"
|
||||||
|
file_picker:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: file_picker
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "4.0.1"
|
||||||
|
file_picker_desktop:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: file_picker_desktop
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.0"
|
||||||
flutter:
|
flutter:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description: flutter
|
description: flutter
|
||||||
|
@ -116,6 +130,13 @@ packages:
|
||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
version: "0.0.0"
|
||||||
|
flutter_plugin_android_lifecycle:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_plugin_android_lifecycle
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.3"
|
||||||
flutter_test:
|
flutter_test:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description: flutter
|
description: flutter
|
||||||
|
@ -174,7 +195,7 @@ packages:
|
||||||
name: matcher
|
name: matcher
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.12.10"
|
version: "0.12.11"
|
||||||
meta:
|
meta:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -382,7 +403,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.2"
|
version: "0.4.3"
|
||||||
typed_data:
|
typed_data:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -427,4 +448,4 @@ packages:
|
||||||
version: "3.1.0"
|
version: "3.1.0"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=2.13.0 <3.0.0"
|
dart: ">=2.13.0 <3.0.0"
|
||||||
flutter: ">=1.20.0"
|
flutter: ">=2.0.0"
|
||||||
|
|
|
@ -41,6 +41,8 @@ dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
scrollable_positioned_list: ^0.2.0-nullsafety.0
|
scrollable_positioned_list: ^0.2.0-nullsafety.0
|
||||||
|
file_picker: ^4.0.1
|
||||||
|
file_picker_desktop: ^1.1.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
msix: ^2.1.3
|
msix: ^2.1.3
|
||||||
|
|
Loading…
Reference in New Issue