Compare commits
6 commits
a65c7d9253
...
fc88d5c22f
Author | SHA1 | Date | |
---|---|---|---|
fc88d5c22f | |||
a2ee915463 | |||
6e5fa0eaa8 | |||
8c3bf06b87 | |||
5e9ac38ca0 | |||
a5dab51765 |
60 changed files with 453 additions and 295 deletions
|
@ -4,7 +4,7 @@ name: default
|
|||
|
||||
steps:
|
||||
- name: build
|
||||
image: cirrusci/flutter:3.3.9
|
||||
image: cirrusci/flutter:3.3.10
|
||||
commands:
|
||||
- flutter doctor
|
||||
- flutter pub get
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
# CHANGELOG
|
||||
|
||||
## 1.5.2+18
|
||||
## 1.6.0+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
|
||||
|
|
|
@ -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 -->
|
||||
|
|
|
@ -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>
|
||||
|
|
25
lib/app.dart
25
lib/app.dart
|
@ -18,8 +18,10 @@ import 'ui/shared/app_colors.dart';
|
|||
import 'ui/views/startup_view.dart';
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
static final _defaultLightColorScheme = ColorScheme.fromSwatch(primarySwatch: myColor, brightness: Brightness.light);
|
||||
static final _defaultDarkColorScheme = ColorScheme.fromSwatch(primarySwatch: myColor, brightness: Brightness.dark);
|
||||
static final _defaultLightColorScheme = ColorScheme.fromSwatch(
|
||||
primarySwatch: myColor, brightness: Brightness.light);
|
||||
static final _defaultDarkColorScheme = ColorScheme.fromSwatch(
|
||||
primarySwatch: myColor, brightness: Brightness.dark);
|
||||
|
||||
MyApp({super.key}) {
|
||||
initializeDateFormatting('en');
|
||||
|
@ -33,23 +35,30 @@ class MyApp extends StatelessWidget {
|
|||
state: LocalizationProvider.of(context).state,
|
||||
child: StreamProvider<RefreshEvent?>(
|
||||
initialData: null,
|
||||
create: (context) => locator<RefreshService>().refreshEventController.stream,
|
||||
create: (context) =>
|
||||
locator<RefreshService>().refreshEventController.stream,
|
||||
child: StreamProvider<Session?>(
|
||||
initialData: Session.initial(),
|
||||
create: (context) => locator<SessionService>().sessionController.stream,
|
||||
child: LifeCycleManager(child: DynamicColorBuilder(builder: (lightColorScheme, darkColorScheme) {
|
||||
create: (context) =>
|
||||
locator<SessionService>().sessionController.stream,
|
||||
child: LifeCycleManager(child: DynamicColorBuilder(
|
||||
builder: (lightColorScheme, darkColorScheme) {
|
||||
return MaterialApp(
|
||||
debugShowCheckedModeBanner: false,
|
||||
title: translate('app.title'),
|
||||
builder: (context, child) => Navigator(
|
||||
key: locator<DialogService>().dialogNavigationKey,
|
||||
onGenerateRoute: (settings) => MaterialPageRoute(builder: (context) => DialogManager(child: child)),
|
||||
onGenerateRoute: (settings) => MaterialPageRoute(
|
||||
builder: (context) => DialogManager(child: child)),
|
||||
),
|
||||
theme: ThemeData(
|
||||
useMaterial3: true,
|
||||
brightness: Brightness.light,
|
||||
colorScheme: lightColorScheme ?? _defaultLightColorScheme),
|
||||
darkTheme: ThemeData(useMaterial3: true, colorScheme: darkColorScheme ?? _defaultDarkColorScheme),
|
||||
colorScheme:
|
||||
lightColorScheme ?? _defaultLightColorScheme),
|
||||
darkTheme: ThemeData(
|
||||
useMaterial3: true,
|
||||
colorScheme: darkColorScheme ?? _defaultDarkColorScheme),
|
||||
onGenerateRoute: AppRouter.generateRoute,
|
||||
navigatorKey: locator<NavigationService>().navigationKey,
|
||||
home: const StartUpView(),
|
||||
|
|
|
@ -31,7 +31,8 @@ class _DialogManagerState extends State<DialogManager> {
|
|||
void _showDialog(DialogRequest request) {
|
||||
List<Widget> actions = <Widget>[];
|
||||
|
||||
if (request.buttonTitleDeny != null && request.buttonTitleDeny!.isNotEmpty) {
|
||||
if (request.buttonTitleDeny != null &&
|
||||
request.buttonTitleDeny!.isNotEmpty) {
|
||||
Widget denyBtn = TextButton(
|
||||
child: Text(request.buttonTitleDeny!),
|
||||
onPressed: () {
|
||||
|
|
|
@ -17,10 +17,14 @@ class LifeCycleManager extends StatefulWidget {
|
|||
_LifeCycleManagerState createState() => _LifeCycleManagerState();
|
||||
}
|
||||
|
||||
class _LifeCycleManagerState extends State<LifeCycleManager> with WidgetsBindingObserver {
|
||||
class _LifeCycleManagerState extends State<LifeCycleManager>
|
||||
with WidgetsBindingObserver {
|
||||
final Logger logger = getLogger();
|
||||
|
||||
List<StoppableService> servicesToManage = [locator<SessionService>(), locator<PermissionService>()];
|
||||
List<StoppableService> servicesToManage = [
|
||||
locator<SessionService>(),
|
||||
locator<PermissionService>()
|
||||
];
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
|
|
@ -15,7 +15,11 @@ class ApiKey {
|
|||
|
||||
final String? comment;
|
||||
|
||||
ApiKey({required this.key, required this.created, required this.accessLevel, this.comment});
|
||||
ApiKey(
|
||||
{required this.key,
|
||||
required this.created,
|
||||
required this.accessLevel,
|
||||
this.comment});
|
||||
|
||||
// JSON Init
|
||||
factory ApiKey.fromJson(Map<String, dynamic> json) => _$ApiKeyFromJson(json);
|
||||
|
|
|
@ -12,7 +12,8 @@ class ApiKeys {
|
|||
ApiKeys({required this.apikeys});
|
||||
|
||||
// JSON Init
|
||||
factory ApiKeys.fromJson(Map<String, dynamic> json) => _$ApiKeysFromJson(json);
|
||||
factory ApiKeys.fromJson(Map<String, dynamic> json) =>
|
||||
_$ApiKeysFromJson(json);
|
||||
|
||||
// JSON Export
|
||||
Map<String, dynamic> toJson() => _$ApiKeysToJson(this);
|
||||
|
|
|
@ -15,7 +15,8 @@ class ApiKeysResponse {
|
|||
ApiKeysResponse({required this.status, required this.data});
|
||||
|
||||
// JSON Init
|
||||
factory ApiKeysResponse.fromJson(Map<String, dynamic> json) => _$ApiKeysResponseFromJson(json);
|
||||
factory ApiKeysResponse.fromJson(Map<String, dynamic> json) =>
|
||||
_$ApiKeysResponseFromJson(json);
|
||||
|
||||
// JSON Export
|
||||
Map<String, dynamic> toJson() => _$ApiKeysResponseToJson(this);
|
||||
|
|
|
@ -15,7 +15,8 @@ class ConfigResponse {
|
|||
ConfigResponse({required this.status, required this.data});
|
||||
|
||||
// JSON Init
|
||||
factory ConfigResponse.fromJson(Map<String, dynamic> json) => _$ConfigResponseFromJson(json);
|
||||
factory ConfigResponse.fromJson(Map<String, dynamic> json) =>
|
||||
_$ConfigResponseFromJson(json);
|
||||
|
||||
// JSON Export
|
||||
Map<String, dynamic> toJson() => _$ConfigResponseToJson(this);
|
||||
|
|
|
@ -13,7 +13,8 @@ class CreateApiKeyResponse {
|
|||
CreateApiKeyResponse({required this.status, required this.data});
|
||||
|
||||
// JSON Init
|
||||
factory CreateApiKeyResponse.fromJson(Map<String, dynamic> json) => _$CreateApiKeyResponseFromJson(json);
|
||||
factory CreateApiKeyResponse.fromJson(Map<String, dynamic> json) =>
|
||||
_$CreateApiKeyResponseFromJson(json);
|
||||
|
||||
// JSON Export
|
||||
Map<String, dynamic> toJson() => _$CreateApiKeyResponseToJson(this);
|
||||
|
|
|
@ -19,7 +19,8 @@ class History {
|
|||
History({required this.items, required this.multipasteItems, this.totalSize});
|
||||
|
||||
// JSON Init
|
||||
factory History.fromJson(Map<String, dynamic> json) => _$HistoryFromJson(json);
|
||||
factory History.fromJson(Map<String, dynamic> json) =>
|
||||
_$HistoryFromJson(json);
|
||||
|
||||
// JSON Export
|
||||
Map<String, dynamic> toJson() => _$HistoryToJson(this);
|
||||
|
|
|
@ -23,7 +23,8 @@ class HistoryItem {
|
|||
this.thumbnail});
|
||||
|
||||
// JSON Init
|
||||
factory HistoryItem.fromJson(Map<String, dynamic> json) => _$HistoryItemFromJson(json);
|
||||
factory HistoryItem.fromJson(Map<String, dynamic> json) =>
|
||||
_$HistoryItemFromJson(json);
|
||||
|
||||
// JSON Export
|
||||
Map<String, dynamic> toJson() => _$HistoryItemToJson(this);
|
||||
|
|
|
@ -15,7 +15,8 @@ class HistoryMultipasteItem {
|
|||
HistoryMultipasteItem(this.items, {required this.date, required this.urlId});
|
||||
|
||||
// JSON Init
|
||||
factory HistoryMultipasteItem.fromJson(Map<String, dynamic> json) => _$HistoryMultipasteItemFromJson(json);
|
||||
factory HistoryMultipasteItem.fromJson(Map<String, dynamic> json) =>
|
||||
_$HistoryMultipasteItemFromJson(json);
|
||||
|
||||
// JSON Export
|
||||
Map<String, dynamic> toJson() => _$HistoryMultipasteItemToJson(this);
|
||||
|
|
|
@ -9,7 +9,8 @@ class HistoryMultipasteItemEntry {
|
|||
HistoryMultipasteItemEntry({required this.id});
|
||||
|
||||
// JSON Init
|
||||
factory HistoryMultipasteItemEntry.fromJson(Map<String, dynamic> json) => _$HistoryMultipasteItemEntryFromJson(json);
|
||||
factory HistoryMultipasteItemEntry.fromJson(Map<String, dynamic> json) =>
|
||||
_$HistoryMultipasteItemEntryFromJson(json);
|
||||
|
||||
// JSON Export
|
||||
Map<String, dynamic> toJson() => _$HistoryMultipasteItemEntryToJson(this);
|
||||
|
|
|
@ -15,7 +15,8 @@ class HistoryResponse {
|
|||
HistoryResponse({required this.status, required this.data});
|
||||
|
||||
// JSON Init
|
||||
factory HistoryResponse.fromJson(Map<String, dynamic> json) => _$HistoryResponseFromJson(json);
|
||||
factory HistoryResponse.fromJson(Map<String, dynamic> json) =>
|
||||
_$HistoryResponseFromJson(json);
|
||||
|
||||
// JSON Export
|
||||
Map<String, dynamic> toJson() => _$HistoryResponseToJson(this);
|
||||
|
|
|
@ -15,7 +15,8 @@ class RestError {
|
|||
required this.errorId,
|
||||
}); // JSON Init
|
||||
|
||||
factory RestError.fromJson(Map<String, dynamic> json) => _$RestErrorFromJson(json);
|
||||
factory RestError.fromJson(Map<String, dynamic> json) =>
|
||||
_$RestErrorFromJson(json);
|
||||
|
||||
// JSON Export
|
||||
Map<String, dynamic> toJson() => _$RestErrorToJson(this);
|
||||
|
|
|
@ -13,7 +13,8 @@ class Uploaded {
|
|||
Uploaded({required this.ids, required this.urls});
|
||||
|
||||
// JSON Init
|
||||
factory Uploaded.fromJson(Map<String, dynamic> json) => _$UploadedFromJson(json);
|
||||
factory Uploaded.fromJson(Map<String, dynamic> json) =>
|
||||
_$UploadedFromJson(json);
|
||||
|
||||
// JSON Export
|
||||
Map<String, dynamic> toJson() => _$UploadedToJson(this);
|
||||
|
|
|
@ -13,7 +13,8 @@ class UploadedMulti {
|
|||
UploadedMulti({required this.url, required this.urlId});
|
||||
|
||||
// JSON Init
|
||||
factory UploadedMulti.fromJson(Map<String, dynamic> json) => _$UploadedMultiFromJson(json);
|
||||
factory UploadedMulti.fromJson(Map<String, dynamic> json) =>
|
||||
_$UploadedMultiFromJson(json);
|
||||
|
||||
// JSON Export
|
||||
Map<String, dynamic> toJson() => _$UploadedMultiToJson(this);
|
||||
|
|
|
@ -15,7 +15,8 @@ class UploadedMultiResponse {
|
|||
UploadedMultiResponse({required this.status, required this.data});
|
||||
|
||||
// JSON Init
|
||||
factory UploadedMultiResponse.fromJson(Map<String, dynamic> json) => _$UploadedMultiResponseFromJson(json);
|
||||
factory UploadedMultiResponse.fromJson(Map<String, dynamic> json) =>
|
||||
_$UploadedMultiResponseFromJson(json);
|
||||
|
||||
// JSON Export
|
||||
Map<String, dynamic> toJson() => _$UploadedMultiResponseToJson(this);
|
||||
|
|
|
@ -15,7 +15,8 @@ class UploadedResponse {
|
|||
UploadedResponse({required this.status, required this.data});
|
||||
|
||||
// JSON Init
|
||||
factory UploadedResponse.fromJson(Map<String, dynamic> json) => _$UploadedResponseFromJson(json);
|
||||
factory UploadedResponse.fromJson(Map<String, dynamic> json) =>
|
||||
_$UploadedResponseFromJson(json);
|
||||
|
||||
// JSON Export
|
||||
Map<String, dynamic> toJson() => _$UploadedResponseToJson(this);
|
||||
|
|
|
@ -13,7 +13,8 @@ class Session {
|
|||
: url = '',
|
||||
apiKey = '';
|
||||
|
||||
factory Session.fromJson(Map<String, dynamic> json) => _$SessionFromJson(json);
|
||||
factory Session.fromJson(Map<String, dynamic> json) =>
|
||||
_$SessionFromJson(json);
|
||||
|
||||
Map<String, dynamic> toJson() => _$SessionToJson(this);
|
||||
}
|
||||
|
|
|
@ -26,7 +26,8 @@ class UploadedPaste {
|
|||
this.items});
|
||||
|
||||
// JSON Init
|
||||
factory UploadedPaste.fromJson(Map<String, dynamic> json) => _$UploadedPasteFromJson(json);
|
||||
factory UploadedPaste.fromJson(Map<String, dynamic> json) =>
|
||||
_$UploadedPasteFromJson(json);
|
||||
|
||||
// JSON Export
|
||||
Map<String, dynamic> toJson() => _$UploadedPasteToJson(this);
|
||||
|
|
|
@ -33,8 +33,10 @@ class FileRepository {
|
|||
return response;
|
||||
}
|
||||
|
||||
Future<UploadedResponse> postUpload(List<File>? files, Map<String, String>? additionalFiles) async {
|
||||
var response = await _api.post('/file/upload', files: files, additionalFiles: additionalFiles);
|
||||
Future<UploadedResponse> postUpload(
|
||||
List<File>? files, Map<String, String>? additionalFiles) async {
|
||||
var response = await _api.post('/file/upload',
|
||||
files: files, additionalFiles: additionalFiles);
|
||||
return UploadedResponse.fromJson(json.decode(response.body));
|
||||
}
|
||||
|
||||
|
@ -42,10 +44,12 @@ class FileRepository {
|
|||
Map<String, String> multiPasteIds = {};
|
||||
|
||||
for (var element in ids) {
|
||||
multiPasteIds.putIfAbsent("ids[${ids.indexOf(element) + 1}]", () => element);
|
||||
multiPasteIds.putIfAbsent(
|
||||
"ids[${ids.indexOf(element) + 1}]", () => element);
|
||||
}
|
||||
|
||||
var response = await _api.post('/file/create_multipaste', fields: multiPasteIds);
|
||||
var response =
|
||||
await _api.post('/file/create_multipaste', fields: multiPasteIds);
|
||||
return UploadedMultiResponse.fromJson(json.decode(response.body));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,8 +8,8 @@ import '../services/api.dart';
|
|||
class UserRepository {
|
||||
final Api _api = locator<Api>();
|
||||
|
||||
Future<CreateApiKeyResponse> postApiKey(
|
||||
String url, String username, String password, String accessLevel, String comment) async {
|
||||
Future<CreateApiKeyResponse> postApiKey(String url, String username,
|
||||
String password, String accessLevel, String comment) async {
|
||||
_api.setUrl(url);
|
||||
|
||||
var fields = Map.fromEntries([
|
||||
|
|
|
@ -24,25 +24,34 @@ class Api implements ApiErrorConverter {
|
|||
String _url = "";
|
||||
String _apiKey = "";
|
||||
|
||||
final Map<String, String> _headers = {"Content-Type": _applicationJson, "Accept": _applicationJson};
|
||||
final Map<String, String> _headers = {
|
||||
"Content-Type": _applicationJson,
|
||||
"Accept": _applicationJson
|
||||
};
|
||||
Duration _timeout = const Duration(seconds: Constants.apiRequestTimeoutLimit);
|
||||
|
||||
Future<http.Response> fetch<T>(String route) async {
|
||||
try {
|
||||
_logger
|
||||
.d("Requesting GET API endpoint '${_url + route}' with headers '$_headers' and maximum timeout '$_timeout'");
|
||||
var response = await http.get(Uri.parse(_url + route), headers: _headers).timeout(_timeout);
|
||||
_logger.d(
|
||||
"Requesting GET API endpoint '${_url + route}' with headers '$_headers' and maximum timeout '$_timeout'");
|
||||
var response = await http
|
||||
.get(Uri.parse(_url + route), headers: _headers)
|
||||
.timeout(_timeout);
|
||||
handleRestErrors(response);
|
||||
return response;
|
||||
} on TimeoutException {
|
||||
throw ServiceException(code: ErrorCode.socketTimeout, message: _errorTimeout);
|
||||
throw ServiceException(
|
||||
code: ErrorCode.socketTimeout, message: _errorTimeout);
|
||||
} on SocketException {
|
||||
throw ServiceException(code: ErrorCode.socketError, message: _errorNoConnection);
|
||||
throw ServiceException(
|
||||
code: ErrorCode.socketError, message: _errorNoConnection);
|
||||
}
|
||||
}
|
||||
|
||||
Future<http.Response> post<T>(String route,
|
||||
{Map<String, String?>? fields, List<File>? files, Map<String, String>? additionalFiles}) async {
|
||||
{Map<String, String?>? fields,
|
||||
List<File>? files,
|
||||
Map<String, String>? additionalFiles}) async {
|
||||
try {
|
||||
var uri = Uri.parse(_url + route);
|
||||
var request = http.MultipartRequest('POST', uri)
|
||||
|
@ -59,27 +68,34 @@ class Api implements ApiErrorConverter {
|
|||
|
||||
if (files != null && files.isNotEmpty) {
|
||||
for (var element in files) {
|
||||
request.files.add(await http.MultipartFile.fromPath('file[${files.indexOf(element) + 1}]', element.path));
|
||||
request.files.add(await http.MultipartFile.fromPath(
|
||||
'file[${files.indexOf(element) + 1}]', element.path));
|
||||
}
|
||||
}
|
||||
|
||||
if (additionalFiles != null && additionalFiles.isNotEmpty) {
|
||||
List<String> keys = additionalFiles.keys.toList();
|
||||
additionalFiles.forEach((key, value) {
|
||||
var index = files != null ? files.length + keys.indexOf(key) + 1 : keys.indexOf(key) + 1;
|
||||
request.files.add(http.MultipartFile.fromString('file[$index]', value, filename: key));
|
||||
var index = files != null
|
||||
? files.length + keys.indexOf(key) + 1
|
||||
: keys.indexOf(key) + 1;
|
||||
request.files.add(http.MultipartFile.fromString('file[$index]', value,
|
||||
filename: key));
|
||||
});
|
||||
}
|
||||
|
||||
_logger.d("Requesting POST API endpoint '${uri.toString()}' and ${request.files.length} files");
|
||||
_logger.d(
|
||||
"Requesting POST API endpoint '${uri.toString()}' and ${request.files.length} files");
|
||||
var multiResponse = await request.send();
|
||||
var response = await http.Response.fromStream(multiResponse);
|
||||
handleRestErrors(response);
|
||||
return response;
|
||||
} on TimeoutException {
|
||||
throw ServiceException(code: ErrorCode.socketTimeout, message: _errorTimeout);
|
||||
throw ServiceException(
|
||||
code: ErrorCode.socketTimeout, message: _errorTimeout);
|
||||
} on SocketException {
|
||||
throw ServiceException(code: ErrorCode.socketError, message: _errorNoConnection);
|
||||
throw ServiceException(
|
||||
code: ErrorCode.socketError, message: _errorNoConnection);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -107,14 +123,17 @@ class Api implements ApiErrorConverter {
|
|||
/// have a json decoded object. Replace this with a custom
|
||||
/// conversion method by overwriting the interface if needed
|
||||
void handleRestErrors(http.Response response) {
|
||||
if (response.statusCode != HttpStatus.ok && response.statusCode != HttpStatus.noContent) {
|
||||
if (response.statusCode != HttpStatus.ok &&
|
||||
response.statusCode != HttpStatus.noContent) {
|
||||
if (response.headers.containsKey(HttpHeaders.contentTypeHeader)) {
|
||||
ContentType responseContentType = ContentType.parse(response.headers[HttpHeaders.contentTypeHeader]!);
|
||||
ContentType responseContentType =
|
||||
ContentType.parse(response.headers[HttpHeaders.contentTypeHeader]!);
|
||||
|
||||
if (ContentType.json.primaryType == responseContentType.primaryType &&
|
||||
ContentType.json.subType == responseContentType.subType) {
|
||||
var parsedBody = convert(response);
|
||||
throw RestServiceException(response.statusCode, responseBody: parsedBody);
|
||||
throw RestServiceException(response.statusCode,
|
||||
responseBody: parsedBody);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -7,7 +7,8 @@ import '../datamodels/dialog_request.dart';
|
|||
import '../datamodels/dialog_response.dart';
|
||||
|
||||
class DialogService {
|
||||
final GlobalKey<NavigatorState> _dialogNavigationKey = GlobalKey<NavigatorState>();
|
||||
final GlobalKey<NavigatorState> _dialogNavigationKey =
|
||||
GlobalKey<NavigatorState>();
|
||||
late Function(DialogRequest) _showDialogListener;
|
||||
Completer<DialogResponse>? _dialogCompleter;
|
||||
|
||||
|
@ -27,20 +28,28 @@ class DialogService {
|
|||
title: title,
|
||||
description: description,
|
||||
buttonTitleAccept:
|
||||
buttonTitleAccept == null || buttonTitleAccept.isEmpty ? translate('dialog.confirm') : buttonTitleAccept));
|
||||
buttonTitleAccept == null || buttonTitleAccept.isEmpty
|
||||
? translate('dialog.confirm')
|
||||
: buttonTitleAccept));
|
||||
return _dialogCompleter!.future;
|
||||
}
|
||||
|
||||
Future<DialogResponse> showConfirmationDialog(
|
||||
{String? title, String? description, String? buttonTitleAccept, String? buttonTitleDeny}) {
|
||||
{String? title,
|
||||
String? description,
|
||||
String? buttonTitleAccept,
|
||||
String? buttonTitleDeny}) {
|
||||
_dialogCompleter = Completer<DialogResponse>();
|
||||
_showDialogListener(DialogRequest(
|
||||
title: title,
|
||||
description: description,
|
||||
buttonTitleAccept:
|
||||
buttonTitleAccept == null || buttonTitleAccept.isEmpty ? translate('dialog.confirm') : buttonTitleAccept,
|
||||
buttonTitleDeny:
|
||||
buttonTitleDeny == null || buttonTitleDeny.isEmpty ? translate('dialog.cancel') : buttonTitleDeny));
|
||||
buttonTitleAccept == null || buttonTitleAccept.isEmpty
|
||||
? translate('dialog.confirm')
|
||||
: buttonTitleAccept,
|
||||
buttonTitleDeny: buttonTitleDeny == null || buttonTitleDeny.isEmpty
|
||||
? translate('dialog.cancel')
|
||||
: buttonTitleDeny));
|
||||
return _dialogCompleter!.future;
|
||||
}
|
||||
|
||||
|
|
|
@ -23,7 +23,8 @@ class FileService {
|
|||
return await _fileRepository.postDelete(id);
|
||||
}
|
||||
|
||||
Future<UploadedResponse> uploadPaste(List<File>? files, Map<String, String>? additionalFiles) async {
|
||||
Future<UploadedResponse> uploadPaste(
|
||||
List<File>? files, Map<String, String>? additionalFiles) async {
|
||||
return await _fileRepository.postUpload(files, additionalFiles);
|
||||
}
|
||||
|
||||
|
|
|
@ -19,7 +19,8 @@ class LinkService {
|
|||
_logger.e('Could not launch link $link');
|
||||
_dialogService.showDialog(
|
||||
title: translate('link.dialog.title'),
|
||||
description: translate('link.dialog.description', args: {'link': link}));
|
||||
description:
|
||||
translate('link.dialog.description', args: {'link': link}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,11 +17,13 @@ class NavigationService {
|
|||
|
||||
Future<dynamic> navigateTo(String routeName, {dynamic arguments}) {
|
||||
logger.d('NavigationService: navigateTo $routeName');
|
||||
return _navigationKey.currentState!.pushNamed(routeName, arguments: arguments);
|
||||
return _navigationKey.currentState!
|
||||
.pushNamed(routeName, arguments: arguments);
|
||||
}
|
||||
|
||||
Future<dynamic> navigateAndReplaceTo(String routeName, {dynamic arguments}) {
|
||||
logger.d('NavigationService: navigateAndReplaceTo $routeName');
|
||||
return _navigationKey.currentState!.pushReplacementNamed(routeName, arguments: arguments);
|
||||
return _navigationKey.currentState!
|
||||
.pushReplacementNamed(routeName, arguments: arguments);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -57,7 +57,8 @@ class PermissionService extends StoppableService {
|
|||
return;
|
||||
}
|
||||
|
||||
var ignoredDialog = await _storageService.hasStoragePermissionDialogIgnored();
|
||||
var ignoredDialog =
|
||||
await _storageService.hasStoragePermissionDialogIgnored();
|
||||
|
||||
if (ignoredDialog) {
|
||||
_logger.d('Permanently ignored permission request, skipping');
|
||||
|
@ -104,8 +105,9 @@ class PermissionService extends StoppableService {
|
|||
super.start();
|
||||
await checkEnabledAndPermission();
|
||||
|
||||
_serviceCheckTimer =
|
||||
Timer.periodic(const Duration(milliseconds: Constants.mediaPermissionCheckInterval), (serviceTimer) async {
|
||||
_serviceCheckTimer = Timer.periodic(
|
||||
const Duration(milliseconds: Constants.mediaPermissionCheckInterval),
|
||||
(serviceTimer) async {
|
||||
if (!super.serviceStopped) {
|
||||
await checkEnabledAndPermission();
|
||||
} else {
|
||||
|
|
|
@ -3,7 +3,8 @@ import 'dart:async';
|
|||
import '../enums/refresh_event.dart';
|
||||
|
||||
class RefreshService {
|
||||
StreamController<RefreshEvent> refreshEventController = StreamController<RefreshEvent>.broadcast();
|
||||
StreamController<RefreshEvent> refreshEventController =
|
||||
StreamController<RefreshEvent>.broadcast();
|
||||
|
||||
void addEvent(RefreshEvent event) {
|
||||
if (refreshEventController.hasListener) {
|
||||
|
|
|
@ -7,7 +7,8 @@ import '../models/session.dart';
|
|||
class StorageService {
|
||||
static const _sessionKey = 'session';
|
||||
static const _lastUrlKey = 'last_url';
|
||||
static const _storagePermissionDialogIgnoredKey = 'storage_permission_ignored';
|
||||
static const _storagePermissionDialogIgnoredKey =
|
||||
'storage_permission_ignored';
|
||||
|
||||
Future<bool> storeLastUrl(String url) {
|
||||
return _store(_lastUrlKey, url);
|
||||
|
|
|
@ -12,9 +12,10 @@ class UserService {
|
|||
final FileService _fileService = locator<FileService>();
|
||||
final UserRepository _userRepository = locator<UserRepository>();
|
||||
|
||||
Future<CreateApiKeyResponse> createApiKey(
|
||||
String url, String username, String password, String accessLevel, String comment) async {
|
||||
return await _userRepository.postApiKey(url, username, password, accessLevel, comment);
|
||||
Future<CreateApiKeyResponse> createApiKey(String url, String username,
|
||||
String password, String accessLevel, String comment) async {
|
||||
return await _userRepository.postApiKey(
|
||||
url, username, password, accessLevel, comment);
|
||||
}
|
||||
|
||||
Future<ApiKeysResponse> getApiKeys() async {
|
||||
|
|
|
@ -6,7 +6,8 @@ class FormatterUtil {
|
|||
/// Format epoch timestamp
|
||||
static String formatEpoch(num millis) {
|
||||
DateFormat dateFormat = DateFormat().add_yMEd().add_Hm();
|
||||
return dateFormat.format(DateTime.fromMillisecondsSinceEpoch(millis as int));
|
||||
return dateFormat
|
||||
.format(DateTime.fromMillisecondsSinceEpoch(millis as int));
|
||||
}
|
||||
|
||||
static String formatBytes(int bytes, int decimals) {
|
||||
|
|
|
@ -12,7 +12,10 @@ class BaseModel extends ChangeNotifier {
|
|||
|
||||
bool _isDisposed = false;
|
||||
|
||||
final Map<String, Object?> _stateMap = {stateViewKey: ViewState.idle, stateMessageKey: null};
|
||||
final Map<String, Object?> _stateMap = {
|
||||
stateViewKey: ViewState.idle,
|
||||
stateMessageKey: null
|
||||
};
|
||||
|
||||
ViewState? get state => _stateMap[stateViewKey] as ViewState?;
|
||||
|
||||
|
@ -42,11 +45,14 @@ class BaseModel extends ChangeNotifier {
|
|||
return null;
|
||||
}
|
||||
|
||||
void setStateBoolValue(String key, bool stateValue) => _setStateValue(key, stateValue);
|
||||
void setStateBoolValue(String key, bool stateValue) =>
|
||||
_setStateValue(key, stateValue);
|
||||
|
||||
void setStateIntValue(String key, int? stateValue) => _setStateValue(key, stateValue);
|
||||
void setStateIntValue(String key, int? stateValue) =>
|
||||
_setStateValue(key, stateValue);
|
||||
|
||||
void setStateStringValue(String key, String? stateValue) => _setStateValue(key, stateValue);
|
||||
void setStateStringValue(String key, String? stateValue) =>
|
||||
_setStateValue(key, stateValue);
|
||||
|
||||
void _setStateValue(String key, Object? stateValue) {
|
||||
if (_stateMap.containsKey(key)) {
|
||||
|
@ -57,7 +63,8 @@ class BaseModel extends ChangeNotifier {
|
|||
|
||||
if (!_isDisposed) {
|
||||
notifyListeners();
|
||||
_logger.d("Notified state value update '($key, ${stateValue.toString()})'");
|
||||
_logger
|
||||
.d("Notified state value update '($key, ${stateValue.toString()})'");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -80,6 +87,7 @@ class BaseModel extends ChangeNotifier {
|
|||
|
||||
@override
|
||||
void dispose() {
|
||||
_logger.d("Calling dispose");
|
||||
super.dispose();
|
||||
_isDisposed = true;
|
||||
}
|
||||
|
|
|
@ -34,7 +34,8 @@ class HistoryModel extends BaseModel {
|
|||
String? errorMessage;
|
||||
|
||||
void init() {
|
||||
_refreshTriggerSubscription = _refreshService.refreshEventController.stream.listen((event) {
|
||||
_refreshTriggerSubscription =
|
||||
_refreshService.refreshEventController.stream.listen((event) {
|
||||
if (event == RefreshEvent.refreshHistory) {
|
||||
_logger.d('History needs a refresh');
|
||||
getHistory();
|
||||
|
@ -54,7 +55,8 @@ class HistoryModel extends BaseModel {
|
|||
pastes.add(
|
||||
UploadedPaste(
|
||||
id: key,
|
||||
date: DateTime.fromMillisecondsSinceEpoch(millisecondsSinceEpoch),
|
||||
date:
|
||||
DateTime.fromMillisecondsSinceEpoch(millisecondsSinceEpoch),
|
||||
filename: value.filename,
|
||||
filesize: int.parse(value.filesize),
|
||||
hash: value.hash,
|
||||
|
@ -90,9 +92,11 @@ class HistoryModel 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');
|
||||
|
@ -115,7 +119,8 @@ class HistoryModel extends BaseModel {
|
|||
Future deletePaste(String id) async {
|
||||
DialogResponse res = await _dialogService.showConfirmationDialog(
|
||||
title: translate('history.delete_dialog.title'),
|
||||
description: translate('history.delete_dialog.description', args: {'id': id}),
|
||||
description:
|
||||
translate('history.delete_dialog.description', args: {'id': id}),
|
||||
buttonTitleAccept: translate('history.delete_dialog.accept'),
|
||||
buttonTitleDeny: translate('history.delete_dialog.deny'));
|
||||
|
||||
|
@ -139,7 +144,8 @@ class HistoryModel extends BaseModel {
|
|||
e.statusCode != HttpStatus.forbidden &&
|
||||
e.responseBody is RestError &&
|
||||
e.responseBody.message != null) {
|
||||
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');
|
||||
}
|
||||
|
|
|
@ -117,13 +117,19 @@ class LoginModel extends BaseModel {
|
|||
try {
|
||||
if (useCredentialsLogin) {
|
||||
CreateApiKeyResponse apiKeyResponse = await _userService.createApiKey(
|
||||
url, username, password, 'apikey', 'fbmobile-${DateTime.now().millisecondsSinceEpoch}');
|
||||
url,
|
||||
username,
|
||||
password,
|
||||
'apikey',
|
||||
'fbmobile-${DateTime.now().millisecondsSinceEpoch}');
|
||||
|
||||
var newKey = apiKeyResponse.data['new_key'];
|
||||
if (newKey != null) {
|
||||
success = await _sessionService.login(url, newKey);
|
||||
} else {
|
||||
throw ServiceException(code: ErrorCode.invalidApiKey, message: translate('login.errors.invalid_api_key'));
|
||||
throw ServiceException(
|
||||
code: ErrorCode.invalidApiKey,
|
||||
message: translate('login.errors.invalid_api_key'));
|
||||
}
|
||||
} else {
|
||||
_sessionService.setApiConfig(url, apiKey);
|
||||
|
@ -135,13 +141,15 @@ class LoginModel extends BaseModel {
|
|||
if (e is RestServiceException) {
|
||||
if (e.statusCode == HttpStatus.unauthorized) {
|
||||
errorMessage = translate('login.errors.wrong_credentials');
|
||||
} else if (e.statusCode != HttpStatus.unauthorized && e.statusCode == HttpStatus.forbidden) {
|
||||
} else if (e.statusCode != HttpStatus.unauthorized &&
|
||||
e.statusCode == HttpStatus.forbidden) {
|
||||
errorMessage = translate('login.errors.forbidden');
|
||||
} else if (e.statusCode == HttpStatus.notFound) {
|
||||
errorMessage = translate('api.incompatible_error_not_found');
|
||||
}
|
||||
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');
|
||||
}
|
||||
|
|
|
@ -31,7 +31,8 @@ class ProfileModel extends BaseModel {
|
|||
|
||||
Future logout() async {
|
||||
var dialogResult = await _dialogService.showConfirmationDialog(
|
||||
title: translate('logout.title'), description: translate('logout.confirm'));
|
||||
title: translate('logout.title'),
|
||||
description: translate('logout.confirm'));
|
||||
|
||||
if (dialogResult.confirmed!) {
|
||||
await _sessionService.logout();
|
||||
|
@ -41,7 +42,8 @@ class ProfileModel extends BaseModel {
|
|||
Future revealApiKey(String? apiKey) async {
|
||||
await _dialogService.showDialog(
|
||||
title: translate('profile.revealed_api_key.title'),
|
||||
description: translate('profile.revealed_api_key.description', args: {'apiKey': apiKey}));
|
||||
description: translate('profile.revealed_api_key.description',
|
||||
args: {'apiKey': apiKey}));
|
||||
}
|
||||
|
||||
Future showConfig(String url) async {
|
||||
|
@ -54,13 +56,15 @@ class ProfileModel extends BaseModel {
|
|||
if (e is RestServiceException) {
|
||||
if (e.statusCode == HttpStatus.unauthorized) {
|
||||
errorMessage = translate('login.errors.wrong_credentials');
|
||||
} else if (e.statusCode != HttpStatus.unauthorized && e.statusCode == HttpStatus.forbidden) {
|
||||
} else if (e.statusCode != HttpStatus.unauthorized &&
|
||||
e.statusCode == HttpStatus.forbidden) {
|
||||
errorMessage = translate('login.errors.forbidden');
|
||||
} else if (e.statusCode == HttpStatus.notFound) {
|
||||
errorMessage = translate('api.incompatible_error_not_found');
|
||||
}
|
||||
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');
|
||||
}
|
||||
|
@ -84,15 +88,18 @@ class ProfileModel extends BaseModel {
|
|||
await _dialogService.showDialog(
|
||||
title: translate('profile.shown_config.title'),
|
||||
description: translate('profile.shown_config.description', args: {
|
||||
'uploadMaxSize': FormatterUtil.formatBytes(config.uploadMaxSize as int, 2),
|
||||
'uploadMaxSize':
|
||||
FormatterUtil.formatBytes(config.uploadMaxSize as int, 2),
|
||||
'maxFilesPerRequest': config.maxFilesPerRequest,
|
||||
'maxInputVars': config.maxInputVars,
|
||||
'requestMaxSize': FormatterUtil.formatBytes(config.requestMaxSize as int, 2)
|
||||
'requestMaxSize':
|
||||
FormatterUtil.formatBytes(config.requestMaxSize as int, 2)
|
||||
}));
|
||||
} else {
|
||||
await _dialogService.showDialog(
|
||||
title: translate('profile.shown_config.error.title'),
|
||||
description: translate('profile.shown_config.error.description', args: {'message': errorMessage}));
|
||||
description: translate('profile.shown_config.error.description',
|
||||
args: {'message': errorMessage}));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,8 @@ void main() async {
|
|||
setupLogger(Level.info);
|
||||
setupLocator();
|
||||
|
||||
var delegate = await LocalizationDelegate.create(fallbackLocale: 'en', supportedLocales: ['en', 'en_US']);
|
||||
var delegate = await LocalizationDelegate.create(
|
||||
fallbackLocale: 'en', supportedLocales: ['en', 'en_US']);
|
||||
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
runApp(LocalizedApp(delegate, MyApp()));
|
||||
|
|
|
@ -27,7 +27,8 @@ class AppRouter {
|
|||
return MaterialPageRoute(
|
||||
builder: (_) => Scaffold(
|
||||
body: Center(
|
||||
child: Text(translate('dev.no_route', args: {'route': settings.name})),
|
||||
child: Text(translate('dev.no_route',
|
||||
args: {'route': settings.name})),
|
||||
),
|
||||
));
|
||||
}
|
||||
|
|
|
@ -38,7 +38,8 @@ class AboutView extends StatelessWidget {
|
|||
padding: const EdgeInsets.all(0),
|
||||
child: ListView(
|
||||
shrinkWrap: true,
|
||||
padding: const EdgeInsets.only(left: 10.0, right: 10.0, bottom: 10, top: 10),
|
||||
padding: const EdgeInsets.only(
|
||||
left: 10.0, right: 10.0, bottom: 10, top: 10),
|
||||
children: <Widget>[
|
||||
UIHelper.verticalSpaceMedium(),
|
||||
Center(child: logo),
|
||||
|
|
|
@ -31,7 +31,9 @@ class _BaseViewState<T extends BaseModel> extends State<BaseView<T>> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ChangeNotifierProvider<T?>(create: (context) => model, child: Consumer<T>(builder: widget.builder!));
|
||||
return ChangeNotifierProvider<T?>(
|
||||
create: (context) => model,
|
||||
child: Consumer<T>(builder: widget.builder!));
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
|
@ -27,8 +27,9 @@ class HistoryView extends StatelessWidget {
|
|||
model.init();
|
||||
return model.getHistory();
|
||||
},
|
||||
builder: (context, model, child) =>
|
||||
Scaffold(appBar: MyAppBar(title: Text(translate('titles.history'))), body: _render(model, context)),
|
||||
builder: (context, model, child) => Scaffold(
|
||||
appBar: MyAppBar(title: Text(translate('titles.history'))),
|
||||
body: _render(model, context)),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -41,7 +42,8 @@ class HistoryView extends StatelessWidget {
|
|||
? Container(
|
||||
padding: const EdgeInsets.all(0),
|
||||
child: RefreshIndicator(
|
||||
onRefresh: () async => await model.getHistory(), child: _renderItems(model, url, context)))
|
||||
onRefresh: () async => await model.getHistory(),
|
||||
child: _renderItems(model, url, context)))
|
||||
: Container(
|
||||
padding: const EdgeInsets.all(25),
|
||||
child: CenteredErrorRow(
|
||||
|
@ -61,7 +63,8 @@ class HistoryView extends StatelessWidget {
|
|||
var openInBrowserButton = _renderOpenInBrowser(model, fullPasteUrl);
|
||||
|
||||
var dateWidget = ListTile(
|
||||
title: Text(FormatterUtil.formatEpoch(paste.date!.millisecondsSinceEpoch)),
|
||||
title: Text(
|
||||
FormatterUtil.formatEpoch(paste.date!.millisecondsSinceEpoch)),
|
||||
subtitle: Text(translate('history.date')),
|
||||
);
|
||||
|
||||
|
@ -73,7 +76,8 @@ class HistoryView extends StatelessWidget {
|
|||
var copyWidget = ListTile(
|
||||
title: Text(translate('history.copy_link.description')),
|
||||
trailing: IconButton(
|
||||
icon: const Icon(Icons.copy, color: blueColor, textDirection: TextDirection.ltr),
|
||||
icon: const Icon(Icons.copy,
|
||||
color: blueColor, textDirection: TextDirection.ltr),
|
||||
onPressed: () {
|
||||
FlutterClipboard.copy(fullPasteUrl).then((value) {
|
||||
final snackBar = SnackBar(
|
||||
|
@ -164,12 +168,14 @@ class HistoryView extends StatelessWidget {
|
|||
trailing: Wrap(children: [
|
||||
openInBrowserButton,
|
||||
IconButton(
|
||||
icon: const Icon(Icons.share, color: blueColor, textDirection: TextDirection.ltr),
|
||||
icon: const Icon(Icons.share,
|
||||
color: blueColor, textDirection: TextDirection.ltr),
|
||||
onPressed: () async {
|
||||
await Share.share(fullPasteUrl);
|
||||
})
|
||||
]),
|
||||
subtitle: Text(!paste.isMulti! ? paste.filename! : '', style: const TextStyle(fontStyle: FontStyle.italic)),
|
||||
subtitle: Text(!paste.isMulti! ? paste.filename! : '',
|
||||
style: const TextStyle(fontStyle: FontStyle.italic)),
|
||||
),
|
||||
));
|
||||
}
|
||||
|
@ -190,7 +196,8 @@ class HistoryView extends StatelessWidget {
|
|||
|
||||
Widget _renderOpenInBrowser(HistoryModel model, String url) {
|
||||
return IconButton(
|
||||
icon: const Icon(Icons.open_in_new, color: blueColor, textDirection: TextDirection.ltr),
|
||||
icon: const Icon(Icons.open_in_new,
|
||||
color: blueColor, textDirection: TextDirection.ltr),
|
||||
onPressed: () {
|
||||
return model.openLink(url);
|
||||
});
|
||||
|
|
|
@ -16,7 +16,9 @@ class HomeView extends StatelessWidget {
|
|||
return BaseView<HomeModel>(
|
||||
builder: (context, model, child) => Scaffold(
|
||||
appBar: MyAppBar(title: Text(translate('app.title'))),
|
||||
body: model.state == ViewState.busy ? const Center(child: CircularProgressIndicator()) : Container()),
|
||||
body: model.state == ViewState.busy
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: Container()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -55,13 +55,18 @@ class LoginView extends StatelessWidget {
|
|||
child: const Icon(Icons.help),
|
||||
onTap: () {
|
||||
_dialogService.showDialog(
|
||||
title: translate('login.compatibility_dialog.title'),
|
||||
description: translate('login.compatibility_dialog.body'));
|
||||
title: translate(
|
||||
'login.compatibility_dialog.title'),
|
||||
description: translate(
|
||||
'login.compatibility_dialog.body'));
|
||||
},
|
||||
),
|
||||
InkWell(
|
||||
child:
|
||||
Icon(model.useCredentialsLogin ? Icons.person_outline : Icons.vpn_key, color: blueColor),
|
||||
child: Icon(
|
||||
model.useCredentialsLogin
|
||||
? Icons.person_outline
|
||||
: Icons.vpn_key,
|
||||
color: blueColor),
|
||||
onTap: () {
|
||||
model.toggleLoginMethod();
|
||||
},
|
||||
|
@ -86,7 +91,8 @@ class LoginView extends StatelessWidget {
|
|||
onPressed: () async {
|
||||
var loginSuccess = await model.login();
|
||||
if (loginSuccess) {
|
||||
_navigationService.navigateAndReplaceTo(HomeView.routeName);
|
||||
_navigationService
|
||||
.navigateAndReplaceTo(HomeView.routeName);
|
||||
}
|
||||
},
|
||||
)
|
||||
|
|
|
@ -14,7 +14,8 @@ class AuthenticatedNavBarView extends StatefulWidget {
|
|||
AuthenticatedNavBarState createState() => AuthenticatedNavBarState();
|
||||
}
|
||||
|
||||
class AuthenticatedNavBarState extends State<AuthenticatedNavBarView> with SingleTickerProviderStateMixin {
|
||||
class AuthenticatedNavBarState extends State<AuthenticatedNavBarView>
|
||||
with SingleTickerProviderStateMixin {
|
||||
final Logger _logger = getLogger();
|
||||
int _currentTabIndex = 0;
|
||||
|
||||
|
|
|
@ -20,8 +20,9 @@ class ProfileView extends StatelessWidget {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BaseView<ProfileModel>(
|
||||
builder: (context, model, child) =>
|
||||
Scaffold(appBar: MyAppBar(title: Text(translate('titles.profile'))), body: _render(model, context)));
|
||||
builder: (context, model, child) => Scaffold(
|
||||
appBar: MyAppBar(title: Text(translate('titles.profile'))),
|
||||
body: _render(model, context)));
|
||||
}
|
||||
|
||||
Widget _render(ProfileModel model, BuildContext context) {
|
||||
|
|
|
@ -13,7 +13,7 @@ class StartUpView extends StatelessWidget {
|
|||
Widget build(BuildContext context) {
|
||||
return ViewModelBuilder<StartUpViewModel>.reactive(
|
||||
viewModelBuilder: () => StartUpViewModel(),
|
||||
onModelReady: (model) => model.handleStartUpLogic(),
|
||||
onViewModelReady: (model) => model.handleStartUpLogic(),
|
||||
builder: (context, model, child) => Scaffold(
|
||||
body: model.state == ViewState.busy
|
||||
? Center(
|
||||
|
@ -22,7 +22,9 @@ class StartUpView extends StatelessWidget {
|
|||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
const CircularProgressIndicator(),
|
||||
(model.stateMessage!.isNotEmpty ? Text(model.stateMessage!) : Container())
|
||||
(model.stateMessage!.isNotEmpty
|
||||
? Text(model.stateMessage!)
|
||||
: Container())
|
||||
]))
|
||||
: Container()));
|
||||
}
|
||||
|
|
|
@ -11,7 +11,8 @@ class TabBarContainerView extends StatelessWidget {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Session? currentSession = Provider.of<Session?>(context);
|
||||
bool isAuthenticated = currentSession != null ? currentSession.apiKey.isNotEmpty : false;
|
||||
bool isAuthenticated =
|
||||
currentSession != null ? currentSession.apiKey.isNotEmpty : false;
|
||||
|
||||
if (isAuthenticated) {
|
||||
return const AuthenticatedNavBarView();
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -20,7 +20,10 @@ class CenteredErrorRow extends StatelessWidget {
|
|||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Expanded(child: Center(child: Text(message!, style: const TextStyle(color: redColor)))),
|
||||
Expanded(
|
||||
child: Center(
|
||||
child: Text(message!,
|
||||
style: const TextStyle(color: redColor)))),
|
||||
],
|
||||
),
|
||||
(retryCallback != null
|
||||
|
|
|
@ -11,13 +11,19 @@ class LoginApiKeyHeaders extends StatelessWidget {
|
|||
final String? validationMessage;
|
||||
|
||||
const LoginApiKeyHeaders(
|
||||
{super.key, required this.uriController, required this.apiKeyController, this.validationMessage});
|
||||
{super.key,
|
||||
required this.uriController,
|
||||
required this.apiKeyController,
|
||||
this.validationMessage});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(children: <Widget>[
|
||||
validationMessage != null ? Text(validationMessage!, style: const TextStyle(color: redColor)) : Container(),
|
||||
LoginTextField(uriController, translate('login.url_placeholder'), const Icon(Icons.link),
|
||||
validationMessage != null
|
||||
? Text(validationMessage!, style: const TextStyle(color: redColor))
|
||||
: Container(),
|
||||
LoginTextField(uriController, translate('login.url_placeholder'),
|
||||
const Icon(Icons.link),
|
||||
keyboardType: TextInputType.url),
|
||||
LoginTextField(
|
||||
apiKeyController,
|
||||
|
|
|
@ -21,12 +21,17 @@ class LoginCredentialsHeaders extends StatelessWidget {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(children: <Widget>[
|
||||
validationMessage != null ? Text(validationMessage!, style: const TextStyle(color: redColor)) : Container(),
|
||||
LoginTextField(uriController, translate('login.url_placeholder'), const Icon(Icons.link),
|
||||
validationMessage != null
|
||||
? Text(validationMessage!, style: const TextStyle(color: redColor))
|
||||
: Container(),
|
||||
LoginTextField(uriController, translate('login.url_placeholder'),
|
||||
const Icon(Icons.link),
|
||||
keyboardType: TextInputType.url),
|
||||
LoginTextField(usernameController, translate('login.username_placeholder'), const Icon(Icons.person),
|
||||
LoginTextField(usernameController,
|
||||
translate('login.username_placeholder'), const Icon(Icons.person),
|
||||
keyboardType: TextInputType.name),
|
||||
LoginTextField(passwordController, translate('login.password_placeholder'), const Icon(Icons.vpn_key),
|
||||
LoginTextField(passwordController,
|
||||
translate('login.password_placeholder'), const Icon(Icons.vpn_key),
|
||||
obscureText: true),
|
||||
]);
|
||||
}
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../shared/app_colors.dart';
|
||||
|
||||
class LoginTextField extends StatelessWidget {
|
||||
final TextEditingController controller;
|
||||
final String placeHolder;
|
||||
|
@ -10,7 +8,9 @@ class LoginTextField extends StatelessWidget {
|
|||
final Widget prefixIcon;
|
||||
|
||||
const LoginTextField(this.controller, this.placeHolder, this.prefixIcon,
|
||||
{super.key, this.keyboardType = TextInputType.text, this.obscureText = false});
|
||||
{super.key,
|
||||
this.keyboardType = TextInputType.text,
|
||||
this.obscureText = false});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
@ -19,7 +19,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,
|
||||
|
@ -31,7 +30,8 @@ class LoginTextField extends StatelessWidget {
|
|||
prefixIcon: prefixIcon,
|
||||
hintText: placeHolder,
|
||||
contentPadding: const EdgeInsets.fromLTRB(20.0, 10.0, 20.0, 10.0),
|
||||
border: OutlineInputBorder(borderRadius: BorderRadius.circular(32.0)),
|
||||
border:
|
||||
OutlineInputBorder(borderRadius: BorderRadius.circular(32.0)),
|
||||
),
|
||||
controller: controller),
|
||||
);
|
||||
|
|
|
@ -6,10 +6,18 @@ class MyAppBar extends AppBar {
|
|||
static final List<Widget> aboutEnabledWidgets = [AboutIconButton()];
|
||||
static final List<Widget> aboutDisabledWidgets = [];
|
||||
|
||||
MyAppBar({Key? key, required Widget title, List<Widget>? actionWidgets, bool enableAbout = true})
|
||||
: super(key: key, title: Row(children: <Widget>[title]), actions: _renderIconButtons(actionWidgets, enableAbout));
|
||||
MyAppBar(
|
||||
{Key? key,
|
||||
required Widget title,
|
||||
List<Widget>? actionWidgets,
|
||||
bool enableAbout = true})
|
||||
: super(
|
||||
key: key,
|
||||
title: Row(children: <Widget>[title]),
|
||||
actions: _renderIconButtons(actionWidgets, enableAbout));
|
||||
|
||||
static List<Widget> _renderIconButtons(List<Widget>? actionWidgets, bool aboutEnabled) {
|
||||
static List<Widget> _renderIconButtons(
|
||||
List<Widget>? actionWidgets, bool aboutEnabled) {
|
||||
actionWidgets ??= [];
|
||||
|
||||
List<Widget> widgets = [...actionWidgets];
|
||||
|
|
40
pubspec.lock
40
pubspec.lock
|
@ -98,7 +98,7 @@ packages:
|
|||
name: built_value_generator
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "8.4.2"
|
||||
version: "8.4.3"
|
||||
characters:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -217,7 +217,7 @@ packages:
|
|||
name: file_picker
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "5.2.4"
|
||||
version: "5.2.5"
|
||||
fixnum:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -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
|
||||
|
@ -581,20 +588,6 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.2.1"
|
||||
quiver:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: quiver
|
||||
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:
|
||||
|
@ -615,7 +608,7 @@ packages:
|
|||
name: shared_preferences
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.15"
|
||||
version: "2.0.16"
|
||||
shared_preferences_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -623,10 +616,10 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.14"
|
||||
shared_preferences_ios:
|
||||
shared_preferences_foundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_ios
|
||||
name: shared_preferences_foundation
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.1"
|
||||
|
@ -637,13 +630,6 @@ packages:
|
|||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
shared_preferences_macos:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_macos
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "2.0.4"
|
||||
shared_preferences_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -718,7 +704,7 @@ packages:
|
|||
name: stacked
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "3.0.1"
|
||||
version: "3.1.0+1"
|
||||
stacked_core:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
12
pubspec.yaml
12
pubspec.yaml
|
@ -11,7 +11,7 @@ description: A mobile client for FileBin.
|
|||
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
|
||||
# Read more about iOS versioning at
|
||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||
version: 1.5.2+18
|
||||
version: 1.6.0+18
|
||||
|
||||
environment:
|
||||
sdk: '>=2.18.5 <3.0.0'
|
||||
|
@ -24,31 +24,31 @@ dependencies:
|
|||
sdk: flutter
|
||||
flutter_translate: 4.0.3
|
||||
provider: 6.0.5
|
||||
stacked: 3.0.1
|
||||
stacked: 3.1.0+1
|
||||
get_it: 7.2.0
|
||||
logger: 1.1.0
|
||||
shared_preferences: 2.0.15
|
||||
shared_preferences: 2.0.16
|
||||
http: 0.13.5
|
||||
validators: 3.0.0
|
||||
flutter_linkify: 5.0.2
|
||||
url_launcher: 6.1.7
|
||||
expandable: 5.0.1
|
||||
share_plus: 6.3.0
|
||||
file_picker: 5.2.4
|
||||
file_picker: 5.2.5
|
||||
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:
|
||||
sdk: flutter
|
||||
build_runner: 2.3.3
|
||||
built_value_generator: 8.4.2
|
||||
built_value_generator: 8.4.3
|
||||
json_serializable: 6.5.4
|
||||
flutter_lints: 2.0.1
|
||||
|
||||
|
|
Loading…
Reference in a new issue