diff --git a/lib/constants.dart b/lib/constants.dart new file mode 100644 index 00000000..04abe41d --- /dev/null +++ b/lib/constants.dart @@ -0,0 +1,3 @@ +const int MaxImageFileSharingSize = 20971520; + +const int MaxGeneralImageFileSharingSize = 10737418240; diff --git a/lib/controllers/filesharing.dart b/lib/controllers/filesharing.dart new file mode 100644 index 00000000..8696af29 --- /dev/null +++ b/lib/controllers/filesharing.dart @@ -0,0 +1,30 @@ +import 'dart:io'; +import 'package:cwtch/models/appstate.dart'; +import 'package:file_picker/file_picker.dart'; +import 'package:flutter/widgets.dart'; +import 'package:provider/provider.dart'; + +void showFilePicker(BuildContext ctx, int maxBytes, Function(File) onSuccess, Function onCancel, Function onError) 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(ctx, listen: false); + appstate.disableFilePicker = true; + // currently lockParentWindow only works on Windows... + FilePickerResult? result = await FilePicker.platform.pickFiles(lockParentWindow: true); + appstate.disableFilePicker = false; + if (result != null && result.files.first.path != null) { + File file = File(result.files.first.path!); + // We have a maximum number of bytes we can represent in terms of + // a manifest (see : https://git.openprivacy.ca/cwtch.im/cwtch/src/branch/master/protocol/files/manifest.go#L25) + if (file.lengthSync() <= maxBytes) { + onSuccess(file); + } else { + onError(); + } + } else { + onCancel(); + } +} diff --git a/lib/views/addeditprofileview.dart b/lib/views/addeditprofileview.dart index 07b2968c..b707f52f 100644 --- a/lib/views/addeditprofileview.dart +++ b/lib/views/addeditprofileview.dart @@ -1,12 +1,10 @@ -import 'dart:convert'; import 'dart:io'; -import 'dart:math'; import 'package:cwtch/config.dart'; import 'package:cwtch/cwtch/cwtch.dart'; import 'package:cwtch/models/appstate.dart'; import 'package:cwtch/models/profile.dart'; -import 'package:file_picker/file_picker.dart'; +import 'package:cwtch/controllers/filesharing.dart' as filesharing; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:cwtch/widgets/buttontextfield.dart'; @@ -17,6 +15,7 @@ import 'package:cwtch/widgets/textfield.dart'; import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import '../constants.dart'; import '../cwtch_icons_icons.dart'; import '../errorHandler.dart'; import '../main.dart'; @@ -68,37 +67,6 @@ class _AddEditProfileViewState extends State { }); } - void _showFilePicker(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(ctx, listen: false); - appstate.disableFilePicker = true; - // currently lockParentWindow only works on Windows... - FilePickerResult? result = await FilePicker.platform.pickFiles(lockParentWindow: true); - appstate.disableFilePicker = false; - if (result != null && result.files.first.path != null) { - File file = File(result.files.first.path!); - // We have a maximum number of bytes we can represent in terms of - // a manifest (see : https://git.openprivacy.ca/cwtch.im/cwtch/src/branch/master/protocol/files/manifest.go#L25) - if (file.lengthSync() <= 10737418240) { - var profile = Provider.of(context, listen: false).onion; - // Share this image publically (conversation handle == -1) - Provider.of(context, listen: false).cwtch.ShareFile(profile, -1, file.path); - // update the image cache locally - Provider.of(context, listen: false).imagePath = file.path; - } else { - final snackBar = SnackBar( - content: Text(AppLocalizations.of(context)!.msgFileTooBig), - duration: Duration(seconds: 4), - ); - ScaffoldMessenger.of(ctx).showSnackBar(snackBar); - } - } - } - // A few implementation notes // We use Visibility to hide optional structures when they are not requested. // We used SizedBox for inter-widget height padding in columns, otherwise elements can render a little too close together. @@ -125,9 +93,23 @@ class _AddEditProfileViewState extends State { MouseRegion( cursor: SystemMouseCursors.click, child: GestureDetector( - onTap: () { - _showFilePicker(context); - }, + onTap: Provider.of(context).disableFilePicker + ? null + : () { + filesharing.showFilePicker(context, MaxImageFileSharingSize, (File file) { + var profile = Provider.of(context, listen: false).onion; + // Share this image publicly (conversation handle == -1) + Provider.of(context, listen: false).cwtch.ShareFile(profile, -1, file.path); + // update the image cache locally + Provider.of(context, listen: false).imagePath = file.path; + }, () { + final snackBar = SnackBar( + content: Text(AppLocalizations.of(context)!.msgFileTooBig), + duration: Duration(seconds: 4), + ); + ScaffoldMessenger.of(context).showSnackBar(snackBar); + }, () {}); + }, child: ProfileImage( imagePath: Provider.of(context).imagePath, diameter: 120, diff --git a/lib/views/messageview.dart b/lib/views/messageview.dart index 8db678ff..d5d9a01c 100644 --- a/lib/views/messageview.dart +++ b/lib/views/messageview.dart @@ -11,7 +11,7 @@ import 'package:cwtch/models/profile.dart'; import 'package:cwtch/widgets/malformedbubble.dart'; import 'package:cwtch/widgets/messageloadingbubble.dart'; import 'package:cwtch/widgets/profileimage.dart'; - +import 'package:cwtch/controllers/filesharing.dart' as filesharing; import 'package:file_picker/file_picker.dart'; import 'package:flutter/cupertino.dart'; @@ -23,6 +23,7 @@ import 'package:provider/provider.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; +import '../constants.dart'; import '../main.dart'; import '../settings.dart'; import '../widgets/messagelist.dart'; @@ -92,7 +93,16 @@ class _MessageViewState extends State { onPressed: Provider.of(context).disableFilePicker ? null : () { - _showFilePicker(context); + imagePreview = null; + filesharing.showFilePicker(context, MaxGeneralImageFileSharingSize, (File file) { + _confirmFileSend(context, file.path); + }, () { + final snackBar = SnackBar( + content: Text(AppLocalizations.of(context)!.msgFileTooBig), + duration: Duration(seconds: 4), + ); + ScaffoldMessenger.of(context).showSnackBar(snackBar); + }, () {}); }, )); } @@ -415,36 +425,6 @@ class _MessageViewState extends State { }); } - void _showFilePicker(BuildContext ctx) async { - imagePreview = null; - - // 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(ctx, listen: false); - appstate.disableFilePicker = true; - // currently lockParentWindow only works on Windows... - FilePickerResult? result = await FilePicker.platform.pickFiles(lockParentWindow: true); - appstate.disableFilePicker = false; - if (result != null && result.files.first.path != null) { - File file = File(result.files.first.path!); - // We have a maximum number of bytes we can represent in terms of - // a manifest (see : https://git.openprivacy.ca/cwtch.im/cwtch/src/branch/master/protocol/files/manifest.go#L25) - if (file.lengthSync() <= 10737418240) { - print("Sending " + file.path); - _confirmFileSend(ctx, file.path); - } else { - final snackBar = SnackBar( - content: Text(AppLocalizations.of(context)!.msgFileTooBig), - duration: Duration(seconds: 4), - ); - ScaffoldMessenger.of(ctx).showSnackBar(snackBar); - } - } - } - void _confirmFileSend(BuildContext ctx, String path) async { showModalBottomSheet( context: ctx,