10: Fixed input colors in login view when using dark theme; Added removal of individual files selected for upload; Added size for individual files selected for upload; Replaced intent sharing library with flutter_sharing_intent

This commit is contained in:
Varakh 2023-01-16 01:43:37 +01:00
parent a65c7d9253
commit a5dab51765
9 changed files with 158 additions and 140 deletions

View File

@ -1,6 +1,10 @@
# CHANGELOG
## 1.5.2+18
* Fixed input colors in login view when using dark theme
* Added removal of individual files selected for upload
* Added size for individual files selected for upload
* Replaced intent sharing library with `flutter_sharing_intent`
* Added proper linting to project
## 1.5.1+17

View File

@ -21,46 +21,6 @@
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/*" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="image/*" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND_MULTIPLE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="image/*" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="video/*" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND_MULTIPLE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="video/*" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="*/*" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND_MULTIPLE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="*/*" />
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->

View File

@ -60,7 +60,7 @@
</dict>
<dict/>
</array>
// TODO follow steps 2) on create share extension (https://pub.dev/packages/receive_sharing_intent)
// TODO follow steps on create share extension (https://pub.dev/packages/flutter_sharing_intent)
<key>NSPhotoLibraryUsageDescription</key>
<string>Allow to select photos and upload them via the app</string>
<key>LSApplicationQueriesSchemes</key>

View File

@ -80,6 +80,7 @@ class BaseModel extends ChangeNotifier {
@override
void dispose() {
_logger.d("Calling dispose");
super.dispose();
_isDisposed = true;
}

View File

@ -4,10 +4,11 @@ import 'dart:io';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_sharing_intent/flutter_sharing_intent.dart';
import 'package:flutter_sharing_intent/model/sharing_file.dart';
import 'package:flutter_translate/flutter_translate.dart';
import 'package:logger/logger.dart';
import 'package:path/path.dart';
import 'package:receive_sharing_intent/receive_sharing_intent.dart';
import '../../locator.dart';
import '../enums/error_code.dart';
@ -45,6 +46,41 @@ class UploadModel extends BaseModel {
TextEditingController get pasteTextController => _pasteTextController;
void _parseIntentFiles(List<SharedFile> files) {
if (files.isNotEmpty) {
setStateView(ViewState.busy);
paths = files.map((sharedFile) {
_logger.d("Shared file name: ${basename(sharedFile.value ?? '')}");
_logger.d("Shared file path: ${sharedFile.value}");
_logger.d(
"Shared file size: ${File(sharedFile.value ?? '').lengthSync()}");
_logger.d("Shared file type: ${sharedFile.type}");
return PlatformFile.fromMap({
'path': sharedFile.value,
'name': basename(sharedFile.value!),
'size': File(sharedFile.value!).lengthSync(),
'bytes': null
});
}).toList();
setStateView(ViewState.idle);
}
}
void deleteIntentFile(String path) {
setStateView(ViewState.busy);
_logger.d("Removing path '$path' from $paths");
paths?.removeWhere((element) => element.path == path);
int length = paths!.length;
if (length == 0) {
paths = null;
}
setStateView(ViewState.idle);
}
void init() {
_pasteTextController.addListener(() {
pasteTextTouched = pasteTextController.text.isNotEmpty;
@ -52,61 +88,21 @@ class UploadModel extends BaseModel {
});
// For sharing images coming from outside the app while the app is in the memory
_intentDataStreamSubscription = ReceiveSharingIntent.getMediaStream().listen((List<SharedMediaFile> value) {
if (value.isNotEmpty) {
setStateView(ViewState.busy);
paths = value.map((sharedFile) {
return PlatformFile.fromMap({
'path': sharedFile.path,
'name': basename(sharedFile.path),
'size': File(sharedFile.path).lengthSync(),
'bytes': null
});
}).toList();
setStateView(ViewState.idle);
}
_intentDataStreamSubscription = FlutterSharingIntent.instance
.getMediaStream()
.listen((List<SharedFile> value) {
_logger.d("Retrieved ${value.length} files from intent");
_parseIntentFiles(value);
}, onError: (err) {
_errorIntentHandle(err);
});
// For sharing images coming from outside the app while the app is closed
ReceiveSharingIntent.getInitialMedia().then((List<SharedMediaFile> value) {
if (value.isNotEmpty) {
setStateView(ViewState.busy);
paths = value.map((sharedFile) {
return PlatformFile.fromMap({
'path': sharedFile.path,
'name': basename(sharedFile.path),
'size': File(sharedFile.path).lengthSync(),
'bytes': null
});
}).toList();
setStateView(ViewState.idle);
}
}, onError: (err) {
_errorIntentHandle(err);
});
// For sharing or opening urls/text coming from outside the app while the app is in the memory
_intentDataStreamSubscription = ReceiveSharingIntent.getTextStream().listen((String value) {
if (value.isNotEmpty) {
setStateView(ViewState.busy);
pasteTextController.text = value;
setStateView(ViewState.idle);
}
}, onError: (err) {
_errorIntentHandle(err);
});
// For sharing or opening urls/text coming from outside the app while the app is closed
ReceiveSharingIntent.getInitialText().then((String? value) {
if (value != null && value.isNotEmpty) {
setStateView(ViewState.busy);
pasteTextController.text = value;
setStateView(ViewState.idle);
}
}, onError: (err) {
_errorIntentHandle(err);
FlutterSharingIntent.instance
.getInitialSharing()
.then((List<SharedFile> value) {
_logger.d("Retrieved ${value.length} files from inactive intent");
_parseIntentFiles(value);
});
}
@ -150,7 +146,9 @@ class UploadModel extends BaseModel {
allowMultiple: true,
withData: false,
withReadStream: true,
allowedExtensions: (_extension?.isNotEmpty ?? false) ? _extension?.replaceAll(' ', '').split(',') : null,
allowedExtensions: (_extension?.isNotEmpty ?? false)
? _extension?.replaceAll(' ', '').split(',')
: null,
))
?.files;
} on PlatformException catch (e) {
@ -185,21 +183,25 @@ class UploadModel extends BaseModel {
Map<String, String>? additionalFiles;
if (pasteTextController.text.isNotEmpty) {
additionalFiles =
Map.from({'paste-${(DateTime.now().millisecondsSinceEpoch / 1000).round()}.txt': pasteTextController.text});
additionalFiles = Map.from({
'paste-${(DateTime.now().millisecondsSinceEpoch / 1000).round()}.txt':
pasteTextController.text
});
}
if (paths != null && paths!.isNotEmpty) {
files = paths!.map((e) => File(e.path!)).toList();
}
UploadedResponse response = await _fileService.uploadPaste(files, additionalFiles);
UploadedResponse response =
await _fileService.uploadPaste(files, additionalFiles);
for (var element in response.data.ids) {
uploadedPasteIds.putIfAbsent(element, () => false);
}
if (createMulti && response.data.ids.length > 1) {
UploadedMultiResponse multiResponse = await _fileService.uploadMultiPaste(response.data.ids);
UploadedMultiResponse multiResponse =
await _fileService.uploadMultiPaste(response.data.ids);
uploadedPasteIds.putIfAbsent(multiResponse.data.urlId, () => true);
}
@ -219,9 +221,11 @@ class UploadModel extends BaseModel {
e.responseBody is RestError &&
e.responseBody.message != null) {
if (e.statusCode == HttpStatus.badRequest) {
errorMessage = translate('api.bad_request', args: {'reason': e.responseBody.message});
errorMessage = translate('api.bad_request',
args: {'reason': e.responseBody.message});
} else {
errorMessage = translate('api.general_rest_error_payload', args: {'message': e.responseBody.message});
errorMessage = translate('api.general_rest_error_payload',
args: {'message': e.responseBody.message});
}
} else {
errorMessage = translate('api.general_rest_error');
@ -252,6 +256,8 @@ class UploadModel extends BaseModel {
void dispose() {
_pasteTextController.dispose();
_intentDataStreamSubscription.cancel();
FlutterSharingIntent.instance.reset();
paths = null;
super.dispose();
}
}

View File

@ -1,4 +1,5 @@
import 'package:clipboard/clipboard.dart';
import 'package:fbmobile/core/util/formatter_util.dart';
import 'package:flutter/material.dart';
import 'package:flutter_translate/flutter_translate.dart';
import 'package:provider/provider.dart';
@ -20,12 +21,14 @@ class UploadView extends StatelessWidget {
Widget build(BuildContext context) {
return BaseView<UploadModel>(
onModelReady: (model) => model.init(),
builder: (context, model, child) =>
Scaffold(appBar: MyAppBar(title: Text(translate('titles.upload'))), body: _render(model, context)));
builder: (context, model, child) => Scaffold(
appBar: MyAppBar(title: Text(translate('titles.upload'))),
body: _render(model, context)));
}
bool _isUploadButtonEnabled(UploadModel model) {
return model.pasteTextTouched || (model.paths != null && model.paths!.isNotEmpty);
return model.pasteTextTouched ||
(model.paths != null && model.paths!.isNotEmpty);
}
Widget _render(UploadModel model, BuildContext context) {
@ -38,7 +41,9 @@ class UploadView extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const CircularProgressIndicator(),
(model.stateMessage != null && model.stateMessage!.isNotEmpty ? Text(model.stateMessage!) : Container())
(model.stateMessage != null && model.stateMessage!.isNotEmpty
? Text(model.stateMessage!)
: Container())
]))
: ListView(children: <Widget>[
Padding(
@ -56,12 +61,15 @@ class UploadView extends StatelessWidget {
Icons.text_snippet,
),
suffixIcon: IconButton(
onPressed: () => model.pasteTextController.clear(),
onPressed: () =>
model.pasteTextController.clear(),
icon: const Icon(Icons.clear),
),
hintText: translate('upload.text_to_be_pasted'),
contentPadding: const EdgeInsets.fromLTRB(20.0, 10.0, 20.0, 10.0),
border: OutlineInputBorder(borderRadius: BorderRadius.circular(32.0)),
contentPadding: const EdgeInsets.fromLTRB(
20.0, 10.0, 20.0, 10.0),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(32.0)),
),
controller: model.pasteTextController)),
Padding(
@ -77,14 +85,17 @@ class UploadView extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.end,
children: [
ElevatedButton.icon(
icon: const Icon(Icons.file_copy_sharp, color: blueColor),
icon: const Icon(Icons.file_copy_sharp,
color: blueColor),
onPressed: () => model.openFileExplorer(),
label: Text(
translate('upload.open_file_explorer'),
)),
ElevatedButton.icon(
icon: const Icon(Icons.cancel, color: orangeColor),
onPressed: model.paths != null && model.paths!.isNotEmpty
icon: const Icon(Icons.cancel,
color: orangeColor),
onPressed: model.paths != null &&
model.paths!.isNotEmpty
? () => model.clearCachedFiles()
: null,
label: Text(
@ -114,34 +125,47 @@ class UploadView extends StatelessWidget {
onPressed: !_isUploadButtonEnabled(model)
? null
: () async {
Map<String, bool>? items = await model.upload();
String? clipboardContent = model.generatePasteLinks(items, url);
Map<String, bool>? items =
await model.upload();
String? clipboardContent = model
.generatePasteLinks(items, url);
if (clipboardContent != null && clipboardContent.isNotEmpty) {
FlutterClipboard.copy(clipboardContent).then((value) {
if (clipboardContent != null &&
clipboardContent.isNotEmpty) {
FlutterClipboard.copy(
clipboardContent)
.then((value) {
final snackBar = SnackBar(
action: SnackBarAction(
label: translate('upload.dismiss'),
label: translate(
'upload.dismiss'),
textColor: blueColor,
onPressed: () {
ScaffoldMessenger.of(context).hideCurrentSnackBar();
ScaffoldMessenger.of(
context)
.hideCurrentSnackBar();
},
),
content: Text(translate('upload.uploaded')),
duration: const Duration(seconds: 10),
content: Text(translate(
'upload.uploaded')),
duration:
const Duration(seconds: 10),
);
ScaffoldMessenger.of(context).showSnackBar(snackBar);
ScaffoldMessenger.of(context)
.showSnackBar(snackBar);
});
}
},
icon: const Icon(Icons.upload_rounded, color: greenColor),
icon: const Icon(Icons.upload_rounded,
color: greenColor),
label: Text(
translate('upload.upload'),
)),
])),
model.errorMessage != null && model.errorMessage!.isNotEmpty
? (Padding(
padding: const EdgeInsets.only(top: 10.0, bottom: 10.0),
padding:
const EdgeInsets.only(top: 10.0, bottom: 10.0),
child: CenteredErrorRow(model.errorMessage)))
: Container(),
Builder(
@ -152,28 +176,54 @@ class UploadView extends StatelessWidget {
)
: model.paths != null
? Container(
padding: const EdgeInsets.only(bottom: 30.0),
height: MediaQuery.of(context).size.height * 0.50,
padding: const EdgeInsets.only(bottom: 20.0),
height:
MediaQuery.of(context).size.height * 0.50,
child: ListView.separated(
itemCount: model.paths != null && model.paths!.isNotEmpty ? model.paths!.length : 1,
itemBuilder: (BuildContext context, int index) {
final bool isMultiPath = model.paths != null && model.paths!.isNotEmpty;
itemCount: model.paths != null &&
model.paths!.isNotEmpty
? model.paths!.length
: 1,
itemBuilder:
(BuildContext context, int index) {
final bool isMultiPath =
model.paths != null &&
model.paths!.isNotEmpty;
final String name = (isMultiPath
? model.paths!.map((e) => e.name).toList()[index]
? model.paths!
.map((e) => e.name)
.toList()[index]
: model.fileName ?? '...');
final size = model.paths!.isNotEmpty
? model.paths!
.map((e) => e.size)
.toList()[index]
.toString()
: '';
final path = model.paths!.isNotEmpty
? model.paths!.map((e) => e.path).toList()[index].toString()
? model.paths!
.map((e) => e.path)
.toList()[index]
.toString()
: '';
return Card(
child: ListTile(
trailing: IconButton(
icon: const Icon(Icons.clear,
color: orangeColor),
onPressed: () {
model.deleteIntentFile(path);
}),
title: Text(
name,
"$name (${FormatterUtil.formatBytes(int.parse(size), 2)})",
),
subtitle: Text(path),
));
},
separatorBuilder: (BuildContext context, int index) => const Divider(),
separatorBuilder:
(BuildContext context, int index) =>
const Divider(),
),
)
: Container(),

View File

@ -1,7 +1,5 @@
import 'package:flutter/material.dart';
import '../shared/app_colors.dart';
class LoginTextField extends StatelessWidget {
final TextEditingController controller;
final String placeHolder;
@ -19,7 +17,6 @@ class LoginTextField extends StatelessWidget {
margin: const EdgeInsets.symmetric(horizontal: 10.0, vertical: 10.0),
height: 50.0,
alignment: Alignment.centerLeft,
decoration: BoxDecoration(color: whiteColor, borderRadius: BorderRadius.circular(10.0)),
child: TextFormField(
keyboardType: keyboardType,
obscureText: obscureText,

View File

@ -256,6 +256,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.7"
flutter_sharing_intent:
dependency: "direct main"
description:
name: flutter_sharing_intent
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.5"
flutter_test:
dependency: "direct dev"
description: flutter
@ -588,13 +595,6 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "3.1.0"
receive_sharing_intent:
dependency: "direct main"
description:
name: receive_sharing_intent
url: "https://pub.dartlang.org"
source: hosted
version: "1.4.5"
share_plus:
dependency: "direct main"
description:

View File

@ -36,13 +36,13 @@ dependencies:
share_plus: 6.3.0
file_picker: 5.2.4
clipboard: 0.1.3
receive_sharing_intent: 1.4.5
permission_handler: 10.2.0
package_info_plus: 3.0.2
json_annotation: 4.7.0
dynamic_color: 1.5.4
intl: 0.17.0
path: 1.8.2
flutter_sharing_intent: 1.0.5
dev_dependencies:
flutter_test: