mirror of
https://github.com/Xevion/paperless-mobile.git
synced 2025-12-06 23:15:43 -06:00
fix: Fixed saved views bug, formatted files, minor changes
This commit is contained in:
@@ -0,0 +1,3 @@
|
|||||||
|
* Fixed bug where document types and correspondents were not correctly synced and loaded
|
||||||
|
* Fixed bug where saved views were not correctly created and loaded
|
||||||
|
*
|
||||||
@@ -41,5 +41,7 @@ class ThemeModeAdapter extends TypeAdapter<ThemeMode> {
|
|||||||
@override
|
@override
|
||||||
bool operator ==(Object other) =>
|
bool operator ==(Object other) =>
|
||||||
identical(this, other) ||
|
identical(this, other) ||
|
||||||
other is ThemeModeAdapter && runtimeType == other.runtimeType && typeId == other.typeId;
|
other is ThemeModeAdapter &&
|
||||||
|
runtimeType == other.runtimeType &&
|
||||||
|
typeId == other.typeId;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ import 'package:hive_flutter/adapters.dart';
|
|||||||
/// Opens an encrypted box, calls [callback] with the now opened box, awaits
|
/// Opens an encrypted box, calls [callback] with the now opened box, awaits
|
||||||
/// [callback] to return and returns the calculated value. Closes the box after.
|
/// [callback] to return and returns the calculated value. Closes the box after.
|
||||||
///
|
///
|
||||||
Future<R?> withEncryptedBox<T, R>(String name, FutureOr<R?> Function(Box<T> box) callback) async {
|
Future<R?> withEncryptedBox<T, R>(
|
||||||
|
String name, FutureOr<R?> Function(Box<T> box) callback) async {
|
||||||
final key = await _getEncryptedBoxKey();
|
final key = await _getEncryptedBoxKey();
|
||||||
final box = await Hive.openBox<T>(
|
final box = await Hive.openBox<T>(
|
||||||
name,
|
name,
|
||||||
|
|||||||
@@ -27,6 +27,9 @@ class LocalUserAccount extends HiveObject {
|
|||||||
required this.paperlessUser,
|
required this.paperlessUser,
|
||||||
});
|
});
|
||||||
|
|
||||||
static LocalUserAccount get current => Hive.box<LocalUserAccount>(HiveBoxes.localUserAccount)
|
static LocalUserAccount get current =>
|
||||||
.get(Hive.box<GlobalSettings>(HiveBoxes.globalSettings).getValue()!.currentLoggedInUser)!;
|
Hive.box<LocalUserAccount>(HiveBoxes.localUserAccount).get(
|
||||||
|
Hive.box<GlobalSettings>(HiveBoxes.globalSettings)
|
||||||
|
.getValue()!
|
||||||
|
.currentLoggedInUser)!;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,7 +41,10 @@ class LocalUserAppState extends HiveObject {
|
|||||||
|
|
||||||
static LocalUserAppState get current {
|
static LocalUserAppState get current {
|
||||||
final currentLocalUserId =
|
final currentLocalUserId =
|
||||||
Hive.box<GlobalSettings>(HiveBoxes.globalSettings).getValue()!.currentLoggedInUser!;
|
Hive.box<GlobalSettings>(HiveBoxes.globalSettings)
|
||||||
return Hive.box<LocalUserAppState>(HiveBoxes.localUserAppState).get(currentLocalUserId)!;
|
.getValue()!
|
||||||
|
.currentLoggedInUser!;
|
||||||
|
return Hive.box<LocalUserAppState>(HiveBoxes.localUserAppState)
|
||||||
|
.get(currentLocalUserId)!;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,9 +3,11 @@ import 'package:paperless_api/paperless_api.dart';
|
|||||||
|
|
||||||
abstract class PaperlessApiFactory {
|
abstract class PaperlessApiFactory {
|
||||||
PaperlessDocumentsApi createDocumentsApi(Dio dio, {required int apiVersion});
|
PaperlessDocumentsApi createDocumentsApi(Dio dio, {required int apiVersion});
|
||||||
PaperlessSavedViewsApi createSavedViewsApi(Dio dio, {required int apiVersion});
|
PaperlessSavedViewsApi createSavedViewsApi(Dio dio,
|
||||||
|
{required int apiVersion});
|
||||||
PaperlessLabelsApi createLabelsApi(Dio dio, {required int apiVersion});
|
PaperlessLabelsApi createLabelsApi(Dio dio, {required int apiVersion});
|
||||||
PaperlessServerStatsApi createServerStatsApi(Dio dio, {required int apiVersion});
|
PaperlessServerStatsApi createServerStatsApi(Dio dio,
|
||||||
|
{required int apiVersion});
|
||||||
PaperlessTasksApi createTasksApi(Dio dio, {required int apiVersion});
|
PaperlessTasksApi createTasksApi(Dio dio, {required int apiVersion});
|
||||||
PaperlessAuthenticationApi createAuthenticationApi(Dio dio);
|
PaperlessAuthenticationApi createAuthenticationApi(Dio dio);
|
||||||
PaperlessUserApi createUserApi(Dio dio, {required int apiVersion});
|
PaperlessUserApi createUserApi(Dio dio, {required int apiVersion});
|
||||||
|
|||||||
@@ -19,12 +19,14 @@ class PaperlessApiFactoryImpl implements PaperlessApiFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
PaperlessSavedViewsApi createSavedViewsApi(Dio dio, {required int apiVersion}) {
|
PaperlessSavedViewsApi createSavedViewsApi(Dio dio,
|
||||||
|
{required int apiVersion}) {
|
||||||
return PaperlessSavedViewsApiImpl(dio);
|
return PaperlessSavedViewsApiImpl(dio);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
PaperlessServerStatsApi createServerStatsApi(Dio dio, {required int apiVersion}) {
|
PaperlessServerStatsApi createServerStatsApi(Dio dio,
|
||||||
|
{required int apiVersion}) {
|
||||||
return PaperlessServerStatsApiImpl(dio);
|
return PaperlessServerStatsApiImpl(dio);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -78,7 +78,8 @@ class DioHttpErrorInterceptor extends Interceptor {
|
|||||||
DioError(
|
DioError(
|
||||||
requestOptions: err.requestOptions,
|
requestOptions: err.requestOptions,
|
||||||
type: DioErrorType.badResponse,
|
type: DioErrorType.badResponse,
|
||||||
error: const PaperlessServerException(ErrorCode.missingClientCertificate),
|
error: const PaperlessServerException(
|
||||||
|
ErrorCode.missingClientCertificate),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
|
||||||
|
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
||||||
@@ -35,8 +36,9 @@ import 'package:provider/provider.dart';
|
|||||||
// Providers unfortunately have to be passed to the routes since they are children of the Navigator, not ancestors.
|
// Providers unfortunately have to be passed to the routes since they are children of the Navigator, not ancestors.
|
||||||
|
|
||||||
Future<void> pushDocumentSearchPage(BuildContext context) {
|
Future<void> pushDocumentSearchPage(BuildContext context) {
|
||||||
final currentUser =
|
final currentUser = Hive.box<GlobalSettings>(HiveBoxes.globalSettings)
|
||||||
Hive.box<GlobalSettings>(HiveBoxes.globalSettings).getValue()!.currentLoggedInUser;
|
.getValue()!
|
||||||
|
.currentLoggedInUser;
|
||||||
final userRepo = context.read<UserRepository>();
|
final userRepo = context.read<UserRepository>();
|
||||||
return Navigator.of(context).push(
|
return Navigator.of(context).push(
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
@@ -53,7 +55,8 @@ Future<void> pushDocumentSearchPage(BuildContext context) {
|
|||||||
create: (context) => DocumentSearchCubit(
|
create: (context) => DocumentSearchCubit(
|
||||||
context.read(),
|
context.read(),
|
||||||
context.read(),
|
context.read(),
|
||||||
Hive.box<LocalUserAppState>(HiveBoxes.localUserAppState).get(currentUser)!,
|
Hive.box<LocalUserAppState>(HiveBoxes.localUserAppState)
|
||||||
|
.get(currentUser)!,
|
||||||
),
|
),
|
||||||
child: const DocumentSearchPage(),
|
child: const DocumentSearchPage(),
|
||||||
);
|
);
|
||||||
@@ -103,7 +106,8 @@ Future<void> pushSavedViewDetailsRoute(
|
|||||||
builder: (_) => MultiProvider(
|
builder: (_) => MultiProvider(
|
||||||
providers: [
|
providers: [
|
||||||
Provider.value(value: apiVersion),
|
Provider.value(value: apiVersion),
|
||||||
if (apiVersion.hasMultiUserSupport) Provider.value(value: context.read<UserRepository>()),
|
if (apiVersion.hasMultiUserSupport)
|
||||||
|
Provider.value(value: context.read<UserRepository>()),
|
||||||
Provider.value(value: context.read<LabelRepository>()),
|
Provider.value(value: context.read<LabelRepository>()),
|
||||||
Provider.value(value: context.read<DocumentChangedNotifier>()),
|
Provider.value(value: context.read<DocumentChangedNotifier>()),
|
||||||
Provider.value(value: context.read<PaperlessDocumentsApi>()),
|
Provider.value(value: context.read<PaperlessDocumentsApi>()),
|
||||||
@@ -119,7 +123,8 @@ Future<void> pushSavedViewDetailsRoute(
|
|||||||
LocalUserAppState.current,
|
LocalUserAppState.current,
|
||||||
savedView: savedView,
|
savedView: savedView,
|
||||||
),
|
),
|
||||||
child: SavedViewDetailsPage(onDelete: context.read<SavedViewCubit>().remove),
|
child: SavedViewDetailsPage(
|
||||||
|
onDelete: context.read<SavedViewCubit>().remove),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -127,7 +132,8 @@ Future<void> pushSavedViewDetailsRoute(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<SavedView?> pushAddSavedViewRoute(BuildContext context, {required DocumentFilter filter}) {
|
Future<SavedView?> pushAddSavedViewRoute(BuildContext context,
|
||||||
|
{required DocumentFilter filter}) {
|
||||||
return Navigator.of(context).push<SavedView?>(
|
return Navigator.of(context).push<SavedView?>(
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (_) => AddSavedViewPage(
|
builder: (_) => AddSavedViewPage(
|
||||||
@@ -141,7 +147,8 @@ Future<SavedView?> pushAddSavedViewRoute(BuildContext context, {required Documen
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> pushLinkedDocumentsView(BuildContext context, {required DocumentFilter filter}) {
|
Future<void> pushLinkedDocumentsView(BuildContext context,
|
||||||
|
{required DocumentFilter filter}) {
|
||||||
return Navigator.push(
|
return Navigator.push(
|
||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
@@ -196,7 +203,9 @@ Future<void> pushBulkEditCorrespondentRoute(
|
|||||||
labelMapper: (document) => document.correspondent,
|
labelMapper: (document) => document.correspondent,
|
||||||
leadingIcon: const Icon(Icons.person_outline),
|
leadingIcon: const Icon(Icons.person_outline),
|
||||||
hintText: S.of(context)!.startTyping,
|
hintText: S.of(context)!.startTyping,
|
||||||
onSubmit: context.read<DocumentBulkActionCubit>().bulkModifyCorrespondent,
|
onSubmit: context
|
||||||
|
.read<DocumentBulkActionCubit>()
|
||||||
|
.bulkModifyCorrespondent,
|
||||||
assignMessageBuilder: (int count, String name) {
|
assignMessageBuilder: (int count, String name) {
|
||||||
return S.of(context)!.bulkEditCorrespondentAssignMessage(
|
return S.of(context)!.bulkEditCorrespondentAssignMessage(
|
||||||
name,
|
name,
|
||||||
@@ -204,7 +213,9 @@ Future<void> pushBulkEditCorrespondentRoute(
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
removeMessageBuilder: (int count) {
|
removeMessageBuilder: (int count) {
|
||||||
return S.of(context)!.bulkEditCorrespondentRemoveMessage(count);
|
return S
|
||||||
|
.of(context)!
|
||||||
|
.bulkEditCorrespondentRemoveMessage(count);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -240,7 +251,9 @@ Future<void> pushBulkEditStoragePathRoute(
|
|||||||
labelMapper: (document) => document.storagePath,
|
labelMapper: (document) => document.storagePath,
|
||||||
leadingIcon: const Icon(Icons.folder_outlined),
|
leadingIcon: const Icon(Icons.folder_outlined),
|
||||||
hintText: S.of(context)!.startTyping,
|
hintText: S.of(context)!.startTyping,
|
||||||
onSubmit: context.read<DocumentBulkActionCubit>().bulkModifyStoragePath,
|
onSubmit: context
|
||||||
|
.read<DocumentBulkActionCubit>()
|
||||||
|
.bulkModifyStoragePath,
|
||||||
assignMessageBuilder: (int count, String name) {
|
assignMessageBuilder: (int count, String name) {
|
||||||
return S.of(context)!.bulkEditStoragePathAssignMessage(
|
return S.of(context)!.bulkEditStoragePathAssignMessage(
|
||||||
count,
|
count,
|
||||||
@@ -308,7 +321,9 @@ Future<void> pushBulkEditDocumentTypeRoute(BuildContext context,
|
|||||||
labelMapper: (document) => document.documentType,
|
labelMapper: (document) => document.documentType,
|
||||||
leadingIcon: const Icon(Icons.description_outlined),
|
leadingIcon: const Icon(Icons.description_outlined),
|
||||||
hintText: S.of(context)!.startTyping,
|
hintText: S.of(context)!.startTyping,
|
||||||
onSubmit: context.read<DocumentBulkActionCubit>().bulkModifyDocumentType,
|
onSubmit: context
|
||||||
|
.read<DocumentBulkActionCubit>()
|
||||||
|
.bulkModifyDocumentType,
|
||||||
assignMessageBuilder: (int count, String name) {
|
assignMessageBuilder: (int count, String name) {
|
||||||
return S.of(context)!.bulkEditDocumentTypeAssignMessage(
|
return S.of(context)!.bulkEditDocumentTypeAssignMessage(
|
||||||
count,
|
count,
|
||||||
@@ -316,7 +331,9 @@ Future<void> pushBulkEditDocumentTypeRoute(BuildContext context,
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
removeMessageBuilder: (int count) {
|
removeMessageBuilder: (int count) {
|
||||||
return S.of(context)!.bulkEditDocumentTypeRemoveMessage(count);
|
return S
|
||||||
|
.of(context)!
|
||||||
|
.bulkEditDocumentTypeRemoveMessage(count);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -336,17 +353,20 @@ Future<DocumentUploadResult?> pushDocumentUploadPreparationPage(
|
|||||||
}) {
|
}) {
|
||||||
final labelRepo = context.read<LabelRepository>();
|
final labelRepo = context.read<LabelRepository>();
|
||||||
final docsApi = context.read<PaperlessDocumentsApi>();
|
final docsApi = context.read<PaperlessDocumentsApi>();
|
||||||
|
final connectivity = context.read<Connectivity>();
|
||||||
return Navigator.of(context).push<DocumentUploadResult>(
|
return Navigator.of(context).push<DocumentUploadResult>(
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (_) => MultiProvider(
|
builder: (_) => MultiProvider(
|
||||||
providers: [
|
providers: [
|
||||||
Provider.value(value: labelRepo),
|
Provider.value(value: labelRepo),
|
||||||
Provider.value(value: docsApi),
|
Provider.value(value: docsApi),
|
||||||
|
Provider.value(value: connectivity),
|
||||||
],
|
],
|
||||||
builder: (_, child) => BlocProvider(
|
builder: (_, child) => BlocProvider(
|
||||||
create: (_) => DocumentUploadCubit(
|
create: (_) => DocumentUploadCubit(
|
||||||
context.read(),
|
context.read(),
|
||||||
context.read(),
|
context.read(),
|
||||||
|
context.read(),
|
||||||
),
|
),
|
||||||
child: DocumentUploadPreparationPage(
|
child: DocumentUploadPreparationPage(
|
||||||
fileBytes: bytes,
|
fileBytes: bytes,
|
||||||
|
|||||||
@@ -1,39 +1,55 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
import 'package:paperless_mobile/core/repository/persistent_repository.dart';
|
import 'package:paperless_mobile/core/repository/persistent_repository.dart';
|
||||||
import 'package:paperless_mobile/core/repository/saved_view_repository_state.dart';
|
|
||||||
|
|
||||||
class SavedViewRepository extends PersistentRepository<SavedViewRepositoryState> {
|
part 'saved_view_repository_state.dart';
|
||||||
|
part 'saved_view_repository.g.dart';
|
||||||
|
part 'saved_view_repository.freezed.dart';
|
||||||
|
|
||||||
|
class SavedViewRepository
|
||||||
|
extends PersistentRepository<SavedViewRepositoryState> {
|
||||||
final PaperlessSavedViewsApi _api;
|
final PaperlessSavedViewsApi _api;
|
||||||
|
final Completer _initialized = Completer();
|
||||||
|
|
||||||
SavedViewRepository(this._api) : super(const SavedViewRepositoryState()) {
|
SavedViewRepository(this._api)
|
||||||
initialize();
|
: super(const SavedViewRepositoryState.initial());
|
||||||
|
|
||||||
|
Future<void> initialize() async {
|
||||||
|
try {
|
||||||
|
await findAll();
|
||||||
|
_initialized.complete();
|
||||||
|
} catch (e) {
|
||||||
|
_initialized.completeError(e);
|
||||||
|
emit(const SavedViewRepositoryState.error());
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> initialize() {
|
|
||||||
return findAll();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<SavedView> create(SavedView object) async {
|
Future<SavedView> create(SavedView object) async {
|
||||||
|
await _initialized.future;
|
||||||
final created = await _api.save(object);
|
final created = await _api.save(object);
|
||||||
final updatedState = {...state.savedViews}..putIfAbsent(created.id!, () => created);
|
final updatedState = {...state.savedViews}
|
||||||
emit(state.copyWith(savedViews: updatedState));
|
..putIfAbsent(created.id!, () => created);
|
||||||
|
emit(SavedViewRepositoryState.loaded(savedViews: updatedState));
|
||||||
return created;
|
return created;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<int> delete(SavedView view) async {
|
Future<int> delete(SavedView view) async {
|
||||||
|
await _initialized.future;
|
||||||
await _api.delete(view);
|
await _api.delete(view);
|
||||||
final updatedState = {...state.savedViews}..remove(view.id);
|
final updatedState = {...state.savedViews}..remove(view.id);
|
||||||
emit(state.copyWith(savedViews: updatedState));
|
emit(SavedViewRepositoryState.loaded(savedViews: updatedState));
|
||||||
return view.id!;
|
return view.id!;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<SavedView?> find(int id) async {
|
Future<SavedView?> find(int id) async {
|
||||||
|
await _initialized.future;
|
||||||
final found = await _api.find(id);
|
final found = await _api.find(id);
|
||||||
if (found != null) {
|
if (found != null) {
|
||||||
final updatedState = {...state.savedViews}..update(id, (_) => found, ifAbsent: () => found);
|
final updatedState = {...state.savedViews}
|
||||||
emit(state.copyWith(savedViews: updatedState));
|
..update(id, (_) => found, ifAbsent: () => found);
|
||||||
|
emit(SavedViewRepositoryState.loaded(savedViews: updatedState));
|
||||||
}
|
}
|
||||||
return found;
|
return found;
|
||||||
}
|
}
|
||||||
@@ -44,14 +60,15 @@ class SavedViewRepository extends PersistentRepository<SavedViewRepositoryState>
|
|||||||
...state.savedViews,
|
...state.savedViews,
|
||||||
...{for (final view in found) view.id!: view},
|
...{for (final view in found) view.id!: view},
|
||||||
};
|
};
|
||||||
emit(state.copyWith(savedViews: updatedState));
|
emit(SavedViewRepositoryState.loaded(savedViews: updatedState));
|
||||||
return found;
|
return found;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> clear() async {
|
Future<void> clear() async {
|
||||||
|
await _initialized.future;
|
||||||
await super.clear();
|
await super.clear();
|
||||||
emit(const SavedViewRepositoryState());
|
emit(const SavedViewRepositoryState.initial());
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -1,14 +1,19 @@
|
|||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
part of 'saved_view_repository.dart';
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
|
||||||
|
|
||||||
part 'saved_view_repository_state.freezed.dart';
|
|
||||||
part 'saved_view_repository_state.g.dart';
|
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
class SavedViewRepositoryState with _$SavedViewRepositoryState {
|
class SavedViewRepositoryState with _$SavedViewRepositoryState {
|
||||||
const factory SavedViewRepositoryState({
|
const factory SavedViewRepositoryState.initial({
|
||||||
@Default({}) Map<int, SavedView> savedViews,
|
@Default({}) Map<int, SavedView> savedViews,
|
||||||
}) = _SavedViewRepositoryState;
|
}) = _Initial;
|
||||||
|
const factory SavedViewRepositoryState.loading({
|
||||||
|
@Default({}) Map<int, SavedView> savedViews,
|
||||||
|
}) = _Loading;
|
||||||
|
const factory SavedViewRepositoryState.loaded({
|
||||||
|
@Default({}) Map<int, SavedView> savedViews,
|
||||||
|
}) = _Loaded;
|
||||||
|
const factory SavedViewRepositoryState.error({
|
||||||
|
@Default({}) Map<int, SavedView> savedViews,
|
||||||
|
}) = _Error;
|
||||||
|
|
||||||
factory SavedViewRepositoryState.fromJson(Map<String, dynamic> json) =>
|
factory SavedViewRepositoryState.fromJson(Map<String, dynamic> json) =>
|
||||||
_$SavedViewRepositoryStateFromJson(json);
|
_$SavedViewRepositoryStateFromJson(json);
|
||||||
|
|||||||
@@ -13,7 +13,8 @@ import 'package:pretty_dio_logger/pretty_dio_logger.dart';
|
|||||||
class SessionManager extends ValueNotifier<Dio> {
|
class SessionManager extends ValueNotifier<Dio> {
|
||||||
Dio get client => value;
|
Dio get client => value;
|
||||||
|
|
||||||
SessionManager([List<Interceptor> interceptors = const []]) : super(_initDio(interceptors));
|
SessionManager([List<Interceptor> interceptors = const []])
|
||||||
|
: super(_initDio(interceptors));
|
||||||
|
|
||||||
static Dio _initDio(List<Interceptor> interceptors) {
|
static Dio _initDio(List<Interceptor> interceptors) {
|
||||||
//en- and decoded by utf8 by default
|
//en- and decoded by utf8 by default
|
||||||
@@ -21,8 +22,8 @@ class SessionManager extends ValueNotifier<Dio> {
|
|||||||
BaseOptions(contentType: Headers.jsonContentType),
|
BaseOptions(contentType: Headers.jsonContentType),
|
||||||
);
|
);
|
||||||
dio.options
|
dio.options
|
||||||
..receiveTimeout = const Duration(seconds: 15)
|
..receiveTimeout = const Duration(seconds: 20)
|
||||||
..sendTimeout = const Duration(seconds: 10)
|
..sendTimeout = const Duration(seconds: 60)
|
||||||
..responseType = ResponseType.json;
|
..responseType = ResponseType.json;
|
||||||
(dio.httpClientAdapter as IOHttpClientAdapter).onHttpClientCreate =
|
(dio.httpClientAdapter as IOHttpClientAdapter).onHttpClientCreate =
|
||||||
(client) => client..badCertificateCallback = (cert, host, port) => true;
|
(client) => client..badCertificateCallback = (cert, host, port) => true;
|
||||||
@@ -62,7 +63,8 @@ class SessionManager extends ValueNotifier<Dio> {
|
|||||||
);
|
);
|
||||||
final adapter = IOHttpClientAdapter()
|
final adapter = IOHttpClientAdapter()
|
||||||
..onHttpClientCreate = (client) => HttpClient(context: context)
|
..onHttpClientCreate = (client) => HttpClient(context: context)
|
||||||
..badCertificateCallback = (X509Certificate cert, String host, int port) => true;
|
..badCertificateCallback =
|
||||||
|
(X509Certificate cert, String host, int port) => true;
|
||||||
|
|
||||||
client.httpClientAdapter = adapter;
|
client.httpClientAdapter = adapter;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,8 @@ class ConnectivityStatusServiceImpl implements ConnectivityStatusService {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<bool> isConnectedToInternet() async {
|
Future<bool> isConnectedToInternet() async {
|
||||||
return _hasActiveInternetConnection(await (Connectivity().checkConnectivity()));
|
return _hasActiveInternetConnection(
|
||||||
|
await (Connectivity().checkConnectivity()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -71,7 +72,8 @@ class ConnectivityStatusServiceImpl implements ConnectivityStatusService {
|
|||||||
return ReachabilityStatus.unknown;
|
return ReachabilityStatus.unknown;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
SessionManager manager = SessionManager([ServerReachabilityErrorInterceptor()])
|
SessionManager manager =
|
||||||
|
SessionManager([ServerReachabilityErrorInterceptor()])
|
||||||
..updateSettings(clientCertificate: clientCertificate)
|
..updateSettings(clientCertificate: clientCertificate)
|
||||||
..client.options.connectTimeout = const Duration(seconds: 5)
|
..client.options.connectTimeout = const Duration(seconds: 5)
|
||||||
..client.options.receiveTimeout = const Duration(seconds: 5);
|
..client.options.receiveTimeout = const Duration(seconds: 5);
|
||||||
@@ -82,7 +84,8 @@ class ConnectivityStatusServiceImpl implements ConnectivityStatusService {
|
|||||||
}
|
}
|
||||||
return ReachabilityStatus.notReachable;
|
return ReachabilityStatus.notReachable;
|
||||||
} on DioError catch (error) {
|
} on DioError catch (error) {
|
||||||
if (error.type == DioErrorType.unknown && error.error is ReachabilityStatus) {
|
if (error.type == DioErrorType.unknown &&
|
||||||
|
error.error is ReachabilityStatus) {
|
||||||
return error.error as ReachabilityStatus;
|
return error.error as ReachabilityStatus;
|
||||||
}
|
}
|
||||||
} on TlsException catch (error) {
|
} on TlsException catch (error) {
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:paperless_mobile/core/model/github_error_report.model.dart';
|
import 'package:paperless_mobile/core/model/github_error_report.model.dart';
|
||||||
import 'package:paperless_mobile/core/widgets/error_report_page.dart';
|
import 'package:paperless_mobile/core/widgets/error_report_page.dart';
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
typedef JSON = Map<String, dynamic>;
|
typedef JSON = Map<String, dynamic>;
|
||||||
typedef PaperlessValidationErrors = Map<String, String>;
|
typedef PaperlessValidationErrors = Map<String, String>;
|
||||||
typedef PaperlessLocalizedErrorMessage = String;
|
typedef PaperlessLocalizedErrorMessage = String;
|
||||||
|
|||||||
@@ -181,7 +181,8 @@ class FormBuilderColorPickerField extends FormBuilderField<Color> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
FormBuilderColorPickerFieldState createState() => FormBuilderColorPickerFieldState();
|
FormBuilderColorPickerFieldState createState() =>
|
||||||
|
FormBuilderColorPickerFieldState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class FormBuilderColorPickerFieldState
|
class FormBuilderColorPickerFieldState
|
||||||
|
|||||||
@@ -334,7 +334,8 @@ class FormBuilderTypeAhead<T> extends FormBuilderField<T> {
|
|||||||
// TODO HACK to satisfy strictness
|
// TODO HACK to satisfy strictness
|
||||||
suggestionsCallback: suggestionsCallback,
|
suggestionsCallback: suggestionsCallback,
|
||||||
itemBuilder: itemBuilder,
|
itemBuilder: itemBuilder,
|
||||||
transitionBuilder: (context, suggestionsBox, controller) => suggestionsBox,
|
transitionBuilder: (context, suggestionsBox, controller) =>
|
||||||
|
suggestionsBox,
|
||||||
onSuggestionSelected: (T suggestion) {
|
onSuggestionSelected: (T suggestion) {
|
||||||
state.didChange(suggestion);
|
state.didChange(suggestion);
|
||||||
onSuggestionSelected?.call(suggestion);
|
onSuggestionSelected?.call(suggestion);
|
||||||
@@ -356,7 +357,8 @@ class FormBuilderTypeAhead<T> extends FormBuilderField<T> {
|
|||||||
keepSuggestionsOnLoading: keepSuggestionsOnLoading,
|
keepSuggestionsOnLoading: keepSuggestionsOnLoading,
|
||||||
autoFlipDirection: autoFlipDirection,
|
autoFlipDirection: autoFlipDirection,
|
||||||
suggestionsBoxController: suggestionsBoxController,
|
suggestionsBoxController: suggestionsBoxController,
|
||||||
keepSuggestionsOnSuggestionSelected: keepSuggestionsOnSuggestionSelected,
|
keepSuggestionsOnSuggestionSelected:
|
||||||
|
keepSuggestionsOnSuggestionSelected,
|
||||||
hideKeyboard: hideKeyboard,
|
hideKeyboard: hideKeyboard,
|
||||||
scrollController: scrollController,
|
scrollController: scrollController,
|
||||||
);
|
);
|
||||||
@@ -367,14 +369,15 @@ class FormBuilderTypeAhead<T> extends FormBuilderField<T> {
|
|||||||
FormBuilderTypeAheadState<T> createState() => FormBuilderTypeAheadState<T>();
|
FormBuilderTypeAheadState<T> createState() => FormBuilderTypeAheadState<T>();
|
||||||
}
|
}
|
||||||
|
|
||||||
class FormBuilderTypeAheadState<T> extends FormBuilderFieldState<FormBuilderTypeAhead<T>, T> {
|
class FormBuilderTypeAheadState<T>
|
||||||
|
extends FormBuilderFieldState<FormBuilderTypeAhead<T>, T> {
|
||||||
late TextEditingController _typeAheadController;
|
late TextEditingController _typeAheadController;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_typeAheadController =
|
_typeAheadController = widget.controller ??
|
||||||
widget.controller ?? TextEditingController(text: _getTextString(initialValue));
|
TextEditingController(text: _getTextString(initialValue));
|
||||||
// _typeAheadController.addListener(_handleControllerChanged);
|
// _typeAheadController.addListener(_handleControllerChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,8 @@ import 'package:flutter/services.dart';
|
|||||||
|
|
||||||
typedef ChipsInputSuggestions<T> = Future<List<T>> Function(String query);
|
typedef ChipsInputSuggestions<T> = Future<List<T>> Function(String query);
|
||||||
typedef ChipSelected<T> = void Function(T data, bool selected);
|
typedef ChipSelected<T> = void Function(T data, bool selected);
|
||||||
typedef ChipsBuilder<T> = Widget Function(BuildContext context, ChipsInputState<T> state, T data);
|
typedef ChipsBuilder<T> = Widget Function(
|
||||||
|
BuildContext context, ChipsInputState<T> state, T data);
|
||||||
|
|
||||||
class ChipsInput<T> extends StatefulWidget {
|
class ChipsInput<T> extends StatefulWidget {
|
||||||
const ChipsInput({
|
const ChipsInput({
|
||||||
@@ -70,7 +71,8 @@ class ChipsInputState<T> extends State<ChipsInput<T>> {
|
|||||||
|
|
||||||
TextEditingValue get currentTextEditingValue => _value;
|
TextEditingValue get currentTextEditingValue => _value;
|
||||||
|
|
||||||
bool get _hasInputConnection => _connection != null && (_connection?.attached ?? false);
|
bool get _hasInputConnection =>
|
||||||
|
_connection != null && (_connection?.attached ?? false);
|
||||||
|
|
||||||
void requestKeyboard() {
|
void requestKeyboard() {
|
||||||
if (_focusNode.hasFocus) {
|
if (_focusNode.hasFocus) {
|
||||||
@@ -189,7 +191,8 @@ class ChipsInputState<T> extends State<ChipsInput<T>> {
|
|||||||
child: ListView.builder(
|
child: ListView.builder(
|
||||||
itemCount: _suggestions.length,
|
itemCount: _suggestions.length,
|
||||||
itemBuilder: (BuildContext context, int index) {
|
itemBuilder: (BuildContext context, int index) {
|
||||||
return widget.suggestionBuilder(context, this, _suggestions[index]);
|
return widget.suggestionBuilder(
|
||||||
|
context, this, _suggestions[index]);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -210,11 +213,14 @@ class ChipsInputState<T> extends State<ChipsInput<T>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int _countReplacements(TextEditingValue value) {
|
int _countReplacements(TextEditingValue value) {
|
||||||
return value.text.codeUnits.where((ch) => ch == kObjectReplacementChar).length;
|
return value.text.codeUnits
|
||||||
|
.where((ch) => ch == kObjectReplacementChar)
|
||||||
|
.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
void _updateTextInputState() {
|
void _updateTextInputState() {
|
||||||
final text = String.fromCharCodes(_chips.map((_) => kObjectReplacementChar));
|
final text =
|
||||||
|
String.fromCharCodes(_chips.map((_) => kObjectReplacementChar));
|
||||||
_value = TextEditingValue(
|
_value = TextEditingValue(
|
||||||
text: text,
|
text: text,
|
||||||
selection: TextSelection.collapsed(offset: text.length),
|
selection: TextSelection.collapsed(offset: text.length),
|
||||||
@@ -227,8 +233,9 @@ class ChipsInputState<T> extends State<ChipsInput<T>> {
|
|||||||
final localId = ++_searchId;
|
final localId = ++_searchId;
|
||||||
final results = await widget.findSuggestions(value);
|
final results = await widget.findSuggestions(value);
|
||||||
if (_searchId == localId && mounted) {
|
if (_searchId == localId && mounted) {
|
||||||
setState(() => _suggestions =
|
setState(() => _suggestions = results
|
||||||
results.where((profile) => !_chips.contains(profile)).toList(growable: false));
|
.where((profile) => !_chips.contains(profile))
|
||||||
|
.toList(growable: false));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -244,7 +251,8 @@ class _TextCaret extends StatefulWidget {
|
|||||||
_TextCursorState createState() => _TextCursorState();
|
_TextCursorState createState() => _TextCursorState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _TextCursorState extends State<_TextCaret> with SingleTickerProviderStateMixin {
|
class _TextCursorState extends State<_TextCaret>
|
||||||
|
with SingleTickerProviderStateMixin {
|
||||||
bool _displayed = false;
|
bool _displayed = false;
|
||||||
late Timer _timer;
|
late Timer _timer;
|
||||||
|
|
||||||
|
|||||||
@@ -95,7 +95,8 @@ class AppDrawer extends StatelessWidget {
|
|||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (_) => MultiProvider(
|
builder: (_) => MultiProvider(
|
||||||
providers: [
|
providers: [
|
||||||
Provider.value(value: context.read<PaperlessServerStatsApi>()),
|
Provider.value(
|
||||||
|
value: context.read<PaperlessServerStatsApi>()),
|
||||||
Provider.value(value: context.read<ApiVersion>()),
|
Provider.value(value: context.read<ApiVersion>()),
|
||||||
],
|
],
|
||||||
child: const SettingsPage(),
|
child: const SettingsPage(),
|
||||||
@@ -128,7 +129,8 @@ class AppDrawer extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
RichText(
|
RichText(
|
||||||
text: TextSpan(
|
text: TextSpan(
|
||||||
style: theme.textTheme.bodyMedium?.copyWith(color: colorScheme.onSurface),
|
style: theme.textTheme.bodyMedium
|
||||||
|
?.copyWith(color: colorScheme.onSurface),
|
||||||
children: [
|
children: [
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: S.of(context)!.findTheSourceCodeOn,
|
text: S.of(context)!.findTheSourceCodeOn,
|
||||||
@@ -151,11 +153,13 @@ class AppDrawer extends StatelessWidget {
|
|||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
Text(
|
Text(
|
||||||
'Credits',
|
'Credits',
|
||||||
style: theme.textTheme.titleMedium?.copyWith(color: colorScheme.onSurface),
|
style: theme.textTheme.titleMedium
|
||||||
|
?.copyWith(color: colorScheme.onSurface),
|
||||||
),
|
),
|
||||||
RichText(
|
RichText(
|
||||||
text: TextSpan(
|
text: TextSpan(
|
||||||
style: theme.textTheme.bodyMedium?.copyWith(color: colorScheme.onSurface),
|
style: theme.textTheme.bodyMedium
|
||||||
|
?.copyWith(color: colorScheme.onSurface),
|
||||||
children: [
|
children: [
|
||||||
const TextSpan(
|
const TextSpan(
|
||||||
text: 'Onboarding images by ',
|
text: 'Onboarding images by ',
|
||||||
@@ -205,16 +209,16 @@ class AppDrawer extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Wrap(
|
//Wrap(
|
||||||
// children: [
|
// children: [
|
||||||
// const Text('Onboarding images by '),
|
// const Text('Onboarding images by '),
|
||||||
// GestureDetector(
|
// GestureDetector(
|
||||||
// onTap: followLink,
|
// onTap: followLink,
|
||||||
// child: RichText(
|
// child: RichText(
|
||||||
|
|
||||||
// 'pch.vector',
|
// 'pch.vector',
|
||||||
// style: TextStyle(color: Colors.blue),
|
// style: TextStyle(color: Colors.blue),
|
||||||
// ),
|
// ),
|
||||||
// ),
|
// ),
|
||||||
// const Text(' on Freepik.')
|
// const Text(' on Freepik.')
|
||||||
// ],
|
// ],
|
||||||
// )
|
// )
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ class ApplicationIntroSlideshow extends StatefulWidget {
|
|||||||
const ApplicationIntroSlideshow({super.key});
|
const ApplicationIntroSlideshow({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<ApplicationIntroSlideshow> createState() => _ApplicationIntroSlideshowState();
|
State<ApplicationIntroSlideshow> createState() =>
|
||||||
|
_ApplicationIntroSlideshowState();
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: INTL ALL
|
//TODO: INTL ALL
|
||||||
|
|||||||
@@ -8,7 +8,8 @@ import 'package:paperless_mobile/features/document_bulk_action/cubit/document_bu
|
|||||||
import 'package:paperless_mobile/features/labels/view/widgets/label_form_field.dart';
|
import 'package:paperless_mobile/features/labels/view/widgets/label_form_field.dart';
|
||||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||||
|
|
||||||
typedef LabelOptionsSelector<T extends Label> = Map<int, T> Function(DocumentBulkActionState state);
|
typedef LabelOptionsSelector<T extends Label> = Map<int, T> Function(
|
||||||
|
DocumentBulkActionState state);
|
||||||
|
|
||||||
class BulkEditLabelBottomSheet<T extends Label> extends StatefulWidget {
|
class BulkEditLabelBottomSheet<T extends Label> extends StatefulWidget {
|
||||||
final String title;
|
final String title;
|
||||||
@@ -31,16 +32,19 @@ class BulkEditLabelBottomSheet<T extends Label> extends StatefulWidget {
|
|||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<BulkEditLabelBottomSheet<T>> createState() => _BulkEditLabelBottomSheetState<T>();
|
State<BulkEditLabelBottomSheet<T>> createState() =>
|
||||||
|
_BulkEditLabelBottomSheetState<T>();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _BulkEditLabelBottomSheetState<T extends Label> extends State<BulkEditLabelBottomSheet<T>> {
|
class _BulkEditLabelBottomSheetState<T extends Label>
|
||||||
|
extends State<BulkEditLabelBottomSheet<T>> {
|
||||||
final _formKey = GlobalKey<FormBuilderState>();
|
final _formKey = GlobalKey<FormBuilderState>();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
|
padding:
|
||||||
|
EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
|
||||||
child: BlocBuilder<DocumentBulkActionCubit, DocumentBulkActionState>(
|
child: BlocBuilder<DocumentBulkActionCubit, DocumentBulkActionState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return Padding(
|
return Padding(
|
||||||
@@ -76,11 +80,13 @@ class _BulkEditLabelBottomSheetState<T extends Label> extends State<BulkEditLabe
|
|||||||
const SizedBox(width: 16),
|
const SizedBox(width: 16),
|
||||||
FilledButton(
|
FilledButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
if (_formKey.currentState?.saveAndValidate() ?? false) {
|
if (_formKey.currentState?.saveAndValidate() ??
|
||||||
final value = _formKey.currentState?.getRawValue('labelFormField')
|
false) {
|
||||||
|
final value = _formKey.currentState
|
||||||
|
?.getRawValue('labelFormField')
|
||||||
as IdQueryParameter?;
|
as IdQueryParameter?;
|
||||||
widget
|
widget.onSubmit(value?.maybeWhen(
|
||||||
.onSubmit(value?.maybeWhen(fromId: (id) => id, orElse: () => null));
|
fromId: (id) => id, orElse: () => null));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: Text(S.of(context)!.apply),
|
child: Text(S.of(context)!.apply),
|
||||||
|
|||||||
@@ -91,8 +91,8 @@ class DocumentDetailsCubit extends Cubit<DocumentDetailsState> {
|
|||||||
_notifier.notifyUpdated(updatedDocument);
|
_notifier.notifyUpdated(updatedDocument);
|
||||||
} else {
|
} else {
|
||||||
final int autoAsn = await _api.findNextAsn();
|
final int autoAsn = await _api.findNextAsn();
|
||||||
final updatedDocument =
|
final updatedDocument = await _api
|
||||||
await _api.update(document.copyWith(archiveSerialNumber: () => autoAsn));
|
.update(document.copyWith(archiveSerialNumber: () => autoAsn));
|
||||||
_notifier.notifyUpdated(updatedDocument);
|
_notifier.notifyUpdated(updatedDocument);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -104,7 +104,8 @@ class DocumentDetailsCubit extends Cubit<DocumentDetailsState> {
|
|||||||
if (state.metaData == null) {
|
if (state.metaData == null) {
|
||||||
await loadMetaData();
|
await loadMetaData();
|
||||||
}
|
}
|
||||||
final desc = FileDescription.fromPath(state.metaData!.mediaFilename.replaceAll("/", " "));
|
final desc = FileDescription.fromPath(
|
||||||
|
state.metaData!.mediaFilename.replaceAll("/", " "));
|
||||||
|
|
||||||
final fileName = "${desc.filename}.pdf";
|
final fileName = "${desc.filename}.pdf";
|
||||||
final file = File("${cacheDir.path}/$fileName");
|
final file = File("${cacheDir.path}/$fileName");
|
||||||
@@ -138,7 +139,8 @@ class DocumentDetailsCubit extends Cubit<DocumentDetailsState> {
|
|||||||
await FileService.downloadsDirectory,
|
await FileService.downloadsDirectory,
|
||||||
);
|
);
|
||||||
final desc = FileDescription.fromPath(
|
final desc = FileDescription.fromPath(
|
||||||
state.metaData!.mediaFilename.replaceAll("/", " "), // Flatten directory structure
|
state.metaData!.mediaFilename
|
||||||
|
.replaceAll("/", " "), // Flatten directory structure
|
||||||
);
|
);
|
||||||
if (!File(filePath).existsSync()) {
|
if (!File(filePath).existsSync()) {
|
||||||
File(filePath).createSync();
|
File(filePath).createSync();
|
||||||
@@ -205,7 +207,8 @@ class DocumentDetailsCubit extends Cubit<DocumentDetailsState> {
|
|||||||
if (state.metaData == null) {
|
if (state.metaData == null) {
|
||||||
await loadMetaData();
|
await loadMetaData();
|
||||||
}
|
}
|
||||||
final filePath = _buildDownloadFilePath(false, await FileService.temporaryDirectory);
|
final filePath =
|
||||||
|
_buildDownloadFilePath(false, await FileService.temporaryDirectory);
|
||||||
await _api.downloadToFile(
|
await _api.downloadToFile(
|
||||||
state.document,
|
state.document,
|
||||||
filePath,
|
filePath,
|
||||||
@@ -223,7 +226,8 @@ class DocumentDetailsCubit extends Cubit<DocumentDetailsState> {
|
|||||||
|
|
||||||
String _buildDownloadFilePath(bool original, Directory dir) {
|
String _buildDownloadFilePath(bool original, Directory dir) {
|
||||||
final description = FileDescription.fromPath(
|
final description = FileDescription.fromPath(
|
||||||
state.metaData!.mediaFilename.replaceAll("/", " "), // Flatten directory structure
|
state.metaData!.mediaFilename
|
||||||
|
.replaceAll("/", " "), // Flatten directory structure
|
||||||
);
|
);
|
||||||
final extension = original ? description.extension : 'pdf';
|
final extension = original ? description.extension : 'pdf';
|
||||||
return "${dir.path}/${description.filename}.$extension";
|
return "${dir.path}/${description.filename}.$extension";
|
||||||
|
|||||||
@@ -45,7 +45,8 @@ class _SelectFileTypeDialogState extends State<SelectFileTypeDialog> {
|
|||||||
CheckboxListTile(
|
CheckboxListTile(
|
||||||
controlAffinity: ListTileControlAffinity.leading,
|
controlAffinity: ListTileControlAffinity.leading,
|
||||||
value: _rememberSelection,
|
value: _rememberSelection,
|
||||||
onChanged: (value) => setState(() => _rememberSelection = value ?? false),
|
onChanged: (value) =>
|
||||||
|
setState(() => _rememberSelection = value ?? false),
|
||||||
title: Text(
|
title: Text(
|
||||||
S.of(context)!.rememberDecision,
|
S.of(context)!.rememberDecision,
|
||||||
style: Theme.of(context).textTheme.labelMedium,
|
style: Theme.of(context).textTheme.labelMedium,
|
||||||
@@ -61,7 +62,8 @@ class _SelectFileTypeDialogState extends State<SelectFileTypeDialog> {
|
|||||||
if (_rememberSelection) {
|
if (_rememberSelection) {
|
||||||
widget.onRememberSelection(_downloadType);
|
widget.onRememberSelection(_downloadType);
|
||||||
}
|
}
|
||||||
Navigator.of(context).pop(_downloadType == FileDownloadType.original);
|
Navigator.of(context)
|
||||||
|
.pop(_downloadType == FileDownloadType.original);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -51,27 +51,35 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
|||||||
final tabLength = 4 + (apiVersion.hasMultiUserSupport ? 1 : 0);
|
final tabLength = 4 + (apiVersion.hasMultiUserSupport ? 1 : 0);
|
||||||
return WillPopScope(
|
return WillPopScope(
|
||||||
onWillPop: () async {
|
onWillPop: () async {
|
||||||
Navigator.of(context).pop(context.read<DocumentDetailsCubit>().state.document);
|
Navigator.of(context)
|
||||||
|
.pop(context.read<DocumentDetailsCubit>().state.document);
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
child: DefaultTabController(
|
child: DefaultTabController(
|
||||||
length: tabLength,
|
length: tabLength,
|
||||||
child: BlocListener<ConnectivityCubit, ConnectivityState>(
|
child: BlocListener<ConnectivityCubit, ConnectivityState>(
|
||||||
listenWhen: (previous, current) => !previous.isConnected && current.isConnected,
|
listenWhen: (previous, current) =>
|
||||||
|
!previous.isConnected && current.isConnected,
|
||||||
listener: (context, state) {
|
listener: (context, state) {
|
||||||
context.read<DocumentDetailsCubit>().loadMetaData();
|
context.read<DocumentDetailsCubit>().loadMetaData();
|
||||||
},
|
},
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
extendBodyBehindAppBar: false,
|
extendBodyBehindAppBar: false,
|
||||||
floatingActionButtonLocation: FloatingActionButtonLocation.endDocked,
|
floatingActionButtonLocation:
|
||||||
|
FloatingActionButtonLocation.endDocked,
|
||||||
floatingActionButton: _buildEditButton(),
|
floatingActionButton: _buildEditButton(),
|
||||||
bottomNavigationBar: _buildBottomAppBar(),
|
bottomNavigationBar: _buildBottomAppBar(),
|
||||||
body: NestedScrollView(
|
body: NestedScrollView(
|
||||||
headerSliverBuilder: (context, innerBoxIsScrolled) => [
|
headerSliverBuilder: (context, innerBoxIsScrolled) => [
|
||||||
SliverOverlapAbsorber(
|
SliverOverlapAbsorber(
|
||||||
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
|
handle:
|
||||||
|
NestedScrollView.sliverOverlapAbsorberHandleFor(context),
|
||||||
sliver: SliverAppBar(
|
sliver: SliverAppBar(
|
||||||
title: Text(context.watch<DocumentDetailsCubit>().state.document.title),
|
title: Text(context
|
||||||
|
.watch<DocumentDetailsCubit>()
|
||||||
|
.state
|
||||||
|
.document
|
||||||
|
.title),
|
||||||
leading: const BackButton(),
|
leading: const BackButton(),
|
||||||
pinned: true,
|
pinned: true,
|
||||||
forceElevated: innerBoxIsScrolled,
|
forceElevated: innerBoxIsScrolled,
|
||||||
@@ -81,7 +89,8 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
|||||||
background: Stack(
|
background: Stack(
|
||||||
alignment: Alignment.topCenter,
|
alignment: Alignment.topCenter,
|
||||||
children: [
|
children: [
|
||||||
BlocBuilder<DocumentDetailsCubit, DocumentDetailsState>(
|
BlocBuilder<DocumentDetailsCubit,
|
||||||
|
DocumentDetailsState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return Positioned.fill(
|
return Positioned.fill(
|
||||||
child: DocumentPreview(
|
child: DocumentPreview(
|
||||||
@@ -97,8 +106,14 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
|||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
gradient: LinearGradient(
|
gradient: LinearGradient(
|
||||||
colors: [
|
colors: [
|
||||||
Theme.of(context).colorScheme.background.withOpacity(0.8),
|
Theme.of(context)
|
||||||
Theme.of(context).colorScheme.background.withOpacity(0.5),
|
.colorScheme
|
||||||
|
.background
|
||||||
|
.withOpacity(0.8),
|
||||||
|
Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.background
|
||||||
|
.withOpacity(0.5),
|
||||||
Colors.transparent,
|
Colors.transparent,
|
||||||
Colors.transparent,
|
Colors.transparent,
|
||||||
Colors.transparent,
|
Colors.transparent,
|
||||||
@@ -120,7 +135,9 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
|||||||
child: Text(
|
child: Text(
|
||||||
S.of(context)!.overview,
|
S.of(context)!.overview,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Theme.of(context).colorScheme.onPrimaryContainer,
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.onPrimaryContainer,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -128,7 +145,9 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
|||||||
child: Text(
|
child: Text(
|
||||||
S.of(context)!.content,
|
S.of(context)!.content,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Theme.of(context).colorScheme.onPrimaryContainer,
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.onPrimaryContainer,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -136,7 +155,9 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
|||||||
child: Text(
|
child: Text(
|
||||||
S.of(context)!.metaData,
|
S.of(context)!.metaData,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Theme.of(context).colorScheme.onPrimaryContainer,
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.onPrimaryContainer,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -144,7 +165,9 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
|||||||
child: Text(
|
child: Text(
|
||||||
S.of(context)!.similarDocuments,
|
S.of(context)!.similarDocuments,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Theme.of(context).colorScheme.onPrimaryContainer,
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.onPrimaryContainer,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -153,7 +176,9 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
|||||||
child: Text(
|
child: Text(
|
||||||
"Permissions",
|
"Permissions",
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Theme.of(context).colorScheme.onPrimaryContainer,
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.onPrimaryContainer,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -182,7 +207,8 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
|||||||
CustomScrollView(
|
CustomScrollView(
|
||||||
slivers: [
|
slivers: [
|
||||||
SliverOverlapInjector(
|
SliverOverlapInjector(
|
||||||
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
|
handle: NestedScrollView
|
||||||
|
.sliverOverlapAbsorberHandleFor(context),
|
||||||
),
|
),
|
||||||
DocumentOverviewWidget(
|
DocumentOverviewWidget(
|
||||||
document: state.document,
|
document: state.document,
|
||||||
@@ -198,7 +224,8 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
|||||||
CustomScrollView(
|
CustomScrollView(
|
||||||
slivers: [
|
slivers: [
|
||||||
SliverOverlapInjector(
|
SliverOverlapInjector(
|
||||||
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
|
handle: NestedScrollView
|
||||||
|
.sliverOverlapAbsorberHandleFor(context),
|
||||||
),
|
),
|
||||||
DocumentContentWidget(
|
DocumentContentWidget(
|
||||||
isFullContentLoaded: state.isFullContentLoaded,
|
isFullContentLoaded: state.isFullContentLoaded,
|
||||||
@@ -211,7 +238,8 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
|||||||
CustomScrollView(
|
CustomScrollView(
|
||||||
slivers: [
|
slivers: [
|
||||||
SliverOverlapInjector(
|
SliverOverlapInjector(
|
||||||
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
|
handle: NestedScrollView
|
||||||
|
.sliverOverlapAbsorberHandleFor(context),
|
||||||
),
|
),
|
||||||
DocumentMetaDataWidget(
|
DocumentMetaDataWidget(
|
||||||
document: state.document,
|
document: state.document,
|
||||||
@@ -223,7 +251,8 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
|||||||
controller: _pagingScrollController,
|
controller: _pagingScrollController,
|
||||||
slivers: [
|
slivers: [
|
||||||
SliverOverlapInjector(
|
SliverOverlapInjector(
|
||||||
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
|
handle: NestedScrollView
|
||||||
|
.sliverOverlapAbsorberHandleFor(context),
|
||||||
),
|
),
|
||||||
SimilarDocumentsView(
|
SimilarDocumentsView(
|
||||||
pagingScrollController: _pagingScrollController,
|
pagingScrollController: _pagingScrollController,
|
||||||
@@ -235,7 +264,8 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
|||||||
controller: _pagingScrollController,
|
controller: _pagingScrollController,
|
||||||
slivers: [
|
slivers: [
|
||||||
SliverOverlapInjector(
|
SliverOverlapInjector(
|
||||||
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
|
handle: NestedScrollView
|
||||||
|
.sliverOverlapAbsorberHandleFor(context),
|
||||||
),
|
),
|
||||||
DocumentPermissionsWidget(
|
DocumentPermissionsWidget(
|
||||||
document: state.document,
|
document: state.document,
|
||||||
@@ -289,15 +319,16 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
|||||||
final isConnected = connectivityState.isConnected;
|
final isConnected = connectivityState.isConnected;
|
||||||
|
|
||||||
final canDelete = isConnected &&
|
final canDelete = isConnected &&
|
||||||
LocalUserAccount.current.paperlessUser
|
LocalUserAccount.current.paperlessUser.hasPermission(
|
||||||
.hasPermission(PermissionAction.delete, PermissionTarget.document);
|
PermissionAction.delete, PermissionTarget.document);
|
||||||
return Row(
|
return Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
IconButton(
|
IconButton(
|
||||||
tooltip: S.of(context)!.deleteDocumentTooltip,
|
tooltip: S.of(context)!.deleteDocumentTooltip,
|
||||||
icon: const Icon(Icons.delete),
|
icon: const Icon(Icons.delete),
|
||||||
onPressed: canDelete ? () => _onDelete(state.document) : null,
|
onPressed:
|
||||||
|
canDelete ? () => _onDelete(state.document) : null,
|
||||||
).paddedSymmetrically(horizontal: 4),
|
).paddedSymmetrically(horizontal: 4),
|
||||||
DocumentDownloadButton(
|
DocumentDownloadButton(
|
||||||
document: state.document,
|
document: state.document,
|
||||||
@@ -307,7 +338,8 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
|||||||
IconButton(
|
IconButton(
|
||||||
tooltip: S.of(context)!.previewTooltip,
|
tooltip: S.of(context)!.previewTooltip,
|
||||||
icon: const Icon(Icons.visibility),
|
icon: const Icon(Icons.visibility),
|
||||||
onPressed: (isConnected) ? () => _onOpen(state.document) : null,
|
onPressed:
|
||||||
|
(isConnected) ? () => _onOpen(state.document) : null,
|
||||||
).paddedOnly(right: 4.0),
|
).paddedOnly(right: 4.0),
|
||||||
IconButton(
|
IconButton(
|
||||||
tooltip: S.of(context)!.openInSystemViewer,
|
tooltip: S.of(context)!.openInSystemViewer,
|
||||||
@@ -317,7 +349,8 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
|||||||
DocumentShareButton(document: state.document),
|
DocumentShareButton(document: state.document),
|
||||||
IconButton(
|
IconButton(
|
||||||
tooltip: S.of(context)!.print, //TODO: INTL
|
tooltip: S.of(context)!.print, //TODO: INTL
|
||||||
onPressed: () => context.read<DocumentDetailsCubit>().printDocument(),
|
onPressed: () =>
|
||||||
|
context.read<DocumentDetailsCubit>().printDocument(),
|
||||||
icon: const Icon(Icons.print),
|
icon: const Icon(Icons.print),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -350,7 +383,8 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
child: BlocListener<DocumentEditCubit, DocumentEditState>(
|
child: BlocListener<DocumentEditCubit, DocumentEditState>(
|
||||||
listenWhen: (previous, current) => previous.document != current.document,
|
listenWhen: (previous, current) =>
|
||||||
|
previous.document != current.document,
|
||||||
listener: (context, state) {
|
listener: (context, state) {
|
||||||
cubit.replace(state.document);
|
cubit.replace(state.document);
|
||||||
},
|
},
|
||||||
@@ -370,7 +404,8 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _onOpenFileInSystemViewer() async {
|
void _onOpenFileInSystemViewer() async {
|
||||||
final status = await context.read<DocumentDetailsCubit>().openDocumentInSystemViewer();
|
final status =
|
||||||
|
await context.read<DocumentDetailsCubit>().openDocumentInSystemViewer();
|
||||||
if (status == ResultType.done) return;
|
if (status == ResultType.done) return;
|
||||||
if (status == ResultType.noAppToOpen) {
|
if (status == ResultType.noAppToOpen) {
|
||||||
showGenericError(context, S.of(context)!.noAppToDisplayPDFFilesFound);
|
showGenericError(context, S.of(context)!.noAppToDisplayPDFFilesFound);
|
||||||
@@ -379,14 +414,16 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
|||||||
showGenericError(context, translateError(context, ErrorCode.unknown));
|
showGenericError(context, translateError(context, ErrorCode.unknown));
|
||||||
}
|
}
|
||||||
if (status == ResultType.permissionDenied) {
|
if (status == ResultType.permissionDenied) {
|
||||||
showGenericError(context, S.of(context)!.couldNotOpenFilePermissionDenied);
|
showGenericError(
|
||||||
|
context, S.of(context)!.couldNotOpenFilePermissionDenied);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onDelete(DocumentModel document) async {
|
void _onDelete(DocumentModel document) async {
|
||||||
final delete = await showDialog(
|
final delete = await showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => DeleteDocumentConfirmationDialog(document: document),
|
builder: (context) =>
|
||||||
|
DeleteDocumentConfirmationDialog(document: document),
|
||||||
) ??
|
) ??
|
||||||
false;
|
false;
|
||||||
if (delete) {
|
if (delete) {
|
||||||
@@ -406,7 +443,8 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
|||||||
Navigator.of(context).push(
|
Navigator.of(context).push(
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (_) => DocumentView(
|
builder: (_) => DocumentView(
|
||||||
documentBytes: context.read<PaperlessDocumentsApi>().download(document),
|
documentBytes:
|
||||||
|
context.read<PaperlessDocumentsApi>().download(document),
|
||||||
title: document.title,
|
title: document.title,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -18,7 +18,8 @@ class ArchiveSerialNumberField extends StatefulWidget {
|
|||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<ArchiveSerialNumberField> createState() => _ArchiveSerialNumberFieldState();
|
State<ArchiveSerialNumberField> createState() =>
|
||||||
|
_ArchiveSerialNumberFieldState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ArchiveSerialNumberFieldState extends State<ArchiveSerialNumberField> {
|
class _ArchiveSerialNumberFieldState extends State<ArchiveSerialNumberField> {
|
||||||
@@ -39,21 +40,25 @@ class _ArchiveSerialNumberFieldState extends State<ArchiveSerialNumberField> {
|
|||||||
void _clearButtonListener() {
|
void _clearButtonListener() {
|
||||||
setState(() {
|
setState(() {
|
||||||
_showClearButton = _asnEditingController.text.isNotEmpty;
|
_showClearButton = _asnEditingController.text.isNotEmpty;
|
||||||
_canUpdate = int.tryParse(_asnEditingController.text) != widget.document.archiveSerialNumber;
|
_canUpdate = int.tryParse(_asnEditingController.text) !=
|
||||||
|
widget.document.archiveSerialNumber;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final userCanEditDocument = LocalUserAccount.current.paperlessUser.hasPermission(
|
final userCanEditDocument =
|
||||||
|
LocalUserAccount.current.paperlessUser.hasPermission(
|
||||||
PermissionAction.change,
|
PermissionAction.change,
|
||||||
PermissionTarget.document,
|
PermissionTarget.document,
|
||||||
);
|
);
|
||||||
return BlocListener<DocumentDetailsCubit, DocumentDetailsState>(
|
return BlocListener<DocumentDetailsCubit, DocumentDetailsState>(
|
||||||
listenWhen: (previous, current) =>
|
listenWhen: (previous, current) =>
|
||||||
previous.document.archiveSerialNumber != current.document.archiveSerialNumber,
|
previous.document.archiveSerialNumber !=
|
||||||
|
current.document.archiveSerialNumber,
|
||||||
listener: (context, state) {
|
listener: (context, state) {
|
||||||
_asnEditingController.text = state.document.archiveSerialNumber?.toString() ?? '';
|
_asnEditingController.text =
|
||||||
|
state.document.archiveSerialNumber?.toString() ?? '';
|
||||||
setState(() {
|
setState(() {
|
||||||
_canUpdate = false;
|
_canUpdate = false;
|
||||||
});
|
});
|
||||||
@@ -80,13 +85,17 @@ class _ArchiveSerialNumberFieldState extends State<ArchiveSerialNumberField> {
|
|||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.clear),
|
icon: const Icon(Icons.clear),
|
||||||
color: Theme.of(context).colorScheme.primary,
|
color: Theme.of(context).colorScheme.primary,
|
||||||
onPressed: userCanEditDocument ? _asnEditingController.clear : null,
|
onPressed: userCanEditDocument
|
||||||
|
? _asnEditingController.clear
|
||||||
|
: null,
|
||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.plus_one_rounded),
|
icon: const Icon(Icons.plus_one_rounded),
|
||||||
color: Theme.of(context).colorScheme.primary,
|
color: Theme.of(context).colorScheme.primary,
|
||||||
onPressed:
|
onPressed:
|
||||||
context.watchInternetConnection && !_showClearButton ? _onAutoAssign : null,
|
context.watchInternetConnection && !_showClearButton
|
||||||
|
? _onAutoAssign
|
||||||
|
: null,
|
||||||
).paddedOnly(right: 8),
|
).paddedOnly(right: 8),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -97,7 +106,9 @@ class _ArchiveSerialNumberFieldState extends State<ArchiveSerialNumberField> {
|
|||||||
),
|
),
|
||||||
TextButton.icon(
|
TextButton.icon(
|
||||||
icon: const Icon(Icons.done),
|
icon: const Icon(Icons.done),
|
||||||
onPressed: context.watchInternetConnection && _canUpdate ? _onSubmitted : null,
|
onPressed: context.watchInternetConnection && _canUpdate
|
||||||
|
? _onSubmitted
|
||||||
|
: null,
|
||||||
label: Text(S.of(context)!.save),
|
label: Text(S.of(context)!.save),
|
||||||
).padded(),
|
).padded(),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -24,7 +24,8 @@ class DetailsItem extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
DetailsItem.text(
|
DetailsItem.text(
|
||||||
String text, {super.key,
|
String text, {
|
||||||
|
super.key,
|
||||||
required this.label,
|
required this.label,
|
||||||
required BuildContext context,
|
required BuildContext context,
|
||||||
}) : content = Text(
|
}) : content = Text(
|
||||||
|
|||||||
@@ -44,14 +44,16 @@ class _DocumentDownloadButtonState extends State<DocumentDownloadButton> {
|
|||||||
width: 16,
|
width: 16,
|
||||||
)
|
)
|
||||||
: const Icon(Icons.download),
|
: const Icon(Icons.download),
|
||||||
onPressed:
|
onPressed: widget.document != null && widget.enabled
|
||||||
widget.document != null && widget.enabled ? () => _onDownload(widget.document!) : null,
|
? () => _onDownload(widget.document!)
|
||||||
|
: null,
|
||||||
).paddedOnly(right: 4);
|
).paddedOnly(right: 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onDownload(DocumentModel document) async {
|
Future<void> _onDownload(DocumentModel document) async {
|
||||||
try {
|
try {
|
||||||
final globalSettings = Hive.box<GlobalSettings>(HiveBoxes.globalSettings).getValue()!;
|
final globalSettings =
|
||||||
|
Hive.box<GlobalSettings>(HiveBoxes.globalSettings).getValue()!;
|
||||||
bool original;
|
bool original;
|
||||||
|
|
||||||
switch (globalSettings.defaultDownloadType) {
|
switch (globalSettings.defaultDownloadType) {
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ class DocumentPermissionsWidget extends StatefulWidget {
|
|||||||
const DocumentPermissionsWidget({super.key, required this.document});
|
const DocumentPermissionsWidget({super.key, required this.document});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<DocumentPermissionsWidget> createState() => _DocumentPermissionsWidgetState();
|
State<DocumentPermissionsWidget> createState() =>
|
||||||
|
_DocumentPermissionsWidgetState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _DocumentPermissionsWidgetState extends State<DocumentPermissionsWidget> {
|
class _DocumentPermissionsWidgetState extends State<DocumentPermissionsWidget> {
|
||||||
|
|||||||
@@ -43,14 +43,16 @@ class _DocumentShareButtonState extends State<DocumentShareButton> {
|
|||||||
child: CircularProgressIndicator(),
|
child: CircularProgressIndicator(),
|
||||||
)
|
)
|
||||||
: const Icon(Icons.share),
|
: const Icon(Icons.share),
|
||||||
onPressed:
|
onPressed: widget.document != null && widget.enabled
|
||||||
widget.document != null && widget.enabled ? () => _onShare(widget.document!) : null,
|
? () => _onShare(widget.document!)
|
||||||
|
: null,
|
||||||
).paddedOnly(right: 4);
|
).paddedOnly(right: 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onShare(DocumentModel document) async {
|
Future<void> _onShare(DocumentModel document) async {
|
||||||
try {
|
try {
|
||||||
final globalSettings = Hive.box<GlobalSettings>(HiveBoxes.globalSettings).getValue()!;
|
final globalSettings =
|
||||||
|
Hive.box<GlobalSettings>(HiveBoxes.globalSettings).getValue()!;
|
||||||
bool original;
|
bool original;
|
||||||
|
|
||||||
switch (globalSettings.defaultShareType) {
|
switch (globalSettings.defaultShareType) {
|
||||||
|
|||||||
@@ -49,8 +49,8 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_filteredSuggestions =
|
_filteredSuggestions = widget.suggestions
|
||||||
widget.suggestions?.documentDifference(context.read<DocumentEditCubit>().state.document);
|
?.documentDifference(context.read<DocumentEditCubit>().state.document);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -94,14 +94,16 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
|
|||||||
ListView(
|
ListView(
|
||||||
children: [
|
children: [
|
||||||
_buildTitleFormField(state.document.title).padded(),
|
_buildTitleFormField(state.document.title).padded(),
|
||||||
_buildCreatedAtFormField(state.document.created).padded(),
|
_buildCreatedAtFormField(state.document.created)
|
||||||
|
.padded(),
|
||||||
// Correspondent form field
|
// Correspondent form field
|
||||||
Column(
|
Column(
|
||||||
children: [
|
children: [
|
||||||
LabelFormField<Correspondent>(
|
LabelFormField<Correspondent>(
|
||||||
showAnyAssignedOption: false,
|
showAnyAssignedOption: false,
|
||||||
showNotAssignedOption: false,
|
showNotAssignedOption: false,
|
||||||
addLabelPageBuilder: (initialValue) => RepositoryProvider.value(
|
addLabelPageBuilder: (initialValue) =>
|
||||||
|
RepositoryProvider.value(
|
||||||
value: context.read<LabelRepository>(),
|
value: context.read<LabelRepository>(),
|
||||||
child: AddCorrespondentPage(
|
child: AddCorrespondentPage(
|
||||||
initialName: initialValue,
|
initialName: initialValue,
|
||||||
@@ -109,26 +111,39 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
|
|||||||
),
|
),
|
||||||
addLabelText: S.of(context)!.addCorrespondent,
|
addLabelText: S.of(context)!.addCorrespondent,
|
||||||
labelText: S.of(context)!.correspondent,
|
labelText: S.of(context)!.correspondent,
|
||||||
options: context.watch<DocumentEditCubit>().state.correspondents,
|
options: context
|
||||||
initialValue: state.document.correspondent != null
|
.watch<DocumentEditCubit>()
|
||||||
? IdQueryParameter.fromId(state.document.correspondent!)
|
.state
|
||||||
|
.correspondents,
|
||||||
|
initialValue:
|
||||||
|
state.document.correspondent != null
|
||||||
|
? IdQueryParameter.fromId(
|
||||||
|
state.document.correspondent!)
|
||||||
: const IdQueryParameter.unset(),
|
: const IdQueryParameter.unset(),
|
||||||
name: fkCorrespondent,
|
name: fkCorrespondent,
|
||||||
prefixIcon: const Icon(Icons.person_outlined),
|
prefixIcon: const Icon(Icons.person_outlined),
|
||||||
allowSelectUnassigned: true,
|
allowSelectUnassigned: true,
|
||||||
canCreateNewLabel:
|
canCreateNewLabel: LocalUserAccount
|
||||||
LocalUserAccount.current.paperlessUser.hasPermission(
|
.current.paperlessUser
|
||||||
|
.hasPermission(
|
||||||
PermissionAction.add,
|
PermissionAction.add,
|
||||||
PermissionTarget.correspondent,
|
PermissionTarget.correspondent,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (_filteredSuggestions?.hasSuggestedCorrespondents ?? false)
|
if (_filteredSuggestions
|
||||||
|
?.hasSuggestedCorrespondents ??
|
||||||
|
false)
|
||||||
_buildSuggestionsSkeleton<int>(
|
_buildSuggestionsSkeleton<int>(
|
||||||
suggestions: _filteredSuggestions!.correspondents,
|
suggestions:
|
||||||
itemBuilder: (context, itemData) => ActionChip(
|
_filteredSuggestions!.correspondents,
|
||||||
label: Text(state.correspondents[itemData]!.name),
|
itemBuilder: (context, itemData) =>
|
||||||
|
ActionChip(
|
||||||
|
label: Text(
|
||||||
|
state.correspondents[itemData]!.name),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
_formKey.currentState?.fields[fkCorrespondent]?.didChange(
|
_formKey
|
||||||
|
.currentState?.fields[fkCorrespondent]
|
||||||
|
?.didChange(
|
||||||
IdQueryParameter.fromId(itemData),
|
IdQueryParameter.fromId(itemData),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -142,34 +157,45 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
|
|||||||
LabelFormField<DocumentType>(
|
LabelFormField<DocumentType>(
|
||||||
showAnyAssignedOption: false,
|
showAnyAssignedOption: false,
|
||||||
showNotAssignedOption: false,
|
showNotAssignedOption: false,
|
||||||
addLabelPageBuilder: (currentInput) => RepositoryProvider.value(
|
addLabelPageBuilder: (currentInput) =>
|
||||||
|
RepositoryProvider.value(
|
||||||
value: context.read<LabelRepository>(),
|
value: context.read<LabelRepository>(),
|
||||||
child: AddDocumentTypePage(
|
child: AddDocumentTypePage(
|
||||||
initialName: currentInput,
|
initialName: currentInput,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
canCreateNewLabel:
|
canCreateNewLabel: LocalUserAccount
|
||||||
LocalUserAccount.current.paperlessUser.hasPermission(
|
.current.paperlessUser
|
||||||
|
.hasPermission(
|
||||||
PermissionAction.add,
|
PermissionAction.add,
|
||||||
PermissionTarget.documentType,
|
PermissionTarget.documentType,
|
||||||
),
|
),
|
||||||
addLabelText: S.of(context)!.addDocumentType,
|
addLabelText: S.of(context)!.addDocumentType,
|
||||||
labelText: S.of(context)!.documentType,
|
labelText: S.of(context)!.documentType,
|
||||||
initialValue: state.document.documentType != null
|
initialValue:
|
||||||
? IdQueryParameter.fromId(state.document.documentType!)
|
state.document.documentType != null
|
||||||
|
? IdQueryParameter.fromId(
|
||||||
|
state.document.documentType!)
|
||||||
: const IdQueryParameter.unset(),
|
: const IdQueryParameter.unset(),
|
||||||
options: state.documentTypes,
|
options: state.documentTypes,
|
||||||
name: _DocumentEditPageState.fkDocumentType,
|
name: _DocumentEditPageState.fkDocumentType,
|
||||||
prefixIcon: const Icon(Icons.description_outlined),
|
prefixIcon:
|
||||||
|
const Icon(Icons.description_outlined),
|
||||||
allowSelectUnassigned: true,
|
allowSelectUnassigned: true,
|
||||||
),
|
),
|
||||||
if (_filteredSuggestions?.hasSuggestedDocumentTypes ?? false)
|
if (_filteredSuggestions
|
||||||
|
?.hasSuggestedDocumentTypes ??
|
||||||
|
false)
|
||||||
_buildSuggestionsSkeleton<int>(
|
_buildSuggestionsSkeleton<int>(
|
||||||
suggestions: _filteredSuggestions!.documentTypes,
|
suggestions:
|
||||||
itemBuilder: (context, itemData) => ActionChip(
|
_filteredSuggestions!.documentTypes,
|
||||||
label: Text(state.documentTypes[itemData]!.name),
|
itemBuilder: (context, itemData) =>
|
||||||
onPressed: () =>
|
ActionChip(
|
||||||
_formKey.currentState?.fields[fkDocumentType]?.didChange(
|
label: Text(
|
||||||
|
state.documentTypes[itemData]!.name),
|
||||||
|
onPressed: () => _formKey
|
||||||
|
.currentState?.fields[fkDocumentType]
|
||||||
|
?.didChange(
|
||||||
IdQueryParameter.fromId(itemData),
|
IdQueryParameter.fromId(itemData),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -182,12 +208,15 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
|
|||||||
LabelFormField<StoragePath>(
|
LabelFormField<StoragePath>(
|
||||||
showAnyAssignedOption: false,
|
showAnyAssignedOption: false,
|
||||||
showNotAssignedOption: false,
|
showNotAssignedOption: false,
|
||||||
addLabelPageBuilder: (initialValue) => RepositoryProvider.value(
|
addLabelPageBuilder: (initialValue) =>
|
||||||
|
RepositoryProvider.value(
|
||||||
value: context.read<LabelRepository>(),
|
value: context.read<LabelRepository>(),
|
||||||
child: AddStoragePathPage(initalName: initialValue),
|
child: AddStoragePathPage(
|
||||||
|
initalName: initialValue),
|
||||||
),
|
),
|
||||||
canCreateNewLabel:
|
canCreateNewLabel: LocalUserAccount
|
||||||
LocalUserAccount.current.paperlessUser.hasPermission(
|
.current.paperlessUser
|
||||||
|
.hasPermission(
|
||||||
PermissionAction.add,
|
PermissionAction.add,
|
||||||
PermissionTarget.storagePath,
|
PermissionTarget.storagePath,
|
||||||
),
|
),
|
||||||
@@ -195,7 +224,8 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
|
|||||||
labelText: S.of(context)!.storagePath,
|
labelText: S.of(context)!.storagePath,
|
||||||
options: state.storagePaths,
|
options: state.storagePaths,
|
||||||
initialValue: state.document.storagePath != null
|
initialValue: state.document.storagePath != null
|
||||||
? IdQueryParameter.fromId(state.document.storagePath!)
|
? IdQueryParameter.fromId(
|
||||||
|
state.document.storagePath!)
|
||||||
: const IdQueryParameter.unset(),
|
: const IdQueryParameter.unset(),
|
||||||
name: fkStoragePath,
|
name: fkStoragePath,
|
||||||
prefixIcon: const Icon(Icons.folder_outlined),
|
prefixIcon: const Icon(Icons.folder_outlined),
|
||||||
@@ -220,7 +250,8 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
|
|||||||
.isNotEmpty ??
|
.isNotEmpty ??
|
||||||
false)
|
false)
|
||||||
_buildSuggestionsSkeleton<int>(
|
_buildSuggestionsSkeleton<int>(
|
||||||
suggestions: (_filteredSuggestions?.tags.toSet() ?? {}),
|
suggestions:
|
||||||
|
(_filteredSuggestions?.tags.toSet() ?? {}),
|
||||||
itemBuilder: (context, itemData) {
|
itemBuilder: (context, itemData) {
|
||||||
final tag = state.tags[itemData]!;
|
final tag = state.tags[itemData]!;
|
||||||
return ActionChip(
|
return ActionChip(
|
||||||
@@ -230,13 +261,17 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
|
|||||||
),
|
),
|
||||||
backgroundColor: tag.color,
|
backgroundColor: tag.color,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
final currentTags =
|
final currentTags = _formKey.currentState
|
||||||
_formKey.currentState?.fields[fkTags]?.value as TagsQuery;
|
?.fields[fkTags]?.value as TagsQuery;
|
||||||
_formKey.currentState?.fields[fkTags]?.didChange(
|
_formKey.currentState?.fields[fkTags]
|
||||||
|
?.didChange(
|
||||||
currentTags.maybeWhen(
|
currentTags.maybeWhen(
|
||||||
ids: (include, exclude) => TagsQuery.ids(
|
ids: (include, exclude) =>
|
||||||
include: [...include, itemData], exclude: exclude),
|
TagsQuery.ids(
|
||||||
orElse: () => TagsQuery.ids(include: [itemData]),
|
include: [...include, itemData],
|
||||||
|
exclude: exclude),
|
||||||
|
orElse: () =>
|
||||||
|
TagsQuery.ids(include: [itemData]),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -278,12 +313,12 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
|
|||||||
var mergedDocument = document.copyWith(
|
var mergedDocument = document.copyWith(
|
||||||
title: values[fkTitle],
|
title: values[fkTitle],
|
||||||
created: values[fkCreatedDate],
|
created: values[fkCreatedDate],
|
||||||
documentType: () =>
|
documentType: () => (values[fkDocumentType] as IdQueryParameter)
|
||||||
(values[fkDocumentType] as IdQueryParameter).whenOrNull(fromId: (id) => id),
|
.whenOrNull(fromId: (id) => id),
|
||||||
correspondent: () =>
|
correspondent: () => (values[fkCorrespondent] as IdQueryParameter)
|
||||||
(values[fkCorrespondent] as IdQueryParameter).whenOrNull(fromId: (id) => id),
|
.whenOrNull(fromId: (id) => id),
|
||||||
storagePath: () =>
|
storagePath: () => (values[fkStoragePath] as IdQueryParameter)
|
||||||
(values[fkStoragePath] as IdQueryParameter).whenOrNull(fromId: (id) => id),
|
.whenOrNull(fromId: (id) => id),
|
||||||
tags: (values[fkTags] as IdsTagsQuery).include,
|
tags: (values[fkTags] as IdsTagsQuery).include,
|
||||||
content: values[fkContent],
|
content: values[fkContent],
|
||||||
);
|
);
|
||||||
@@ -340,7 +375,8 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
|
|||||||
suggestions: _filteredSuggestions!.dates,
|
suggestions: _filteredSuggestions!.dates,
|
||||||
itemBuilder: (context, itemData) => ActionChip(
|
itemBuilder: (context, itemData) => ActionChip(
|
||||||
label: Text(DateFormat.yMMMd().format(itemData)),
|
label: Text(DateFormat.yMMMd().format(itemData)),
|
||||||
onPressed: () => _formKey.currentState?.fields[fkCreatedDate]?.didChange(itemData),
|
onPressed: () => _formKey.currentState?.fields[fkCreatedDate]
|
||||||
|
?.didChange(itemData),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -369,7 +405,8 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
|
|||||||
itemBuilder: (context, index) => ColoredChipWrapper(
|
itemBuilder: (context, index) => ColoredChipWrapper(
|
||||||
child: itemBuilder(context, suggestions.elementAt(index)),
|
child: itemBuilder(context, suggestions.elementAt(index)),
|
||||||
),
|
),
|
||||||
separatorBuilder: (BuildContext context, int index) => const SizedBox(width: 4.0),
|
separatorBuilder: (BuildContext context, int index) =>
|
||||||
|
const SizedBox(width: 4.0),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -405,7 +442,6 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
|
|||||||
// final List<Option> options;
|
// final List<Option> options;
|
||||||
// final void Function(Option option) onAddOption;
|
// final void Function(Option option) onAddOption;
|
||||||
|
|
||||||
|
|
||||||
// const OptionsFormField({
|
// const OptionsFormField({
|
||||||
// super.key,
|
// super.key,
|
||||||
// required this.options,
|
// required this.options,
|
||||||
|
|||||||
@@ -35,9 +35,12 @@ class ScannerPage extends StatefulWidget {
|
|||||||
State<ScannerPage> createState() => _ScannerPageState();
|
State<ScannerPage> createState() => _ScannerPageState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ScannerPageState extends State<ScannerPage> with SingleTickerProviderStateMixin {
|
class _ScannerPageState extends State<ScannerPage>
|
||||||
final SliverOverlapAbsorberHandle searchBarHandle = SliverOverlapAbsorberHandle();
|
with SingleTickerProviderStateMixin {
|
||||||
final SliverOverlapAbsorberHandle actionsHandle = SliverOverlapAbsorberHandle();
|
final SliverOverlapAbsorberHandle searchBarHandle =
|
||||||
|
SliverOverlapAbsorberHandle();
|
||||||
|
final SliverOverlapAbsorberHandle actionsHandle =
|
||||||
|
SliverOverlapAbsorberHandle();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -120,7 +123,6 @@ class _ScannerPageState extends State<ScannerPage> with SingleTickerProviderStat
|
|||||||
? () => Navigator.of(context).push(
|
? () => Navigator.of(context).push(
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) => DocumentView(
|
builder: (context) => DocumentView(
|
||||||
|
|
||||||
documentBytes: _assembleFileBytes(
|
documentBytes: _assembleFileBytes(
|
||||||
state,
|
state,
|
||||||
forcePdf: true,
|
forcePdf: true,
|
||||||
@@ -175,7 +177,8 @@ class _ScannerPageState extends State<ScannerPage> with SingleTickerProviderStat
|
|||||||
final success = await EdgeDetection.detectEdge(file.path);
|
final success = await EdgeDetection.detectEdge(file.path);
|
||||||
if (!success) {
|
if (!success) {
|
||||||
if (kDebugMode) {
|
if (kDebugMode) {
|
||||||
dev.log('[ScannerPage] Scan either not successful or canceled by user.');
|
dev.log(
|
||||||
|
'[ScannerPage] Scan either not successful or canceled by user.');
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -197,7 +200,9 @@ class _ScannerPageState extends State<ScannerPage> with SingleTickerProviderStat
|
|||||||
if ((uploadResult?.success ?? false) && uploadResult?.taskId != null) {
|
if ((uploadResult?.success ?? false) && uploadResult?.taskId != null) {
|
||||||
// For paperless version older than 1.11.3, task id will always be null!
|
// For paperless version older than 1.11.3, task id will always be null!
|
||||||
context.read<DocumentScannerCubit>().reset();
|
context.read<DocumentScannerCubit>().reset();
|
||||||
context.read<TaskStatusCubit>().listenToTaskChanges(uploadResult!.taskId!);
|
context
|
||||||
|
.read<TaskStatusCubit>()
|
||||||
|
.listenToTaskChanges(uploadResult!.taskId!);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,8 @@ import 'package:paperless_mobile/features/settings/model/view_type.dart';
|
|||||||
part 'document_search_cubit.g.dart';
|
part 'document_search_cubit.g.dart';
|
||||||
part 'document_search_state.dart';
|
part 'document_search_state.dart';
|
||||||
|
|
||||||
class DocumentSearchCubit extends Cubit<DocumentSearchState> with DocumentPagingBlocMixin {
|
class DocumentSearchCubit extends Cubit<DocumentSearchState>
|
||||||
|
with DocumentPagingBlocMixin {
|
||||||
@override
|
@override
|
||||||
final PaperlessDocumentsApi api;
|
final PaperlessDocumentsApi api;
|
||||||
|
|
||||||
@@ -23,7 +24,8 @@ class DocumentSearchCubit extends Cubit<DocumentSearchState> with DocumentPaging
|
|||||||
this.api,
|
this.api,
|
||||||
this.notifier,
|
this.notifier,
|
||||||
this._userAppState,
|
this._userAppState,
|
||||||
) : super(DocumentSearchState(searchHistory: _userAppState.documentSearchHistory)) {
|
) : super(DocumentSearchState(
|
||||||
|
searchHistory: _userAppState.documentSearchHistory)) {
|
||||||
notifier.addListener(
|
notifier.addListener(
|
||||||
this,
|
this,
|
||||||
onDeleted: remove,
|
onDeleted: remove,
|
||||||
@@ -46,7 +48,8 @@ class DocumentSearchCubit extends Cubit<DocumentSearchState> with DocumentPaging
|
|||||||
state.copyWith(
|
state.copyWith(
|
||||||
searchHistory: [
|
searchHistory: [
|
||||||
query,
|
query,
|
||||||
...state.searchHistory.whereNot((previousQuery) => previousQuery == query)
|
...state.searchHistory
|
||||||
|
.whereNot((previousQuery) => previousQuery == query)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -62,7 +65,9 @@ class DocumentSearchCubit extends Cubit<DocumentSearchState> with DocumentPaging
|
|||||||
void removeHistoryEntry(String entry) {
|
void removeHistoryEntry(String entry) {
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
searchHistory: state.searchHistory.whereNot((element) => element == entry).toList(),
|
searchHistory: state.searchHistory
|
||||||
|
.whereNot((element) => element == entry)
|
||||||
|
.toList(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
_userAppState
|
_userAppState
|
||||||
|
|||||||
@@ -65,7 +65,10 @@ class _DocumentSearchBarState extends State<DocumentSearchBar> {
|
|||||||
Flexible(
|
Flexible(
|
||||||
child: Text(
|
child: Text(
|
||||||
S.of(context)!.searchDocuments,
|
S.of(context)!.searchDocuments,
|
||||||
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
|
style: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.bodyLarge
|
||||||
|
?.copyWith(
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
color: Theme.of(context).hintColor,
|
color: Theme.of(context).hintColor,
|
||||||
),
|
),
|
||||||
@@ -112,7 +115,9 @@ class _DocumentSearchBarState extends State<DocumentSearchBar> {
|
|||||||
icon: GlobalSettingsBuilder(
|
icon: GlobalSettingsBuilder(
|
||||||
builder: (context, settings) {
|
builder: (context, settings) {
|
||||||
return ValueListenableBuilder(
|
return ValueListenableBuilder(
|
||||||
valueListenable: Hive.box<LocalUserAccount>(HiveBoxes.localUserAccount).listenable(),
|
valueListenable:
|
||||||
|
Hive.box<LocalUserAccount>(HiveBoxes.localUserAccount)
|
||||||
|
.listenable(),
|
||||||
builder: (context, box, _) {
|
builder: (context, box, _) {
|
||||||
final account = box.get(settings.currentLoggedInUser!)!;
|
final account = box.get(settings.currentLoggedInUser!)!;
|
||||||
return UserAvatar(account: account);
|
return UserAvatar(account: account);
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:bloc/bloc.dart';
|
import 'package:bloc/bloc.dart';
|
||||||
|
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
|
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
|
||||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||||
|
|
||||||
part 'document_upload_state.dart';
|
part 'document_upload_state.dart';
|
||||||
@@ -12,9 +14,13 @@ class DocumentUploadCubit extends Cubit<DocumentUploadState> {
|
|||||||
final PaperlessDocumentsApi _documentApi;
|
final PaperlessDocumentsApi _documentApi;
|
||||||
|
|
||||||
final LabelRepository _labelRepository;
|
final LabelRepository _labelRepository;
|
||||||
|
final Connectivity _connectivity;
|
||||||
|
|
||||||
DocumentUploadCubit(this._labelRepository, this._documentApi)
|
DocumentUploadCubit(
|
||||||
: super(const DocumentUploadState()) {
|
this._labelRepository,
|
||||||
|
this._documentApi,
|
||||||
|
this._connectivity,
|
||||||
|
) : super(const DocumentUploadState()) {
|
||||||
_labelRepository.addListener(
|
_labelRepository.addListener(
|
||||||
this,
|
this,
|
||||||
onChanged: (labels) {
|
onChanged: (labels) {
|
||||||
@@ -31,6 +37,7 @@ class DocumentUploadCubit extends Cubit<DocumentUploadState> {
|
|||||||
Uint8List bytes, {
|
Uint8List bytes, {
|
||||||
required String filename,
|
required String filename,
|
||||||
required String title,
|
required String title,
|
||||||
|
required String userId,
|
||||||
int? documentType,
|
int? documentType,
|
||||||
int? correspondent,
|
int? correspondent,
|
||||||
Iterable<int> tags = const [],
|
Iterable<int> tags = const [],
|
||||||
|
|||||||
@@ -3,10 +3,13 @@ import 'dart:typed_data';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||||
|
import 'package:hive/hive.dart';
|
||||||
|
|
||||||
import 'package:intl/date_symbol_data_local.dart';
|
import 'package:intl/date_symbol_data_local.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
|
import 'package:paperless_mobile/core/config/hive/hive_config.dart';
|
||||||
|
import 'package:paperless_mobile/core/database/tables/global_settings.dart';
|
||||||
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
||||||
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||||
import 'package:paperless_mobile/core/type/types.dart';
|
import 'package:paperless_mobile/core/type/types.dart';
|
||||||
@@ -42,10 +45,12 @@ class DocumentUploadPreparationPage extends StatefulWidget {
|
|||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<DocumentUploadPreparationPage> createState() => _DocumentUploadPreparationPageState();
|
State<DocumentUploadPreparationPage> createState() =>
|
||||||
|
_DocumentUploadPreparationPageState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _DocumentUploadPreparationPageState extends State<DocumentUploadPreparationPage> {
|
class _DocumentUploadPreparationPageState
|
||||||
|
extends State<DocumentUploadPreparationPage> {
|
||||||
static const fkFileName = "filename";
|
static const fkFileName = "filename";
|
||||||
static final fileNameDateFormat = DateFormat("yyyy_MM_ddTHH_mm_ss");
|
static final fileNameDateFormat = DateFormat("yyyy_MM_ddTHH_mm_ss");
|
||||||
|
|
||||||
@@ -72,7 +77,8 @@ class _DocumentUploadPreparationPageState extends State<DocumentUploadPreparatio
|
|||||||
title: Text(S.of(context)!.prepareDocument),
|
title: Text(S.of(context)!.prepareDocument),
|
||||||
bottom: _isUploadLoading
|
bottom: _isUploadLoading
|
||||||
? const PreferredSize(
|
? const PreferredSize(
|
||||||
child: LinearProgressIndicator(), preferredSize: Size.fromHeight(4.0))
|
child: LinearProgressIndicator(),
|
||||||
|
preferredSize: Size.fromHeight(4.0))
|
||||||
: null,
|
: null,
|
||||||
),
|
),
|
||||||
floatingActionButton: Visibility(
|
floatingActionButton: Visibility(
|
||||||
@@ -93,7 +99,8 @@ class _DocumentUploadPreparationPageState extends State<DocumentUploadPreparatio
|
|||||||
FormBuilderTextField(
|
FormBuilderTextField(
|
||||||
autovalidateMode: AutovalidateMode.always,
|
autovalidateMode: AutovalidateMode.always,
|
||||||
name: DocumentModel.titleKey,
|
name: DocumentModel.titleKey,
|
||||||
initialValue: widget.title ?? "scan_${fileNameDateFormat.format(_now)}",
|
initialValue:
|
||||||
|
widget.title ?? "scan_${fileNameDateFormat.format(_now)}",
|
||||||
validator: (value) {
|
validator: (value) {
|
||||||
if (value?.trim().isEmpty ?? true) {
|
if (value?.trim().isEmpty ?? true) {
|
||||||
return S.of(context)!.thisFieldIsRequired;
|
return S.of(context)!.thisFieldIsRequired;
|
||||||
@@ -105,18 +112,22 @@ class _DocumentUploadPreparationPageState extends State<DocumentUploadPreparatio
|
|||||||
suffixIcon: IconButton(
|
suffixIcon: IconButton(
|
||||||
icon: const Icon(Icons.close),
|
icon: const Icon(Icons.close),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
_formKey.currentState?.fields[DocumentModel.titleKey]?.didChange("");
|
_formKey.currentState?.fields[DocumentModel.titleKey]
|
||||||
|
?.didChange("");
|
||||||
if (_syncTitleAndFilename) {
|
if (_syncTitleAndFilename) {
|
||||||
_formKey.currentState?.fields[fkFileName]?.didChange("");
|
_formKey.currentState?.fields[fkFileName]
|
||||||
|
?.didChange("");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
errorText: _errors[DocumentModel.titleKey],
|
errorText: _errors[DocumentModel.titleKey],
|
||||||
),
|
),
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
final String transformedValue = _formatFilename(value ?? '');
|
final String transformedValue =
|
||||||
|
_formatFilename(value ?? '');
|
||||||
if (_syncTitleAndFilename) {
|
if (_syncTitleAndFilename) {
|
||||||
_formKey.currentState?.fields[fkFileName]?.didChange(transformedValue);
|
_formKey.currentState?.fields[fkFileName]
|
||||||
|
?.didChange(transformedValue);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -131,10 +142,12 @@ class _DocumentUploadPreparationPageState extends State<DocumentUploadPreparatio
|
|||||||
suffixText: widget.fileExtension,
|
suffixText: widget.fileExtension,
|
||||||
suffixIcon: IconButton(
|
suffixIcon: IconButton(
|
||||||
icon: const Icon(Icons.clear),
|
icon: const Icon(Icons.clear),
|
||||||
onPressed: () => _formKey.currentState?.fields[fkFileName]?.didChange(''),
|
onPressed: () => _formKey.currentState?.fields[fkFileName]
|
||||||
|
?.didChange(''),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
initialValue: widget.filename ?? "scan_${fileNameDateFormat.format(_now)}",
|
initialValue: widget.filename ??
|
||||||
|
"scan_${fileNameDateFormat.format(_now)}",
|
||||||
),
|
),
|
||||||
// Synchronize title and filename
|
// Synchronize title and filename
|
||||||
SwitchListTile(
|
SwitchListTile(
|
||||||
@@ -144,10 +157,13 @@ class _DocumentUploadPreparationPageState extends State<DocumentUploadPreparatio
|
|||||||
() => _syncTitleAndFilename = value,
|
() => _syncTitleAndFilename = value,
|
||||||
);
|
);
|
||||||
if (_syncTitleAndFilename) {
|
if (_syncTitleAndFilename) {
|
||||||
final String transformedValue = _formatFilename(
|
final String transformedValue = _formatFilename(_formKey
|
||||||
_formKey.currentState?.fields[DocumentModel.titleKey]?.value as String);
|
.currentState
|
||||||
|
?.fields[DocumentModel.titleKey]
|
||||||
|
?.value as String);
|
||||||
if (_syncTitleAndFilename) {
|
if (_syncTitleAndFilename) {
|
||||||
_formKey.currentState?.fields[fkFileName]?.didChange(transformedValue);
|
_formKey.currentState?.fields[fkFileName]
|
||||||
|
?.didChange(transformedValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -172,7 +188,8 @@ class _DocumentUploadPreparationPageState extends State<DocumentUploadPreparatio
|
|||||||
? IconButton(
|
? IconButton(
|
||||||
icon: const Icon(Icons.close),
|
icon: const Icon(Icons.close),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
_formKey.currentState!.fields[DocumentModel.createdKey]
|
_formKey.currentState!
|
||||||
|
.fields[DocumentModel.createdKey]
|
||||||
?.didChange(null);
|
?.didChange(null);
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -183,7 +200,8 @@ class _DocumentUploadPreparationPageState extends State<DocumentUploadPreparatio
|
|||||||
LabelFormField<Correspondent>(
|
LabelFormField<Correspondent>(
|
||||||
showAnyAssignedOption: false,
|
showAnyAssignedOption: false,
|
||||||
showNotAssignedOption: false,
|
showNotAssignedOption: false,
|
||||||
addLabelPageBuilder: (initialName) => RepositoryProvider.value(
|
addLabelPageBuilder: (initialName) =>
|
||||||
|
RepositoryProvider.value(
|
||||||
value: context.read<LabelRepository>(),
|
value: context.read<LabelRepository>(),
|
||||||
child: AddCorrespondentPage(initialName: initialName),
|
child: AddCorrespondentPage(initialName: initialName),
|
||||||
),
|
),
|
||||||
@@ -193,7 +211,8 @@ class _DocumentUploadPreparationPageState extends State<DocumentUploadPreparatio
|
|||||||
options: state.correspondents,
|
options: state.correspondents,
|
||||||
prefixIcon: const Icon(Icons.person_outline),
|
prefixIcon: const Icon(Icons.person_outline),
|
||||||
allowSelectUnassigned: true,
|
allowSelectUnassigned: true,
|
||||||
canCreateNewLabel: LocalUserAccount.current.paperlessUser.hasPermission(
|
canCreateNewLabel:
|
||||||
|
LocalUserAccount.current.paperlessUser.hasPermission(
|
||||||
PermissionAction.add,
|
PermissionAction.add,
|
||||||
PermissionTarget.correspondent,
|
PermissionTarget.correspondent,
|
||||||
),
|
),
|
||||||
@@ -202,7 +221,8 @@ class _DocumentUploadPreparationPageState extends State<DocumentUploadPreparatio
|
|||||||
LabelFormField<DocumentType>(
|
LabelFormField<DocumentType>(
|
||||||
showAnyAssignedOption: false,
|
showAnyAssignedOption: false,
|
||||||
showNotAssignedOption: false,
|
showNotAssignedOption: false,
|
||||||
addLabelPageBuilder: (initialName) => RepositoryProvider.value(
|
addLabelPageBuilder: (initialName) =>
|
||||||
|
RepositoryProvider.value(
|
||||||
value: context.read<LabelRepository>(),
|
value: context.read<LabelRepository>(),
|
||||||
child: AddDocumentTypePage(initialName: initialName),
|
child: AddDocumentTypePage(initialName: initialName),
|
||||||
),
|
),
|
||||||
@@ -212,7 +232,8 @@ class _DocumentUploadPreparationPageState extends State<DocumentUploadPreparatio
|
|||||||
options: state.documentTypes,
|
options: state.documentTypes,
|
||||||
prefixIcon: const Icon(Icons.description_outlined),
|
prefixIcon: const Icon(Icons.description_outlined),
|
||||||
allowSelectUnassigned: true,
|
allowSelectUnassigned: true,
|
||||||
canCreateNewLabel: LocalUserAccount.current.paperlessUser.hasPermission(
|
canCreateNewLabel:
|
||||||
|
LocalUserAccount.current.paperlessUser.hasPermission(
|
||||||
PermissionAction.add,
|
PermissionAction.add,
|
||||||
PermissionTarget.documentType,
|
PermissionTarget.documentType,
|
||||||
),
|
),
|
||||||
@@ -252,7 +273,8 @@ class _DocumentUploadPreparationPageState extends State<DocumentUploadPreparatio
|
|||||||
final tags = (fv[DocumentModel.tagsKey] as TagsQuery?)
|
final tags = (fv[DocumentModel.tagsKey] as TagsQuery?)
|
||||||
?.whenOrNull(ids: (include, exclude) => include) ??
|
?.whenOrNull(ids: (include, exclude) => include) ??
|
||||||
[];
|
[];
|
||||||
final correspondent = (fv[DocumentModel.correspondentKey] as IdQueryParameter?)
|
final correspondent =
|
||||||
|
(fv[DocumentModel.correspondentKey] as IdQueryParameter?)
|
||||||
?.whenOrNull(fromId: (id) => id);
|
?.whenOrNull(fromId: (id) => id);
|
||||||
final asn = fv[DocumentModel.asnKey] as int?;
|
final asn = fv[DocumentModel.asnKey] as int?;
|
||||||
final taskId = await cubit.upload(
|
final taskId = await cubit.upload(
|
||||||
@@ -261,6 +283,9 @@ class _DocumentUploadPreparationPageState extends State<DocumentUploadPreparatio
|
|||||||
_formKey.currentState?.value[fkFileName],
|
_formKey.currentState?.value[fkFileName],
|
||||||
widget.fileExtension,
|
widget.fileExtension,
|
||||||
),
|
),
|
||||||
|
userId: Hive.box<GlobalSettings>(HiveBoxes.globalSettings)
|
||||||
|
.getValue()!
|
||||||
|
.currentLoggedInUser!,
|
||||||
title: title,
|
title: title,
|
||||||
documentType: docType,
|
documentType: docType,
|
||||||
correspondent: correspondent,
|
correspondent: correspondent,
|
||||||
@@ -282,7 +307,8 @@ class _DocumentUploadPreparationPageState extends State<DocumentUploadPreparatio
|
|||||||
setState(() => _errors = errors);
|
setState(() => _errors = errors);
|
||||||
} catch (unknownError, stackTrace) {
|
} catch (unknownError, stackTrace) {
|
||||||
debugPrint(unknownError.toString());
|
debugPrint(unknownError.toString());
|
||||||
showErrorMessage(context, const PaperlessServerException.unknown(), stackTrace);
|
showErrorMessage(
|
||||||
|
context, const PaperlessServerException.unknown(), stackTrace);
|
||||||
} finally {
|
} finally {
|
||||||
setState(() {
|
setState(() {
|
||||||
_isUploadLoading = false;
|
_isUploadLoading = false;
|
||||||
|
|||||||
@@ -14,7 +14,8 @@ import 'package:paperless_mobile/features/settings/model/view_type.dart';
|
|||||||
part 'documents_cubit.g.dart';
|
part 'documents_cubit.g.dart';
|
||||||
part 'documents_state.dart';
|
part 'documents_state.dart';
|
||||||
|
|
||||||
class DocumentsCubit extends HydratedCubit<DocumentsState> with DocumentPagingBlocMixin {
|
class DocumentsCubit extends HydratedCubit<DocumentsState>
|
||||||
|
with DocumentPagingBlocMixin {
|
||||||
@override
|
@override
|
||||||
final PaperlessDocumentsApi api;
|
final PaperlessDocumentsApi api;
|
||||||
|
|
||||||
@@ -40,7 +41,9 @@ class DocumentsCubit extends HydratedCubit<DocumentsState> with DocumentPagingBl
|
|||||||
replace(document);
|
replace(document);
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
selection: state.selection.map((e) => e.id == document.id ? document : e).toList(),
|
selection: state.selection
|
||||||
|
.map((e) => e.id == document.id ? document : e)
|
||||||
|
.toList(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -48,7 +51,8 @@ class DocumentsCubit extends HydratedCubit<DocumentsState> with DocumentPagingBl
|
|||||||
remove(document);
|
remove(document);
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
selection: state.selection.where((e) => e.id != document.id).toList(),
|
selection:
|
||||||
|
state.selection.where((e) => e.id != document.id).toList(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -82,7 +86,9 @@ class DocumentsCubit extends HydratedCubit<DocumentsState> with DocumentPagingBl
|
|||||||
if (state.selectedIds.contains(model.id)) {
|
if (state.selectedIds.contains(model.id)) {
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
selection: state.selection.where((element) => element.id != model.id).toList(),
|
selection: state.selection
|
||||||
|
.where((element) => element.id != model.id)
|
||||||
|
.toList(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -86,7 +86,8 @@ class DocumentsState extends DocumentPagingState {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
factory DocumentsState.fromJson(Map<String, dynamic> json) => _$DocumentsStateFromJson(json);
|
factory DocumentsState.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$DocumentsStateFromJson(json);
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => _$DocumentsStateToJson(this);
|
Map<String, dynamic> toJson() => _$DocumentsStateToJson(this);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,8 @@ class _DocumentViewState extends State<DocumentView> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final isInitialized = _controller != null && _currentPage != null && _totalPages != null;
|
final isInitialized =
|
||||||
|
_controller != null && _currentPage != null && _totalPages != null;
|
||||||
final canGoToNextPage = isInitialized && _currentPage! + 1 < _totalPages!;
|
final canGoToNextPage = isInitialized && _currentPage! + 1 < _totalPages!;
|
||||||
final canGoToPreviousPage = isInitialized && _currentPage! > 0;
|
final canGoToPreviousPage = isInitialized && _currentPage! > 0;
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
|
|||||||
@@ -161,9 +161,7 @@ class _DocumentsPageState extends State<DocumentsPage>
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
child: Stack(
|
child: NestedScrollView(
|
||||||
children: [
|
|
||||||
NestedScrollView(
|
|
||||||
floatHeaderSlivers: true,
|
floatHeaderSlivers: true,
|
||||||
headerSliverBuilder: (context, innerBoxIsScrolled) => [
|
headerSliverBuilder: (context, innerBoxIsScrolled) => [
|
||||||
SliverOverlapAbsorber(
|
SliverOverlapAbsorber(
|
||||||
@@ -217,8 +215,7 @@ class _DocumentsPageState extends State<DocumentsPage>
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
final desiredTab =
|
final desiredTab =
|
||||||
(metrics.pixels / metrics.maxScrollExtent)
|
(metrics.pixels / metrics.maxScrollExtent).round();
|
||||||
.round();
|
|
||||||
if (metrics.axis == Axis.horizontal &&
|
if (metrics.axis == Axis.horizontal &&
|
||||||
_currentTab != desiredTab) {
|
_currentTab != desiredTab) {
|
||||||
setState(() => _currentTab = desiredTab);
|
setState(() => _currentTab = desiredTab);
|
||||||
@@ -255,8 +252,6 @@ class _DocumentsPageState extends State<DocumentsPage>
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -43,7 +43,9 @@ class DocumentPreview extends StatelessWidget {
|
|||||||
fit: fit,
|
fit: fit,
|
||||||
alignment: alignment,
|
alignment: alignment,
|
||||||
cacheKey: "thumb_${document.id}",
|
cacheKey: "thumb_${document.id}",
|
||||||
imageUrl: context.read<PaperlessDocumentsApi>().getThumbnailUrl(document.id),
|
imageUrl: context
|
||||||
|
.read<PaperlessDocumentsApi>()
|
||||||
|
.getThumbnailUrl(document.id),
|
||||||
errorWidget: (ctxt, msg, __) => Text(msg),
|
errorWidget: (ctxt, msg, __) => Text(msg),
|
||||||
placeholder: (context, value) => Shimmer.fromColors(
|
placeholder: (context, value) => Shimmer.fromColors(
|
||||||
baseColor: Colors.grey[300]!,
|
baseColor: Colors.grey[300]!,
|
||||||
|
|||||||
@@ -42,8 +42,9 @@ class DocumentDetailedItem extends DocumentItem {
|
|||||||
padding.bottom -
|
padding.bottom -
|
||||||
kBottomNavigationBarHeight -
|
kBottomNavigationBarHeight -
|
||||||
kToolbarHeight;
|
kToolbarHeight;
|
||||||
final maxHeight =
|
final maxHeight = highlights != null
|
||||||
highlights != null ? min(600.0, availableHeight) : min(500.0, availableHeight);
|
? min(600.0, availableHeight)
|
||||||
|
: min(500.0, availableHeight);
|
||||||
return Card(
|
return Card(
|
||||||
color: isSelected ? Theme.of(context).colorScheme.inversePrimary : null,
|
color: isSelected ? Theme.of(context).colorScheme.inversePrimary : null,
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
@@ -114,8 +115,10 @@ class DocumentDetailedItem extends DocumentItem {
|
|||||||
textStyle: Theme.of(context).textTheme.titleSmall?.apply(
|
textStyle: Theme.of(context).textTheme.titleSmall?.apply(
|
||||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
),
|
),
|
||||||
correspondent:
|
correspondent: context
|
||||||
context.watch<LabelRepository>().state.correspondents[document.correspondent],
|
.watch<LabelRepository>()
|
||||||
|
.state
|
||||||
|
.correspondents[document.correspondent],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
).paddedLTRB(8, 0, 8, 4),
|
).paddedLTRB(8, 0, 8, 4),
|
||||||
@@ -130,8 +133,10 @@ class DocumentDetailedItem extends DocumentItem {
|
|||||||
textStyle: Theme.of(context).textTheme.titleSmall?.apply(
|
textStyle: Theme.of(context).textTheme.titleSmall?.apply(
|
||||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
),
|
),
|
||||||
documentType:
|
documentType: context
|
||||||
context.watch<LabelRepository>().state.documentTypes[document.documentType],
|
.watch<LabelRepository>()
|
||||||
|
.state
|
||||||
|
.documentTypes[document.documentType],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
).paddedLTRB(8, 0, 8, 4),
|
).paddedLTRB(8, 0, 8, 4),
|
||||||
|
|||||||
@@ -30,8 +30,9 @@ class DocumentGridItem extends DocumentItem {
|
|||||||
padding: const EdgeInsets.all(8.0),
|
padding: const EdgeInsets.all(8.0),
|
||||||
child: Card(
|
child: Card(
|
||||||
elevation: 1.0,
|
elevation: 1.0,
|
||||||
color:
|
color: isSelected
|
||||||
isSelected ? Theme.of(context).colorScheme.inversePrimary : Theme.of(context).cardColor,
|
? Theme.of(context).colorScheme.inversePrimary
|
||||||
|
: Theme.of(context).cardColor,
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
onTap: _onTap,
|
onTap: _onTap,
|
||||||
@@ -74,7 +75,8 @@ class DocumentGridItem extends DocumentItem {
|
|||||||
const Spacer(),
|
const Spacer(),
|
||||||
TagsWidget(
|
TagsWidget(
|
||||||
tags: document.tags
|
tags: document.tags
|
||||||
.map((e) => context.watch<LabelRepository>().state.tags[e]!)
|
.map((e) =>
|
||||||
|
context.watch<LabelRepository>().state.tags[e]!)
|
||||||
.toList(),
|
.toList(),
|
||||||
isMultiLine: false,
|
isMultiLine: false,
|
||||||
onTagSelected: onTagSelected,
|
onTagSelected: onTagSelected,
|
||||||
|
|||||||
@@ -22,14 +22,17 @@ class DocumentFilterForm extends StatefulWidget {
|
|||||||
formKey.currentState?.save();
|
formKey.currentState?.save();
|
||||||
final v = formKey.currentState!.value;
|
final v = formKey.currentState!.value;
|
||||||
return DocumentFilter(
|
return DocumentFilter(
|
||||||
correspondent: v[DocumentFilterForm.fkCorrespondent] as IdQueryParameter? ??
|
correspondent:
|
||||||
|
v[DocumentFilterForm.fkCorrespondent] as IdQueryParameter? ??
|
||||||
DocumentFilter.initial.correspondent,
|
DocumentFilter.initial.correspondent,
|
||||||
documentType: v[DocumentFilterForm.fkDocumentType] as IdQueryParameter? ??
|
documentType: v[DocumentFilterForm.fkDocumentType] as IdQueryParameter? ??
|
||||||
DocumentFilter.initial.documentType,
|
DocumentFilter.initial.documentType,
|
||||||
storagePath: v[DocumentFilterForm.fkStoragePath] as IdQueryParameter? ??
|
storagePath: v[DocumentFilterForm.fkStoragePath] as IdQueryParameter? ??
|
||||||
DocumentFilter.initial.storagePath,
|
DocumentFilter.initial.storagePath,
|
||||||
tags: v[DocumentModel.tagsKey] as TagsQuery? ?? DocumentFilter.initial.tags,
|
tags:
|
||||||
query: v[DocumentFilterForm.fkQuery] as TextQuery? ?? DocumentFilter.initial.query,
|
v[DocumentModel.tagsKey] as TagsQuery? ?? DocumentFilter.initial.tags,
|
||||||
|
query: v[DocumentFilterForm.fkQuery] as TextQuery? ??
|
||||||
|
DocumentFilter.initial.query,
|
||||||
created: (v[DocumentFilterForm.fkCreatedAt] as DateRangeQuery),
|
created: (v[DocumentFilterForm.fkCreatedAt] as DateRangeQuery),
|
||||||
added: (v[DocumentFilterForm.fkAddedAt] as DateRangeQuery),
|
added: (v[DocumentFilterForm.fkAddedAt] as DateRangeQuery),
|
||||||
asnQuery: initialFilter.asnQuery,
|
asnQuery: initialFilter.asnQuery,
|
||||||
@@ -134,12 +137,15 @@ class _DocumentFilterFormState extends State<DocumentFilterForm> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _checkQueryConstraints() {
|
void _checkQueryConstraints() {
|
||||||
final filter = DocumentFilterForm.assembleFilter(widget.formKey, widget.initialFilter);
|
final filter =
|
||||||
|
DocumentFilterForm.assembleFilter(widget.formKey, widget.initialFilter);
|
||||||
if (filter.forceExtendedQuery) {
|
if (filter.forceExtendedQuery) {
|
||||||
setState(() => _allowOnlyExtendedQuery = true);
|
setState(() => _allowOnlyExtendedQuery = true);
|
||||||
final queryField = widget.formKey.currentState?.fields[DocumentFilterForm.fkQuery];
|
final queryField =
|
||||||
|
widget.formKey.currentState?.fields[DocumentFilterForm.fkQuery];
|
||||||
queryField?.didChange(
|
queryField?.didChange(
|
||||||
(queryField.value as TextQuery?)?.copyWith(queryType: QueryType.extended),
|
(queryField.value as TextQuery?)
|
||||||
|
?.copyWith(queryType: QueryType.extended),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
setState(() => _allowOnlyExtendedQuery = false);
|
setState(() => _allowOnlyExtendedQuery = false);
|
||||||
|
|||||||
@@ -27,10 +27,12 @@ class SortFieldSelectionBottomSheet extends StatefulWidget {
|
|||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<SortFieldSelectionBottomSheet> createState() => _SortFieldSelectionBottomSheetState();
|
State<SortFieldSelectionBottomSheet> createState() =>
|
||||||
|
_SortFieldSelectionBottomSheetState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _SortFieldSelectionBottomSheetState extends State<SortFieldSelectionBottomSheet> {
|
class _SortFieldSelectionBottomSheetState
|
||||||
|
extends State<SortFieldSelectionBottomSheet> {
|
||||||
late SortField? _currentSortField;
|
late SortField? _currentSortField;
|
||||||
late SortOrder _currentSortOrder;
|
late SortOrder _currentSortOrder;
|
||||||
|
|
||||||
|
|||||||
@@ -33,12 +33,15 @@ class DocumentSelectionSliverAppBar extends StatelessWidget {
|
|||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
final shouldDelete = await showDialog<bool>(
|
final shouldDelete = await showDialog<bool>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => BulkDeleteConfirmationDialog(state: state),
|
builder: (context) =>
|
||||||
|
BulkDeleteConfirmationDialog(state: state),
|
||||||
) ??
|
) ??
|
||||||
false;
|
false;
|
||||||
if (shouldDelete) {
|
if (shouldDelete) {
|
||||||
try {
|
try {
|
||||||
await context.read<DocumentsCubit>().bulkDelete(state.selection);
|
await context
|
||||||
|
.read<DocumentsCubit>()
|
||||||
|
.bulkDelete(state.selection);
|
||||||
showSnackBar(
|
showSnackBar(
|
||||||
context,
|
context,
|
||||||
S.of(context)!.documentsSuccessfullyDeleted,
|
S.of(context)!.documentsSuccessfullyDeleted,
|
||||||
@@ -62,21 +65,24 @@ class DocumentSelectionSliverAppBar extends StatelessWidget {
|
|||||||
label: Text(S.of(context)!.correspondent),
|
label: Text(S.of(context)!.correspondent),
|
||||||
avatar: const Icon(Icons.edit),
|
avatar: const Icon(Icons.edit),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
pushBulkEditCorrespondentRoute(context, selection: state.selection);
|
pushBulkEditCorrespondentRoute(context,
|
||||||
|
selection: state.selection);
|
||||||
},
|
},
|
||||||
).paddedOnly(left: 8, right: 4),
|
).paddedOnly(left: 8, right: 4),
|
||||||
ActionChip(
|
ActionChip(
|
||||||
label: Text(S.of(context)!.documentType),
|
label: Text(S.of(context)!.documentType),
|
||||||
avatar: const Icon(Icons.edit),
|
avatar: const Icon(Icons.edit),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
pushBulkEditDocumentTypeRoute(context, selection: state.selection);
|
pushBulkEditDocumentTypeRoute(context,
|
||||||
|
selection: state.selection);
|
||||||
},
|
},
|
||||||
).paddedOnly(left: 8, right: 4),
|
).paddedOnly(left: 8, right: 4),
|
||||||
ActionChip(
|
ActionChip(
|
||||||
label: Text(S.of(context)!.storagePath),
|
label: Text(S.of(context)!.storagePath),
|
||||||
avatar: const Icon(Icons.edit),
|
avatar: const Icon(Icons.edit),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
pushBulkEditStoragePathRoute(context, selection: state.selection);
|
pushBulkEditStoragePathRoute(context,
|
||||||
|
selection: state.selection);
|
||||||
},
|
},
|
||||||
).paddedOnly(left: 8, right: 4),
|
).paddedOnly(left: 8, right: 4),
|
||||||
_buildBulkEditTagsChip(context).paddedOnly(left: 4, right: 4),
|
_buildBulkEditTagsChip(context).paddedOnly(left: 4, right: 4),
|
||||||
|
|||||||
@@ -50,7 +50,9 @@ class SortDocumentsButton extends StatelessWidget {
|
|||||||
initialSortField: state.filter.sortField,
|
initialSortField: state.filter.sortField,
|
||||||
initialSortOrder: state.filter.sortOrder,
|
initialSortOrder: state.filter.sortOrder,
|
||||||
onSubmit: (field, order) {
|
onSubmit: (field, order) {
|
||||||
return context.read<DocumentsCubit>().updateCurrentFilter(
|
return context
|
||||||
|
.read<DocumentsCubit>()
|
||||||
|
.updateCurrentFilter(
|
||||||
(filter) => filter.copyWith(
|
(filter) => filter.copyWith(
|
||||||
sortField: field,
|
sortField: field,
|
||||||
sortOrder: order,
|
sortOrder: order,
|
||||||
|
|||||||
@@ -20,8 +20,10 @@ class EditCorrespondentPage extends StatelessWidget {
|
|||||||
return EditLabelPage<Correspondent>(
|
return EditLabelPage<Correspondent>(
|
||||||
label: correspondent,
|
label: correspondent,
|
||||||
fromJsonT: Correspondent.fromJson,
|
fromJsonT: Correspondent.fromJson,
|
||||||
onSubmit: (context, label) => context.read<EditLabelCubit>().replaceCorrespondent(label),
|
onSubmit: (context, label) =>
|
||||||
onDelete: (context, label) => context.read<EditLabelCubit>().removeCorrespondent(label),
|
context.read<EditLabelCubit>().replaceCorrespondent(label),
|
||||||
|
onDelete: (context, label) =>
|
||||||
|
context.read<EditLabelCubit>().removeCorrespondent(label),
|
||||||
canDelete: LocalUserAccount.current.paperlessUser.hasPermission(
|
canDelete: LocalUserAccount.current.paperlessUser.hasPermission(
|
||||||
PermissionAction.delete,
|
PermissionAction.delete,
|
||||||
PermissionTarget.correspondent,
|
PermissionTarget.correspondent,
|
||||||
|
|||||||
@@ -18,8 +18,10 @@ class EditDocumentTypePage extends StatelessWidget {
|
|||||||
child: EditLabelPage<DocumentType>(
|
child: EditLabelPage<DocumentType>(
|
||||||
label: documentType,
|
label: documentType,
|
||||||
fromJsonT: DocumentType.fromJson,
|
fromJsonT: DocumentType.fromJson,
|
||||||
onSubmit: (context, label) => context.read<EditLabelCubit>().replaceDocumentType(label),
|
onSubmit: (context, label) =>
|
||||||
onDelete: (context, label) => context.read<EditLabelCubit>().removeDocumentType(label),
|
context.read<EditLabelCubit>().replaceDocumentType(label),
|
||||||
|
onDelete: (context, label) =>
|
||||||
|
context.read<EditLabelCubit>().removeDocumentType(label),
|
||||||
canDelete: LocalUserAccount.current.paperlessUser.hasPermission(
|
canDelete: LocalUserAccount.current.paperlessUser.hasPermission(
|
||||||
PermissionAction.delete,
|
PermissionAction.delete,
|
||||||
PermissionTarget.documentType,
|
PermissionTarget.documentType,
|
||||||
|
|||||||
@@ -19,8 +19,10 @@ class EditStoragePathPage extends StatelessWidget {
|
|||||||
child: EditLabelPage<StoragePath>(
|
child: EditLabelPage<StoragePath>(
|
||||||
label: storagePath,
|
label: storagePath,
|
||||||
fromJsonT: StoragePath.fromJson,
|
fromJsonT: StoragePath.fromJson,
|
||||||
onSubmit: (context, label) => context.read<EditLabelCubit>().replaceStoragePath(label),
|
onSubmit: (context, label) =>
|
||||||
onDelete: (context, label) => context.read<EditLabelCubit>().removeStoragePath(label),
|
context.read<EditLabelCubit>().replaceStoragePath(label),
|
||||||
|
onDelete: (context, label) =>
|
||||||
|
context.read<EditLabelCubit>().removeStoragePath(label),
|
||||||
canDelete: LocalUserAccount.current.paperlessUser.hasPermission(
|
canDelete: LocalUserAccount.current.paperlessUser.hasPermission(
|
||||||
PermissionAction.delete,
|
PermissionAction.delete,
|
||||||
PermissionTarget.storagePath,
|
PermissionTarget.storagePath,
|
||||||
|
|||||||
@@ -22,8 +22,10 @@ class EditTagPage extends StatelessWidget {
|
|||||||
child: EditLabelPage<Tag>(
|
child: EditLabelPage<Tag>(
|
||||||
label: tag,
|
label: tag,
|
||||||
fromJsonT: Tag.fromJson,
|
fromJsonT: Tag.fromJson,
|
||||||
onSubmit: (context, label) => context.read<EditLabelCubit>().replaceTag(label),
|
onSubmit: (context, label) =>
|
||||||
onDelete: (context, label) => context.read<EditLabelCubit>().removeTag(label),
|
context.read<EditLabelCubit>().replaceTag(label),
|
||||||
|
onDelete: (context, label) =>
|
||||||
|
context.read<EditLabelCubit>().removeTag(label),
|
||||||
canDelete: LocalUserAccount.current.paperlessUser.hasPermission(
|
canDelete: LocalUserAccount.current.paperlessUser.hasPermission(
|
||||||
PermissionAction.delete,
|
PermissionAction.delete,
|
||||||
PermissionTarget.tag,
|
PermissionTarget.tag,
|
||||||
|
|||||||
@@ -6,8 +6,11 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:fluttertoast/fluttertoast.dart';
|
import 'package:fluttertoast/fluttertoast.dart';
|
||||||
|
import 'package:hive/hive.dart';
|
||||||
import 'package:paperless_api/paperless_api.dart';
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
|
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
|
||||||
|
import 'package:paperless_mobile/core/config/hive/hive_config.dart';
|
||||||
|
import 'package:paperless_mobile/core/database/tables/global_settings.dart';
|
||||||
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
||||||
import 'package:paperless_mobile/core/global/constants.dart';
|
import 'package:paperless_mobile/core/global/constants.dart';
|
||||||
import 'package:paperless_mobile/core/navigation/push_routes.dart';
|
import 'package:paperless_mobile/core/navigation/push_routes.dart';
|
||||||
@@ -42,12 +45,23 @@ class HomePage extends StatefulWidget {
|
|||||||
class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
|
class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
|
||||||
int _currentIndex = 0;
|
int _currentIndex = 0;
|
||||||
late Timer _inboxTimer;
|
late Timer _inboxTimer;
|
||||||
|
late final StreamSubscription _shareMediaSubscription;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
WidgetsBinding.instance.addObserver(this);
|
WidgetsBinding.instance.addObserver(this);
|
||||||
_listenToInboxChanges();
|
_listenToInboxChanges();
|
||||||
|
final currentUser = Hive.box<GlobalSettings>(HiveBoxes.globalSettings)
|
||||||
|
.getValue()!
|
||||||
|
.currentLoggedInUser!;
|
||||||
|
// For sharing files coming from outside the app while the app is still opened
|
||||||
|
_shareMediaSubscription = ReceiveSharingIntent.getMediaStream().listen(
|
||||||
|
(files) =>
|
||||||
|
ShareIntentQueue.instance.addAll(files, userId: currentUser));
|
||||||
|
// For sharing files coming from outside the app while the app is closed
|
||||||
|
ReceiveSharingIntent.getInitialMedia().then((files) =>
|
||||||
|
ShareIntentQueue.instance.addAll(files, userId: currentUser));
|
||||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
||||||
_listenForReceivedFiles();
|
_listenForReceivedFiles();
|
||||||
});
|
});
|
||||||
@@ -59,7 +73,7 @@ class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _listenToInboxChanges() {
|
void _listenToInboxChanges() {
|
||||||
_inboxTimer = Timer.periodic(const Duration(seconds: 10), (timer) {
|
_inboxTimer = Timer.periodic(const Duration(seconds: 60), (timer) {
|
||||||
if (!mounted) {
|
if (!mounted) {
|
||||||
timer.cancel();
|
timer.cancel();
|
||||||
} else {
|
} else {
|
||||||
@@ -93,17 +107,21 @@ class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
|
|||||||
void dispose() {
|
void dispose() {
|
||||||
WidgetsBinding.instance.removeObserver(this);
|
WidgetsBinding.instance.removeObserver(this);
|
||||||
_inboxTimer.cancel();
|
_inboxTimer.cancel();
|
||||||
|
_shareMediaSubscription.cancel();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _listenForReceivedFiles() async {
|
void _listenForReceivedFiles() async {
|
||||||
if (ShareIntentQueue.instance.hasUnhandledFiles) {
|
final currentUser = Hive.box<GlobalSettings>(HiveBoxes.globalSettings)
|
||||||
await _handleReceivedFile(ShareIntentQueue.instance.pop()!);
|
.getValue()!
|
||||||
|
.currentLoggedInUser!;
|
||||||
|
if (ShareIntentQueue.instance.userHasUnhandlesFiles(currentUser)) {
|
||||||
|
await _handleReceivedFile(ShareIntentQueue.instance.pop(currentUser)!);
|
||||||
}
|
}
|
||||||
ShareIntentQueue.instance.addListener(() async {
|
ShareIntentQueue.instance.addListener(() async {
|
||||||
final queue = ShareIntentQueue.instance;
|
final queue = ShareIntentQueue.instance;
|
||||||
while (queue.hasUnhandledFiles) {
|
while (queue.userHasUnhandlesFiles(currentUser)) {
|
||||||
final file = queue.pop()!;
|
final file = queue.pop(currentUser)!;
|
||||||
await _handleReceivedFile(file);
|
await _handleReceivedFile(file);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -115,7 +133,7 @@ class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _handleReceivedFile(SharedMediaFile file) async {
|
Future<void> _handleReceivedFile(final SharedMediaFile file) async {
|
||||||
SharedMediaFile mediaFile;
|
SharedMediaFile mediaFile;
|
||||||
if (Platform.isIOS) {
|
if (Platform.isIOS) {
|
||||||
// Workaround for file not found on iOS: https://stackoverflow.com/a/72813212
|
// Workaround for file not found on iOS: https://stackoverflow.com/a/72813212
|
||||||
@@ -128,7 +146,7 @@ class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
|
|||||||
} else {
|
} else {
|
||||||
mediaFile = file;
|
mediaFile = file;
|
||||||
}
|
}
|
||||||
|
debugPrint("Consuming media file: ${mediaFile.path}");
|
||||||
if (!_isFileTypeSupported(mediaFile)) {
|
if (!_isFileTypeSupported(mediaFile)) {
|
||||||
Fluttertoast.showToast(
|
Fluttertoast.showToast(
|
||||||
msg: translateError(context, ErrorCode.unsupportedFileFormat),
|
msg: translateError(context, ErrorCode.unsupportedFileFormat),
|
||||||
@@ -149,7 +167,7 @@ class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
|
|||||||
}
|
}
|
||||||
final fileDescription = FileDescription.fromPath(mediaFile.path);
|
final fileDescription = FileDescription.fromPath(mediaFile.path);
|
||||||
if (await File(mediaFile.path).exists()) {
|
if (await File(mediaFile.path).exists()) {
|
||||||
final bytes = File(mediaFile.path).readAsBytesSync();
|
final bytes = await File(mediaFile.path).readAsBytes();
|
||||||
final result = await pushDocumentUploadPreparationPage(
|
final result = await pushDocumentUploadPreparationPage(
|
||||||
context,
|
context,
|
||||||
bytes: bytes,
|
bytes: bytes,
|
||||||
|
|||||||
@@ -53,36 +53,42 @@ class HomeRoute extends StatelessWidget {
|
|||||||
Config(
|
Config(
|
||||||
// Isolated cache per user.
|
// Isolated cache per user.
|
||||||
localUserId,
|
localUserId,
|
||||||
fileService: DioFileService(context.read<SessionManager>().client),
|
fileService:
|
||||||
|
DioFileService(context.read<SessionManager>().client),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
ProxyProvider<SessionManager, PaperlessDocumentsApi>(
|
ProxyProvider<SessionManager, PaperlessDocumentsApi>(
|
||||||
update: (context, value, previous) => paperlessProviderFactory.createDocumentsApi(
|
update: (context, value, previous) =>
|
||||||
|
paperlessProviderFactory.createDocumentsApi(
|
||||||
value.client,
|
value.client,
|
||||||
apiVersion: paperlessApiVersion,
|
apiVersion: paperlessApiVersion,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
ProxyProvider<SessionManager, PaperlessLabelsApi>(
|
ProxyProvider<SessionManager, PaperlessLabelsApi>(
|
||||||
update: (context, value, previous) => paperlessProviderFactory.createLabelsApi(
|
update: (context, value, previous) =>
|
||||||
|
paperlessProviderFactory.createLabelsApi(
|
||||||
value.client,
|
value.client,
|
||||||
apiVersion: paperlessApiVersion,
|
apiVersion: paperlessApiVersion,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
ProxyProvider<SessionManager, PaperlessSavedViewsApi>(
|
ProxyProvider<SessionManager, PaperlessSavedViewsApi>(
|
||||||
update: (context, value, previous) => paperlessProviderFactory.createSavedViewsApi(
|
update: (context, value, previous) =>
|
||||||
|
paperlessProviderFactory.createSavedViewsApi(
|
||||||
value.client,
|
value.client,
|
||||||
apiVersion: paperlessApiVersion,
|
apiVersion: paperlessApiVersion,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
ProxyProvider<SessionManager, PaperlessServerStatsApi>(
|
ProxyProvider<SessionManager, PaperlessServerStatsApi>(
|
||||||
update: (context, value, previous) => paperlessProviderFactory.createServerStatsApi(
|
update: (context, value, previous) =>
|
||||||
|
paperlessProviderFactory.createServerStatsApi(
|
||||||
value.client,
|
value.client,
|
||||||
apiVersion: paperlessApiVersion,
|
apiVersion: paperlessApiVersion,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
ProxyProvider<SessionManager, PaperlessTasksApi>(
|
ProxyProvider<SessionManager, PaperlessTasksApi>(
|
||||||
update: (context, value, previous) => paperlessProviderFactory.createTasksApi(
|
update: (context, value, previous) =>
|
||||||
|
paperlessProviderFactory.createTasksApi(
|
||||||
value.client,
|
value.client,
|
||||||
apiVersion: paperlessApiVersion,
|
apiVersion: paperlessApiVersion,
|
||||||
),
|
),
|
||||||
@@ -98,29 +104,41 @@ class HomeRoute extends StatelessWidget {
|
|||||||
return MultiProvider(
|
return MultiProvider(
|
||||||
providers: [
|
providers: [
|
||||||
ProxyProvider<PaperlessLabelsApi, LabelRepository>(
|
ProxyProvider<PaperlessLabelsApi, LabelRepository>(
|
||||||
update: (context, value, previous) => LabelRepository(value)..initialize(),
|
update: (context, value, previous) =>
|
||||||
|
LabelRepository(value)..initialize(),
|
||||||
),
|
),
|
||||||
ProxyProvider<PaperlessSavedViewsApi, SavedViewRepository>(
|
ProxyProvider<PaperlessSavedViewsApi, SavedViewRepository>(
|
||||||
update: (context, value, previous) => SavedViewRepository(value)..initialize(),
|
update: (context, value, previous) =>
|
||||||
|
SavedViewRepository(value)..initialize(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
builder: (context, child) {
|
builder: (context, child) {
|
||||||
return MultiProvider(
|
return MultiProvider(
|
||||||
providers: [
|
providers: [
|
||||||
ProxyProvider3<PaperlessDocumentsApi, DocumentChangedNotifier, LabelRepository,
|
ProxyProvider3<
|
||||||
|
PaperlessDocumentsApi,
|
||||||
|
DocumentChangedNotifier,
|
||||||
|
LabelRepository,
|
||||||
DocumentsCubit>(
|
DocumentsCubit>(
|
||||||
update: (context, docApi, notifier, labelRepo, previous) => DocumentsCubit(
|
update:
|
||||||
|
(context, docApi, notifier, labelRepo, previous) =>
|
||||||
|
DocumentsCubit(
|
||||||
docApi,
|
docApi,
|
||||||
notifier,
|
notifier,
|
||||||
labelRepo,
|
labelRepo,
|
||||||
Hive.box<LocalUserAppState>(HiveBoxes.localUserAppState)
|
Hive.box<LocalUserAppState>(HiveBoxes.localUserAppState)
|
||||||
.get(currentLocalUserId)!,
|
.get(currentLocalUserId)!,
|
||||||
)..reload(),
|
)..initialize(),
|
||||||
),
|
),
|
||||||
Provider(create: (context) => DocumentScannerCubit()),
|
Provider(create: (context) => DocumentScannerCubit()),
|
||||||
ProxyProvider4<PaperlessDocumentsApi, PaperlessServerStatsApi, LabelRepository,
|
ProxyProvider4<
|
||||||
DocumentChangedNotifier, InboxCubit>(
|
PaperlessDocumentsApi,
|
||||||
update: (context, docApi, statsApi, labelRepo, notifier, previous) =>
|
PaperlessServerStatsApi,
|
||||||
|
LabelRepository,
|
||||||
|
DocumentChangedNotifier,
|
||||||
|
InboxCubit>(
|
||||||
|
update: (context, docApi, statsApi, labelRepo, notifier,
|
||||||
|
previous) =>
|
||||||
InboxCubit(
|
InboxCubit(
|
||||||
docApi,
|
docApi,
|
||||||
statsApi,
|
statsApi,
|
||||||
@@ -129,19 +147,22 @@ class HomeRoute extends StatelessWidget {
|
|||||||
)..initialize(),
|
)..initialize(),
|
||||||
),
|
),
|
||||||
ProxyProvider<SavedViewRepository, SavedViewCubit>(
|
ProxyProvider<SavedViewRepository, SavedViewCubit>(
|
||||||
update: (context, savedViewRepo, previous) => SavedViewCubit(
|
update: (context, savedViewRepo, previous) =>
|
||||||
|
SavedViewCubit(
|
||||||
savedViewRepo,
|
savedViewRepo,
|
||||||
)..initialize(),
|
),
|
||||||
),
|
),
|
||||||
ProxyProvider<LabelRepository, LabelCubit>(
|
ProxyProvider<LabelRepository, LabelCubit>(
|
||||||
update: (context, value, previous) => LabelCubit(value),
|
update: (context, value, previous) => LabelCubit(value),
|
||||||
),
|
),
|
||||||
ProxyProvider<PaperlessTasksApi, TaskStatusCubit>(
|
ProxyProvider<PaperlessTasksApi, TaskStatusCubit>(
|
||||||
update: (context, value, previous) => TaskStatusCubit(value),
|
update: (context, value, previous) =>
|
||||||
|
TaskStatusCubit(value),
|
||||||
),
|
),
|
||||||
if (paperlessApiVersion >= 3)
|
if (paperlessApiVersion >= 3)
|
||||||
ProxyProvider<PaperlessUserApiV3, UserRepository>(
|
ProxyProvider<PaperlessUserApiV3, UserRepository>(
|
||||||
update: (context, value, previous) => UserRepository(value)..initialize(),
|
update: (context, value, previous) =>
|
||||||
|
UserRepository(value)..initialize(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
child: HomePage(paperlessApiVersion: paperlessApiVersion),
|
child: HomePage(paperlessApiVersion: paperlessApiVersion),
|
||||||
|
|||||||
@@ -30,7 +30,9 @@ class VerifyIdentityPage extends StatelessWidget {
|
|||||||
return Column(
|
return Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
Text(S.of(context)!.useTheConfiguredBiometricFactorToAuthenticate)
|
Text(S
|
||||||
|
.of(context)!
|
||||||
|
.useTheConfiguredBiometricFactorToAuthenticate)
|
||||||
.paddedSymmetrically(horizontal: 16),
|
.paddedSymmetrically(horizontal: 16),
|
||||||
const Icon(
|
const Icon(
|
||||||
Icons.fingerprint,
|
Icons.fingerprint,
|
||||||
@@ -52,7 +54,9 @@ class VerifyIdentityPage extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
onPressed: () => context.read<AuthenticationCubit>().restoreSessionState(),
|
onPressed: () => context
|
||||||
|
.read<AuthenticationCubit>()
|
||||||
|
.restoreSessionState(),
|
||||||
child: Text(S.of(context)!.verifyIdentity),
|
child: Text(S.of(context)!.verifyIdentity),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -129,8 +129,9 @@ class _FullscreenTagsFormState extends State<FullscreenTagsForm> {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
bottom: PreferredSize(
|
bottom: PreferredSize(
|
||||||
preferredSize:
|
preferredSize: !widget.allowOnlySelection
|
||||||
!widget.allowOnlySelection ? const Size.fromHeight(32) : const Size.fromHeight(1),
|
? const Size.fromHeight(32)
|
||||||
|
: const Size.fromHeight(1),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Divider(color: theme.colorScheme.outline),
|
Divider(color: theme.colorScheme.outline),
|
||||||
@@ -233,7 +234,8 @@ class _FullscreenTagsFormState extends State<FullscreenTagsForm> {
|
|||||||
yield _buildNotAssignedOption();
|
yield _buildNotAssignedOption();
|
||||||
}
|
}
|
||||||
|
|
||||||
var matches = _options.where((e) => e.name.trim().toLowerCase().contains(normalizedQuery));
|
var matches = _options
|
||||||
|
.where((e) => e.name.trim().toLowerCase().contains(normalizedQuery));
|
||||||
if (matches.isEmpty && widget.allowCreation) {
|
if (matches.isEmpty && widget.allowCreation) {
|
||||||
yield Text(S.of(context)!.noItemsFound);
|
yield Text(S.of(context)!.noItemsFound);
|
||||||
yield TextButton(
|
yield TextButton(
|
||||||
@@ -299,7 +301,9 @@ class SelectableTagWidget extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ListTile(
|
return ListTile(
|
||||||
title: Text(tag.name),
|
title: Text(tag.name),
|
||||||
trailing: excluded ? const Icon(Icons.close) : (selected ? const Icon(Icons.done) : null),
|
trailing: excluded
|
||||||
|
? const Icon(Icons.close)
|
||||||
|
: (selected ? const Icon(Icons.done) : null),
|
||||||
leading: CircleAvatar(
|
leading: CircleAvatar(
|
||||||
backgroundColor: tag.color,
|
backgroundColor: tag.color,
|
||||||
child: (tag.isInboxTag)
|
child: (tag.isInboxTag)
|
||||||
|
|||||||
@@ -32,8 +32,8 @@ class TagsFormField extends StatelessWidget {
|
|||||||
initialValue: initialValue,
|
initialValue: initialValue,
|
||||||
builder: (field) {
|
builder: (field) {
|
||||||
final values = _generateOptions(context, field.value, field).toList();
|
final values = _generateOptions(context, field.value, field).toList();
|
||||||
final isEmpty =
|
final isEmpty = (field.value is IdsTagsQuery &&
|
||||||
(field.value is IdsTagsQuery && (field.value as IdsTagsQuery).include.isEmpty) ||
|
(field.value as IdsTagsQuery).include.isEmpty) ||
|
||||||
field.value == null;
|
field.value == null;
|
||||||
bool anyAssigned = field.value is AnyAssignedTagsQuery;
|
bool anyAssigned = field.value is AnyAssignedTagsQuery;
|
||||||
return OpenContainer<TagsQuery>(
|
return OpenContainer<TagsQuery>(
|
||||||
@@ -59,7 +59,8 @@ class TagsFormField extends StatelessWidget {
|
|||||||
height: 32,
|
height: 32,
|
||||||
child: ListView.separated(
|
child: ListView.separated(
|
||||||
scrollDirection: Axis.horizontal,
|
scrollDirection: Axis.horizontal,
|
||||||
separatorBuilder: (context, index) => const SizedBox(width: 4),
|
separatorBuilder: (context, index) =>
|
||||||
|
const SizedBox(width: 4),
|
||||||
itemBuilder: (context, index) => values[index],
|
itemBuilder: (context, index) => values[index],
|
||||||
itemCount: values.length,
|
itemCount: values.length,
|
||||||
),
|
),
|
||||||
@@ -99,11 +100,14 @@ class TagsFormField extends StatelessWidget {
|
|||||||
} else {
|
} else {
|
||||||
final widgets = query.map(
|
final widgets = query.map(
|
||||||
ids: (value) => [
|
ids: (value) => [
|
||||||
for (var inc in value.include) _buildTagIdQueryWidget(context, inc, field, false),
|
for (var inc in value.include)
|
||||||
for (var exc in value.exclude) _buildTagIdQueryWidget(context, exc, field, true),
|
_buildTagIdQueryWidget(context, inc, field, false),
|
||||||
|
for (var exc in value.exclude)
|
||||||
|
_buildTagIdQueryWidget(context, exc, field, true),
|
||||||
],
|
],
|
||||||
anyAssigned: (value) => [
|
anyAssigned: (value) => [
|
||||||
for (var id in value.tagIds) _buildAnyAssignedTagWidget(context, id, field, value),
|
for (var id in value.tagIds)
|
||||||
|
_buildAnyAssignedTagWidget(context, id, field, value),
|
||||||
],
|
],
|
||||||
notAssigned: (value) => [_buildNotAssignedTagWidget(context, field)],
|
notAssigned: (value) => [_buildNotAssignedTagWidget(context, field)],
|
||||||
);
|
);
|
||||||
@@ -124,15 +128,19 @@ class TagsFormField extends StatelessWidget {
|
|||||||
final tag = options[id]!;
|
final tag = options[id]!;
|
||||||
return QueryTagChip(
|
return QueryTagChip(
|
||||||
onDeleted: () => field.didChange(formValue.copyWith(
|
onDeleted: () => field.didChange(formValue.copyWith(
|
||||||
include: formValue.include.whereNot((element) => element == id).toList(),
|
include:
|
||||||
exclude: formValue.exclude.whereNot((element) => element == id).toList(),
|
formValue.include.whereNot((element) => element == id).toList(),
|
||||||
|
exclude:
|
||||||
|
formValue.exclude.whereNot((element) => element == id).toList(),
|
||||||
)),
|
)),
|
||||||
onSelected: allowExclude
|
onSelected: allowExclude
|
||||||
? () {
|
? () {
|
||||||
if (formValue.include.contains(id)) {
|
if (formValue.include.contains(id)) {
|
||||||
field.didChange(
|
field.didChange(
|
||||||
formValue.copyWith(
|
formValue.copyWith(
|
||||||
include: formValue.include.whereNot((element) => element == id).toList(),
|
include: formValue.include
|
||||||
|
.whereNot((element) => element == id)
|
||||||
|
.toList(),
|
||||||
exclude: [...formValue.exclude, id],
|
exclude: [...formValue.exclude, id],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -140,7 +148,9 @@ class TagsFormField extends StatelessWidget {
|
|||||||
field.didChange(
|
field.didChange(
|
||||||
formValue.copyWith(
|
formValue.copyWith(
|
||||||
include: [...formValue.include, id],
|
include: [...formValue.include, id],
|
||||||
exclude: formValue.exclude.whereNot((element) => element == id).toList(),
|
exclude: formValue.exclude
|
||||||
|
.whereNot((element) => element == id)
|
||||||
|
.toList(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,7 +35,8 @@ class FullscreenLabelForm<T extends Label> extends StatefulWidget {
|
|||||||
!(initialValue?.isOnlyAssigned() ?? false) || showAnyAssignedOption,
|
!(initialValue?.isOnlyAssigned() ?? false) || showAnyAssignedOption,
|
||||||
),
|
),
|
||||||
assert(
|
assert(
|
||||||
!(initialValue?.isOnlyNotAssigned() ?? false) || showNotAssignedOption,
|
!(initialValue?.isOnlyNotAssigned() ?? false) ||
|
||||||
|
showNotAssignedOption,
|
||||||
),
|
),
|
||||||
assert((addNewLabelText != null) == (onCreateNewLabel != null));
|
assert((addNewLabelText != null) == (onCreateNewLabel != null));
|
||||||
|
|
||||||
@@ -43,7 +44,8 @@ class FullscreenLabelForm<T extends Label> extends StatefulWidget {
|
|||||||
State<FullscreenLabelForm> createState() => _FullscreenLabelFormState();
|
State<FullscreenLabelForm> createState() => _FullscreenLabelFormState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _FullscreenLabelFormState<T extends Label> extends State<FullscreenLabelForm<T>> {
|
class _FullscreenLabelFormState<T extends Label>
|
||||||
|
extends State<FullscreenLabelForm<T>> {
|
||||||
bool _showClearIcon = false;
|
bool _showClearIcon = false;
|
||||||
final _textEditingController = TextEditingController();
|
final _textEditingController = TextEditingController();
|
||||||
final _focusNode = FocusNode();
|
final _focusNode = FocusNode();
|
||||||
@@ -133,9 +135,11 @@ class _FullscreenLabelFormState<T extends Label> extends State<FullscreenLabelFo
|
|||||||
itemCount: options.length,
|
itemCount: options.length,
|
||||||
itemBuilder: (BuildContext context, int index) {
|
itemBuilder: (BuildContext context, int index) {
|
||||||
final option = options.elementAt(index);
|
final option = options.elementAt(index);
|
||||||
final highlight = AutocompleteHighlightedOption.of(context) == index;
|
final highlight =
|
||||||
|
AutocompleteHighlightedOption.of(context) == index;
|
||||||
if (highlight) {
|
if (highlight) {
|
||||||
SchedulerBinding.instance.addPostFrameCallback((Duration timeStamp) {
|
SchedulerBinding.instance
|
||||||
|
.addPostFrameCallback((Duration timeStamp) {
|
||||||
Scrollable.ensureVisible(
|
Scrollable.ensureVisible(
|
||||||
context,
|
context,
|
||||||
alignment: 0,
|
alignment: 0,
|
||||||
@@ -191,7 +195,8 @@ class _FullscreenLabelFormState<T extends Label> extends State<FullscreenLabelFo
|
|||||||
for (final option in widget.options.values) {
|
for (final option in widget.options.values) {
|
||||||
// Don't include the initial value in the selection
|
// Don't include the initial value in the selection
|
||||||
final initialValue = widget.initialValue;
|
final initialValue = widget.initialValue;
|
||||||
if (initialValue is SetIdQueryParameter && option.id == initialValue.id) {
|
if (initialValue is SetIdQueryParameter &&
|
||||||
|
option.id == initialValue.id) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
yield IdQueryParameter.fromId(option.id!);
|
yield IdQueryParameter.fromId(option.id!);
|
||||||
@@ -199,8 +204,8 @@ class _FullscreenLabelFormState<T extends Label> extends State<FullscreenLabelFo
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Show filtered options, if no matching option is found, always show not assigned and any assigned (if enabled) and proceed.
|
// Show filtered options, if no matching option is found, always show not assigned and any assigned (if enabled) and proceed.
|
||||||
final matches =
|
final matches = widget.options.values
|
||||||
widget.options.values.where((e) => e.name.trim().toLowerCase().contains(normalizedQuery));
|
.where((e) => e.name.trim().toLowerCase().contains(normalizedQuery));
|
||||||
if (matches.isNotEmpty) {
|
if (matches.isNotEmpty) {
|
||||||
for (final match in matches) {
|
for (final match in matches) {
|
||||||
yield IdQueryParameter.fromId(match.id!);
|
yield IdQueryParameter.fromId(match.id!);
|
||||||
@@ -270,7 +275,9 @@ class _FullscreenLabelFormState<T extends Label> extends State<FullscreenLabelFo
|
|||||||
selectedTileColor: Theme.of(context).focusColor,
|
selectedTileColor: Theme.of(context).focusColor,
|
||||||
title: Text(widget.options[id]!.name),
|
title: Text(widget.options[id]!.name),
|
||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
enabled: widget.allowSelectUnassigned ? true : widget.options[id]!.documentCount != 0,
|
enabled: widget.allowSelectUnassigned
|
||||||
|
? true
|
||||||
|
: widget.options[id]!.documentCount != 0,
|
||||||
),
|
),
|
||||||
)!; // Never null, since we already return on unset before
|
)!; // Never null, since we already return on unset before
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,8 +58,8 @@ class LabelFormField<T extends Label> extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final isEnabled =
|
final isEnabled = options.values.any((e) => (e.documentCount ?? 0) > 0) ||
|
||||||
options.values.any((e) => (e.documentCount ?? 0) > 0) || addLabelPageBuilder != null;
|
addLabelPageBuilder != null;
|
||||||
return FormBuilderField<IdQueryParameter>(
|
return FormBuilderField<IdQueryParameter>(
|
||||||
name: name,
|
name: name,
|
||||||
initialValue: initialValue,
|
initialValue: initialValue,
|
||||||
@@ -70,7 +70,9 @@ class LabelFormField<T extends Label> extends StatelessWidget {
|
|||||||
text: _buildText(context, field.value),
|
text: _buildText(context, field.value),
|
||||||
);
|
);
|
||||||
final displayedSuggestions = suggestions
|
final displayedSuggestions = suggestions
|
||||||
.whereNot((e) => e.id == field.value?.maybeWhen(fromId: (id) => id, orElse: () => -1))
|
.whereNot((e) =>
|
||||||
|
e.id ==
|
||||||
|
field.value?.maybeWhen(fromId: (id) => id, orElse: () => -1))
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
@@ -95,7 +97,8 @@ class LabelFormField<T extends Label> extends StatelessWidget {
|
|||||||
suffixIcon: controller.text.isNotEmpty
|
suffixIcon: controller.text.isNotEmpty
|
||||||
? IconButton(
|
? IconButton(
|
||||||
icon: const Icon(Icons.clear),
|
icon: const Icon(Icons.clear),
|
||||||
onPressed: () => field.didChange(const IdQueryParameter.unset()),
|
onPressed: () =>
|
||||||
|
field.didChange(const IdQueryParameter.unset()),
|
||||||
)
|
)
|
||||||
: null,
|
: null,
|
||||||
),
|
),
|
||||||
@@ -110,7 +113,8 @@ class LabelFormField<T extends Label> extends StatelessWidget {
|
|||||||
? (initialName) {
|
? (initialName) {
|
||||||
return Navigator.of(context).push<T>(
|
return Navigator.of(context).push<T>(
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) => addLabelPageBuilder!(initialName),
|
builder: (context) =>
|
||||||
|
addLabelPageBuilder!(initialName),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -141,7 +145,8 @@ class LabelFormField<T extends Label> extends StatelessWidget {
|
|||||||
scrollDirection: Axis.horizontal,
|
scrollDirection: Axis.horizontal,
|
||||||
itemCount: displayedSuggestions.length,
|
itemCount: displayedSuggestions.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final suggestion = displayedSuggestions.elementAt(index);
|
final suggestion =
|
||||||
|
displayedSuggestions.elementAt(index);
|
||||||
return ColoredChipWrapper(
|
return ColoredChipWrapper(
|
||||||
child: ActionChip(
|
child: ActionChip(
|
||||||
label: Text(suggestion.name),
|
label: Text(suggestion.name),
|
||||||
|
|||||||
@@ -74,8 +74,11 @@ class LabelTabView<T extends Label> extends StatelessWidget {
|
|||||||
name: l.name,
|
name: l.name,
|
||||||
content: contentBuilder?.call(l) ??
|
content: contentBuilder?.call(l) ??
|
||||||
Text(
|
Text(
|
||||||
translateMatchingAlgorithmName(context, l.matchingAlgorithm) +
|
translateMatchingAlgorithmName(
|
||||||
((l.match?.isNotEmpty ?? false) ? ": ${l.match}" : ""),
|
context, l.matchingAlgorithm) +
|
||||||
|
((l.match?.isNotEmpty ?? false)
|
||||||
|
? ": ${l.match}"
|
||||||
|
: ""),
|
||||||
maxLines: 2,
|
maxLines: 2,
|
||||||
),
|
),
|
||||||
onOpenEditPage: canEdit ? onEdit : null,
|
onOpenEditPage: canEdit ? onEdit : null,
|
||||||
|
|||||||
@@ -49,7 +49,8 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
|
|||||||
final apiVersion = await _getApiVersion(_sessionManager.client);
|
final apiVersion = await _getApiVersion(_sessionManager.client);
|
||||||
|
|
||||||
// Mark logged in user as currently active user.
|
// Mark logged in user as currently active user.
|
||||||
final globalSettings = Hive.box<GlobalSettings>(HiveBoxes.globalSettings).getValue()!;
|
final globalSettings =
|
||||||
|
Hive.box<GlobalSettings>(HiveBoxes.globalSettings).getValue()!;
|
||||||
globalSettings.currentLoggedInUser = localUserId;
|
globalSettings.currentLoggedInUser = localUserId;
|
||||||
await globalSettings.save();
|
await globalSettings.save();
|
||||||
|
|
||||||
@@ -64,11 +65,13 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
|
|||||||
/// Switches to another account if it exists.
|
/// Switches to another account if it exists.
|
||||||
Future<void> switchAccount(String localUserId) async {
|
Future<void> switchAccount(String localUserId) async {
|
||||||
emit(const AuthenticationState.switchingAccounts());
|
emit(const AuthenticationState.switchingAccounts());
|
||||||
final globalSettings = Hive.box<GlobalSettings>(HiveBoxes.globalSettings).getValue()!;
|
final globalSettings =
|
||||||
|
Hive.box<GlobalSettings>(HiveBoxes.globalSettings).getValue()!;
|
||||||
if (globalSettings.currentLoggedInUser == localUserId) {
|
if (globalSettings.currentLoggedInUser == localUserId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final userAccountBox = Hive.box<LocalUserAccount>(HiveBoxes.localUserAccount);
|
final userAccountBox =
|
||||||
|
Hive.box<LocalUserAccount>(HiveBoxes.localUserAccount);
|
||||||
|
|
||||||
if (!userAccountBox.containsKey(localUserId)) {
|
if (!userAccountBox.containsKey(localUserId)) {
|
||||||
debugPrint("User $localUserId not yet registered.");
|
debugPrint("User $localUserId not yet registered.");
|
||||||
@@ -78,15 +81,15 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
|
|||||||
final account = userAccountBox.get(localUserId)!;
|
final account = userAccountBox.get(localUserId)!;
|
||||||
|
|
||||||
if (account.settings.isBiometricAuthenticationEnabled) {
|
if (account.settings.isBiometricAuthenticationEnabled) {
|
||||||
final authenticated =
|
final authenticated = await _localAuthService
|
||||||
await _localAuthService.authenticateLocalUser("Authenticate to switch your account.");
|
.authenticateLocalUser("Authenticate to switch your account.");
|
||||||
if (!authenticated) {
|
if (!authenticated) {
|
||||||
debugPrint("User not authenticated.");
|
debugPrint("User not authenticated.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await withEncryptedBox<UserCredentials, void>(HiveBoxes.localUserCredentials,
|
await withEncryptedBox<UserCredentials, void>(
|
||||||
(credentialsBox) async {
|
HiveBoxes.localUserCredentials, (credentialsBox) async {
|
||||||
if (!credentialsBox.containsKey(localUserId)) {
|
if (!credentialsBox.containsKey(localUserId)) {
|
||||||
await credentialsBox.close();
|
await credentialsBox.close();
|
||||||
debugPrint("Invalid authentication for $localUserId");
|
debugPrint("Invalid authentication for $localUserId");
|
||||||
@@ -108,7 +111,8 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
|
|||||||
|
|
||||||
await _updateRemoteUser(
|
await _updateRemoteUser(
|
||||||
_sessionManager,
|
_sessionManager,
|
||||||
Hive.box<LocalUserAccount>(HiveBoxes.localUserAccount).get(localUserId)!,
|
Hive.box<LocalUserAccount>(HiveBoxes.localUserAccount)
|
||||||
|
.get(localUserId)!,
|
||||||
apiVersion,
|
apiVersion,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -140,12 +144,15 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> removeAccount(String userId) async {
|
Future<void> removeAccount(String userId) async {
|
||||||
final userAccountBox = Hive.box<LocalUserAccount>(HiveBoxes.localUserAccount);
|
final userAccountBox =
|
||||||
final userAppStateBox = Hive.box<LocalUserAppState>(HiveBoxes.localUserAppState);
|
Hive.box<LocalUserAccount>(HiveBoxes.localUserAccount);
|
||||||
|
final userAppStateBox =
|
||||||
|
Hive.box<LocalUserAppState>(HiveBoxes.localUserAppState);
|
||||||
|
|
||||||
await userAccountBox.delete(userId);
|
await userAccountBox.delete(userId);
|
||||||
await userAppStateBox.delete(userId);
|
await userAppStateBox.delete(userId);
|
||||||
await withEncryptedBox<UserCredentials, void>(HiveBoxes.localUserCredentials, (box) {
|
await withEncryptedBox<UserCredentials, void>(
|
||||||
|
HiveBoxes.localUserCredentials, (box) {
|
||||||
box.delete(userId);
|
box.delete(userId);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -154,25 +161,28 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
|
|||||||
/// Performs a conditional hydration based on the local authentication success.
|
/// Performs a conditional hydration based on the local authentication success.
|
||||||
///
|
///
|
||||||
Future<void> restoreSessionState() async {
|
Future<void> restoreSessionState() async {
|
||||||
final globalSettings = Hive.box<GlobalSettings>(HiveBoxes.globalSettings).getValue()!;
|
final globalSettings =
|
||||||
|
Hive.box<GlobalSettings>(HiveBoxes.globalSettings).getValue()!;
|
||||||
final localUserId = globalSettings.currentLoggedInUser;
|
final localUserId = globalSettings.currentLoggedInUser;
|
||||||
if (localUserId == null) {
|
if (localUserId == null) {
|
||||||
// If there is nothing to restore, we can quit here.
|
// If there is nothing to restore, we can quit here.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final localUserAccountBox = Hive.box<LocalUserAccount>(HiveBoxes.localUserAccount);
|
final localUserAccountBox =
|
||||||
|
Hive.box<LocalUserAccount>(HiveBoxes.localUserAccount);
|
||||||
final localUserAccount = localUserAccountBox.get(localUserId)!;
|
final localUserAccount = localUserAccountBox.get(localUserId)!;
|
||||||
|
|
||||||
if (localUserAccount.settings.isBiometricAuthenticationEnabled) {
|
if (localUserAccount.settings.isBiometricAuthenticationEnabled) {
|
||||||
final localAuthSuccess =
|
final localAuthSuccess = await _localAuthService
|
||||||
await _localAuthService.authenticateLocalUser("Authenticate to log back in"); //TODO: INTL
|
.authenticateLocalUser("Authenticate to log back in"); //TODO: INTL
|
||||||
if (!localAuthSuccess) {
|
if (!localAuthSuccess) {
|
||||||
emit(const AuthenticationState.requriresLocalAuthentication());
|
emit(const AuthenticationState.requriresLocalAuthentication());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final authentication = await withEncryptedBox<UserCredentials, UserCredentials>(
|
final authentication =
|
||||||
|
await withEncryptedBox<UserCredentials, UserCredentials>(
|
||||||
HiveBoxes.localUserCredentials, (box) {
|
HiveBoxes.localUserCredentials, (box) {
|
||||||
return box.get(globalSettings.currentLoggedInUser!);
|
return box.get(globalSettings.currentLoggedInUser!);
|
||||||
});
|
});
|
||||||
@@ -202,7 +212,8 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
|
|||||||
|
|
||||||
Future<void> logout() async {
|
Future<void> logout() async {
|
||||||
await _resetExternalState();
|
await _resetExternalState();
|
||||||
final globalSettings = Hive.box<GlobalSettings>(HiveBoxes.globalSettings).getValue()!;
|
final globalSettings =
|
||||||
|
Hive.box<GlobalSettings>(HiveBoxes.globalSettings).getValue()!;
|
||||||
globalSettings.currentLoggedInUser = null;
|
globalSettings.currentLoggedInUser = null;
|
||||||
await globalSettings.save();
|
await globalSettings.save();
|
||||||
emit(const AuthenticationState.unauthenticated());
|
emit(const AuthenticationState.unauthenticated());
|
||||||
@@ -240,8 +251,10 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
|
|||||||
authToken: token,
|
authToken: token,
|
||||||
);
|
);
|
||||||
|
|
||||||
final userAccountBox = Hive.box<LocalUserAccount>(HiveBoxes.localUserAccount);
|
final userAccountBox =
|
||||||
final userStateBox = Hive.box<LocalUserAppState>(HiveBoxes.localUserAppState);
|
Hive.box<LocalUserAccount>(HiveBoxes.localUserAccount);
|
||||||
|
final userStateBox =
|
||||||
|
Hive.box<LocalUserAppState>(HiveBoxes.localUserAppState);
|
||||||
|
|
||||||
if (userAccountBox.containsKey(localUserId)) {
|
if (userAccountBox.containsKey(localUserId)) {
|
||||||
throw Exception("User already exists!");
|
throw Exception("User already exists!");
|
||||||
|
|||||||
@@ -3,7 +3,8 @@ part of 'authentication_cubit.dart';
|
|||||||
@freezed
|
@freezed
|
||||||
class AuthenticationState with _$AuthenticationState {
|
class AuthenticationState with _$AuthenticationState {
|
||||||
const factory AuthenticationState.unauthenticated() = _Unauthenticated;
|
const factory AuthenticationState.unauthenticated() = _Unauthenticated;
|
||||||
const factory AuthenticationState.requriresLocalAuthentication() = _RequiresLocalAuthentication;
|
const factory AuthenticationState.requriresLocalAuthentication() =
|
||||||
|
_RequiresLocalAuthentication;
|
||||||
const factory AuthenticationState.authenticated({
|
const factory AuthenticationState.authenticated({
|
||||||
required String localUserId,
|
required String localUserId,
|
||||||
required int apiVersion,
|
required int apiVersion,
|
||||||
|
|||||||
@@ -27,8 +27,8 @@ class OldAuthenticationState with EquatableMixin {
|
|||||||
}) {
|
}) {
|
||||||
return OldAuthenticationState(
|
return OldAuthenticationState(
|
||||||
isAuthenticated: isAuthenticated ?? this.isAuthenticated,
|
isAuthenticated: isAuthenticated ?? this.isAuthenticated,
|
||||||
showBiometricAuthenticationScreen:
|
showBiometricAuthenticationScreen: showBiometricAuthenticationScreen ??
|
||||||
showBiometricAuthenticationScreen ?? this.showBiometricAuthenticationScreen,
|
this.showBiometricAuthenticationScreen,
|
||||||
username: username ?? this.username,
|
username: username ?? this.username,
|
||||||
fullName: fullName ?? this.fullName,
|
fullName: fullName ?? this.fullName,
|
||||||
localUserId: localUserId ?? this.localUserId,
|
localUserId: localUserId ?? this.localUserId,
|
||||||
|
|||||||
@@ -54,7 +54,8 @@ class _LoginPageState extends State<LoginPage> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final localAccounts = Hive.box<LocalUserAccount>(HiveBoxes.localUserAccount);
|
final localAccounts =
|
||||||
|
Hive.box<LocalUserAccount>(HiveBoxes.localUserAccount);
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
resizeToAvoidBottomInset: false,
|
resizeToAvoidBottomInset: false,
|
||||||
body: FormBuilder(
|
body: FormBuilder(
|
||||||
@@ -91,7 +92,9 @@ class _LoginPageState extends State<LoginPage> {
|
|||||||
child: UserAccountListTile(
|
child: UserAccountListTile(
|
||||||
account: account,
|
account: account,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
context.read<AuthenticationCubit>().switchAccount(account.id);
|
context
|
||||||
|
.read<AuthenticationCubit>()
|
||||||
|
.switchAccount(account.id);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -126,14 +129,16 @@ class _LoginPageState extends State<LoginPage> {
|
|||||||
final form = _formKey.currentState!.value;
|
final form = _formKey.currentState!.value;
|
||||||
ClientCertificate? clientCert;
|
ClientCertificate? clientCert;
|
||||||
final clientCertFormModel =
|
final clientCertFormModel =
|
||||||
form[ClientCertificateFormField.fkClientCertificate] as ClientCertificateFormModel?;
|
form[ClientCertificateFormField.fkClientCertificate]
|
||||||
|
as ClientCertificateFormModel?;
|
||||||
if (clientCertFormModel != null) {
|
if (clientCertFormModel != null) {
|
||||||
clientCert = ClientCertificate(
|
clientCert = ClientCertificate(
|
||||||
bytes: clientCertFormModel.bytes,
|
bytes: clientCertFormModel.bytes,
|
||||||
passphrase: clientCertFormModel.passphrase,
|
passphrase: clientCertFormModel.passphrase,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
final credentials = form[UserCredentialsFormField.fkCredentials] as LoginFormCredentials;
|
final credentials =
|
||||||
|
form[UserCredentialsFormField.fkCredentials] as LoginFormCredentials;
|
||||||
try {
|
try {
|
||||||
await widget.onSubmit(
|
await widget.onSubmit(
|
||||||
context,
|
context,
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
|||||||
import 'package:paperless_mobile/features/login/model/client_certificate_form_model.dart';
|
import 'package:paperless_mobile/features/login/model/client_certificate_form_model.dart';
|
||||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||||
|
|
||||||
|
|
||||||
import 'obscured_input_text_form_field.dart';
|
import 'obscured_input_text_form_field.dart';
|
||||||
|
|
||||||
class ClientCertificateFormField extends StatefulWidget {
|
class ClientCertificateFormField extends StatefulWidget {
|
||||||
@@ -20,10 +19,12 @@ class ClientCertificateFormField extends StatefulWidget {
|
|||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<ClientCertificateFormField> createState() => _ClientCertificateFormFieldState();
|
State<ClientCertificateFormField> createState() =>
|
||||||
|
_ClientCertificateFormFieldState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ClientCertificateFormFieldState extends State<ClientCertificateFormField> {
|
class _ClientCertificateFormFieldState
|
||||||
|
extends State<ClientCertificateFormField> {
|
||||||
File? _selectedFile;
|
File? _selectedFile;
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -42,7 +43,8 @@ class _ClientCertificateFormFieldState extends State<ClientCertificateFormField>
|
|||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
builder: (field) {
|
builder: (field) {
|
||||||
final theme = Theme.of(context).copyWith(dividerColor: Colors.transparent); //new
|
final theme =
|
||||||
|
Theme.of(context).copyWith(dividerColor: Colors.transparent); //new
|
||||||
return Theme(
|
return Theme(
|
||||||
data: theme,
|
data: theme,
|
||||||
child: ExpansionTile(
|
child: ExpansionTile(
|
||||||
@@ -119,7 +121,8 @@ class _ClientCertificateFormFieldState extends State<ClientCertificateFormField>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onSelectFile(FormFieldState<ClientCertificateFormModel?> field) async {
|
Future<void> _onSelectFile(
|
||||||
|
FormFieldState<ClientCertificateFormModel?> field) async {
|
||||||
FilePickerResult? result = await FilePicker.platform.pickFiles(
|
FilePickerResult? result = await FilePicker.platform.pickFiles(
|
||||||
allowMultiple: false,
|
allowMultiple: false,
|
||||||
);
|
);
|
||||||
@@ -128,13 +131,15 @@ class _ClientCertificateFormFieldState extends State<ClientCertificateFormField>
|
|||||||
setState(() {
|
setState(() {
|
||||||
_selectedFile = file;
|
_selectedFile = file;
|
||||||
});
|
});
|
||||||
final changedValue = field.value?.copyWith(bytes: file.readAsBytesSync()) ??
|
final changedValue =
|
||||||
|
field.value?.copyWith(bytes: file.readAsBytesSync()) ??
|
||||||
ClientCertificateFormModel(bytes: file.readAsBytesSync());
|
ClientCertificateFormModel(bytes: file.readAsBytesSync());
|
||||||
field.didChange(changedValue);
|
field.didChange(changedValue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildSelectedFileText(FormFieldState<ClientCertificateFormModel?> field) {
|
Widget _buildSelectedFileText(
|
||||||
|
FormFieldState<ClientCertificateFormModel?> field) {
|
||||||
if (field.value == null) {
|
if (field.value == null) {
|
||||||
assert(_selectedFile == null);
|
assert(_selectedFile == null);
|
||||||
return Text(
|
return Text(
|
||||||
|
|||||||
@@ -67,7 +67,8 @@ class _ServerAddressFormFieldState extends State<ServerAddressFormField> {
|
|||||||
.where((element) => element.contains(textEditingValue.text));
|
.where((element) => element.contains(textEditingValue.text));
|
||||||
},
|
},
|
||||||
onSelected: (option) => _formatInput(),
|
onSelected: (option) => _formatInput(),
|
||||||
fieldViewBuilder: (context, textEditingController, focusNode, onFieldSubmitted) {
|
fieldViewBuilder:
|
||||||
|
(context, textEditingController, focusNode, onFieldSubmitted) {
|
||||||
return TextField(
|
return TextField(
|
||||||
controller: textEditingController,
|
controller: textEditingController,
|
||||||
focusNode: focusNode,
|
focusNode: focusNode,
|
||||||
@@ -146,9 +147,11 @@ class _AutocompleteOptions extends StatelessWidget {
|
|||||||
onSelected(option);
|
onSelected(option);
|
||||||
},
|
},
|
||||||
child: Builder(builder: (BuildContext context) {
|
child: Builder(builder: (BuildContext context) {
|
||||||
final bool highlight = AutocompleteHighlightedOption.of(context) == index;
|
final bool highlight =
|
||||||
|
AutocompleteHighlightedOption.of(context) == index;
|
||||||
if (highlight) {
|
if (highlight) {
|
||||||
SchedulerBinding.instance.addPostFrameCallback((Duration timeStamp) {
|
SchedulerBinding.instance
|
||||||
|
.addPostFrameCallback((Duration timeStamp) {
|
||||||
Scrollable.ensureVisible(context, alignment: 0.5);
|
Scrollable.ensureVisible(context, alignment: 0.5);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,8 @@ class UserCredentialsFormField extends StatefulWidget {
|
|||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<UserCredentialsFormField> createState() => _UserCredentialsFormFieldState();
|
State<UserCredentialsFormField> createState() =>
|
||||||
|
_UserCredentialsFormFieldState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _UserCredentialsFormFieldState extends State<UserCredentialsFormField> {
|
class _UserCredentialsFormFieldState extends State<UserCredentialsFormField> {
|
||||||
|
|||||||
@@ -38,8 +38,9 @@ class _ServerConnectionPageState extends State<ServerConnectionPage> {
|
|||||||
toolbarHeight: kToolbarHeight - 4,
|
toolbarHeight: kToolbarHeight - 4,
|
||||||
title: Text(widget.titleString),
|
title: Text(widget.titleString),
|
||||||
bottom: PreferredSize(
|
bottom: PreferredSize(
|
||||||
child:
|
child: _isCheckingConnection
|
||||||
_isCheckingConnection ? const LinearProgressIndicator() : const SizedBox(height: 4.0),
|
? const LinearProgressIndicator()
|
||||||
|
: const SizedBox(height: 4.0),
|
||||||
preferredSize: const Size.fromHeight(4.0),
|
preferredSize: const Size.fromHeight(4.0),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -69,8 +70,9 @@ class _ServerConnectionPageState extends State<ServerConnectionPage> {
|
|||||||
),
|
),
|
||||||
FilledButton(
|
FilledButton(
|
||||||
child: Text(S.of(context)!.continueLabel),
|
child: Text(S.of(context)!.continueLabel),
|
||||||
onPressed:
|
onPressed: _reachabilityStatus == ReachabilityStatus.reachable
|
||||||
_reachabilityStatus == ReachabilityStatus.reachable ? widget.onContinue : null,
|
? widget.onContinue
|
||||||
|
: null,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -85,12 +87,15 @@ class _ServerConnectionPageState extends State<ServerConnectionPage> {
|
|||||||
final certForm = widget.formBuilderKey.currentState
|
final certForm = widget.formBuilderKey.currentState
|
||||||
?.getRawValue(ClientCertificateFormField.fkClientCertificate)
|
?.getRawValue(ClientCertificateFormField.fkClientCertificate)
|
||||||
as ClientCertificateFormModel?;
|
as ClientCertificateFormModel?;
|
||||||
final status = await context.read<ConnectivityStatusService>().isPaperlessServerReachable(
|
final status = await context
|
||||||
|
.read<ConnectivityStatusService>()
|
||||||
|
.isPaperlessServerReachable(
|
||||||
address ??
|
address ??
|
||||||
widget.formBuilderKey.currentState!
|
widget.formBuilderKey.currentState!
|
||||||
.getRawValue(ServerAddressFormField.fkServerAddress),
|
.getRawValue(ServerAddressFormField.fkServerAddress),
|
||||||
certForm != null
|
certForm != null
|
||||||
? ClientCertificate(bytes: certForm.bytes, passphrase: certForm.passphrase)
|
? ClientCertificate(
|
||||||
|
bytes: certForm.bytes, passphrase: certForm.passphrase)
|
||||||
: null,
|
: null,
|
||||||
);
|
);
|
||||||
setState(() {
|
setState(() {
|
||||||
|
|||||||
@@ -25,7 +25,8 @@ class _ServerLoginPageState extends State<ServerLoginPage> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final serverAddress = (widget.formBuilderKey.currentState
|
final serverAddress = (widget.formBuilderKey.currentState
|
||||||
?.getRawValue(ServerAddressFormField.fkServerAddress) as String?)
|
?.getRawValue(ServerAddressFormField.fkServerAddress)
|
||||||
|
as String?)
|
||||||
?.replaceAll(RegExp(r'https?://'), '') ??
|
?.replaceAll(RegExp(r'https?://'), '') ??
|
||||||
'';
|
'';
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
|
|||||||
@@ -35,6 +35,10 @@ mixin DocumentPagingBlocMixin<State extends DocumentPagingState>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> initialize() {
|
||||||
|
return updateFilter();
|
||||||
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
/// Updates document filter and automatically reloads documents. Always resets page to 1.
|
/// Updates document filter and automatically reloads documents. Always resets page to 1.
|
||||||
/// Use [loadMore] to load more data.
|
/// Use [loadMore] to load more data.
|
||||||
|
|||||||
@@ -11,17 +11,17 @@ part 'saved_view_cubit.freezed.dart';
|
|||||||
class SavedViewCubit extends Cubit<SavedViewState> {
|
class SavedViewCubit extends Cubit<SavedViewState> {
|
||||||
final SavedViewRepository _savedViewRepository;
|
final SavedViewRepository _savedViewRepository;
|
||||||
|
|
||||||
SavedViewCubit(this._savedViewRepository) : super(const SavedViewState.initial()) {
|
SavedViewCubit(this._savedViewRepository)
|
||||||
|
: super(const SavedViewState.initial()) {
|
||||||
_savedViewRepository.addListener(
|
_savedViewRepository.addListener(
|
||||||
this,
|
this,
|
||||||
onChanged: (views) {
|
onChanged: (views) {
|
||||||
emit(
|
views.when(
|
||||||
state.maybeWhen(
|
initial: (savedViews) => emit(const SavedViewState.initial()),
|
||||||
loaded: (savedViews) => (state as _SavedViewLoadedState).copyWith(
|
loading: (savedViews) => emit(const SavedViewState.loading()),
|
||||||
savedViews: views.savedViews,
|
loaded: (savedViews) =>
|
||||||
),
|
emit(SavedViewState.loaded(savedViews: savedViews)),
|
||||||
orElse: () => state,
|
error: (savedViews) => emit(const SavedViewState.error()),
|
||||||
),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -35,7 +35,7 @@ class SavedViewCubit extends Cubit<SavedViewState> {
|
|||||||
return _savedViewRepository.delete(view);
|
return _savedViewRepository.delete(view);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> initialize() async {
|
Future<void> reload() async {
|
||||||
final views = await _savedViewRepository.findAll();
|
final views = await _savedViewRepository.findAll();
|
||||||
final values = {for (var element in views) element.id!: element};
|
final values = {for (var element in views) element.id!: element};
|
||||||
if (!isClosed) {
|
if (!isClosed) {
|
||||||
@@ -47,8 +47,6 @@ class SavedViewCubit extends Cubit<SavedViewState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> reload() => initialize();
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> close() {
|
Future<void> close() {
|
||||||
_savedViewRepository.removeListener(this);
|
_savedViewRepository.removeListener(this);
|
||||||
|
|||||||
@@ -2,12 +2,12 @@ part of 'saved_view_cubit.dart';
|
|||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
class SavedViewState with _$SavedViewState {
|
class SavedViewState with _$SavedViewState {
|
||||||
const factory SavedViewState.initial() = _SavedViewIntialState;
|
const factory SavedViewState.initial() = _Initial;
|
||||||
|
|
||||||
const factory SavedViewState.loading() = _SavedViewLoadingState;
|
const factory SavedViewState.loading() = _Loading;
|
||||||
|
|
||||||
const factory SavedViewState.loaded({required Map<int, SavedView> savedViews}) =
|
const factory SavedViewState.loaded(
|
||||||
_SavedViewLoadedState;
|
{required Map<int, SavedView> savedViews}) = _Loaded;
|
||||||
|
|
||||||
const factory SavedViewState.error() = _SavedViewErrorState;
|
const factory SavedViewState.error() = _Error;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
|
|||||||
import 'package:paperless_mobile/core/navigation/push_routes.dart';
|
import 'package:paperless_mobile/core/navigation/push_routes.dart';
|
||||||
import 'package:paperless_mobile/core/widgets/hint_card.dart';
|
import 'package:paperless_mobile/core/widgets/hint_card.dart';
|
||||||
import 'package:paperless_mobile/features/saved_view/cubit/saved_view_cubit.dart';
|
import 'package:paperless_mobile/features/saved_view/cubit/saved_view_cubit.dart';
|
||||||
|
import 'package:paperless_mobile/features/saved_view/view/saved_view_loading_sliver_list.dart';
|
||||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||||
|
|
||||||
class SavedViewList extends StatelessWidget {
|
class SavedViewList extends StatelessWidget {
|
||||||
@@ -16,23 +17,20 @@ class SavedViewList extends StatelessWidget {
|
|||||||
return BlocBuilder<SavedViewCubit, SavedViewState>(
|
return BlocBuilder<SavedViewCubit, SavedViewState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return state.when(
|
return state.when(
|
||||||
initial: () => SliverToBoxAdapter(child: Container()),
|
initial: () => const SavedViewLoadingSliverList(),
|
||||||
loading: () => const SliverToBoxAdapter(
|
loading: () => const SavedViewLoadingSliverList(),
|
||||||
child: Center(
|
|
||||||
child: Text("Saved views loading..."), //TODO: INTL
|
|
||||||
),
|
|
||||||
),
|
|
||||||
loaded: (savedViews) {
|
loaded: (savedViews) {
|
||||||
if (savedViews.isEmpty) {
|
if (savedViews.isEmpty) {
|
||||||
return SliverToBoxAdapter(
|
return SliverToBoxAdapter(
|
||||||
child: HintCard(
|
child: HintCard(
|
||||||
hintText: S.of(context)!.createViewsToQuicklyFilterYourDocuments,
|
hintText: S
|
||||||
|
.of(context)!
|
||||||
|
.createViewsToQuicklyFilterYourDocuments,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return SliverList(
|
return SliverList.builder(
|
||||||
delegate: SliverChildBuilderDelegate(
|
itemBuilder: (context, index) {
|
||||||
(context, index) {
|
|
||||||
final view = savedViews.values.elementAt(index);
|
final view = savedViews.values.elementAt(index);
|
||||||
return ListTile(
|
return ListTile(
|
||||||
enabled: connectivity.isConnected,
|
enabled: connectivity.isConnected,
|
||||||
@@ -45,15 +43,16 @@ class SavedViewList extends StatelessWidget {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
childCount: savedViews.length,
|
itemCount: savedViews.length,
|
||||||
),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
error: () => const Center(
|
error: () => const SliverToBoxAdapter(
|
||||||
|
child: Center(
|
||||||
child: Text(
|
child: Text(
|
||||||
"An error occurred while trying to load the saved views.",
|
"An error occurred while trying to load the saved views.",
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:paperless_mobile/core/widgets/shimmer_placeholder.dart';
|
||||||
|
|
||||||
|
class SavedViewLoadingSliverList extends StatelessWidget {
|
||||||
|
const SavedViewLoadingSliverList({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return SliverList.builder(
|
||||||
|
itemBuilder: (context, index) => ShimmerPlaceholder(
|
||||||
|
child: ListTile(
|
||||||
|
title: Align(
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
|
child: Container(
|
||||||
|
width: 300,
|
||||||
|
height: 14,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
subtitle: Align(
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
|
child: Container(
|
||||||
|
width: 150,
|
||||||
|
height: 12,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,7 +9,8 @@ import 'package:paperless_mobile/features/settings/model/view_type.dart';
|
|||||||
|
|
||||||
part 'saved_view_details_state.dart';
|
part 'saved_view_details_state.dart';
|
||||||
|
|
||||||
class SavedViewDetailsCubit extends Cubit<SavedViewDetailsState> with DocumentPagingBlocMixin {
|
class SavedViewDetailsCubit extends Cubit<SavedViewDetailsState>
|
||||||
|
with DocumentPagingBlocMixin {
|
||||||
@override
|
@override
|
||||||
final PaperlessDocumentsApi api;
|
final PaperlessDocumentsApi api;
|
||||||
|
|
||||||
|
|||||||
@@ -26,11 +26,14 @@ class ManageAccountsPage extends StatelessWidget {
|
|||||||
return const SizedBox.shrink();
|
return const SizedBox.shrink();
|
||||||
}
|
}
|
||||||
return ValueListenableBuilder(
|
return ValueListenableBuilder(
|
||||||
valueListenable: Hive.box<LocalUserAccount>(HiveBoxes.localUserAccount).listenable(),
|
valueListenable:
|
||||||
|
Hive.box<LocalUserAccount>(HiveBoxes.localUserAccount)
|
||||||
|
.listenable(),
|
||||||
builder: (context, box, _) {
|
builder: (context, box, _) {
|
||||||
final userIds = box.keys.toList().cast<String>();
|
final userIds = box.keys.toList().cast<String>();
|
||||||
final otherAccounts = userIds
|
final otherAccounts = userIds
|
||||||
.whereNot((element) => element == globalSettings.currentLoggedInUser)
|
.whereNot(
|
||||||
|
(element) => element == globalSettings.currentLoggedInUser)
|
||||||
.toList();
|
.toList();
|
||||||
return SimpleDialog(
|
return SimpleDialog(
|
||||||
insetPadding: const EdgeInsets.all(24),
|
insetPadding: const EdgeInsets.all(24),
|
||||||
@@ -68,10 +71,13 @@ class ManageAccountsPage extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
onSelected: (value) async {
|
onSelected: (value) async {
|
||||||
if (value == 0) {
|
if (value == 0) {
|
||||||
final currentUser = globalSettings.currentLoggedInUser!;
|
final currentUser =
|
||||||
|
globalSettings.currentLoggedInUser!;
|
||||||
await context.read<AuthenticationCubit>().logout();
|
await context.read<AuthenticationCubit>().logout();
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
await context.read<AuthenticationCubit>().removeAccount(currentUser);
|
await context
|
||||||
|
.read<AuthenticationCubit>()
|
||||||
|
.removeAccount(currentUser);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -89,7 +95,8 @@ class ManageAccountsPage extends StatelessWidget {
|
|||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
child: ListTile(
|
child: ListTile(
|
||||||
title: Text(S.of(context)!.switchAccount),
|
title: Text(S.of(context)!.switchAccount),
|
||||||
leading: const Icon(Icons.switch_account_rounded),
|
leading:
|
||||||
|
const Icon(Icons.switch_account_rounded),
|
||||||
),
|
),
|
||||||
value: 0,
|
value: 0,
|
||||||
),
|
),
|
||||||
@@ -150,7 +157,8 @@ class ManageAccountsPage extends StatelessWidget {
|
|||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) => LoginPage(
|
builder: (context) => LoginPage(
|
||||||
titleString: S.of(context)!.addAccount,
|
titleString: S.of(context)!.addAccount,
|
||||||
onSubmit: (context, username, password, serverUrl, clientCertificate) async {
|
onSubmit: (context, username, password, serverUrl,
|
||||||
|
clientCertificate) async {
|
||||||
final userId = await context.read<AuthenticationCubit>().addAccount(
|
final userId = await context.read<AuthenticationCubit>().addAccount(
|
||||||
credentials: LoginFormCredentials(
|
credentials: LoginFormCredentials(
|
||||||
username: username,
|
username: username,
|
||||||
@@ -179,7 +187,8 @@ class ManageAccountsPage extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onSwitchAccount(BuildContext context, String currentUser, String newUser) async {
|
void _onSwitchAccount(
|
||||||
|
BuildContext context, String currentUser, String newUser) async {
|
||||||
if (currentUser == newUser) return;
|
if (currentUser == newUser) return;
|
||||||
|
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
|
|||||||
@@ -26,7 +26,9 @@ class SettingsPage extends StatelessWidget {
|
|||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
subtitle: FutureBuilder<PaperlessServerInformationModel>(
|
subtitle: FutureBuilder<PaperlessServerInformationModel>(
|
||||||
future: context.read<PaperlessServerStatsApi>().getServerInformation(),
|
future: context
|
||||||
|
.read<PaperlessServerStatsApi>()
|
||||||
|
.getServerInformation(),
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if (snapshot.hasError) {
|
if (snapshot.hasError) {
|
||||||
return Text(
|
return Text(
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ class ClearCacheSetting extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ListTile(
|
return ListTile(
|
||||||
title: const Text("Clear downloaded files"), //TODO: INTL
|
title: const Text("Clear downloaded files"), //TODO: INTL
|
||||||
subtitle:
|
subtitle: const Text(
|
||||||
const Text("Deletes all files downloaded from this app."), //TODO: INTL
|
"Deletes all files downloaded from this app."), //TODO: INTL
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
final dir = await FileService.downloadsDirectory;
|
final dir = await FileService.downloadsDirectory;
|
||||||
final deletedSize = _dirSize(dir);
|
final deletedSize = _dirSize(dir);
|
||||||
|
|||||||
@@ -32,7 +32,8 @@ class ColorSchemeOptionSetting extends StatelessWidget {
|
|||||||
options: [
|
options: [
|
||||||
RadioOption(
|
RadioOption(
|
||||||
value: ColorSchemeOption.classic,
|
value: ColorSchemeOption.classic,
|
||||||
label: translateColorSchemeOption(context, ColorSchemeOption.classic),
|
label: translateColorSchemeOption(
|
||||||
|
context, ColorSchemeOption.classic),
|
||||||
),
|
),
|
||||||
RadioOption(
|
RadioOption(
|
||||||
value: ColorSchemeOption.dynamic,
|
value: ColorSchemeOption.dynamic,
|
||||||
|
|||||||
@@ -25,15 +25,18 @@ class DefaultDownloadFileTypeSetting extends StatelessWidget {
|
|||||||
options: [
|
options: [
|
||||||
RadioOption(
|
RadioOption(
|
||||||
value: FileDownloadType.alwaysAsk,
|
value: FileDownloadType.alwaysAsk,
|
||||||
label: _downloadFileTypeToString(context, FileDownloadType.alwaysAsk),
|
label: _downloadFileTypeToString(
|
||||||
|
context, FileDownloadType.alwaysAsk),
|
||||||
),
|
),
|
||||||
RadioOption(
|
RadioOption(
|
||||||
value: FileDownloadType.original,
|
value: FileDownloadType.original,
|
||||||
label: _downloadFileTypeToString(context, FileDownloadType.original),
|
label: _downloadFileTypeToString(
|
||||||
|
context, FileDownloadType.original),
|
||||||
),
|
),
|
||||||
RadioOption(
|
RadioOption(
|
||||||
value: FileDownloadType.archived,
|
value: FileDownloadType.archived,
|
||||||
label: _downloadFileTypeToString(context, FileDownloadType.archived),
|
label: _downloadFileTypeToString(
|
||||||
|
context, FileDownloadType.archived),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
initialValue: settings.defaultDownloadType,
|
initialValue: settings.defaultDownloadType,
|
||||||
@@ -51,7 +54,8 @@ class DefaultDownloadFileTypeSetting extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
String _downloadFileTypeToString(BuildContext context, FileDownloadType type) {
|
String _downloadFileTypeToString(
|
||||||
|
BuildContext context, FileDownloadType type) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case FileDownloadType.original:
|
case FileDownloadType.original:
|
||||||
return S.of(context)!.original;
|
return S.of(context)!.original;
|
||||||
|
|||||||
@@ -25,15 +25,18 @@ class DefaultShareFileTypeSetting extends StatelessWidget {
|
|||||||
options: [
|
options: [
|
||||||
RadioOption(
|
RadioOption(
|
||||||
value: FileDownloadType.alwaysAsk,
|
value: FileDownloadType.alwaysAsk,
|
||||||
label: _downloadFileTypeToString(context, FileDownloadType.alwaysAsk),
|
label: _downloadFileTypeToString(
|
||||||
|
context, FileDownloadType.alwaysAsk),
|
||||||
),
|
),
|
||||||
RadioOption(
|
RadioOption(
|
||||||
value: FileDownloadType.original,
|
value: FileDownloadType.original,
|
||||||
label: _downloadFileTypeToString(context, FileDownloadType.original),
|
label: _downloadFileTypeToString(
|
||||||
|
context, FileDownloadType.original),
|
||||||
),
|
),
|
||||||
RadioOption(
|
RadioOption(
|
||||||
value: FileDownloadType.archived,
|
value: FileDownloadType.archived,
|
||||||
label: _downloadFileTypeToString(context, FileDownloadType.archived),
|
label: _downloadFileTypeToString(
|
||||||
|
context, FileDownloadType.archived),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
initialValue: settings.defaultShareType,
|
initialValue: settings.defaultShareType,
|
||||||
@@ -51,7 +54,8 @@ class DefaultShareFileTypeSetting extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
String _downloadFileTypeToString(BuildContext context, FileDownloadType type) {
|
String _downloadFileTypeToString(
|
||||||
|
BuildContext context, FileDownloadType type) {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case FileDownloadType.original:
|
case FileDownloadType.original:
|
||||||
return S.of(context)!.original;
|
return S.of(context)!.original;
|
||||||
|
|||||||
@@ -10,7 +10,8 @@ class GlobalSettingsBuilder extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ValueListenableBuilder(
|
return ValueListenableBuilder(
|
||||||
valueListenable: Hive.box<GlobalSettings>(HiveBoxes.globalSettings).listenable(),
|
valueListenable:
|
||||||
|
Hive.box<GlobalSettings>(HiveBoxes.globalSettings).listenable(),
|
||||||
builder: (context, value, _) {
|
builder: (context, value, _) {
|
||||||
final settings = value.getValue()!;
|
final settings = value.getValue()!;
|
||||||
return builder(context, settings);
|
return builder(context, settings);
|
||||||
|
|||||||
@@ -7,7 +7,8 @@ class LanguageSelectionSetting extends StatefulWidget {
|
|||||||
const LanguageSelectionSetting({super.key});
|
const LanguageSelectionSetting({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<LanguageSelectionSetting> createState() => _LanguageSelectionSettingState();
|
State<LanguageSelectionSetting> createState() =>
|
||||||
|
_LanguageSelectionSettingState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _LanguageSelectionSettingState extends State<LanguageSelectionSetting> {
|
class _LanguageSelectionSettingState extends State<LanguageSelectionSetting> {
|
||||||
@@ -27,7 +28,8 @@ class _LanguageSelectionSettingState extends State<LanguageSelectionSetting> {
|
|||||||
builder: (context, settings) {
|
builder: (context, settings) {
|
||||||
return ListTile(
|
return ListTile(
|
||||||
title: Text(S.of(context)!.language),
|
title: Text(S.of(context)!.language),
|
||||||
subtitle: Text(_languageOptions[settings.preferredLocaleSubtag]!.name),
|
subtitle:
|
||||||
|
Text(_languageOptions[settings.preferredLocaleSubtag]!.name),
|
||||||
onTap: () => showDialog<String>(
|
onTap: () => showDialog<String>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (_) => RadioSettingsDialog<String>(
|
builder: (_) => RadioSettingsDialog<String>(
|
||||||
@@ -39,7 +41,8 @@ class _LanguageSelectionSettingState extends State<LanguageSelectionSetting> {
|
|||||||
for (var language in _languageOptions.entries)
|
for (var language in _languageOptions.entries)
|
||||||
RadioOption(
|
RadioOption(
|
||||||
value: language.key,
|
value: language.key,
|
||||||
label: language.value.name + (language.value.isComplete ? '' : '*'),
|
label: language.value.name +
|
||||||
|
(language.value.isComplete ? '' : '*'),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
initialValue: settings.preferredLocaleSubtag,
|
initialValue: settings.preferredLocaleSubtag,
|
||||||
|
|||||||
@@ -50,7 +50,8 @@ class _RadioSettingsDialogState<T> extends State<RadioSettingsDialog<T>> {
|
|||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
if (widget.descriptionText != null)
|
if (widget.descriptionText != null)
|
||||||
Text(widget.descriptionText!, style: Theme.of(context).textTheme.bodySmall),
|
Text(widget.descriptionText!,
|
||||||
|
style: Theme.of(context).textTheme.bodySmall),
|
||||||
...widget.options.map(_buildOptionListTile),
|
...widget.options.map(_buildOptionListTile),
|
||||||
if (widget.footer != null) widget.footer!,
|
if (widget.footer != null) widget.footer!,
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ class ThemeModeSetting extends StatelessWidget {
|
|||||||
builder: (context, settings) {
|
builder: (context, settings) {
|
||||||
return ListTile(
|
return ListTile(
|
||||||
title: Text(S.of(context)!.appearance),
|
title: Text(S.of(context)!.appearance),
|
||||||
subtitle: Text(_mapThemeModeToLocalizedString(settings.preferredThemeMode, context)),
|
subtitle: Text(_mapThemeModeToLocalizedString(
|
||||||
|
settings.preferredThemeMode, context)),
|
||||||
onTap: () => showDialog<ThemeMode>(
|
onTap: () => showDialog<ThemeMode>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (_) => RadioSettingsDialog<ThemeMode>(
|
builder: (_) => RadioSettingsDialog<ThemeMode>(
|
||||||
|
|||||||
@@ -11,10 +11,13 @@ class UserAvatar extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final backgroundColor = Colors.primaries[account.id.hashCode % Colors.primaries.length];
|
final backgroundColor =
|
||||||
final foregroundColor = backgroundColor.computeLuminance() > 0.5 ? Colors.black : Colors.white;
|
Colors.primaries[account.id.hashCode % Colors.primaries.length];
|
||||||
|
final foregroundColor =
|
||||||
|
backgroundColor.computeLuminance() > 0.5 ? Colors.black : Colors.white;
|
||||||
return CircleAvatar(
|
return CircleAvatar(
|
||||||
child: Text((account.paperlessUser.fullName ?? account.paperlessUser.username)
|
child: Text(
|
||||||
|
(account.paperlessUser.fullName ?? account.paperlessUser.username)
|
||||||
.split(" ")
|
.split(" ")
|
||||||
.take(2)
|
.take(2)
|
||||||
.map((e) => e.substring(0, 1))
|
.map((e) => e.substring(0, 1))
|
||||||
|
|||||||
@@ -18,10 +18,12 @@ class UserAccountBuilder extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ValueListenableBuilder<Box<LocalUserAccount>>(
|
return ValueListenableBuilder<Box<LocalUserAccount>>(
|
||||||
valueListenable: Hive.box<LocalUserAccount>(HiveBoxes.localUserAccount).listenable(),
|
valueListenable:
|
||||||
|
Hive.box<LocalUserAccount>(HiveBoxes.localUserAccount).listenable(),
|
||||||
builder: (context, accountBox, _) {
|
builder: (context, accountBox, _) {
|
||||||
final currentUser =
|
final currentUser = Hive.box<GlobalSettings>(HiveBoxes.globalSettings)
|
||||||
Hive.box<GlobalSettings>(HiveBoxes.globalSettings).getValue()!.currentLoggedInUser;
|
.getValue()!
|
||||||
|
.currentLoggedInUser;
|
||||||
if (currentUser != null) {
|
if (currentUser != null) {
|
||||||
final account = accountBox.get(currentUser);
|
final account = accountBox.get(currentUser);
|
||||||
return builder(context, account);
|
return builder(context, account);
|
||||||
|
|||||||
@@ -4,33 +4,53 @@ import 'package:flutter/widgets.dart';
|
|||||||
import 'package:receive_sharing_intent/receive_sharing_intent.dart';
|
import 'package:receive_sharing_intent/receive_sharing_intent.dart';
|
||||||
|
|
||||||
class ShareIntentQueue extends ChangeNotifier {
|
class ShareIntentQueue extends ChangeNotifier {
|
||||||
final Queue<SharedMediaFile> _queue = Queue();
|
final Map<String, Queue<SharedMediaFile>> _queues = {};
|
||||||
|
|
||||||
ShareIntentQueue._();
|
ShareIntentQueue._();
|
||||||
|
|
||||||
static final instance = ShareIntentQueue._();
|
static final instance = ShareIntentQueue._();
|
||||||
|
|
||||||
void add(SharedMediaFile file) {
|
void add(
|
||||||
|
SharedMediaFile file, {
|
||||||
|
required String userId,
|
||||||
|
}) {
|
||||||
debugPrint("Adding received file to queue: ${file.path}");
|
debugPrint("Adding received file to queue: ${file.path}");
|
||||||
_queue.add(file);
|
_getQueue(userId).add(file);
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
void addAll(Iterable<SharedMediaFile> files) {
|
void addAll(
|
||||||
|
Iterable<SharedMediaFile> files, {
|
||||||
|
required String userId,
|
||||||
|
}) {
|
||||||
debugPrint(
|
debugPrint(
|
||||||
"Adding received files to queue: ${files.map((e) => e.path).join(",")}");
|
"Adding received files to queue: ${files.map((e) => e.path).join(",")}");
|
||||||
_queue.addAll(files);
|
_getQueue(userId).addAll(files);
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
SharedMediaFile? pop() {
|
SharedMediaFile? pop(String userId) {
|
||||||
if (hasUnhandledFiles) {
|
if (userHasUnhandlesFiles(userId)) {
|
||||||
return _queue.removeFirst();
|
return _getQueue(userId).removeFirst();
|
||||||
// Don't notify listeners, only when new item is added.
|
// Don't notify listeners, only when new item is added.
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool get hasUnhandledFiles => _queue.isNotEmpty;
|
Queue<SharedMediaFile> _getQueue(String userId) {
|
||||||
|
if (!_queues.containsKey(userId)) {
|
||||||
|
_queues[userId] = Queue<SharedMediaFile>();
|
||||||
|
}
|
||||||
|
return _queues[userId]!;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool userHasUnhandlesFiles(String userId) => _getQueue(userId).isNotEmpty;
|
||||||
|
}
|
||||||
|
|
||||||
|
class UserAwareShareMediaFile {
|
||||||
|
final String userId;
|
||||||
|
final SharedMediaFile sharedFile;
|
||||||
|
|
||||||
|
UserAwareShareMediaFile(this.userId, this.sharedFile);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,8 @@ import 'package:paperless_mobile/features/paged_document_view/cubit/paged_docume
|
|||||||
|
|
||||||
part 'similar_documents_state.dart';
|
part 'similar_documents_state.dart';
|
||||||
|
|
||||||
class SimilarDocumentsCubit extends Cubit<SimilarDocumentsState> with DocumentPagingBlocMixin {
|
class SimilarDocumentsCubit extends Cubit<SimilarDocumentsState>
|
||||||
|
with DocumentPagingBlocMixin {
|
||||||
final int documentId;
|
final int documentId;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -35,8 +35,10 @@ class _SimilarDocumentsViewState extends State<SimilarDocumentsView>
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocConsumer<ConnectivityCubit, ConnectivityState>(
|
return BlocConsumer<ConnectivityCubit, ConnectivityState>(
|
||||||
listenWhen: (previous, current) => !previous.isConnected && current.isConnected,
|
listenWhen: (previous, current) =>
|
||||||
listener: (context, state) => context.read<SimilarDocumentsCubit>().initialize(),
|
!previous.isConnected && current.isConnected,
|
||||||
|
listener: (context, state) =>
|
||||||
|
context.read<SimilarDocumentsCubit>().initialize(),
|
||||||
builder: (context, connectivity) {
|
builder: (context, connectivity) {
|
||||||
return BlocBuilder<SimilarDocumentsCubit, SimilarDocumentsState>(
|
return BlocBuilder<SimilarDocumentsCubit, SimilarDocumentsState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
@@ -45,7 +47,9 @@ class _SimilarDocumentsViewState extends State<SimilarDocumentsView>
|
|||||||
child: OfflineWidget(),
|
child: OfflineWidget(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (state.hasLoaded && !state.isLoading && state.documents.isEmpty) {
|
if (state.hasLoaded &&
|
||||||
|
!state.isLoading &&
|
||||||
|
state.documents.isEmpty) {
|
||||||
return SliverToBoxAdapter(
|
return SliverToBoxAdapter(
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Text(S.of(context)!.noItemsFound),
|
child: Text(S.of(context)!.noItemsFound),
|
||||||
|
|||||||
@@ -25,7 +25,8 @@ class UserAccountListTile extends StatelessWidget {
|
|||||||
subtitle: Column(
|
subtitle: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
if (account.paperlessUser.fullName != null) Text(account.paperlessUser.fullName!),
|
if (account.paperlessUser.fullName != null)
|
||||||
|
Text(account.paperlessUser.fullName!),
|
||||||
Text(
|
Text(
|
||||||
account.serverUrl.replaceFirst(RegExp(r'https://?'), ''),
|
account.serverUrl.replaceFirst(RegExp(r'https://?'), ''),
|
||||||
style: TextStyle(color: theme.colorScheme.primary),
|
style: TextStyle(color: theme.colorScheme.primary),
|
||||||
|
|||||||
@@ -52,7 +52,8 @@ import 'package:mock_server/mock_server.dart';
|
|||||||
|
|
||||||
String get defaultPreferredLocaleSubtag {
|
String get defaultPreferredLocaleSubtag {
|
||||||
String preferredLocale = Platform.localeName.split("_").first;
|
String preferredLocale = Platform.localeName.split("_").first;
|
||||||
if (!S.supportedLocales.any((locale) => locale.languageCode == preferredLocale)) {
|
if (!S.supportedLocales
|
||||||
|
.any((locale) => locale.languageCode == preferredLocale)) {
|
||||||
preferredLocale = 'en';
|
preferredLocale = 'en';
|
||||||
}
|
}
|
||||||
return preferredLocale;
|
return preferredLocale;
|
||||||
@@ -65,7 +66,8 @@ Future<void> _initHive() async {
|
|||||||
await Hive.openBox<LocalUserAccount>(HiveBoxes.localUserAccount);
|
await Hive.openBox<LocalUserAccount>(HiveBoxes.localUserAccount);
|
||||||
await Hive.openBox<LocalUserAppState>(HiveBoxes.localUserAppState);
|
await Hive.openBox<LocalUserAppState>(HiveBoxes.localUserAppState);
|
||||||
await Hive.openBox<String>(HiveBoxes.hosts);
|
await Hive.openBox<String>(HiveBoxes.hosts);
|
||||||
final globalSettingsBox = await Hive.openBox<GlobalSettings>(HiveBoxes.globalSettings);
|
final globalSettingsBox =
|
||||||
|
await Hive.openBox<GlobalSettings>(HiveBoxes.globalSettings);
|
||||||
|
|
||||||
if (!globalSettingsBox.hasValue) {
|
if (!globalSettingsBox.hasValue) {
|
||||||
await globalSettingsBox.setValue(
|
await globalSettingsBox.setValue(
|
||||||
@@ -132,7 +134,8 @@ void main() async {
|
|||||||
|
|
||||||
//Update language header in interceptor on language change.
|
//Update language header in interceptor on language change.
|
||||||
globalSettingsBox.listenable().addListener(() {
|
globalSettingsBox.listenable().addListener(() {
|
||||||
languageHeaderInterceptor.preferredLocaleSubtag = globalSettings.preferredLocaleSubtag;
|
languageHeaderInterceptor.preferredLocaleSubtag =
|
||||||
|
globalSettings.preferredLocaleSubtag;
|
||||||
});
|
});
|
||||||
|
|
||||||
final apiFactory = PaperlessApiFactoryImpl(sessionManager);
|
final apiFactory = PaperlessApiFactoryImpl(sessionManager);
|
||||||
@@ -142,15 +145,19 @@ void main() async {
|
|||||||
providers: [
|
providers: [
|
||||||
ChangeNotifierProvider.value(value: sessionManager),
|
ChangeNotifierProvider.value(value: sessionManager),
|
||||||
Provider<LocalAuthenticationService>.value(value: localAuthService),
|
Provider<LocalAuthenticationService>.value(value: localAuthService),
|
||||||
Provider<ConnectivityStatusService>.value(value: connectivityStatusService),
|
Provider<Connectivity>.value(value: connectivity),
|
||||||
Provider<LocalNotificationService>.value(value: localNotificationService),
|
Provider<ConnectivityStatusService>.value(
|
||||||
|
value: connectivityStatusService),
|
||||||
|
Provider<LocalNotificationService>.value(
|
||||||
|
value: localNotificationService),
|
||||||
Provider.value(value: DocumentChangedNotifier()),
|
Provider.value(value: DocumentChangedNotifier()),
|
||||||
],
|
],
|
||||||
child: MultiBlocProvider(
|
child: MultiBlocProvider(
|
||||||
providers: [
|
providers: [
|
||||||
BlocProvider<ConnectivityCubit>.value(value: connectivityCubit),
|
BlocProvider<ConnectivityCubit>.value(value: connectivityCubit),
|
||||||
BlocProvider(
|
BlocProvider(
|
||||||
create: (context) => AuthenticationCubit(localAuthService, apiFactory, sessionManager),
|
create: (context) => AuthenticationCubit(
|
||||||
|
localAuthService, apiFactory, sessionManager),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
child: PaperlessMobileEntrypoint(
|
child: PaperlessMobileEntrypoint(
|
||||||
@@ -169,7 +176,8 @@ class PaperlessMobileEntrypoint extends StatefulWidget {
|
|||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<PaperlessMobileEntrypoint> createState() => _PaperlessMobileEntrypointState();
|
State<PaperlessMobileEntrypoint> createState() =>
|
||||||
|
_PaperlessMobileEntrypointState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _PaperlessMobileEntrypointState extends State<PaperlessMobileEntrypoint> {
|
class _PaperlessMobileEntrypointState extends State<PaperlessMobileEntrypoint> {
|
||||||
@@ -224,7 +232,6 @@ class AuthenticationWrapper extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _AuthenticationWrapperState extends State<AuthenticationWrapper> {
|
class _AuthenticationWrapperState extends State<AuthenticationWrapper> {
|
||||||
late final StreamSubscription _shareMediaSubscription;
|
|
||||||
@override
|
@override
|
||||||
void didChangeDependencies() {
|
void didChangeDependencies() {
|
||||||
super.didChangeDependencies();
|
super.didChangeDependencies();
|
||||||
@@ -233,12 +240,6 @@ class _AuthenticationWrapperState extends State<AuthenticationWrapper> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
_shareMediaSubscription.cancel();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
@@ -248,11 +249,6 @@ class _AuthenticationWrapperState extends State<AuthenticationWrapper> {
|
|||||||
_setOptimalDisplayMode();
|
_setOptimalDisplayMode();
|
||||||
}
|
}
|
||||||
initializeDateFormatting();
|
initializeDateFormatting();
|
||||||
// For sharing files coming from outside the app while the app is still opened
|
|
||||||
_shareMediaSubscription =
|
|
||||||
ReceiveSharingIntent.getMediaStream().listen(ShareIntentQueue.instance.addAll);
|
|
||||||
// For sharing files coming from outside the app while the app is closed
|
|
||||||
ReceiveSharingIntent.getInitialMedia().then(ShareIntentQueue.instance.addAll);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _setOptimalDisplayMode() async {
|
Future<void> _setOptimalDisplayMode() async {
|
||||||
@@ -264,7 +260,8 @@ class _AuthenticationWrapperState extends State<AuthenticationWrapper> {
|
|||||||
.toList()
|
.toList()
|
||||||
..sort((a, b) => b.refreshRate.compareTo(a.refreshRate));
|
..sort((a, b) => b.refreshRate.compareTo(a.refreshRate));
|
||||||
|
|
||||||
final DisplayMode mostOptimalMode = sameResolution.isNotEmpty ? sameResolution.first : active;
|
final DisplayMode mostOptimalMode =
|
||||||
|
sameResolution.isNotEmpty ? sameResolution.first : active;
|
||||||
debugPrint('Setting refresh rate to ${mostOptimalMode.refreshRate}');
|
debugPrint('Setting refresh rate to ${mostOptimalMode.refreshRate}');
|
||||||
|
|
||||||
await FlutterDisplayMode.setPreferredMode(mostOptimalMode);
|
await FlutterDisplayMode.setPreferredMode(mostOptimalMode);
|
||||||
@@ -303,12 +300,14 @@ class _AuthenticationWrapperState extends State<AuthenticationWrapper> {
|
|||||||
) async {
|
) async {
|
||||||
try {
|
try {
|
||||||
await context.read<AuthenticationCubit>().login(
|
await context.read<AuthenticationCubit>().login(
|
||||||
credentials: LoginFormCredentials(username: username, password: password),
|
credentials:
|
||||||
|
LoginFormCredentials(username: username, password: password),
|
||||||
serverUrl: serverUrl,
|
serverUrl: serverUrl,
|
||||||
clientCertificate: clientCertificate,
|
clientCertificate: clientCertificate,
|
||||||
);
|
);
|
||||||
// Show onboarding after first login!
|
// Show onboarding after first login!
|
||||||
final globalSettings = Hive.box<GlobalSettings>(HiveBoxes.globalSettings).getValue()!;
|
final globalSettings =
|
||||||
|
Hive.box<GlobalSettings>(HiveBoxes.globalSettings).getValue()!;
|
||||||
if (globalSettings.showOnboarding) {
|
if (globalSettings.showOnboarding) {
|
||||||
Navigator.push(
|
Navigator.push(
|
||||||
context,
|
context,
|
||||||
|
|||||||
@@ -250,7 +250,7 @@ class FilterRule with EquatableMixin {
|
|||||||
filterRules.add(docTypeRule);
|
filterRules.add(docTypeRule);
|
||||||
}
|
}
|
||||||
|
|
||||||
final sPathRule = filter.documentType.whenOrNull(
|
final sPathRule = filter.storagePath.whenOrNull(
|
||||||
notAssigned: () => FilterRule(storagePathRule, null),
|
notAssigned: () => FilterRule(storagePathRule, null),
|
||||||
fromId: (id) => FilterRule(storagePathRule, id.toString()),
|
fromId: (id) => FilterRule(storagePathRule, id.toString()),
|
||||||
);
|
);
|
||||||
@@ -344,8 +344,7 @@ class FilterRule with EquatableMixin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Join values of all extended filter rules if exist
|
//Join values of all extended filter rules if exist
|
||||||
if (filterRules.isNotEmpty &&
|
if (filterRules.where((e) => e.ruleType == FilterRule.extendedRule).isNotEmpty) {
|
||||||
filterRules.where((e) => e.ruleType == FilterRule.extendedRule).length > 1) {
|
|
||||||
final mergedExtendedRule = filterRules
|
final mergedExtendedRule = filterRules
|
||||||
.where((r) => r.ruleType == FilterRule.extendedRule)
|
.where((r) => r.ruleType == FilterRule.extendedRule)
|
||||||
.map((e) => e.value)
|
.map((e) => e.value)
|
||||||
|
|||||||
@@ -52,6 +52,9 @@ class PaperlessDocumentsApiImpl implements PaperlessDocumentsApi {
|
|||||||
final response = await client.post(
|
final response = await client.post(
|
||||||
'/api/documents/post_document/',
|
'/api/documents/post_document/',
|
||||||
data: formData,
|
data: formData,
|
||||||
|
onSendProgress: (count, total) {
|
||||||
|
debugPrint("Uploading ${(count / total) * 100}%...");
|
||||||
|
},
|
||||||
);
|
);
|
||||||
if (response.statusCode == 200) {
|
if (response.statusCode == 200) {
|
||||||
if (response.data is String && response.data != "OK") {
|
if (response.data is String && response.data != "OK") {
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
|
|||||||
# 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: 2.3.3+38
|
version: 2.3.4+39
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=3.0.0 <4.0.0"
|
sdk: ">=3.0.0 <4.0.0"
|
||||||
|
|||||||
Reference in New Issue
Block a user