Initial upgrade to null-safety and latest dependency versions
This commit is contained in:
parent
35e957a049
commit
ccb780be50
60 changed files with 561 additions and 339 deletions
|
@ -1,7 +1,10 @@
|
||||||
# CHANGELOG
|
# CHANGELOG
|
||||||
|
|
||||||
## 1.3.3+13 - UNRELEASED
|
## 1.4.0+13 - UNRELEASED
|
||||||
* Increased target SDK to 30
|
* Increased target SDK to `30`
|
||||||
|
* Upgraded to Dart `2.14.4`
|
||||||
|
* Upgraded to use null-safety
|
||||||
|
* Upgraded internal dependencies to latest versions
|
||||||
|
|
||||||
## 1.3.3+12
|
## 1.3.3+12
|
||||||
* Automatically switch to initial tab when coming from the share menu
|
* Automatically switch to initial tab when coming from the share menu
|
||||||
|
|
|
@ -32,7 +32,8 @@ Start by installing dependencies and generating entities!
|
||||||
### Working versions for SDK
|
### Working versions for SDK
|
||||||
|
|
||||||
```
|
```
|
||||||
[✓] Flutter (Channel stable, 2.0.6, on Linux, locale de_DE.UTF-8)
|
Flutter version 2.5.3
|
||||||
|
Dart version 2.14.4
|
||||||
```
|
```
|
||||||
|
|
||||||
## Dependencies
|
## Dependencies
|
||||||
|
|
21
lib/app.dart
21
lib/app.dart
|
@ -24,26 +24,29 @@ class MyApp extends StatelessWidget {
|
||||||
|
|
||||||
return LocalizationProvider(
|
return LocalizationProvider(
|
||||||
state: LocalizationProvider.of(context).state,
|
state: LocalizationProvider.of(context).state,
|
||||||
child: StreamProvider<SwipeEvent>(
|
child: StreamProvider<SwipeEvent?>(
|
||||||
initialData: null,
|
initialData: null,
|
||||||
create: (context) => locator<SwipeService>().swipeEventController.stream,
|
create: (context) =>
|
||||||
child: StreamProvider<RefreshEvent>(
|
locator<SwipeService>().swipeEventController.stream,
|
||||||
|
child: StreamProvider<RefreshEvent?>(
|
||||||
initialData: null,
|
initialData: null,
|
||||||
create: (context) => locator<RefreshService>().refreshEventController.stream,
|
create: (context) =>
|
||||||
child: StreamProvider<Session>(
|
locator<RefreshService>().refreshEventController.stream,
|
||||||
|
child: StreamProvider<Session?>(
|
||||||
initialData: Session.initial(),
|
initialData: Session.initial(),
|
||||||
create: (context) => locator<SessionService>().sessionController.stream,
|
create: (context) =>
|
||||||
|
locator<SessionService>().sessionController.stream,
|
||||||
child: LifeCycleManager(
|
child: LifeCycleManager(
|
||||||
child: MaterialApp(
|
child: MaterialApp(
|
||||||
title: translate('app.title'),
|
title: translate('app.title'),
|
||||||
builder: (context, child) => Navigator(
|
builder: (context, child) => Navigator(
|
||||||
key: locator<DialogService>().dialogNavigationKey,
|
key: locator<DialogService>().dialogNavigationKey,
|
||||||
onGenerateRoute: (settings) =>
|
onGenerateRoute: (settings) => MaterialPageRoute(
|
||||||
MaterialPageRoute(builder: (context) => DialogManager(child: child)),
|
builder: (context) => DialogManager(child: child)),
|
||||||
),
|
),
|
||||||
theme: ThemeData(
|
theme: ThemeData(
|
||||||
brightness: Brightness.light,
|
brightness: Brightness.light,
|
||||||
primarySwatch: primaryAccentColor,
|
primarySwatch: primaryAccentColor as MaterialColor?,
|
||||||
primaryColor: primaryAccentColor),
|
primaryColor: primaryAccentColor),
|
||||||
onGenerateRoute: AppRouter.generateRoute,
|
onGenerateRoute: AppRouter.generateRoute,
|
||||||
navigatorKey: locator<NavigationService>().navigationKey,
|
navigatorKey: locator<NavigationService>().navigationKey,
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
class DialogRequest {
|
class DialogRequest {
|
||||||
final String title;
|
final String? title;
|
||||||
final String description;
|
final String? description;
|
||||||
final String buttonTitleAccept;
|
final String? buttonTitleAccept;
|
||||||
final String buttonTitleDeny;
|
final String? buttonTitleDeny;
|
||||||
|
|
||||||
DialogRequest({
|
DialogRequest({
|
||||||
this.title,
|
this.title,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
class DialogResponse {
|
class DialogResponse {
|
||||||
final bool confirmed;
|
final bool? confirmed;
|
||||||
|
|
||||||
DialogResponse({
|
DialogResponse({
|
||||||
this.confirmed,
|
this.confirmed,
|
||||||
|
|
|
@ -5,7 +5,7 @@ class RestServiceException extends ServiceException {
|
||||||
final int statusCode;
|
final int statusCode;
|
||||||
final dynamic responseBody;
|
final dynamic responseBody;
|
||||||
|
|
||||||
RestServiceException(this.statusCode, {this.responseBody, String message})
|
RestServiceException(this.statusCode, {this.responseBody, String? message})
|
||||||
: super(code: ErrorCode.REST_ERROR, message: message);
|
: super(code: ErrorCode.REST_ERROR, message: message);
|
||||||
|
|
||||||
String toString() {
|
String toString() {
|
||||||
|
|
|
@ -2,7 +2,7 @@ import '../enums/error_code.dart';
|
||||||
|
|
||||||
class ServiceException implements Exception {
|
class ServiceException implements Exception {
|
||||||
final ErrorCode code;
|
final ErrorCode code;
|
||||||
final String message;
|
final String? message;
|
||||||
|
|
||||||
ServiceException({this.code = ErrorCode.GENERAL_ERROR, this.message = ''});
|
ServiceException({this.code = ErrorCode.GENERAL_ERROR, this.message = ''});
|
||||||
|
|
||||||
|
|
|
@ -6,9 +6,9 @@ import '../datamodels/dialog_response.dart';
|
||||||
import '../services/dialog_service.dart';
|
import '../services/dialog_service.dart';
|
||||||
|
|
||||||
class DialogManager extends StatefulWidget {
|
class DialogManager extends StatefulWidget {
|
||||||
final Widget child;
|
final Widget? child;
|
||||||
|
|
||||||
DialogManager({Key key, this.child}) : super(key: key);
|
DialogManager({Key? key, this.child}) : super(key: key);
|
||||||
|
|
||||||
_DialogManagerState createState() => _DialogManagerState();
|
_DialogManagerState createState() => _DialogManagerState();
|
||||||
}
|
}
|
||||||
|
@ -24,15 +24,16 @@ class _DialogManagerState extends State<DialogManager> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return widget.child;
|
return widget.child!;
|
||||||
}
|
}
|
||||||
|
|
||||||
void _showDialog(DialogRequest request) {
|
void _showDialog(DialogRequest request) {
|
||||||
List<Widget> actions = <Widget>[];
|
List<Widget> actions = <Widget>[];
|
||||||
|
|
||||||
if (request.buttonTitleDeny != null && request.buttonTitleDeny.isNotEmpty) {
|
if (request.buttonTitleDeny != null &&
|
||||||
|
request.buttonTitleDeny!.isNotEmpty) {
|
||||||
Widget denyBtn = TextButton(
|
Widget denyBtn = TextButton(
|
||||||
child: Text(request.buttonTitleDeny),
|
child: Text(request.buttonTitleDeny!),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
_dialogService.dialogComplete(DialogResponse(confirmed: false));
|
_dialogService.dialogComplete(DialogResponse(confirmed: false));
|
||||||
},
|
},
|
||||||
|
@ -41,7 +42,7 @@ class _DialogManagerState extends State<DialogManager> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget confirmBtn = TextButton(
|
Widget confirmBtn = TextButton(
|
||||||
child: Text(request.buttonTitleAccept),
|
child: Text(request.buttonTitleAccept!),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
_dialogService.dialogComplete(DialogResponse(confirmed: true));
|
_dialogService.dialogComplete(DialogResponse(confirmed: true));
|
||||||
},
|
},
|
||||||
|
@ -49,8 +50,8 @@ class _DialogManagerState extends State<DialogManager> {
|
||||||
actions.add(confirmBtn);
|
actions.add(confirmBtn);
|
||||||
|
|
||||||
AlertDialog alert = AlertDialog(
|
AlertDialog alert = AlertDialog(
|
||||||
title: Text(request.title),
|
title: Text(request.title!),
|
||||||
content: Text(request.description),
|
content: Text(request.description!),
|
||||||
actions: actions,
|
actions: actions,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -9,33 +9,37 @@ import '../util/logger.dart';
|
||||||
|
|
||||||
/// Stop and start long running services
|
/// Stop and start long running services
|
||||||
class LifeCycleManager extends StatefulWidget {
|
class LifeCycleManager extends StatefulWidget {
|
||||||
final Widget child;
|
final Widget? child;
|
||||||
|
|
||||||
LifeCycleManager({Key key, this.child}) : super(key: key);
|
LifeCycleManager({Key? key, this.child}) : super(key: key);
|
||||||
|
|
||||||
_LifeCycleManagerState createState() => _LifeCycleManagerState();
|
_LifeCycleManagerState createState() => _LifeCycleManagerState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _LifeCycleManagerState extends State<LifeCycleManager> with WidgetsBindingObserver {
|
class _LifeCycleManagerState extends State<LifeCycleManager>
|
||||||
|
with WidgetsBindingObserver {
|
||||||
final Logger logger = getLogger();
|
final Logger logger = getLogger();
|
||||||
|
|
||||||
List<StoppableService> servicesToManage = [locator<SessionService>(), locator<PermissionService>()];
|
List<StoppableService> servicesToManage = [
|
||||||
|
locator<SessionService>(),
|
||||||
|
locator<PermissionService>()
|
||||||
|
];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return widget.child;
|
return widget.child!;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
WidgetsBinding.instance.addObserver(this);
|
WidgetsBinding.instance!.addObserver(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
super.dispose();
|
super.dispose();
|
||||||
WidgetsBinding.instance.removeObserver(this);
|
WidgetsBinding.instance!.removeObserver(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
|
@ -13,9 +13,13 @@ class ApiKey {
|
||||||
@JsonKey(required: true, name: 'access_level')
|
@JsonKey(required: true, name: 'access_level')
|
||||||
final String accessLevel;
|
final String accessLevel;
|
||||||
|
|
||||||
final String comment;
|
final String? comment;
|
||||||
|
|
||||||
ApiKey({this.key, this.created, this.accessLevel, this.comment});
|
ApiKey(
|
||||||
|
{required this.key,
|
||||||
|
required this.created,
|
||||||
|
required this.accessLevel,
|
||||||
|
this.comment});
|
||||||
|
|
||||||
// JSON Init
|
// JSON Init
|
||||||
factory ApiKey.fromJson(Map<String, dynamic> json) => _$ApiKeyFromJson(json);
|
factory ApiKey.fromJson(Map<String, dynamic> json) => _$ApiKeyFromJson(json);
|
||||||
|
|
|
@ -6,13 +6,14 @@ part 'apikeys.g.dart';
|
||||||
|
|
||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
class ApiKeys {
|
class ApiKeys {
|
||||||
@JsonKey(name: "items")
|
@JsonKey(name: "items", required: true)
|
||||||
final Map<String, ApiKey> apikeys;
|
final Map<String, ApiKey> apikeys;
|
||||||
|
|
||||||
ApiKeys({this.apikeys});
|
ApiKeys({required this.apikeys});
|
||||||
|
|
||||||
// JSON Init
|
// JSON Init
|
||||||
factory ApiKeys.fromJson(Map<String, dynamic> json) => _$ApiKeysFromJson(json);
|
factory ApiKeys.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$ApiKeysFromJson(json);
|
||||||
|
|
||||||
// JSON Export
|
// JSON Export
|
||||||
Map<String, dynamic> toJson() => _$ApiKeysToJson(this);
|
Map<String, dynamic> toJson() => _$ApiKeysToJson(this);
|
||||||
|
|
|
@ -12,10 +12,11 @@ class ApiKeysResponse {
|
||||||
@JsonKey(required: true)
|
@JsonKey(required: true)
|
||||||
final ApiKeys data;
|
final ApiKeys data;
|
||||||
|
|
||||||
ApiKeysResponse({this.status, this.data});
|
ApiKeysResponse({required this.status, required this.data});
|
||||||
|
|
||||||
// JSON Init
|
// JSON Init
|
||||||
factory ApiKeysResponse.fromJson(Map<String, dynamic> json) => _$ApiKeysResponseFromJson(json);
|
factory ApiKeysResponse.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$ApiKeysResponseFromJson(json);
|
||||||
|
|
||||||
// JSON Export
|
// JSON Export
|
||||||
Map<String, dynamic> toJson() => _$ApiKeysResponseToJson(this);
|
Map<String, dynamic> toJson() => _$ApiKeysResponseToJson(this);
|
||||||
|
|
|
@ -16,7 +16,11 @@ class Config {
|
||||||
@JsonKey(name: "request_max_size", required: true)
|
@JsonKey(name: "request_max_size", required: true)
|
||||||
final num requestMaxSize;
|
final num requestMaxSize;
|
||||||
|
|
||||||
Config({this.uploadMaxSize, this.maxFilesPerRequest, this.maxInputVars, this.requestMaxSize});
|
Config(
|
||||||
|
{required this.uploadMaxSize,
|
||||||
|
required this.maxFilesPerRequest,
|
||||||
|
required this.maxInputVars,
|
||||||
|
required this.requestMaxSize});
|
||||||
|
|
||||||
// JSON Init
|
// JSON Init
|
||||||
factory Config.fromJson(Map<String, dynamic> json) => _$ConfigFromJson(json);
|
factory Config.fromJson(Map<String, dynamic> json) => _$ConfigFromJson(json);
|
||||||
|
|
|
@ -12,10 +12,11 @@ class ConfigResponse {
|
||||||
@JsonKey(required: true)
|
@JsonKey(required: true)
|
||||||
final Config data;
|
final Config data;
|
||||||
|
|
||||||
ConfigResponse({this.status, this.data});
|
ConfigResponse({required this.status, required this.data});
|
||||||
|
|
||||||
// JSON Init
|
// JSON Init
|
||||||
factory ConfigResponse.fromJson(Map<String, dynamic> json) => _$ConfigResponseFromJson(json);
|
factory ConfigResponse.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$ConfigResponseFromJson(json);
|
||||||
|
|
||||||
// JSON Export
|
// JSON Export
|
||||||
Map<String, dynamic> toJson() => _$ConfigResponseToJson(this);
|
Map<String, dynamic> toJson() => _$ConfigResponseToJson(this);
|
||||||
|
|
|
@ -10,10 +10,11 @@ class CreateApiKeyResponse {
|
||||||
@JsonKey(required: true)
|
@JsonKey(required: true)
|
||||||
final Map<String, String> data;
|
final Map<String, String> data;
|
||||||
|
|
||||||
CreateApiKeyResponse({this.status, this.data});
|
CreateApiKeyResponse({required this.status, required this.data});
|
||||||
|
|
||||||
// JSON Init
|
// JSON Init
|
||||||
factory CreateApiKeyResponse.fromJson(Map<String, dynamic> json) => _$CreateApiKeyResponseFromJson(json);
|
factory CreateApiKeyResponse.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$CreateApiKeyResponseFromJson(json);
|
||||||
|
|
||||||
// JSON Export
|
// JSON Export
|
||||||
Map<String, dynamic> toJson() => _$CreateApiKeyResponseToJson(this);
|
Map<String, dynamic> toJson() => _$CreateApiKeyResponseToJson(this);
|
||||||
|
|
|
@ -14,12 +14,13 @@ class History {
|
||||||
final Map<String, HistoryMultipasteItem> multipasteItems;
|
final Map<String, HistoryMultipasteItem> multipasteItems;
|
||||||
|
|
||||||
@JsonKey(name: "total_size")
|
@JsonKey(name: "total_size")
|
||||||
final String totalSize;
|
final String? totalSize;
|
||||||
|
|
||||||
History({this.items, this.multipasteItems, this.totalSize});
|
History({required this.items, required this.multipasteItems, this.totalSize});
|
||||||
|
|
||||||
// JSON Init
|
// JSON Init
|
||||||
factory History.fromJson(Map<String, dynamic> json) => _$HistoryFromJson(json);
|
factory History.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$HistoryFromJson(json);
|
||||||
|
|
||||||
// JSON Export
|
// JSON Export
|
||||||
Map<String, dynamic> toJson() => _$HistoryToJson(this);
|
Map<String, dynamic> toJson() => _$HistoryToJson(this);
|
||||||
|
|
|
@ -11,12 +11,20 @@ class HistoryItem {
|
||||||
final String filesize;
|
final String filesize;
|
||||||
final String hash;
|
final String hash;
|
||||||
final String mimetype;
|
final String mimetype;
|
||||||
final String thumbnail;
|
final String? thumbnail;
|
||||||
|
|
||||||
HistoryItem({this.date, this.filename, this.filesize, this.hash, this.id, this.mimetype, this.thumbnail});
|
HistoryItem(
|
||||||
|
{required this.date,
|
||||||
|
required this.filename,
|
||||||
|
required this.filesize,
|
||||||
|
required this.hash,
|
||||||
|
required this.id,
|
||||||
|
required this.mimetype,
|
||||||
|
this.thumbnail});
|
||||||
|
|
||||||
// JSON Init
|
// JSON Init
|
||||||
factory HistoryItem.fromJson(Map<String, dynamic> json) => _$HistoryItemFromJson(json);
|
factory HistoryItem.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$HistoryItemFromJson(json);
|
||||||
|
|
||||||
// JSON Export
|
// JSON Export
|
||||||
Map<String, dynamic> toJson() => _$HistoryItemToJson(this);
|
Map<String, dynamic> toJson() => _$HistoryItemToJson(this);
|
||||||
|
|
|
@ -1,21 +1,22 @@
|
||||||
import 'package:json_annotation/json_annotation.dart';
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
|
|
||||||
import 'history_item.dart';
|
import 'history_multipaste_item_entry.dart';
|
||||||
|
|
||||||
part 'history_multipaste_item.g.dart';
|
part 'history_multipaste_item.g.dart';
|
||||||
|
|
||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
class HistoryMultipasteItem {
|
class HistoryMultipasteItem {
|
||||||
final String date;
|
final String date;
|
||||||
final Map<String, HistoryItem> items;
|
final Map<String, HistoryMultipasteItemEntry> items;
|
||||||
|
|
||||||
@JsonKey(name: "url_id")
|
@JsonKey(name: "url_id")
|
||||||
final String urlId;
|
final String urlId;
|
||||||
|
|
||||||
HistoryMultipasteItem({this.date, this.items, this.urlId});
|
HistoryMultipasteItem(this.items, {required this.date, required this.urlId});
|
||||||
|
|
||||||
// JSON Init
|
// JSON Init
|
||||||
factory HistoryMultipasteItem.fromJson(Map<String, dynamic> json) => _$HistoryMultipasteItemFromJson(json);
|
factory HistoryMultipasteItem.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$HistoryMultipasteItemFromJson(json);
|
||||||
|
|
||||||
// JSON Export
|
// JSON Export
|
||||||
Map<String, dynamic> toJson() => _$HistoryMultipasteItemToJson(this);
|
Map<String, dynamic> toJson() => _$HistoryMultipasteItemToJson(this);
|
||||||
|
|
17
lib/core/models/rest/history_multipaste_item_entry.dart
Normal file
17
lib/core/models/rest/history_multipaste_item_entry.dart
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
|
|
||||||
|
part 'history_multipaste_item_entry.g.dart';
|
||||||
|
|
||||||
|
@JsonSerializable()
|
||||||
|
class HistoryMultipasteItemEntry {
|
||||||
|
final String id;
|
||||||
|
|
||||||
|
HistoryMultipasteItemEntry({required this.id});
|
||||||
|
|
||||||
|
// JSON Init
|
||||||
|
factory HistoryMultipasteItemEntry.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$HistoryMultipasteItemEntryFromJson(json);
|
||||||
|
|
||||||
|
// JSON Export
|
||||||
|
Map<String, dynamic> toJson() => _$HistoryMultipasteItemEntryToJson(this);
|
||||||
|
}
|
|
@ -12,10 +12,11 @@ class HistoryResponse {
|
||||||
@JsonKey(required: true)
|
@JsonKey(required: true)
|
||||||
final History data;
|
final History data;
|
||||||
|
|
||||||
HistoryResponse({this.status, this.data});
|
HistoryResponse({required this.status, required this.data});
|
||||||
|
|
||||||
// JSON Init
|
// JSON Init
|
||||||
factory HistoryResponse.fromJson(Map<String, dynamic> json) => _$HistoryResponseFromJson(json);
|
factory HistoryResponse.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$HistoryResponseFromJson(json);
|
||||||
|
|
||||||
// JSON Export
|
// JSON Export
|
||||||
Map<String, dynamic> toJson() => _$HistoryResponseToJson(this);
|
Map<String, dynamic> toJson() => _$HistoryResponseToJson(this);
|
||||||
|
|
|
@ -10,12 +10,13 @@ class RestError {
|
||||||
final String errorId;
|
final String errorId;
|
||||||
|
|
||||||
RestError({
|
RestError({
|
||||||
this.status,
|
required this.status,
|
||||||
this.message,
|
required this.message,
|
||||||
this.errorId,
|
required this.errorId,
|
||||||
}); // JSON Init
|
}); // JSON Init
|
||||||
|
|
||||||
factory RestError.fromJson(Map<String, dynamic> json) => _$RestErrorFromJson(json);
|
factory RestError.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$RestErrorFromJson(json);
|
||||||
|
|
||||||
// JSON Export
|
// JSON Export
|
||||||
Map<String, dynamic> toJson() => _$RestErrorToJson(this);
|
Map<String, dynamic> toJson() => _$RestErrorToJson(this);
|
||||||
|
|
|
@ -10,10 +10,11 @@ class Uploaded {
|
||||||
@JsonKey(required: true)
|
@JsonKey(required: true)
|
||||||
final List<String> urls;
|
final List<String> urls;
|
||||||
|
|
||||||
Uploaded({this.ids, this.urls});
|
Uploaded({required this.ids, required this.urls});
|
||||||
|
|
||||||
// JSON Init
|
// JSON Init
|
||||||
factory Uploaded.fromJson(Map<String, dynamic> json) => _$UploadedFromJson(json);
|
factory Uploaded.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$UploadedFromJson(json);
|
||||||
|
|
||||||
// JSON Export
|
// JSON Export
|
||||||
Map<String, dynamic> toJson() => _$UploadedToJson(this);
|
Map<String, dynamic> toJson() => _$UploadedToJson(this);
|
||||||
|
|
|
@ -10,10 +10,11 @@ class UploadedMulti {
|
||||||
@JsonKey(required: true, name: "url_id")
|
@JsonKey(required: true, name: "url_id")
|
||||||
final String urlId;
|
final String urlId;
|
||||||
|
|
||||||
UploadedMulti({this.url, this.urlId});
|
UploadedMulti({required this.url, required this.urlId});
|
||||||
|
|
||||||
// JSON Init
|
// JSON Init
|
||||||
factory UploadedMulti.fromJson(Map<String, dynamic> json) => _$UploadedMultiFromJson(json);
|
factory UploadedMulti.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$UploadedMultiFromJson(json);
|
||||||
|
|
||||||
// JSON Export
|
// JSON Export
|
||||||
Map<String, dynamic> toJson() => _$UploadedMultiToJson(this);
|
Map<String, dynamic> toJson() => _$UploadedMultiToJson(this);
|
||||||
|
|
|
@ -12,10 +12,11 @@ class UploadedMultiResponse {
|
||||||
@JsonKey(required: true)
|
@JsonKey(required: true)
|
||||||
final UploadedMulti data;
|
final UploadedMulti data;
|
||||||
|
|
||||||
UploadedMultiResponse({this.status, this.data});
|
UploadedMultiResponse({required this.status, required this.data});
|
||||||
|
|
||||||
// JSON Init
|
// JSON Init
|
||||||
factory UploadedMultiResponse.fromJson(Map<String, dynamic> json) => _$UploadedMultiResponseFromJson(json);
|
factory UploadedMultiResponse.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$UploadedMultiResponseFromJson(json);
|
||||||
|
|
||||||
// JSON Export
|
// JSON Export
|
||||||
Map<String, dynamic> toJson() => _$UploadedMultiResponseToJson(this);
|
Map<String, dynamic> toJson() => _$UploadedMultiResponseToJson(this);
|
||||||
|
|
|
@ -12,10 +12,11 @@ class UploadedResponse {
|
||||||
@JsonKey(required: true)
|
@JsonKey(required: true)
|
||||||
final Uploaded data;
|
final Uploaded data;
|
||||||
|
|
||||||
UploadedResponse({this.status, this.data});
|
UploadedResponse({required this.status, required this.data});
|
||||||
|
|
||||||
// JSON Init
|
// JSON Init
|
||||||
factory UploadedResponse.fromJson(Map<String, dynamic> json) => _$UploadedResponseFromJson(json);
|
factory UploadedResponse.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$UploadedResponseFromJson(json);
|
||||||
|
|
||||||
// JSON Export
|
// JSON Export
|
||||||
Map<String, dynamic> toJson() => _$UploadedResponseToJson(this);
|
Map<String, dynamic> toJson() => _$UploadedResponseToJson(this);
|
||||||
|
|
|
@ -7,13 +7,14 @@ class Session {
|
||||||
final String url;
|
final String url;
|
||||||
final String apiKey;
|
final String apiKey;
|
||||||
|
|
||||||
Session({this.url, this.apiKey});
|
Session({required this.url, required this.apiKey});
|
||||||
|
|
||||||
Session.initial()
|
Session.initial()
|
||||||
: url = '',
|
: url = '',
|
||||||
apiKey = '';
|
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);
|
Map<String, dynamic> toJson() => _$SessionToJson(this);
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,29 +4,30 @@ part 'uploaded_paste.g.dart';
|
||||||
|
|
||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
class UploadedPaste {
|
class UploadedPaste {
|
||||||
final DateTime date;
|
final DateTime? date;
|
||||||
final String filename;
|
final String? filename;
|
||||||
final num filesize;
|
final num? filesize;
|
||||||
final String hash;
|
final String? hash;
|
||||||
final String id;
|
final String id;
|
||||||
final String mimetype;
|
final String? mimetype;
|
||||||
final String thumbnail;
|
final String? thumbnail;
|
||||||
final bool isMulti;
|
final bool? isMulti;
|
||||||
final List<String> items;
|
final List<String?>? items;
|
||||||
|
|
||||||
UploadedPaste(
|
UploadedPaste(
|
||||||
{this.date,
|
{this.date,
|
||||||
this.filename,
|
this.filename,
|
||||||
this.filesize,
|
this.filesize,
|
||||||
this.hash,
|
this.hash,
|
||||||
this.id,
|
required this.id,
|
||||||
this.mimetype,
|
this.mimetype,
|
||||||
this.thumbnail,
|
this.thumbnail,
|
||||||
this.isMulti,
|
this.isMulti,
|
||||||
this.items});
|
this.items});
|
||||||
|
|
||||||
// JSON Init
|
// JSON Init
|
||||||
factory UploadedPaste.fromJson(Map<String, dynamic> json) => _$UploadedPasteFromJson(json);
|
factory UploadedPaste.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$UploadedPasteFromJson(json);
|
||||||
|
|
||||||
// JSON Export
|
// JSON Export
|
||||||
Map<String, dynamic> toJson() => _$UploadedPasteToJson(this);
|
Map<String, dynamic> toJson() => _$UploadedPasteToJson(this);
|
||||||
|
|
|
@ -27,12 +27,16 @@ class FileRepository {
|
||||||
return parsedResponse.data;
|
return parsedResponse.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> postDelete(String id) async {
|
Future postDelete(String id) async {
|
||||||
await _api.post('/file/delete', fields: {'ids[1]': id});
|
var fields = Map.fromEntries([MapEntry("ids[1]", id)]);
|
||||||
|
var response = await _api.post('/file/delete', fields: fields);
|
||||||
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<UploadedResponse> postUpload(List<File> files, Map<String, String> additionalFiles) async {
|
Future<UploadedResponse> postUpload(
|
||||||
var response = await _api.post('/file/upload', files: files, additionalFiles: additionalFiles);
|
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));
|
return UploadedResponse.fromJson(json.decode(response.body));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,10 +44,12 @@ class FileRepository {
|
||||||
Map<String, String> multiPasteIds = Map();
|
Map<String, String> multiPasteIds = Map();
|
||||||
|
|
||||||
ids.forEach((element) {
|
ids.forEach((element) {
|
||||||
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));
|
return UploadedMultiResponse.fromJson(json.decode(response.body));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,12 +8,17 @@ import '../services/api.dart';
|
||||||
class UserRepository {
|
class UserRepository {
|
||||||
Api _api = locator<Api>();
|
Api _api = locator<Api>();
|
||||||
|
|
||||||
Future<CreateApiKeyResponse> postApiKey(
|
Future<CreateApiKeyResponse> postApiKey(String url, String username,
|
||||||
String url, String username, String password, String accessLevel, String comment) async {
|
String password, String accessLevel, String comment) async {
|
||||||
_api.setUrl(url);
|
_api.setUrl(url);
|
||||||
|
|
||||||
var response = await _api.post('/user/create_apikey',
|
var fields = Map.fromEntries([
|
||||||
fields: {'username': username, 'password': password, 'access_level': accessLevel, 'comment': comment});
|
MapEntry("username", username),
|
||||||
|
MapEntry("password", password),
|
||||||
|
MapEntry("access_level", accessLevel),
|
||||||
|
MapEntry("comment", comment),
|
||||||
|
]);
|
||||||
|
var response = await _api.post('/user/create_apikey', fields: fields);
|
||||||
return CreateApiKeyResponse.fromJson(json.decode(response.body));
|
return CreateApiKeyResponse.fromJson(json.decode(response.body));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,25 +24,34 @@ class Api implements ApiErrorConverter {
|
||||||
String _url = "";
|
String _url = "";
|
||||||
String _apiKey = "";
|
String _apiKey = "";
|
||||||
|
|
||||||
Map<String, String> _headers = {"Content-Type": _applicationJson, "Accept": _applicationJson};
|
Map<String, String> _headers = {
|
||||||
|
"Content-Type": _applicationJson,
|
||||||
|
"Accept": _applicationJson
|
||||||
|
};
|
||||||
Duration _timeout = Duration(seconds: Constants.apiRequestTimeoutLimit);
|
Duration _timeout = Duration(seconds: Constants.apiRequestTimeoutLimit);
|
||||||
|
|
||||||
Future<http.Response> fetch<T>(String route) async {
|
Future<http.Response> fetch<T>(String route) async {
|
||||||
try {
|
try {
|
||||||
_logger
|
_logger.d(
|
||||||
.d("Requesting GET API endpoint '${_url + route}' with headers '$_headers' and maximum timeout '$_timeout'");
|
"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);
|
var response = await http
|
||||||
|
.get(Uri.parse(_url + route), headers: _headers)
|
||||||
|
.timeout(_timeout);
|
||||||
handleRestErrors(response);
|
handleRestErrors(response);
|
||||||
return response;
|
return response;
|
||||||
} on TimeoutException {
|
} on TimeoutException {
|
||||||
throw ServiceException(code: ErrorCode.SOCKET_TIMEOUT, message: _errorTimeout);
|
throw ServiceException(
|
||||||
|
code: ErrorCode.SOCKET_TIMEOUT, message: _errorTimeout);
|
||||||
} on SocketException {
|
} on SocketException {
|
||||||
throw ServiceException(code: ErrorCode.SOCKET_ERROR, message: _errorNoConnection);
|
throw ServiceException(
|
||||||
|
code: ErrorCode.SOCKET_ERROR, message: _errorNoConnection);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<http.Response> post<T>(String route,
|
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 {
|
try {
|
||||||
var uri = Uri.parse(_url + route);
|
var uri = Uri.parse(_url + route);
|
||||||
var request = http.MultipartRequest('POST', uri)
|
var request = http.MultipartRequest('POST', uri)
|
||||||
|
@ -54,32 +63,39 @@ class Api implements ApiErrorConverter {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fields != null && fields.isNotEmpty) {
|
if (fields != null && fields.isNotEmpty) {
|
||||||
request.fields.addAll(fields);
|
request.fields.addAll(fields as Map<String, String>);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (files != null && files.isNotEmpty) {
|
if (files != null && files.isNotEmpty) {
|
||||||
files.forEach((element) async {
|
files.forEach((element) async {
|
||||||
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.length > 0) {
|
if (additionalFiles != null && additionalFiles.length > 0) {
|
||||||
List<String> keys = additionalFiles.keys.toList();
|
List<String> keys = additionalFiles.keys.toList();
|
||||||
additionalFiles.forEach((key, value) {
|
additionalFiles.forEach((key, value) {
|
||||||
var index = files != null ? files.length + keys.indexOf(key) + 1 : keys.indexOf(key) + 1;
|
var index = files != null
|
||||||
request.files.add(http.MultipartFile.fromString('file[$index]', value, filename: key));
|
? 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 multiResponse = await request.send();
|
||||||
var response = await http.Response.fromStream(multiResponse);
|
var response = await http.Response.fromStream(multiResponse);
|
||||||
handleRestErrors(response);
|
handleRestErrors(response);
|
||||||
return response;
|
return response;
|
||||||
} on TimeoutException {
|
} on TimeoutException {
|
||||||
throw ServiceException(code: ErrorCode.SOCKET_TIMEOUT, message: _errorTimeout);
|
throw ServiceException(
|
||||||
|
code: ErrorCode.SOCKET_TIMEOUT, message: _errorTimeout);
|
||||||
} on SocketException {
|
} on SocketException {
|
||||||
throw ServiceException(code: ErrorCode.SOCKET_ERROR, message: _errorNoConnection);
|
throw ServiceException(
|
||||||
|
code: ErrorCode.SOCKET_ERROR, message: _errorNoConnection);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,20 +123,21 @@ class Api implements ApiErrorConverter {
|
||||||
/// have a json decoded object. Replace this with a custom
|
/// have a json decoded object. Replace this with a custom
|
||||||
/// conversion method by overwriting the interface if needed
|
/// conversion method by overwriting the interface if needed
|
||||||
void handleRestErrors(http.Response response) {
|
void handleRestErrors(http.Response response) {
|
||||||
if (response != null) {
|
if (response.statusCode != HttpStatus.ok &&
|
||||||
if (response.statusCode != HttpStatus.ok && response.statusCode != HttpStatus.noContent) {
|
response.statusCode != HttpStatus.noContent) {
|
||||||
if (response.headers.containsKey(HttpHeaders.contentTypeHeader)) {
|
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 &&
|
if (ContentType.json.primaryType == responseContentType.primaryType &&
|
||||||
ContentType.json.subType == responseContentType.subType) {
|
ContentType.json.subType == responseContentType.subType) {
|
||||||
var parsedBody = convert(response);
|
var parsedBody = convert(response);
|
||||||
throw new RestServiceException(response.statusCode, responseBody: parsedBody);
|
throw new RestServiceException(response.statusCode,
|
||||||
}
|
responseBody: parsedBody);
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new RestServiceException(response.statusCode);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
throw new RestServiceException(response.statusCode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,8 +8,8 @@ import '../datamodels/dialog_response.dart';
|
||||||
|
|
||||||
class DialogService {
|
class DialogService {
|
||||||
GlobalKey<NavigatorState> _dialogNavigationKey = GlobalKey<NavigatorState>();
|
GlobalKey<NavigatorState> _dialogNavigationKey = GlobalKey<NavigatorState>();
|
||||||
Function(DialogRequest) _showDialogListener;
|
late Function(DialogRequest) _showDialogListener;
|
||||||
Completer<DialogResponse> _dialogCompleter;
|
Completer<DialogResponse>? _dialogCompleter;
|
||||||
|
|
||||||
GlobalKey<NavigatorState> get dialogNavigationKey => _dialogNavigationKey;
|
GlobalKey<NavigatorState> get dialogNavigationKey => _dialogNavigationKey;
|
||||||
|
|
||||||
|
@ -18,35 +18,43 @@ class DialogService {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<DialogResponse> showDialog({
|
Future<DialogResponse> showDialog({
|
||||||
String title,
|
String? title,
|
||||||
String description,
|
String? description,
|
||||||
String buttonTitleAccept,
|
String? buttonTitleAccept,
|
||||||
}) {
|
}) {
|
||||||
_dialogCompleter = Completer<DialogResponse>();
|
_dialogCompleter = Completer<DialogResponse>();
|
||||||
_showDialogListener(DialogRequest(
|
_showDialogListener(DialogRequest(
|
||||||
title: title,
|
title: title,
|
||||||
description: description,
|
description: description,
|
||||||
buttonTitleAccept:
|
buttonTitleAccept:
|
||||||
buttonTitleAccept == null || buttonTitleAccept.isEmpty ? translate('dialog.confirm') : buttonTitleAccept));
|
buttonTitleAccept == null || buttonTitleAccept.isEmpty
|
||||||
return _dialogCompleter.future;
|
? translate('dialog.confirm')
|
||||||
|
: buttonTitleAccept));
|
||||||
|
return _dialogCompleter!.future;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<DialogResponse> showConfirmationDialog(
|
Future<DialogResponse> showConfirmationDialog(
|
||||||
{String title, String description, String buttonTitleAccept, String buttonTitleDeny}) {
|
{String? title,
|
||||||
|
String? description,
|
||||||
|
String? buttonTitleAccept,
|
||||||
|
String? buttonTitleDeny}) {
|
||||||
_dialogCompleter = Completer<DialogResponse>();
|
_dialogCompleter = Completer<DialogResponse>();
|
||||||
_showDialogListener(DialogRequest(
|
_showDialogListener(DialogRequest(
|
||||||
title: title,
|
title: title,
|
||||||
description: description,
|
description: description,
|
||||||
buttonTitleAccept:
|
buttonTitleAccept:
|
||||||
buttonTitleAccept == null || buttonTitleAccept.isEmpty ? translate('dialog.confirm') : buttonTitleAccept,
|
buttonTitleAccept == null || buttonTitleAccept.isEmpty
|
||||||
buttonTitleDeny:
|
? translate('dialog.confirm')
|
||||||
buttonTitleDeny == null || buttonTitleDeny.isEmpty ? translate('dialog.cancel') : buttonTitleDeny));
|
: buttonTitleAccept,
|
||||||
return _dialogCompleter.future;
|
buttonTitleDeny: buttonTitleDeny == null || buttonTitleDeny.isEmpty
|
||||||
|
? translate('dialog.cancel')
|
||||||
|
: buttonTitleDeny));
|
||||||
|
return _dialogCompleter!.future;
|
||||||
}
|
}
|
||||||
|
|
||||||
void dialogComplete(DialogResponse response) {
|
void dialogComplete(DialogResponse response) {
|
||||||
_dialogNavigationKey.currentState.pop();
|
_dialogNavigationKey.currentState!.pop();
|
||||||
_dialogCompleter.complete(response);
|
_dialogCompleter!.complete(response);
|
||||||
_dialogCompleter = null;
|
_dialogCompleter = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,15 +15,16 @@ class FileService {
|
||||||
return await _fileRepository.getConfig(url);
|
return await _fileRepository.getConfig(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<History> getHistory() async {
|
FutureOr<History> getHistory() async {
|
||||||
return await _fileRepository.getHistory();
|
return await _fileRepository.getHistory();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> deletePaste(String id) async {
|
Future deletePaste(String id) async {
|
||||||
return await _fileRepository.postDelete(id);
|
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);
|
return await _fileRepository.postUpload(files, additionalFiles);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,8 @@ class LinkService {
|
||||||
_logger.e('Could not launch link $link');
|
_logger.e('Could not launch link $link');
|
||||||
_dialogService.showDialog(
|
_dialogService.showDialog(
|
||||||
title: translate('link.dialog.title'),
|
title: translate('link.dialog.title'),
|
||||||
description: translate('link.dialog.description', args: {'link': link}));
|
description:
|
||||||
|
translate('link.dialog.description', args: {'link': link}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,16 +12,16 @@ class NavigationService {
|
||||||
|
|
||||||
void pop() {
|
void pop() {
|
||||||
logger.d('NavigationService: pop');
|
logger.d('NavigationService: pop');
|
||||||
_navigationKey.currentState.pop();
|
_navigationKey.currentState!.pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<dynamic> navigateTo(String routeName, {dynamic arguments}) {
|
Future<dynamic> navigateTo(String routeName, {dynamic arguments}) {
|
||||||
logger.d('NavigationService: navigateTo $routeName');
|
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}) {
|
Future<dynamic> navigateAndReplaceTo(String routeName, {dynamic arguments}) {
|
||||||
logger.d('NavigationService: navigateAndReplaceTo $routeName');
|
logger.d('NavigationService: navigateAndReplaceTo $routeName');
|
||||||
return _navigationKey.currentState.pushReplacementNamed(routeName, arguments: arguments);
|
return _navigationKey.currentState!.pushReplacementNamed(routeName, arguments: arguments);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,9 +17,9 @@ class PermissionService extends StoppableService {
|
||||||
final DialogService _dialogService = locator<DialogService>();
|
final DialogService _dialogService = locator<DialogService>();
|
||||||
final StorageService _storageService = locator<StorageService>();
|
final StorageService _storageService = locator<StorageService>();
|
||||||
|
|
||||||
Timer _serviceCheckTimer;
|
Timer? _serviceCheckTimer;
|
||||||
|
|
||||||
PermissionStatus _permissionStatus;
|
PermissionStatus? _permissionStatus;
|
||||||
|
|
||||||
bool _permanentlyIgnored = false;
|
bool _permanentlyIgnored = false;
|
||||||
bool _devicePermissionDialogActive = false;
|
bool _devicePermissionDialogActive = false;
|
||||||
|
@ -57,7 +57,8 @@ class PermissionService extends StoppableService {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var ignoredDialog = await _storageService.hasStoragePermissionDialogIgnored();
|
var ignoredDialog =
|
||||||
|
await _storageService.hasStoragePermissionDialogIgnored();
|
||||||
|
|
||||||
if (ignoredDialog) {
|
if (ignoredDialog) {
|
||||||
_logger.d('Permanently ignored permission request, skipping');
|
_logger.d('Permanently ignored permission request, skipping');
|
||||||
|
@ -79,7 +80,7 @@ class PermissionService extends StoppableService {
|
||||||
buttonTitleAccept: translate('permission_service.dialog.grant'),
|
buttonTitleAccept: translate('permission_service.dialog.grant'),
|
||||||
buttonTitleDeny: translate('permission_service.dialog.ignore'));
|
buttonTitleDeny: translate('permission_service.dialog.ignore'));
|
||||||
|
|
||||||
if (!response.confirmed) {
|
if (!response.confirmed!) {
|
||||||
await _storageService.storeStoragePermissionDialogIgnored();
|
await _storageService.storeStoragePermissionDialogIgnored();
|
||||||
} else {
|
} else {
|
||||||
_devicePermissionDialogActive = true;
|
_devicePermissionDialogActive = true;
|
||||||
|
@ -104,8 +105,9 @@ class PermissionService extends StoppableService {
|
||||||
super.start();
|
super.start();
|
||||||
await checkEnabledAndPermission();
|
await checkEnabledAndPermission();
|
||||||
|
|
||||||
_serviceCheckTimer =
|
_serviceCheckTimer = Timer.periodic(
|
||||||
Timer.periodic(Duration(milliseconds: Constants.mediaPermissionCheckInterval), (_serviceTimer) async {
|
Duration(milliseconds: Constants.mediaPermissionCheckInterval),
|
||||||
|
(_serviceTimer) async {
|
||||||
if (!super.serviceStopped) {
|
if (!super.serviceStopped) {
|
||||||
await checkEnabledAndPermission();
|
await checkEnabledAndPermission();
|
||||||
} else {
|
} else {
|
||||||
|
@ -124,7 +126,7 @@ class PermissionService extends StoppableService {
|
||||||
|
|
||||||
void _removeServiceCheckTimer() {
|
void _removeServiceCheckTimer() {
|
||||||
if (_serviceCheckTimer != null) {
|
if (_serviceCheckTimer != null) {
|
||||||
_serviceCheckTimer.cancel();
|
_serviceCheckTimer!.cancel();
|
||||||
_serviceCheckTimer = null;
|
_serviceCheckTimer = null;
|
||||||
_logger.d('Removed service check timer');
|
_logger.d('Removed service check timer');
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,9 +14,9 @@ class SessionService extends StoppableService {
|
||||||
final StorageService _storageService = locator<StorageService>();
|
final StorageService _storageService = locator<StorageService>();
|
||||||
final Api _api = locator<Api>();
|
final Api _api = locator<Api>();
|
||||||
|
|
||||||
StreamController<Session> sessionController = StreamController<Session>();
|
StreamController<Session?> sessionController = StreamController<Session?>();
|
||||||
|
|
||||||
void setApiConfig(String url, String apiKey) {
|
void setApiConfig(String url, String? apiKey) {
|
||||||
_logger.d('Setting API config for session');
|
_logger.d('Setting API config for session');
|
||||||
_api.setUrl(url);
|
_api.setUrl(url);
|
||||||
_api.addApiKeyAuthorization(apiKey);
|
_api.addApiKeyAuthorization(apiKey);
|
||||||
|
|
|
@ -13,7 +13,7 @@ class StorageService {
|
||||||
return _store(_LAST_URL_KEY, url);
|
return _store(_LAST_URL_KEY, url);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String> retrieveLastUrl() async {
|
Future<String?> retrieveLastUrl() async {
|
||||||
return await _retrieve(_LAST_URL_KEY);
|
return await _retrieve(_LAST_URL_KEY);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ class StorageService {
|
||||||
|
|
||||||
Future<Session> retrieveSession() async {
|
Future<Session> retrieveSession() async {
|
||||||
var retrieve = await _retrieve(_SESSION_KEY);
|
var retrieve = await _retrieve(_SESSION_KEY);
|
||||||
return Session.fromJson(json.decode(retrieve));
|
return Session.fromJson(json.decode(retrieve!));
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> hasSession() {
|
Future<bool> hasSession() {
|
||||||
|
@ -56,7 +56,7 @@ class StorageService {
|
||||||
return prefs.remove(key);
|
return prefs.remove(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String> _retrieve(String key) async {
|
Future<String?> _retrieve(String key) async {
|
||||||
final SharedPreferences prefs = await SharedPreferences.getInstance();
|
final SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||||
return prefs.getString(key);
|
return prefs.getString(key);
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,9 +12,10 @@ class UserService {
|
||||||
final FileService _fileService = locator<FileService>();
|
final FileService _fileService = locator<FileService>();
|
||||||
final UserRepository _userRepository = locator<UserRepository>();
|
final UserRepository _userRepository = locator<UserRepository>();
|
||||||
|
|
||||||
Future<CreateApiKeyResponse> createApiKey(
|
Future<CreateApiKeyResponse> createApiKey(String url, String username,
|
||||||
String url, String username, String password, String accessLevel, String comment) async {
|
String password, String accessLevel, String comment) async {
|
||||||
return await _userRepository.postApiKey(url, username, password, accessLevel, comment);
|
return await _userRepository.postApiKey(
|
||||||
|
url, username, password, accessLevel, comment);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<ApiKeysResponse> getApiKeys() async {
|
Future<ApiKeysResponse> getApiKeys() async {
|
||||||
|
@ -25,8 +26,9 @@ class UserService {
|
||||||
Future<void> checkAccessLevelIsAtLeastApiKey() async {
|
Future<void> checkAccessLevelIsAtLeastApiKey() async {
|
||||||
try {
|
try {
|
||||||
await _fileService.getHistory();
|
await _fileService.getHistory();
|
||||||
} catch (e) {
|
} on ServiceException catch (e) {
|
||||||
throw new ServiceException(code: ErrorCode.INVALID_API_KEY, message: e.message);
|
throw new ServiceException(
|
||||||
|
code: ErrorCode.INVALID_API_KEY, message: e.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ class FormatterUtil {
|
||||||
/// Format epoch timestamp
|
/// Format epoch timestamp
|
||||||
static String formatEpoch(num millis) {
|
static String formatEpoch(num millis) {
|
||||||
DateFormat dateFormat = DateFormat().add_yMEd().add_Hm();
|
DateFormat dateFormat = DateFormat().add_yMEd().add_Hm();
|
||||||
return dateFormat.format(DateTime.fromMillisecondsSinceEpoch(millis));
|
return dateFormat.format(DateTime.fromMillisecondsSinceEpoch(millis as int));
|
||||||
}
|
}
|
||||||
|
|
||||||
static String formatBytes(int bytes, int decimals) {
|
static String formatBytes(int bytes, int decimals) {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import 'package:package_info/package_info.dart';
|
import 'package:package_info_plus/package_info_plus.dart';
|
||||||
|
|
||||||
import '../../core/services/link_service.dart';
|
import '../../core/services/link_service.dart';
|
||||||
import '../../locator.dart';
|
import '../../locator.dart';
|
||||||
|
|
|
@ -12,13 +12,13 @@ class BaseModel extends ChangeNotifier {
|
||||||
|
|
||||||
bool _isDisposed = false;
|
bool _isDisposed = false;
|
||||||
|
|
||||||
Map<String, Object> _stateMap = {STATE_VIEW: ViewState.Idle, STATE_MESSAGE: null};
|
Map<String, Object?> _stateMap = {STATE_VIEW: ViewState.Idle, STATE_MESSAGE: null};
|
||||||
|
|
||||||
ViewState get state => _stateMap[STATE_VIEW];
|
ViewState? get state => _stateMap[STATE_VIEW] as ViewState?;
|
||||||
|
|
||||||
String get stateMessage => _stateMap[STATE_MESSAGE];
|
String? get stateMessage => _stateMap[STATE_MESSAGE] as String?;
|
||||||
|
|
||||||
void setStateValue(String key, Object stateValue) {
|
void setStateValue(String key, Object? stateValue) {
|
||||||
if (_stateMap.containsKey(key)) {
|
if (_stateMap.containsKey(key)) {
|
||||||
_stateMap.update(key, (value) => stateValue);
|
_stateMap.update(key, (value) => stateValue);
|
||||||
} else {
|
} else {
|
||||||
|
@ -44,7 +44,7 @@ class BaseModel extends ChangeNotifier {
|
||||||
setStateValue(STATE_VIEW, stateView);
|
setStateValue(STATE_VIEW, stateView);
|
||||||
}
|
}
|
||||||
|
|
||||||
void setStateMessage(String stateMessage) {
|
void setStateMessage(String? stateMessage) {
|
||||||
setStateValue(STATE_MESSAGE, stateMessage);
|
setStateValue(STATE_MESSAGE, stateMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -28,13 +28,14 @@ class HistoryModel extends BaseModel {
|
||||||
final LinkService _linkService = locator<LinkService>();
|
final LinkService _linkService = locator<LinkService>();
|
||||||
final DialogService _dialogService = locator<DialogService>();
|
final DialogService _dialogService = locator<DialogService>();
|
||||||
|
|
||||||
StreamSubscription _refreshTriggerSubscription;
|
late StreamSubscription _refreshTriggerSubscription;
|
||||||
|
|
||||||
List<UploadedPaste> pastes = [];
|
List<UploadedPaste> pastes = [];
|
||||||
String errorMessage;
|
String? errorMessage;
|
||||||
|
|
||||||
void init() {
|
void init() {
|
||||||
_refreshTriggerSubscription = _refreshService.refreshEventController.stream.listen((event) {
|
_refreshTriggerSubscription =
|
||||||
|
_refreshService.refreshEventController.stream.listen((event) {
|
||||||
if (event == RefreshEvent.RefreshHistory) {
|
if (event == RefreshEvent.RefreshHistory) {
|
||||||
_logger.d('History needs a refresh');
|
_logger.d('History needs a refresh');
|
||||||
getHistory();
|
getHistory();
|
||||||
|
@ -48,13 +49,14 @@ class HistoryModel extends BaseModel {
|
||||||
try {
|
try {
|
||||||
pastes.clear();
|
pastes.clear();
|
||||||
History _history = await _fileService.getHistory();
|
History _history = await _fileService.getHistory();
|
||||||
if (_history.items != null) {
|
if (_history.items.isNotEmpty) {
|
||||||
_history.items.forEach((key, value) {
|
_history.items.forEach((key, value) {
|
||||||
var millisecondsSinceEpoch = int.parse(value.date) * 1000;
|
var millisecondsSinceEpoch = int.parse(value.date) * 1000;
|
||||||
pastes.add(
|
pastes.add(
|
||||||
UploadedPaste(
|
UploadedPaste(
|
||||||
id: key,
|
id: key,
|
||||||
date: DateTime.fromMillisecondsSinceEpoch(millisecondsSinceEpoch),
|
date:
|
||||||
|
DateTime.fromMillisecondsSinceEpoch(millisecondsSinceEpoch),
|
||||||
filename: value.filename,
|
filename: value.filename,
|
||||||
filesize: int.parse(value.filesize),
|
filesize: int.parse(value.filesize),
|
||||||
hash: value.hash,
|
hash: value.hash,
|
||||||
|
@ -66,7 +68,7 @@ class HistoryModel extends BaseModel {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_history.multipasteItems != null) {
|
if (_history.multipasteItems.isNotEmpty) {
|
||||||
_history.multipasteItems.forEach((key, multiPaste) {
|
_history.multipasteItems.forEach((key, multiPaste) {
|
||||||
var millisecondsSinceEpoch = int.parse(multiPaste.date) * 1000;
|
var millisecondsSinceEpoch = int.parse(multiPaste.date) * 1000;
|
||||||
pastes.add(UploadedPaste(
|
pastes.add(UploadedPaste(
|
||||||
|
@ -77,7 +79,7 @@ class HistoryModel extends BaseModel {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pastes.sort((a, b) => a.date.compareTo(b.date));
|
pastes.sort((a, b) => a.date!.compareTo(b.date!));
|
||||||
errorMessage = null;
|
errorMessage = null;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e is RestServiceException) {
|
if (e is RestServiceException) {
|
||||||
|
@ -90,9 +92,11 @@ class HistoryModel extends BaseModel {
|
||||||
e.responseBody is RestError &&
|
e.responseBody is RestError &&
|
||||||
e.responseBody.message != null) {
|
e.responseBody.message != null) {
|
||||||
if (e.statusCode == HttpStatus.badRequest) {
|
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 {
|
} 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 {
|
} else {
|
||||||
errorMessage = translate('api.general_rest_error');
|
errorMessage = translate('api.general_rest_error');
|
||||||
|
@ -115,11 +119,12 @@ class HistoryModel extends BaseModel {
|
||||||
Future deletePaste(String id) async {
|
Future deletePaste(String id) async {
|
||||||
DialogResponse res = await _dialogService.showConfirmationDialog(
|
DialogResponse res = await _dialogService.showConfirmationDialog(
|
||||||
title: translate('history.delete_dialog.title'),
|
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'),
|
buttonTitleAccept: translate('history.delete_dialog.accept'),
|
||||||
buttonTitleDeny: translate('history.delete_dialog.deny'));
|
buttonTitleDeny: translate('history.delete_dialog.deny'));
|
||||||
|
|
||||||
if (!res.confirmed) {
|
if (!res.confirmed!) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -139,7 +144,8 @@ class HistoryModel extends BaseModel {
|
||||||
e.statusCode != HttpStatus.forbidden &&
|
e.statusCode != HttpStatus.forbidden &&
|
||||||
e.responseBody is RestError &&
|
e.responseBody is RestError &&
|
||||||
e.responseBody.message != null) {
|
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 {
|
} else {
|
||||||
errorMessage = translate('api.general_rest_error');
|
errorMessage = translate('api.general_rest_error');
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
@ -40,7 +41,7 @@ class LoginModel extends BaseModel {
|
||||||
final Logger _logger = getLogger();
|
final Logger _logger = getLogger();
|
||||||
|
|
||||||
bool useCredentialsLogin = true;
|
bool useCredentialsLogin = true;
|
||||||
String errorMessage;
|
String? errorMessage;
|
||||||
|
|
||||||
void toggleLoginMethod() {
|
void toggleLoginMethod() {
|
||||||
setStateView(ViewState.Busy);
|
setStateView(ViewState.Busy);
|
||||||
|
@ -53,7 +54,7 @@ class LoginModel extends BaseModel {
|
||||||
|
|
||||||
if (hasLastUrl) {
|
if (hasLastUrl) {
|
||||||
setStateView(ViewState.Busy);
|
setStateView(ViewState.Busy);
|
||||||
var s = await _storageService.retrieveLastUrl();
|
var s = await (_storageService.retrieveLastUrl() as FutureOr<String>);
|
||||||
|
|
||||||
if (s.isNotEmpty) {
|
if (s.isNotEmpty) {
|
||||||
_uriController = new TextEditingController(text: s);
|
_uriController = new TextEditingController(text: s);
|
||||||
|
@ -116,8 +117,20 @@ class LoginModel extends BaseModel {
|
||||||
try {
|
try {
|
||||||
if (useCredentialsLogin) {
|
if (useCredentialsLogin) {
|
||||||
CreateApiKeyResponse apiKeyResponse = await _userService.createApiKey(
|
CreateApiKeyResponse apiKeyResponse = await _userService.createApiKey(
|
||||||
url, username, password, 'apikey', 'fbmobile-${new DateTime.now().millisecondsSinceEpoch}');
|
url,
|
||||||
success = await _sessionService.login(url, apiKeyResponse.data['new_key']);
|
username,
|
||||||
|
password,
|
||||||
|
'apikey',
|
||||||
|
'fbmobile-${new DateTime.now().millisecondsSinceEpoch}');
|
||||||
|
|
||||||
|
var newKey = apiKeyResponse.data['new_key'];
|
||||||
|
if (newKey != null) {
|
||||||
|
success = await _sessionService.login(url, newKey);
|
||||||
|
} else {
|
||||||
|
throw new ServiceException(
|
||||||
|
code: ErrorCode.INVALID_API_KEY,
|
||||||
|
message: translate('login.errors.invalid_api_key'));
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
_sessionService.setApiConfig(url, apiKey);
|
_sessionService.setApiConfig(url, apiKey);
|
||||||
await _userService.checkAccessLevelIsAtLeastApiKey();
|
await _userService.checkAccessLevelIsAtLeastApiKey();
|
||||||
|
@ -128,13 +141,15 @@ class LoginModel extends BaseModel {
|
||||||
if (e is RestServiceException) {
|
if (e is RestServiceException) {
|
||||||
if (e.statusCode == HttpStatus.unauthorized) {
|
if (e.statusCode == HttpStatus.unauthorized) {
|
||||||
errorMessage = translate('login.errors.wrong_credentials');
|
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');
|
errorMessage = translate('login.errors.forbidden');
|
||||||
} else if (e.statusCode == HttpStatus.notFound) {
|
} else if (e.statusCode == HttpStatus.notFound) {
|
||||||
errorMessage = translate('api.incompatible_error_not_found');
|
errorMessage = translate('api.incompatible_error_not_found');
|
||||||
}
|
}
|
||||||
if (e.statusCode == HttpStatus.badRequest) {
|
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 {
|
} else {
|
||||||
errorMessage = translate('api.general_rest_error');
|
errorMessage = translate('api.general_rest_error');
|
||||||
}
|
}
|
||||||
|
@ -152,7 +167,7 @@ class LoginModel extends BaseModel {
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (errorMessage.isNotEmpty) {
|
if (errorMessage!.isNotEmpty) {
|
||||||
_sessionService.logout();
|
_sessionService.logout();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,25 +24,28 @@ class ProfileModel extends BaseModel {
|
||||||
final FileService _fileService = locator<FileService>();
|
final FileService _fileService = locator<FileService>();
|
||||||
final Logger _logger = getLogger();
|
final Logger _logger = getLogger();
|
||||||
|
|
||||||
String errorMessage;
|
String? errorMessage;
|
||||||
|
|
||||||
Future logout() async {
|
Future logout() async {
|
||||||
var dialogResult = await _dialogService.showConfirmationDialog(
|
var dialogResult = await _dialogService.showConfirmationDialog(
|
||||||
title: translate('logout.title'), description: translate('logout.confirm'));
|
title: translate('logout.title'),
|
||||||
|
description: translate('logout.confirm'));
|
||||||
|
|
||||||
if (dialogResult.confirmed) {
|
if (dialogResult.confirmed!) {
|
||||||
await _sessionService.logout();
|
await _sessionService.logout();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future revealApiKey(String apiKey) async {
|
Future revealApiKey(String? apiKey) async {
|
||||||
await _dialogService.showDialog(
|
await _dialogService.showDialog(
|
||||||
title: translate('profile.revealed_api_key.title'),
|
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 {
|
Future showConfig(String url) async {
|
||||||
Config config;
|
setStateView(ViewState.Busy);
|
||||||
|
Config? config;
|
||||||
try {
|
try {
|
||||||
config = await _fileService.getConfig(url);
|
config = await _fileService.getConfig(url);
|
||||||
errorMessage = null;
|
errorMessage = null;
|
||||||
|
@ -50,13 +53,15 @@ class ProfileModel extends BaseModel {
|
||||||
if (e is RestServiceException) {
|
if (e is RestServiceException) {
|
||||||
if (e.statusCode == HttpStatus.unauthorized) {
|
if (e.statusCode == HttpStatus.unauthorized) {
|
||||||
errorMessage = translate('login.errors.wrong_credentials');
|
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');
|
errorMessage = translate('login.errors.forbidden');
|
||||||
} else if (e.statusCode == HttpStatus.notFound) {
|
} else if (e.statusCode == HttpStatus.notFound) {
|
||||||
errorMessage = translate('api.incompatible_error_not_found');
|
errorMessage = translate('api.incompatible_error_not_found');
|
||||||
}
|
}
|
||||||
if (e.statusCode == HttpStatus.badRequest) {
|
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 {
|
} else {
|
||||||
errorMessage = translate('api.general_rest_error');
|
errorMessage = translate('api.general_rest_error');
|
||||||
}
|
}
|
||||||
|
@ -66,6 +71,7 @@ class ProfileModel extends BaseModel {
|
||||||
errorMessage = translate('api.socket_timeout');
|
errorMessage = translate('api.socket_timeout');
|
||||||
} else {
|
} else {
|
||||||
errorMessage = translate('app.unknown_error');
|
errorMessage = translate('app.unknown_error');
|
||||||
|
setStateView(ViewState.Idle);
|
||||||
_sessionService.logout();
|
_sessionService.logout();
|
||||||
setStateView(ViewState.Idle);
|
setStateView(ViewState.Idle);
|
||||||
_logger.e('An unknown error occurred', e);
|
_logger.e('An unknown error occurred', e);
|
||||||
|
@ -73,19 +79,24 @@ class ProfileModel extends BaseModel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setStateView(ViewState.Idle);
|
||||||
|
|
||||||
if (config != null && errorMessage == null) {
|
if (config != null && errorMessage == null) {
|
||||||
await _dialogService.showDialog(
|
await _dialogService.showDialog(
|
||||||
title: translate('profile.shown_config.title'),
|
title: translate('profile.shown_config.title'),
|
||||||
description: translate('profile.shown_config.description', args: {
|
description: translate('profile.shown_config.description', args: {
|
||||||
'uploadMaxSize': FormatterUtil.formatBytes(config.uploadMaxSize, 2),
|
'uploadMaxSize':
|
||||||
|
FormatterUtil.formatBytes(config.uploadMaxSize as int, 2),
|
||||||
'maxFilesPerRequest': config.maxFilesPerRequest,
|
'maxFilesPerRequest': config.maxFilesPerRequest,
|
||||||
'maxInputVars': config.maxInputVars,
|
'maxInputVars': config.maxInputVars,
|
||||||
'requestMaxSize': FormatterUtil.formatBytes(config.requestMaxSize, 2)
|
'requestMaxSize':
|
||||||
|
FormatterUtil.formatBytes(config.requestMaxSize as int, 2)
|
||||||
}));
|
}));
|
||||||
} else {
|
} else {
|
||||||
await _dialogService.showDialog(
|
await _dialogService.showDialog(
|
||||||
title: translate('profile.shown_config.error.title'),
|
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}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -37,14 +37,14 @@ class UploadModel extends BaseModel {
|
||||||
TextEditingController _pasteTextController = TextEditingController();
|
TextEditingController _pasteTextController = TextEditingController();
|
||||||
bool pasteTextTouched = false;
|
bool pasteTextTouched = false;
|
||||||
|
|
||||||
StreamSubscription _intentDataStreamSubscription;
|
late StreamSubscription _intentDataStreamSubscription;
|
||||||
|
|
||||||
bool createMulti = false;
|
bool createMulti = false;
|
||||||
String fileName;
|
String? fileName;
|
||||||
List<PlatformFile> paths;
|
List<PlatformFile>? paths;
|
||||||
String _extension;
|
String? _extension;
|
||||||
bool loadingPath = false;
|
bool loadingPath = false;
|
||||||
String errorMessage;
|
String? errorMessage;
|
||||||
|
|
||||||
TextEditingController get pasteTextController => _pasteTextController;
|
TextEditingController get pasteTextController => _pasteTextController;
|
||||||
|
|
||||||
|
@ -55,8 +55,9 @@ class UploadModel extends BaseModel {
|
||||||
});
|
});
|
||||||
|
|
||||||
// For sharing images coming from outside the app while the app is in the memory
|
// For sharing images coming from outside the app while the app is in the memory
|
||||||
_intentDataStreamSubscription = ReceiveSharingIntent.getMediaStream().listen((List<SharedMediaFile> value) {
|
_intentDataStreamSubscription = ReceiveSharingIntent.getMediaStream()
|
||||||
if (value != null && value.length > 0) {
|
.listen((List<SharedMediaFile> value) {
|
||||||
|
if (value.length > 0) {
|
||||||
setStateView(ViewState.Busy);
|
setStateView(ViewState.Busy);
|
||||||
paths = value.map((sharedFile) {
|
paths = value.map((sharedFile) {
|
||||||
return PlatformFile.fromMap({
|
return PlatformFile.fromMap({
|
||||||
|
@ -67,7 +68,7 @@ class UploadModel extends BaseModel {
|
||||||
});
|
});
|
||||||
}).toList();
|
}).toList();
|
||||||
setStateView(ViewState.Idle);
|
setStateView(ViewState.Idle);
|
||||||
if (paths.isNotEmpty && paths.length > 0) {
|
if (paths!.isNotEmpty && paths!.length > 0) {
|
||||||
_swipeService.addEvent(SwipeEvent.Start);
|
_swipeService.addEvent(SwipeEvent.Start);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -77,7 +78,7 @@ class UploadModel extends BaseModel {
|
||||||
|
|
||||||
// For sharing images coming from outside the app while the app is closed
|
// For sharing images coming from outside the app while the app is closed
|
||||||
ReceiveSharingIntent.getInitialMedia().then((List<SharedMediaFile> value) {
|
ReceiveSharingIntent.getInitialMedia().then((List<SharedMediaFile> value) {
|
||||||
if (value != null && value.length > 0) {
|
if (value.length > 0) {
|
||||||
setStateView(ViewState.Busy);
|
setStateView(ViewState.Busy);
|
||||||
paths = value.map((sharedFile) {
|
paths = value.map((sharedFile) {
|
||||||
return PlatformFile.fromMap({
|
return PlatformFile.fromMap({
|
||||||
|
@ -88,7 +89,7 @@ class UploadModel extends BaseModel {
|
||||||
});
|
});
|
||||||
}).toList();
|
}).toList();
|
||||||
setStateView(ViewState.Idle);
|
setStateView(ViewState.Idle);
|
||||||
if (paths.isNotEmpty && paths.length > 0) {
|
if (paths!.isNotEmpty && paths!.length > 0) {
|
||||||
_swipeService.addEvent(SwipeEvent.Start);
|
_swipeService.addEvent(SwipeEvent.Start);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -97,8 +98,9 @@ class UploadModel extends BaseModel {
|
||||||
});
|
});
|
||||||
|
|
||||||
// For sharing or opening urls/text coming from outside the app while the app is in the memory
|
// For sharing or opening urls/text coming from outside the app while the app is in the memory
|
||||||
_intentDataStreamSubscription = ReceiveSharingIntent.getTextStream().listen((String value) {
|
_intentDataStreamSubscription =
|
||||||
if (value != null && value.isNotEmpty) {
|
ReceiveSharingIntent.getTextStream().listen((String value) {
|
||||||
|
if (value.isNotEmpty) {
|
||||||
setStateView(ViewState.Busy);
|
setStateView(ViewState.Busy);
|
||||||
pasteTextController.text = value;
|
pasteTextController.text = value;
|
||||||
setStateView(ViewState.Idle);
|
setStateView(ViewState.Idle);
|
||||||
|
@ -109,12 +111,12 @@ class UploadModel extends BaseModel {
|
||||||
});
|
});
|
||||||
|
|
||||||
// For sharing or opening urls/text coming from outside the app while the app is closed
|
// For sharing or opening urls/text coming from outside the app while the app is closed
|
||||||
ReceiveSharingIntent.getInitialText().then((String value) {
|
ReceiveSharingIntent.getInitialText().then((String? value) {
|
||||||
if (value != null && value.isNotEmpty) {
|
if (value != null && value.isNotEmpty) {
|
||||||
setStateView(ViewState.Busy);
|
setStateView(ViewState.Busy);
|
||||||
pasteTextController.text = value;
|
pasteTextController.text = value;
|
||||||
setStateView(ViewState.Idle);
|
setStateView(ViewState.Idle);
|
||||||
if (paths.isNotEmpty && paths.length > 0) {
|
if (paths!.isNotEmpty && paths!.length > 0) {
|
||||||
_swipeService.addEvent(SwipeEvent.Start);
|
_swipeService.addEvent(SwipeEvent.Start);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -132,7 +134,7 @@ class UploadModel extends BaseModel {
|
||||||
_swipeService.addEvent(SwipeEvent.Start);
|
_swipeService.addEvent(SwipeEvent.Start);
|
||||||
}
|
}
|
||||||
|
|
||||||
String generatePasteLinks(Map<String, bool> uploads, String url) {
|
String? generatePasteLinks(Map<String, bool>? uploads, String url) {
|
||||||
if (uploads != null && uploads.length > 0) {
|
if (uploads != null && uploads.length > 0) {
|
||||||
var links = '';
|
var links = '';
|
||||||
|
|
||||||
|
@ -165,7 +167,9 @@ class UploadModel extends BaseModel {
|
||||||
allowMultiple: true,
|
allowMultiple: true,
|
||||||
withData: false,
|
withData: false,
|
||||||
withReadStream: true,
|
withReadStream: true,
|
||||||
allowedExtensions: (_extension?.isNotEmpty ?? false) ? _extension?.replaceAll(' ', '')?.split(',') : null,
|
allowedExtensions: (_extension?.isNotEmpty ?? false)
|
||||||
|
? _extension?.replaceAll(' ', '').split(',')
|
||||||
|
: null,
|
||||||
))
|
))
|
||||||
?.files;
|
?.files;
|
||||||
} on PlatformException catch (e) {
|
} on PlatformException catch (e) {
|
||||||
|
@ -175,7 +179,7 @@ class UploadModel extends BaseModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
loadingPath = false;
|
loadingPath = false;
|
||||||
fileName = paths != null ? paths.map((e) => e.name).toString() : '...';
|
fileName = paths != null ? paths!.map((e) => e.name).toString() : '...';
|
||||||
|
|
||||||
setStateMessage(null);
|
setStateMessage(null);
|
||||||
setStateView(ViewState.Idle);
|
setStateView(ViewState.Idle);
|
||||||
|
@ -190,31 +194,35 @@ class UploadModel extends BaseModel {
|
||||||
setStateView(ViewState.Idle);
|
setStateView(ViewState.Idle);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Map<String, bool>> upload() async {
|
Future<Map<String, bool>?> upload() async {
|
||||||
setStateView(ViewState.Busy);
|
setStateView(ViewState.Busy);
|
||||||
setStateMessage(translate('upload.uploading_now'));
|
setStateMessage(translate('upload.uploading_now'));
|
||||||
|
|
||||||
Map<String, bool> uploadedPasteIds = new Map();
|
Map<String, bool> uploadedPasteIds = new Map();
|
||||||
try {
|
try {
|
||||||
List<File> files;
|
List<File>? files;
|
||||||
Map<String, String> additionalFiles;
|
Map<String, String>? additionalFiles;
|
||||||
|
|
||||||
if (pasteTextController.text != null && pasteTextController.text.isNotEmpty) {
|
if (pasteTextController.text.isNotEmpty) {
|
||||||
additionalFiles = Map.from(
|
additionalFiles = Map.from({
|
||||||
{'paste-${(new DateTime.now().millisecondsSinceEpoch / 1000).round()}.txt': pasteTextController.text});
|
'paste-${(new DateTime.now().millisecondsSinceEpoch / 1000).round()}.txt':
|
||||||
|
pasteTextController.text
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (paths != null && paths.length > 0) {
|
if (paths != null && paths!.length > 0) {
|
||||||
files = paths.map((e) => new File(e.path)).toList();
|
files = paths!.map((e) => new File(e.path!)).toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
UploadedResponse response = await _fileService.uploadPaste(files, additionalFiles);
|
UploadedResponse response =
|
||||||
|
await _fileService.uploadPaste(files, additionalFiles);
|
||||||
response.data.ids.forEach((element) {
|
response.data.ids.forEach((element) {
|
||||||
uploadedPasteIds.putIfAbsent(element, () => false);
|
uploadedPasteIds.putIfAbsent(element, () => false);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (createMulti && response.data.ids.length > 1) {
|
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);
|
uploadedPasteIds.putIfAbsent(multiResponse.data.urlId, () => true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -234,9 +242,11 @@ class UploadModel extends BaseModel {
|
||||||
e.responseBody is RestError &&
|
e.responseBody is RestError &&
|
||||||
e.responseBody.message != null) {
|
e.responseBody.message != null) {
|
||||||
if (e.statusCode == HttpStatus.badRequest) {
|
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 {
|
} 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 {
|
} else {
|
||||||
errorMessage = translate('api.general_rest_error');
|
errorMessage = translate('api.general_rest_error');
|
||||||
|
|
|
@ -7,8 +7,8 @@ import '../../core/viewmodels/base_model.dart';
|
||||||
import '../../locator.dart';
|
import '../../locator.dart';
|
||||||
|
|
||||||
class BaseView<T extends BaseModel> extends StatefulWidget {
|
class BaseView<T extends BaseModel> extends StatefulWidget {
|
||||||
final Widget Function(BuildContext context, T model, Widget child) builder;
|
final Widget Function(BuildContext context, T model, Widget? child)? builder;
|
||||||
final Function(T) onModelReady;
|
final Function(T)? onModelReady;
|
||||||
|
|
||||||
BaseView({this.builder, this.onModelReady});
|
BaseView({this.builder, this.onModelReady});
|
||||||
|
|
||||||
|
@ -24,14 +24,16 @@ class _BaseViewState<T extends BaseModel> extends State<BaseView<T>> {
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
if (widget.onModelReady != null) {
|
if (widget.onModelReady != null) {
|
||||||
widget.onModelReady(model);
|
widget.onModelReady!(model);
|
||||||
}
|
}
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
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
|
@override
|
||||||
|
|
|
@ -41,7 +41,9 @@ class HistoryView extends StatelessWidget {
|
||||||
: (model.errorMessage == null
|
: (model.errorMessage == null
|
||||||
? Container(
|
? Container(
|
||||||
padding: EdgeInsets.all(0),
|
padding: EdgeInsets.all(0),
|
||||||
child: RefreshIndicator(onRefresh: () => model.getHistory(), child: _renderItems(model, url, context)))
|
child: RefreshIndicator(
|
||||||
|
onRefresh: () async => await model.getHistory(),
|
||||||
|
child: _renderItems(model, url, context)))
|
||||||
: Container(
|
: Container(
|
||||||
padding: EdgeInsets.all(25),
|
padding: EdgeInsets.all(25),
|
||||||
child: CenteredErrorRow(
|
child: CenteredErrorRow(
|
||||||
|
@ -61,7 +63,8 @@ class HistoryView extends StatelessWidget {
|
||||||
var openInBrowserButton = _renderOpenInBrowser(model, fullPasteUrl);
|
var openInBrowserButton = _renderOpenInBrowser(model, fullPasteUrl);
|
||||||
|
|
||||||
var dateWidget = ListTile(
|
var dateWidget = ListTile(
|
||||||
title: Text(FormatterUtil.formatEpoch(paste.date.millisecondsSinceEpoch)),
|
title: Text(
|
||||||
|
FormatterUtil.formatEpoch(paste.date!.millisecondsSinceEpoch)),
|
||||||
subtitle: Text(translate('history.date')),
|
subtitle: Text(translate('history.date')),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -73,7 +76,8 @@ class HistoryView extends StatelessWidget {
|
||||||
var copyWidget = ListTile(
|
var copyWidget = ListTile(
|
||||||
title: Text(translate('history.copy_link.description')),
|
title: Text(translate('history.copy_link.description')),
|
||||||
trailing: IconButton(
|
trailing: IconButton(
|
||||||
icon: Icon(Icons.copy, color: blueColor, textDirection: TextDirection.ltr),
|
icon: Icon(Icons.copy,
|
||||||
|
color: blueColor, textDirection: TextDirection.ltr),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
FlutterClipboard.copy(fullPasteUrl).then((value) {
|
FlutterClipboard.copy(fullPasteUrl).then((value) {
|
||||||
final snackBar = SnackBar(
|
final snackBar = SnackBar(
|
||||||
|
@ -96,16 +100,16 @@ class HistoryView extends StatelessWidget {
|
||||||
trailing: IconButton(
|
trailing: IconButton(
|
||||||
icon: Icon(Icons.delete, color: redColor),
|
icon: Icon(Icons.delete, color: redColor),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
return model.deletePaste(paste.id);
|
model.deletePaste(paste.id);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
if (!paste.isMulti) {
|
if (!paste.isMulti!) {
|
||||||
var titleWidget = ListTile(
|
var titleWidget = ListTile(
|
||||||
title: Text(paste.filename ?? paste.id),
|
title: Text(paste.filename ?? paste.id),
|
||||||
subtitle: Text(translate('history.filename')),
|
subtitle: Text(translate('history.filename')),
|
||||||
);
|
);
|
||||||
var fileSizeWidget = ListTile(
|
var fileSizeWidget = ListTile(
|
||||||
title: Text(FormatterUtil.formatBytes(paste.filesize, 2)),
|
title: Text(FormatterUtil.formatBytes(paste.filesize as int, 2)),
|
||||||
subtitle: Text(translate('history.filesize')),
|
subtitle: Text(translate('history.filesize')),
|
||||||
);
|
);
|
||||||
var idWidget = ListTile(
|
var idWidget = ListTile(
|
||||||
|
@ -113,7 +117,7 @@ class HistoryView extends StatelessWidget {
|
||||||
subtitle: Text(translate('history.id')),
|
subtitle: Text(translate('history.id')),
|
||||||
);
|
);
|
||||||
var mimeTypeWidget = ListTile(
|
var mimeTypeWidget = ListTile(
|
||||||
title: Text(paste.mimetype),
|
title: Text(paste.mimetype!),
|
||||||
subtitle: Text(translate('history.mimetype')),
|
subtitle: Text(translate('history.mimetype')),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -122,9 +126,9 @@ class HistoryView extends StatelessWidget {
|
||||||
widgets.add(fileSizeWidget);
|
widgets.add(fileSizeWidget);
|
||||||
widgets.add(mimeTypeWidget);
|
widgets.add(mimeTypeWidget);
|
||||||
} else {
|
} else {
|
||||||
paste.items.forEach((element) {
|
paste.items!.forEach((element) {
|
||||||
widgets.add(ListTile(
|
widgets.add(ListTile(
|
||||||
title: Text(element),
|
title: Text(element!),
|
||||||
subtitle: Text(translate('history.multipaste_element')),
|
subtitle: Text(translate('history.multipaste_element')),
|
||||||
trailing: _renderOpenInBrowser(model, '$url/$element'),
|
trailing: _renderOpenInBrowser(model, '$url/$element'),
|
||||||
));
|
));
|
||||||
|
@ -164,12 +168,14 @@ class HistoryView extends StatelessWidget {
|
||||||
trailing: Wrap(children: [
|
trailing: Wrap(children: [
|
||||||
openInBrowserButton,
|
openInBrowserButton,
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: Icon(Icons.share, color: blueColor, textDirection: TextDirection.ltr),
|
icon: Icon(Icons.share,
|
||||||
onPressed: () {
|
color: blueColor, textDirection: TextDirection.ltr),
|
||||||
return Share.share(fullPasteUrl);
|
onPressed: () async {
|
||||||
|
await Share.share(fullPasteUrl);
|
||||||
})
|
})
|
||||||
]),
|
]),
|
||||||
subtitle: Text(!paste.isMulti ? paste.filename : '', style: TextStyle(fontStyle: FontStyle.italic)),
|
subtitle: Text(!paste.isMulti! ? paste.filename! : '',
|
||||||
|
style: TextStyle(fontStyle: FontStyle.italic)),
|
||||||
),
|
),
|
||||||
));
|
));
|
||||||
});
|
});
|
||||||
|
@ -190,7 +196,8 @@ class HistoryView extends StatelessWidget {
|
||||||
|
|
||||||
Widget _renderOpenInBrowser(HistoryModel model, String url) {
|
Widget _renderOpenInBrowser(HistoryModel model, String url) {
|
||||||
return IconButton(
|
return IconButton(
|
||||||
icon: Icon(Icons.open_in_new, color: blueColor, textDirection: TextDirection.ltr),
|
icon: Icon(Icons.open_in_new,
|
||||||
|
color: blueColor, textDirection: TextDirection.ltr),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
return model.openLink(url);
|
return model.openLink(url);
|
||||||
});
|
});
|
||||||
|
|
|
@ -59,13 +59,18 @@ class LoginView extends StatelessWidget {
|
||||||
child: Icon(Icons.help, color: buttonBackgroundColor),
|
child: Icon(Icons.help, color: buttonBackgroundColor),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
_dialogService.showDialog(
|
_dialogService.showDialog(
|
||||||
title: translate('login.compatibility_dialog.title'),
|
title: translate(
|
||||||
description: translate('login.compatibility_dialog.body'));
|
'login.compatibility_dialog.title'),
|
||||||
|
description: translate(
|
||||||
|
'login.compatibility_dialog.body'));
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
InkWell(
|
InkWell(
|
||||||
child:
|
child: Icon(
|
||||||
Icon(model.useCredentialsLogin ? Icons.person_outline : Icons.vpn_key, color: blueColor),
|
model.useCredentialsLogin
|
||||||
|
? Icons.person_outline
|
||||||
|
: Icons.vpn_key,
|
||||||
|
color: blueColor),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
model.toggleLoginMethod();
|
model.toggleLoginMethod();
|
||||||
},
|
},
|
||||||
|
@ -85,11 +90,13 @@ class LoginView extends StatelessWidget {
|
||||||
apiKeyController: model.apiKeyController),
|
apiKeyController: model.apiKeyController),
|
||||||
UIHelper.verticalSpaceMedium(),
|
UIHelper.verticalSpaceMedium(),
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
child: Text(translate('login.button'), style: TextStyle(color: buttonForegroundColor)),
|
child: Text(translate('login.button'),
|
||||||
|
style: TextStyle(color: buttonForegroundColor)),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
var loginSuccess = await model.login();
|
var loginSuccess = await model.login();
|
||||||
if (loginSuccess) {
|
if (loginSuccess) {
|
||||||
_navigationService.navigateAndReplaceTo(HomeView.routeName);
|
_navigationService
|
||||||
|
.navigateAndReplaceTo(HomeView.routeName);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
|
@ -59,8 +59,8 @@ class ProfileView extends StatelessWidget {
|
||||||
translate('profile.show_config'),
|
translate('profile.show_config'),
|
||||||
style: TextStyle(color: buttonForegroundColor),
|
style: TextStyle(color: buttonForegroundColor),
|
||||||
),
|
),
|
||||||
onPressed: () {
|
onPressed: () async {
|
||||||
return model.showConfig(url);
|
await model.showConfig(url);
|
||||||
})),
|
})),
|
||||||
UIHelper.verticalSpaceMedium(),
|
UIHelper.verticalSpaceMedium(),
|
||||||
Padding(
|
Padding(
|
||||||
|
@ -72,7 +72,7 @@ class ProfileView extends StatelessWidget {
|
||||||
style: TextStyle(color: buttonForegroundColor),
|
style: TextStyle(color: buttonForegroundColor),
|
||||||
),
|
),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
return model.revealApiKey(apiKey);
|
model.revealApiKey(apiKey);
|
||||||
})),
|
})),
|
||||||
UIHelper.verticalSpaceMedium(),
|
UIHelper.verticalSpaceMedium(),
|
||||||
Padding(
|
Padding(
|
||||||
|
@ -83,8 +83,8 @@ class ProfileView extends StatelessWidget {
|
||||||
translate('profile.logout'),
|
translate('profile.logout'),
|
||||||
style: TextStyle(color: buttonForegroundColor),
|
style: TextStyle(color: buttonForegroundColor),
|
||||||
),
|
),
|
||||||
onPressed: () {
|
onPressed: () async {
|
||||||
return model.logout();
|
await model.logout();
|
||||||
})),
|
})),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
|
@ -22,7 +22,7 @@ class StartUpView extends StatelessWidget {
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
CircularProgressIndicator(),
|
CircularProgressIndicator(),
|
||||||
(model.stateMessage.isNotEmpty ? Text(model.stateMessage) : Container())
|
(model.stateMessage!.isNotEmpty ? Text(model.stateMessage!) : Container())
|
||||||
]))
|
]))
|
||||||
: Container()));
|
: Container()));
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ class AnonymousTabBarView extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class AnonymousTabBarState extends State<AnonymousTabBarView> with SingleTickerProviderStateMixin {
|
class AnonymousTabBarState extends State<AnonymousTabBarView> with SingleTickerProviderStateMixin {
|
||||||
TabController _tabController;
|
TabController? _tabController;
|
||||||
int _currentTabIndex = 0;
|
int _currentTabIndex = 0;
|
||||||
|
|
||||||
List<Widget> _realPages = [LoginView()];
|
List<Widget> _realPages = [LoginView()];
|
||||||
|
@ -33,7 +33,7 @@ class AnonymousTabBarState extends State<AnonymousTabBarView> with SingleTickerP
|
||||||
super.initState();
|
super.initState();
|
||||||
_tabController = TabController(length: _realPages.length, vsync: this)
|
_tabController = TabController(length: _realPages.length, vsync: this)
|
||||||
..addListener(() {
|
..addListener(() {
|
||||||
int selectedIndex = _tabController.index;
|
int selectedIndex = _tabController!.index;
|
||||||
if (_currentTabIndex != selectedIndex) {
|
if (_currentTabIndex != selectedIndex) {
|
||||||
if (!_hasInit[selectedIndex]) {
|
if (!_hasInit[selectedIndex]) {
|
||||||
_tabPages[selectedIndex] = _realPages[selectedIndex];
|
_tabPages[selectedIndex] = _realPages[selectedIndex];
|
||||||
|
@ -46,7 +46,7 @@ class AnonymousTabBarState extends State<AnonymousTabBarView> with SingleTickerP
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_tabController.dispose();
|
_tabController!.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,12 +20,13 @@ class AuthenticatedTabBarView extends StatefulWidget {
|
||||||
AuthenticatedTabBarState createState() => AuthenticatedTabBarState();
|
AuthenticatedTabBarState createState() => AuthenticatedTabBarState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class AuthenticatedTabBarState extends State<AuthenticatedTabBarView> with SingleTickerProviderStateMixin {
|
class AuthenticatedTabBarState extends State<AuthenticatedTabBarView>
|
||||||
|
with SingleTickerProviderStateMixin {
|
||||||
final Logger _logger = getLogger();
|
final Logger _logger = getLogger();
|
||||||
final SwipeService _swipeService = locator<SwipeService>();
|
final SwipeService _swipeService = locator<SwipeService>();
|
||||||
|
|
||||||
StreamSubscription _swipeEventSubscription;
|
late StreamSubscription _swipeEventSubscription;
|
||||||
TabController _tabController;
|
TabController? _tabController;
|
||||||
int _currentTabIndex = 0;
|
int _currentTabIndex = 0;
|
||||||
|
|
||||||
List<Widget> _realPages = [UploadView(), HistoryView(), ProfileView()];
|
List<Widget> _realPages = [UploadView(), HistoryView(), ProfileView()];
|
||||||
|
@ -41,7 +42,7 @@ class AuthenticatedTabBarState extends State<AuthenticatedTabBarView> with Singl
|
||||||
super.initState();
|
super.initState();
|
||||||
_tabController = TabController(length: _realPages.length, vsync: this)
|
_tabController = TabController(length: _realPages.length, vsync: this)
|
||||||
..addListener(() {
|
..addListener(() {
|
||||||
int selectedIndex = _tabController.index;
|
int selectedIndex = _tabController!.index;
|
||||||
if (_currentTabIndex != selectedIndex) {
|
if (_currentTabIndex != selectedIndex) {
|
||||||
if (!_hasInit[selectedIndex]) {
|
if (!_hasInit[selectedIndex]) {
|
||||||
_tabPages[selectedIndex] = _realPages[selectedIndex];
|
_tabPages[selectedIndex] = _realPages[selectedIndex];
|
||||||
|
@ -51,7 +52,8 @@ class AuthenticatedTabBarState extends State<AuthenticatedTabBarView> with Singl
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
_swipeEventSubscription = _swipeService.swipeEventController.stream.listen((SwipeEvent event) {
|
_swipeEventSubscription =
|
||||||
|
_swipeService.swipeEventController.stream.listen((SwipeEvent event) {
|
||||||
_logger.d('Received a swipe event for the authenticated tab bar: $event');
|
_logger.d('Received a swipe event for the authenticated tab bar: $event');
|
||||||
|
|
||||||
int targetIndex = _currentTabIndex;
|
int targetIndex = _currentTabIndex;
|
||||||
|
@ -71,14 +73,15 @@ class AuthenticatedTabBarState extends State<AuthenticatedTabBarView> with Singl
|
||||||
targetIndex = _tabPages.length - 1;
|
targetIndex = _tabPages.length - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
_logger.d("Changing to tab '$targetIndex' because of a swipe event '$event'");
|
_logger.d(
|
||||||
_tabController.animateTo(targetIndex);
|
"Changing to tab '$targetIndex' because of a swipe event '$event'");
|
||||||
|
_tabController!.animateTo(targetIndex);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_tabController.dispose();
|
_tabController!.dispose();
|
||||||
_swipeEventSubscription.cancel();
|
_swipeEventSubscription.cancel();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
@ -89,9 +92,12 @@ class AuthenticatedTabBarState extends State<AuthenticatedTabBarView> with Singl
|
||||||
double yourWidth = width / 3;
|
double yourWidth = width / 3;
|
||||||
double yourHeight = 55;
|
double yourHeight = 55;
|
||||||
|
|
||||||
Color colorTabItem0 = _currentTabIndex == 0 ? blueColor : primaryAccentColor;
|
Color colorTabItem0 =
|
||||||
Color colorTabItem1 = _currentTabIndex == 1 ? blueColor : primaryAccentColor;
|
_currentTabIndex == 0 ? blueColor : primaryAccentColor;
|
||||||
Color colorTabItem2 = _currentTabIndex == 2 ? blueColor : primaryAccentColor;
|
Color colorTabItem1 =
|
||||||
|
_currentTabIndex == 1 ? blueColor : primaryAccentColor;
|
||||||
|
Color colorTabItem2 =
|
||||||
|
_currentTabIndex == 2 ? blueColor : primaryAccentColor;
|
||||||
|
|
||||||
List<Widget> _tabsButton = [
|
List<Widget> _tabsButton = [
|
||||||
Container(
|
Container(
|
||||||
|
@ -100,10 +106,13 @@ class AuthenticatedTabBarState extends State<AuthenticatedTabBarView> with Singl
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
child: Tab(
|
child: Tab(
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
_currentTabIndex == 0 ? Icons.upload_outlined : Icons.upload_rounded,
|
_currentTabIndex == 0
|
||||||
|
? Icons.upload_outlined
|
||||||
|
: Icons.upload_rounded,
|
||||||
color: colorTabItem0,
|
color: colorTabItem0,
|
||||||
),
|
),
|
||||||
child: Text(translate('tabs.upload'), style: TextStyle(color: colorTabItem0)),
|
child: Text(translate('tabs.upload'),
|
||||||
|
style: TextStyle(color: colorTabItem0)),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Container(
|
Container(
|
||||||
|
@ -112,10 +121,13 @@ class AuthenticatedTabBarState extends State<AuthenticatedTabBarView> with Singl
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
child: Tab(
|
child: Tab(
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
_currentTabIndex == 1 ? Icons.history_outlined : Icons.history_rounded,
|
_currentTabIndex == 1
|
||||||
|
? Icons.history_outlined
|
||||||
|
: Icons.history_rounded,
|
||||||
color: colorTabItem1,
|
color: colorTabItem1,
|
||||||
),
|
),
|
||||||
child: Text(translate('tabs.history'), style: TextStyle(color: colorTabItem1)),
|
child: Text(translate('tabs.history'),
|
||||||
|
style: TextStyle(color: colorTabItem1)),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Container(
|
Container(
|
||||||
|
@ -124,10 +136,13 @@ class AuthenticatedTabBarState extends State<AuthenticatedTabBarView> with Singl
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
child: Tab(
|
child: Tab(
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
_currentTabIndex == 2 ? Icons.person_outlined : Icons.person_rounded,
|
_currentTabIndex == 2
|
||||||
|
? Icons.person_outlined
|
||||||
|
: Icons.person_rounded,
|
||||||
color: colorTabItem2,
|
color: colorTabItem2,
|
||||||
),
|
),
|
||||||
child: Text(translate('tabs.profile'), style: TextStyle(color: colorTabItem2)),
|
child: Text(translate('tabs.profile'),
|
||||||
|
style: TextStyle(color: colorTabItem2)),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|
|
@ -8,8 +8,9 @@ import 'tabbar_authenticated.dart';
|
||||||
class TabBarContainerView extends StatelessWidget {
|
class TabBarContainerView extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
Session currentSession = Provider.of<Session>(context);
|
Session? currentSession = Provider.of<Session?>(context);
|
||||||
bool isAuthenticated = currentSession != null && currentSession.apiKey.isNotEmpty;
|
bool isAuthenticated =
|
||||||
|
currentSession != null ? currentSession.apiKey.isNotEmpty : false;
|
||||||
|
|
||||||
if (isAuthenticated) {
|
if (isAuthenticated) {
|
||||||
return AuthenticatedTabBarView();
|
return AuthenticatedTabBarView();
|
||||||
|
|
|
@ -26,7 +26,8 @@ class UploadView extends StatelessWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _isUploadButtonEnabled(UploadModel model) {
|
bool _isUploadButtonEnabled(UploadModel model) {
|
||||||
return model.pasteTextTouched || (model.paths != null && model.paths.length > 0);
|
return model.pasteTextTouched ||
|
||||||
|
(model.paths != null && model.paths!.length > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _render(UploadModel model, BuildContext context) {
|
Widget _render(UploadModel model, BuildContext context) {
|
||||||
|
@ -39,7 +40,9 @@ class UploadView extends StatelessWidget {
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
CircularProgressIndicator(),
|
CircularProgressIndicator(),
|
||||||
(model.stateMessage != null && model.stateMessage.isNotEmpty ? Text(model.stateMessage) : Container())
|
(model.stateMessage != null && model.stateMessage!.isNotEmpty
|
||||||
|
? Text(model.stateMessage!)
|
||||||
|
: Container())
|
||||||
]))
|
]))
|
||||||
: ListView(children: <Widget>[
|
: ListView(children: <Widget>[
|
||||||
Padding(
|
Padding(
|
||||||
|
@ -58,12 +61,15 @@ class UploadView extends StatelessWidget {
|
||||||
color: buttonBackgroundColor,
|
color: buttonBackgroundColor,
|
||||||
),
|
),
|
||||||
suffixIcon: IconButton(
|
suffixIcon: IconButton(
|
||||||
onPressed: () => model.pasteTextController.clear(),
|
onPressed: () =>
|
||||||
|
model.pasteTextController.clear(),
|
||||||
icon: Icon(Icons.clear),
|
icon: Icon(Icons.clear),
|
||||||
),
|
),
|
||||||
hintText: translate('upload.text_to_be_pasted'),
|
hintText: translate('upload.text_to_be_pasted'),
|
||||||
contentPadding: EdgeInsets.fromLTRB(20.0, 10.0, 20.0, 10.0),
|
contentPadding:
|
||||||
border: OutlineInputBorder(borderRadius: BorderRadius.circular(32.0)),
|
EdgeInsets.fromLTRB(20.0, 10.0, 20.0, 10.0),
|
||||||
|
border: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(32.0)),
|
||||||
),
|
),
|
||||||
controller: model.pasteTextController)),
|
controller: model.pasteTextController)),
|
||||||
Padding(
|
Padding(
|
||||||
|
@ -79,20 +85,24 @@ class UploadView extends StatelessWidget {
|
||||||
crossAxisAlignment: CrossAxisAlignment.end,
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
children: [
|
children: [
|
||||||
ElevatedButton.icon(
|
ElevatedButton.icon(
|
||||||
icon: Icon(Icons.file_copy_sharp, color: blueColor),
|
icon: Icon(Icons.file_copy_sharp,
|
||||||
|
color: blueColor),
|
||||||
onPressed: () => model.openFileExplorer(),
|
onPressed: () => model.openFileExplorer(),
|
||||||
label: Text(
|
label: Text(
|
||||||
translate('upload.open_file_explorer'),
|
translate('upload.open_file_explorer'),
|
||||||
style: TextStyle(color: buttonForegroundColor),
|
style:
|
||||||
|
TextStyle(color: buttonForegroundColor),
|
||||||
)),
|
)),
|
||||||
ElevatedButton.icon(
|
ElevatedButton.icon(
|
||||||
icon: Icon(Icons.cancel, color: orangeColor),
|
icon: Icon(Icons.cancel, color: orangeColor),
|
||||||
onPressed: model.paths != null && model.paths.length > 0
|
onPressed: model.paths != null &&
|
||||||
|
model.paths!.length > 0
|
||||||
? () => model.clearCachedFiles()
|
? () => model.clearCachedFiles()
|
||||||
: null,
|
: null,
|
||||||
label: Text(
|
label: Text(
|
||||||
translate('upload.clear_temporary_files'),
|
translate('upload.clear_temporary_files'),
|
||||||
style: TextStyle(color: buttonForegroundColor),
|
style:
|
||||||
|
TextStyle(color: buttonForegroundColor),
|
||||||
)),
|
)),
|
||||||
],
|
],
|
||||||
)),
|
)),
|
||||||
|
@ -118,35 +128,48 @@ class UploadView extends StatelessWidget {
|
||||||
onPressed: !_isUploadButtonEnabled(model)
|
onPressed: !_isUploadButtonEnabled(model)
|
||||||
? null
|
? null
|
||||||
: () async {
|
: () async {
|
||||||
Map<String, bool> items = await model.upload();
|
Map<String, bool>? items =
|
||||||
String clipboardContent = model.generatePasteLinks(items, url);
|
await model.upload();
|
||||||
|
String? clipboardContent = model
|
||||||
|
.generatePasteLinks(items, url);
|
||||||
|
|
||||||
if (clipboardContent != null && clipboardContent.isNotEmpty) {
|
if (clipboardContent != null &&
|
||||||
FlutterClipboard.copy(clipboardContent).then((value) {
|
clipboardContent.isNotEmpty) {
|
||||||
|
FlutterClipboard.copy(
|
||||||
|
clipboardContent)
|
||||||
|
.then((value) {
|
||||||
final snackBar = SnackBar(
|
final snackBar = SnackBar(
|
||||||
action: SnackBarAction(
|
action: SnackBarAction(
|
||||||
label: translate('upload.dismiss'),
|
label: translate(
|
||||||
|
'upload.dismiss'),
|
||||||
textColor: blueColor,
|
textColor: blueColor,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
ScaffoldMessenger.of(context).hideCurrentSnackBar();
|
ScaffoldMessenger.of(
|
||||||
|
context)
|
||||||
|
.hideCurrentSnackBar();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
content: Text(translate('upload.uploaded')),
|
content: Text(translate(
|
||||||
|
'upload.uploaded')),
|
||||||
duration: Duration(seconds: 10),
|
duration: Duration(seconds: 10),
|
||||||
);
|
);
|
||||||
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
ScaffoldMessenger.of(context)
|
||||||
|
.showSnackBar(snackBar);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
icon: Icon(Icons.upload_rounded, color: greenColor),
|
icon: Icon(Icons.upload_rounded,
|
||||||
|
color: greenColor),
|
||||||
label: Text(
|
label: Text(
|
||||||
translate('upload.upload'),
|
translate('upload.upload'),
|
||||||
style: TextStyle(color: buttonForegroundColor),
|
style:
|
||||||
|
TextStyle(color: buttonForegroundColor),
|
||||||
)),
|
)),
|
||||||
])),
|
])),
|
||||||
model.errorMessage != null && model.errorMessage.isNotEmpty
|
model.errorMessage != null && model.errorMessage!.isNotEmpty
|
||||||
? (Padding(
|
? (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)))
|
child: CenteredErrorRow(model.errorMessage)))
|
||||||
: Container(),
|
: Container(),
|
||||||
Builder(
|
Builder(
|
||||||
|
@ -158,16 +181,28 @@ class UploadView extends StatelessWidget {
|
||||||
: model.paths != null
|
: model.paths != null
|
||||||
? Container(
|
? Container(
|
||||||
padding: const EdgeInsets.only(bottom: 30.0),
|
padding: const EdgeInsets.only(bottom: 30.0),
|
||||||
height: MediaQuery.of(context).size.height * 0.50,
|
height:
|
||||||
|
MediaQuery.of(context).size.height * 0.50,
|
||||||
child: ListView.separated(
|
child: ListView.separated(
|
||||||
itemCount: model.paths != null && model.paths.isNotEmpty ? model.paths.length : 1,
|
itemCount: model.paths != null &&
|
||||||
itemBuilder: (BuildContext context, int index) {
|
model.paths!.isNotEmpty
|
||||||
final bool isMultiPath = 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
|
final String name = (isMultiPath
|
||||||
? model.paths.map((e) => e.name).toList()[index]
|
? model.paths!
|
||||||
|
.map((e) => e.name)
|
||||||
|
.toList()[index]!
|
||||||
: model.fileName ?? '...');
|
: model.fileName ?? '...');
|
||||||
final path = model.paths.length > 0
|
final path = model.paths!.length > 0
|
||||||
? model.paths.map((e) => e.path).toList()[index].toString()
|
? model.paths!
|
||||||
|
.map((e) => e.path)
|
||||||
|
.toList()[index]
|
||||||
|
.toString()
|
||||||
: '';
|
: '';
|
||||||
|
|
||||||
return Card(
|
return Card(
|
||||||
|
@ -178,7 +213,9 @@ class UploadView extends StatelessWidget {
|
||||||
subtitle: Text(path),
|
subtitle: Text(path),
|
||||||
));
|
));
|
||||||
},
|
},
|
||||||
separatorBuilder: (BuildContext context, int index) => const Divider(),
|
separatorBuilder:
|
||||||
|
(BuildContext context, int index) =>
|
||||||
|
const Divider(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
: Container(),
|
: Container(),
|
||||||
|
|
|
@ -3,8 +3,8 @@ import 'package:flutter/material.dart';
|
||||||
import '../shared/app_colors.dart';
|
import '../shared/app_colors.dart';
|
||||||
|
|
||||||
class CenteredErrorRow extends StatelessWidget {
|
class CenteredErrorRow extends StatelessWidget {
|
||||||
final Function retryCallback;
|
final Function? retryCallback;
|
||||||
final String message;
|
final String? message;
|
||||||
|
|
||||||
CenteredErrorRow(this.message, {this.retryCallback});
|
CenteredErrorRow(this.message, {this.retryCallback});
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@ class CenteredErrorRow extends StatelessWidget {
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Expanded(child: Center(child: Text(message, style: TextStyle(color: redColor)))),
|
Expanded(child: Center(child: Text(message!, style: TextStyle(color: redColor)))),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
(retryCallback != null
|
(retryCallback != null
|
||||||
|
@ -33,7 +33,7 @@ class CenteredErrorRow extends StatelessWidget {
|
||||||
icon: Icon(Icons.refresh),
|
icon: Icon(Icons.refresh),
|
||||||
color: primaryAccentColor,
|
color: primaryAccentColor,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
retryCallback();
|
retryCallback!();
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
])
|
])
|
||||||
|
|
|
@ -8,14 +8,14 @@ class LoginApiKeyHeaders extends StatelessWidget {
|
||||||
final TextEditingController uriController;
|
final TextEditingController uriController;
|
||||||
final TextEditingController apiKeyController;
|
final TextEditingController apiKeyController;
|
||||||
|
|
||||||
final String validationMessage;
|
final String? validationMessage;
|
||||||
|
|
||||||
LoginApiKeyHeaders({@required this.uriController, @required this.apiKeyController, this.validationMessage});
|
LoginApiKeyHeaders({required this.uriController, required this.apiKeyController, this.validationMessage});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Column(children: <Widget>[
|
return Column(children: <Widget>[
|
||||||
this.validationMessage != null ? Text(validationMessage, style: TextStyle(color: redColor)) : Container(),
|
this.validationMessage != null ? Text(validationMessage!, style: TextStyle(color: redColor)) : Container(),
|
||||||
LoginTextField(uriController, translate('login.url_placeholder'), Icon(Icons.link),
|
LoginTextField(uriController, translate('login.url_placeholder'), Icon(Icons.link),
|
||||||
keyboardType: TextInputType.url),
|
keyboardType: TextInputType.url),
|
||||||
LoginTextField(
|
LoginTextField(
|
||||||
|
|
|
@ -9,18 +9,18 @@ class LoginCredentialsHeaders extends StatelessWidget {
|
||||||
final TextEditingController usernameController;
|
final TextEditingController usernameController;
|
||||||
final TextEditingController passwordController;
|
final TextEditingController passwordController;
|
||||||
|
|
||||||
final String validationMessage;
|
final String? validationMessage;
|
||||||
|
|
||||||
LoginCredentialsHeaders(
|
LoginCredentialsHeaders(
|
||||||
{@required this.uriController,
|
{required this.uriController,
|
||||||
@required this.usernameController,
|
required this.usernameController,
|
||||||
@required this.passwordController,
|
required this.passwordController,
|
||||||
this.validationMessage});
|
this.validationMessage});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Column(children: <Widget>[
|
return Column(children: <Widget>[
|
||||||
this.validationMessage != null ? Text(validationMessage, style: TextStyle(color: redColor)) : Container(),
|
this.validationMessage != null ? Text(validationMessage!, style: TextStyle(color: redColor)) : Container(),
|
||||||
LoginTextField(uriController, translate('login.url_placeholder'), Icon(Icons.link),
|
LoginTextField(uriController, translate('login.url_placeholder'), Icon(Icons.link),
|
||||||
keyboardType: TextInputType.url),
|
keyboardType: TextInputType.url),
|
||||||
LoginTextField(usernameController, translate('login.username_placeholder'), Icon(Icons.person),
|
LoginTextField(usernameController, translate('login.username_placeholder'), Icon(Icons.person),
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
import '../shared/app_colors.dart';
|
import '../shared/app_colors.dart';
|
||||||
import '../widgets/about_iconbutton.dart';
|
import '../widgets/about_iconbutton.dart';
|
||||||
|
@ -7,15 +8,23 @@ class MyAppBar extends AppBar {
|
||||||
static final List<Widget> aboutEnabledWidgets = [AboutIconButton()];
|
static final List<Widget> aboutEnabledWidgets = [AboutIconButton()];
|
||||||
static final List<Widget> aboutDisabledWidgets = [];
|
static final List<Widget> aboutDisabledWidgets = [];
|
||||||
|
|
||||||
MyAppBar({Key key, Widget title, List<Widget> actionWidgets, bool enableAbout = true})
|
MyAppBar(
|
||||||
|
{Key? key,
|
||||||
|
required Widget title,
|
||||||
|
List<Widget>? actionWidgets,
|
||||||
|
bool enableAbout = true})
|
||||||
: super(
|
: super(
|
||||||
key: key,
|
key: key,
|
||||||
title: Row(children: <Widget>[title]),
|
title: Row(children: <Widget>[title]),
|
||||||
actions: _renderIconButtons(actionWidgets, enableAbout),
|
actions: _renderIconButtons(actionWidgets, enableAbout),
|
||||||
brightness: appBarBrightness,
|
systemOverlayStyle: SystemUiOverlayStyle(
|
||||||
|
systemNavigationBarColor: primaryAccentColor, // Navigation bar
|
||||||
|
statusBarColor: primaryAccentColor, // Status bar
|
||||||
|
),
|
||||||
backgroundColor: primaryAccentColor);
|
backgroundColor: primaryAccentColor);
|
||||||
|
|
||||||
static List<Widget> _renderIconButtons(List<Widget> actionWidgets, bool aboutEnabled) {
|
static List<Widget> _renderIconButtons(
|
||||||
|
List<Widget>? actionWidgets, bool aboutEnabled) {
|
||||||
if (actionWidgets == null) {
|
if (actionWidgets == null) {
|
||||||
actionWidgets = [];
|
actionWidgets = [];
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,11 +7,11 @@ import '../../locator.dart';
|
||||||
|
|
||||||
class SwipeNavigation extends StatefulWidget {
|
class SwipeNavigation extends StatefulWidget {
|
||||||
/// Widget to be augmented with gesture detection.
|
/// Widget to be augmented with gesture detection.
|
||||||
final Widget child;
|
final Widget? child;
|
||||||
|
|
||||||
/// Creates a [SwipeNavigation] widget.
|
/// Creates a [SwipeNavigation] widget.
|
||||||
const SwipeNavigation({
|
const SwipeNavigation({
|
||||||
Key key,
|
Key? key,
|
||||||
this.child,
|
this.child,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@ -32,6 +32,7 @@ class _SwipeNavigationState extends State<SwipeNavigation> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return SimpleGestureDetector(onHorizontalSwipe: _onHorizontalSwipe, child: widget.child);
|
return SimpleGestureDetector(
|
||||||
|
onHorizontalSwipe: _onHorizontalSwipe, child: widget.child!);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
34
pubspec.yaml
34
pubspec.yaml
|
@ -11,43 +11,43 @@ description: A mobile client for FileBin.
|
||||||
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
|
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
|
||||||
# Read more about iOS versioning at
|
# Read more about iOS versioning at
|
||||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||||
version: 1.3.4+13
|
version: 1.4.0+13
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=2.10.5 <3.0.0"
|
sdk: '>=2.14.4 <3.0.0'
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
cupertino_icons: 1.0.3
|
cupertino_icons: 1.0.4
|
||||||
flutter_localizations:
|
flutter_localizations:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
flutter_translate: 3.0.0
|
flutter_translate: 3.0.1
|
||||||
provider: 5.0.0
|
provider: 6.0.1
|
||||||
stacked: 2.1.1
|
stacked: 2.2.7+1
|
||||||
get_it: 6.1.1 # stacked requires ^6
|
get_it: 7.2.0
|
||||||
logger: 1.0.0
|
logger: 1.1.0
|
||||||
shared_preferences: 2.0.5
|
shared_preferences: 2.0.9
|
||||||
http: 0.13.3
|
http: 0.13.4
|
||||||
validators: 3.0.0
|
validators: 3.0.0
|
||||||
flutter_linkify: 5.0.2
|
flutter_linkify: 5.0.2
|
||||||
url_launcher: 6.0.3
|
url_launcher: 6.0.16
|
||||||
expandable: 5.0.1
|
expandable: 5.0.1
|
||||||
share: 2.0.1
|
share: 2.0.4
|
||||||
file_picker: 3.0.1
|
file_picker: 3.0.1
|
||||||
clipboard: 0.1.3
|
clipboard: 0.1.3
|
||||||
receive_sharing_intent: 1.4.5
|
receive_sharing_intent: 1.4.5
|
||||||
permission_handler: 7.1.0
|
permission_handler: 7.1.0
|
||||||
package_info: 2.0.0
|
package_info_plus: 1.3.0
|
||||||
simple_gesture_detector: 0.2.0
|
simple_gesture_detector: 0.2.0
|
||||||
json_annotation: 3.1.1 # requires null-safety update
|
json_annotation: 4.3.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
build_runner: 1.11.5 # requires null-safety update
|
build_runner: 2.1.5
|
||||||
built_value_generator: 8.0.4 # requires null-safety update
|
built_value_generator: 8.1.3
|
||||||
json_serializable: 3.5.1 # requires null-safety update
|
json_serializable: 6.0.1
|
||||||
|
|
||||||
# For information on the generic Dart part of this file, see the
|
# For information on the generic Dart part of this file, see the
|
||||||
# following page: https://www.dartlang.org/tools/pub/pubspec
|
# following page: https://www.dartlang.org/tools/pub/pubspec
|
||||||
|
|
Loading…
Reference in a new issue