feat: Externalize settings into LocalUserAppState, fix bugs

This commit is contained in:
Anton Stubenbord
2023-04-26 01:29:14 +02:00
parent 8c2a6928b4
commit 37c9559888
41 changed files with 340 additions and 316 deletions

View File

@@ -1,19 +0,0 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/bloc/paperless_server_information_state.dart';
class PaperlessServerInformationCubit
extends Cubit<PaperlessServerInformationState> {
final PaperlessServerStatsApi _api;
PaperlessServerInformationCubit(this._api)
: super(PaperlessServerInformationState());
Future<void> updateInformtion() async {
final information = await _api.getServerInformation();
emit(PaperlessServerInformationState(
isLoaded: true,
information: information,
));
}
}

View File

@@ -0,0 +1,17 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/bloc/server_information_state.dart';
class ServerInformationCubit extends Cubit<ServerInformationState> {
final PaperlessServerStatsApi _api;
ServerInformationCubit(this._api) : super(ServerInformationState());
Future<void> updateInformation() async {
final information = await _api.getServerInformation();
emit(ServerInformationState(
isLoaded: true,
information: information,
));
}
}

View File

@@ -1,10 +1,10 @@
import 'package:paperless_api/paperless_api.dart';
class PaperlessServerInformationState {
class ServerInformationState {
final bool isLoaded;
final PaperlessServerInformationModel? information;
PaperlessServerInformationState({
ServerInformationState({
this.isLoaded = false,
this.information,
});

View File

@@ -2,35 +2,37 @@ import 'package:hive_flutter/adapters.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/config/hive/custom_adapters/theme_mode_adapter.dart';
import 'package:paperless_mobile/core/database/tables/global_settings.dart';
import 'package:paperless_mobile/core/database/tables/user_app_state.dart';
import 'package:paperless_mobile/core/database/tables/local_user_app_state.dart';
import 'package:paperless_mobile/core/database/tables/user_credentials.dart';
import 'package:paperless_mobile/features/login/model/authentication_information.dart';
import 'package:paperless_mobile/features/login/model/client_certificate.dart';
import 'package:paperless_mobile/core/database/tables/user_account.dart';
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
import 'package:paperless_mobile/features/settings/model/color_scheme_option.dart';
import 'package:paperless_mobile/core/database/tables/user_settings.dart';
import 'package:paperless_mobile/core/database/tables/local_user_settings.dart';
import 'package:paperless_mobile/features/settings/model/view_type.dart';
class HiveBoxes {
HiveBoxes._();
static const globalSettings = 'globalSettings';
static const authentication = 'authentication';
static const userCredentials = 'userCredentials';
static const userAccount = 'userAccount';
static const userAppState = 'userAppState';
static const userSettings = 'userSettings';
static const localUserCredentials = 'localUserCredentials';
static const localUserAccount = 'localUserAccount';
static const localUserAppState = 'localUserAppState';
static const localUserSettings = 'localUserSettings';
}
class HiveTypeIds {
HiveTypeIds._();
static const globalSettings = 0;
static const userSettings = 1;
static const localUserSettings = 1;
static const themeMode = 2;
static const colorSchemeOption = 3;
static const authentication = 4;
static const clientCertificate = 5;
static const userCredentials = 6;
static const userAccount = 7;
static const userAppState = 8;
static const localUserCredentials = 6;
static const localUserAccount = 7;
static const localUserAppState = 8;
static const viewType = 9;
}
void registerHiveAdapters() {
@@ -40,10 +42,11 @@ void registerHiveAdapters() {
Hive.registerAdapter(GlobalSettingsAdapter());
Hive.registerAdapter(AuthenticationInformationAdapter());
Hive.registerAdapter(ClientCertificateAdapter());
Hive.registerAdapter(UserSettingsAdapter());
Hive.registerAdapter(LocalUserSettingsAdapter());
Hive.registerAdapter(UserCredentialsAdapter());
Hive.registerAdapter(UserAccountAdapter());
Hive.registerAdapter(UserAppStateAdapter());
Hive.registerAdapter(LocalUserAccountAdapter());
Hive.registerAdapter(LocalUserAppStateAdapter());
Hive.registerAdapter(ViewTypeAdapter());
}
extension HiveSingleValueBox<T> on Box<T> {

View File

@@ -1,11 +1,11 @@
import 'package:hive_flutter/adapters.dart';
import 'package:paperless_mobile/core/config/hive/hive_config.dart';
import 'package:paperless_mobile/core/database/tables/user_settings.dart';
import 'package:paperless_mobile/core/database/tables/local_user_settings.dart';
part 'user_account.g.dart';
part 'local_user_account.g.dart';
@HiveType(typeId: HiveTypeIds.userAccount)
class UserAccount extends HiveObject {
@HiveType(typeId: HiveTypeIds.localUserAccount)
class LocalUserAccount extends HiveObject {
@HiveField(0)
final String serverUrl;
@@ -19,9 +19,9 @@ class UserAccount extends HiveObject {
final String id;
@HiveField(4)
UserSettings settings;
LocalUserSettings settings;
UserAccount({
LocalUserAccount({
required this.id,
required this.serverUrl,
required this.username,

View File

@@ -0,0 +1,40 @@
import 'package:hive_flutter/adapters.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/config/hive/hive_config.dart';
import 'package:paperless_mobile/features/settings/model/view_type.dart';
part 'local_user_app_state.g.dart';
///
/// Object used for the persistence of app state, e.g. set filters,
/// search history and implicit settings.
///
@HiveType(typeId: HiveTypeIds.localUserAppState)
class LocalUserAppState extends HiveObject {
@HiveField(0)
final String userId;
@HiveField(1)
DocumentFilter currentDocumentFilter;
@HiveField(2)
List<String> documentSearchHistory;
@HiveField(3)
ViewType documentsPageViewType;
@HiveField(4)
ViewType savedViewsViewType;
@HiveField(5)
ViewType documentSearchViewType;
LocalUserAppState({
required this.userId,
this.currentDocumentFilter = const DocumentFilter(),
this.documentSearchHistory = const [],
this.documentsPageViewType = ViewType.list,
this.documentSearchViewType = ViewType.list,
this.savedViewsViewType = ViewType.list,
});
}

View File

@@ -1,15 +1,14 @@
import 'package:hive/hive.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/config/hive/hive_config.dart';
part 'user_settings.g.dart';
part 'local_user_settings.g.dart';
@HiveType(typeId: HiveTypeIds.userSettings)
class UserSettings with HiveObjectMixin {
@HiveType(typeId: HiveTypeIds.localUserSettings)
class LocalUserSettings with HiveObjectMixin {
@HiveField(0)
bool isBiometricAuthenticationEnabled;
UserSettings({
LocalUserSettings({
this.isBiometricAuthenticationEnabled = false,
});
}

View File

@@ -1,22 +0,0 @@
import 'package:hive_flutter/adapters.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/config/hive/hive_config.dart';
part 'user_app_state.g.dart';
@HiveType(typeId: HiveTypeIds.userAppState)
class UserAppState extends HiveObject {
@HiveField(0)
final String userId;
@HiveField(1)
DocumentFilter currentDocumentFilter;
@HiveField(2)
List<String> documentSearchHistory;
UserAppState({
required this.userId,
this.currentDocumentFilter = const DocumentFilter(),
this.documentSearchHistory = const [],
});
}

View File

@@ -4,7 +4,7 @@ import 'package:paperless_mobile/features/login/model/client_certificate.dart';
part 'user_credentials.g.dart';
@HiveType(typeId: HiveTypeIds.userCredentials)
@HiveType(typeId: HiveTypeIds.localUserCredentials)
class UserCredentials extends HiveObject {
@HiveField(0)
final String token;

View File

@@ -1,4 +1,3 @@
import 'dart:convert';
import 'dart:io';
import 'package:dio/dio.dart';
@@ -8,14 +7,18 @@ import 'package:paperless_mobile/core/interceptor/retry_on_connection_change_int
import 'package:paperless_mobile/features/login/model/client_certificate.dart';
import 'package:pretty_dio_logger/pretty_dio_logger.dart';
/// Manages the security context, authentication and base request URL for
/// an underlying [Dio] client which is injected into all services
/// requiring authenticated access to the Paperless HTTP API.
class SessionManager {
final Dio client;
final List<Interceptor> interceptors;
PaperlessServerInformationModel serverInformation;
final Dio _client;
PaperlessServerInformationModel _serverInformation;
SessionManager([this.interceptors = const []])
: client = _initDio(interceptors),
serverInformation = PaperlessServerInformationModel();
Dio get client => _client;
SessionManager([List<Interceptor> interceptors = const []])
: _client = _initDio(interceptors),
_serverInformation = PaperlessServerInformationModel();
static Dio _initDio(List<Interceptor> interceptors) {
//en- and decoded by utf8 by default
@@ -63,8 +66,7 @@ class SessionManager {
);
final adapter = IOHttpClientAdapter()
..onHttpClientCreate = (client) => HttpClient(context: context)
..badCertificateCallback =
(X509Certificate cert, String host, int port) => true;
..badCertificateCallback = (X509Certificate cert, String host, int port) => true;
client.httpClientAdapter = adapter;
}
@@ -80,7 +82,7 @@ class SessionManager {
}
if (serverInformation != null) {
this.serverInformation = serverInformation;
_serverInformation = serverInformation;
}
}
@@ -88,6 +90,6 @@ class SessionManager {
client.httpClientAdapter = IOHttpClientAdapter();
client.options.baseUrl = '';
client.options.headers.remove(HttpHeaders.authorizationHeader);
serverInformation = PaperlessServerInformationModel();
_serverInformation = PaperlessServerInformationModel();
}
}

View File

@@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:paperless_mobile/constants.dart';
import 'package:paperless_mobile/core/bloc/paperless_server_information_cubit.dart';
import 'package:paperless_mobile/core/bloc/server_information_cubit.dart';
import 'package:paperless_mobile/core/widgets/paperless_logo.dart';
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
import 'package:paperless_mobile/features/settings/view/settings_page.dart';
@@ -69,7 +69,7 @@ class AppDrawer extends StatelessWidget {
onTap: () => Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => BlocProvider.value(
value: context.read<PaperlessServerInformationCubit>(),
value: context.read<ServerInformationCubit>(),
child: const SettingsPage(),
),
),

View File

@@ -55,7 +55,9 @@ class _BulkEditLabelBottomSheetState<T extends Label> extends State<BulkEditLabe
FormBuilder(
key: _formKey,
child: LabelFormField<T>(
initialValue: IdQueryParameter.fromId(widget.initialValue),
initialValue: widget.initialValue != null
? IdQueryParameter.fromId(widget.initialValue!)
: const IdQueryParameter.unset(),
name: "labelFormField",
options: widget.availableOptionsSelector(state),
labelText: widget.formFieldLabel,

View File

@@ -111,9 +111,9 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
addLabelText: S.of(context)!.addCorrespondent,
labelText: S.of(context)!.correspondent,
options: context.watch<DocumentEditCubit>().state.correspondents,
initialValue: IdQueryParameter.fromId(
state.document.correspondent,
),
initialValue: state.document.correspondent != null
? IdQueryParameter.fromId(state.document.correspondent!)
: const IdQueryParameter.unset(),
name: fkCorrespondent,
prefixIcon: const Icon(Icons.person_outlined),
),
@@ -145,7 +145,9 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
),
addLabelText: S.of(context)!.addDocumentType,
labelText: S.of(context)!.documentType,
initialValue: IdQueryParameter.fromId(state.document.documentType),
initialValue: state.document.documentType != null
? IdQueryParameter.fromId(state.document.documentType!)
: const IdQueryParameter.unset(),
options: state.documentTypes,
name: _DocumentEditPageState.fkDocumentType,
prefixIcon: const Icon(Icons.description_outlined),
@@ -176,7 +178,9 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
addLabelText: S.of(context)!.addStoragePath,
labelText: S.of(context)!.storagePath,
options: state.storagePaths,
initialValue: IdQueryParameter.fromId(state.document.storagePath),
initialValue: state.document.storagePath != null
? IdQueryParameter.fromId(state.document.storagePath!)
: const IdQueryParameter.unset(),
name: fkStoragePath,
prefixIcon: const Icon(Icons.folder_outlined),
),

View File

@@ -2,7 +2,7 @@ import 'package:collection/collection.dart';
import 'package:hydrated_bloc/hydrated_bloc.dart';
import 'package:json_annotation/json_annotation.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/database/tables/user_app_state.dart';
import 'package:paperless_mobile/core/database/tables/local_user_app_state.dart';
import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart';
import 'package:paperless_mobile/core/repository/label_repository.dart';
import 'package:paperless_mobile/features/paged_document_view/cubit/document_paging_bloc_mixin.dart';
@@ -20,7 +20,7 @@ class DocumentSearchCubit extends Cubit<DocumentSearchState> with DocumentPaging
@override
final DocumentChangedNotifier notifier;
final UserAppState _userAppState;
final LocalUserAppState _userAppState;
DocumentSearchCubit(
this.api,
this.notifier,

View File

@@ -6,7 +6,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:hive/hive.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/user_app_state.dart';
import 'package:paperless_mobile/core/database/tables/local_user_app_state.dart';
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
import 'package:paperless_mobile/features/document_search/cubit/document_search_cubit.dart';
import 'package:paperless_mobile/features/document_search/view/remove_history_entry_dialog.dart';
@@ -27,7 +27,7 @@ Future<void> showDocumentSearchPage(BuildContext context) {
context.read(),
context.read(),
context.read(),
Hive.box<UserAppState>(HiveBoxes.userAppState).get(currentUser)!,
Hive.box<LocalUserAppState>(HiveBoxes.localUserAppState).get(currentUser)!,
),
child: const DocumentSearchPage(),
),

View File

@@ -1,9 +1,9 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:hive_flutter/adapters.dart';
import 'package:paperless_mobile/core/bloc/paperless_server_information_cubit.dart';
import 'package:paperless_mobile/core/bloc/server_information_cubit.dart';
import 'package:paperless_mobile/core/config/hive/hive_config.dart';
import 'package:paperless_mobile/core/database/tables/user_account.dart';
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
import 'package:paperless_mobile/core/delegate/customizable_sliver_persistent_header_delegate.dart';
import 'package:paperless_mobile/core/widgets/material/search/m3_search_bar.dart' as s;
import 'package:paperless_mobile/features/document_search/view/document_search_page.dart';
@@ -43,7 +43,8 @@ class SliverSearchBar extends StatelessWidget {
icon: GlobalSettingsBuilder(
builder: (context, settings) {
return ValueListenableBuilder(
valueListenable: Hive.box<UserAccount>(HiveBoxes.userAccount).listenable(),
valueListenable:
Hive.box<LocalUserAccount>(HiveBoxes.localUserAccount).listenable(),
builder: (context, box, _) {
final account = box.get(settings.currentLoggedInUser!)!;
return UserAvatar(userId: settings.currentLoggedInUser!, account: account);
@@ -55,7 +56,7 @@ class SliverSearchBar extends StatelessWidget {
showDialog(
context: context,
builder: (_) => BlocProvider.value(
value: context.read<PaperlessServerInformationCubit>(),
value: context.read<ServerInformationCubit>(),
child: const ManageAccountsPage(),
),
);

View File

@@ -4,10 +4,9 @@ import 'package:flutter/foundation.dart';
import 'package:hydrated_bloc/hydrated_bloc.dart';
import 'package:json_annotation/json_annotation.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/database/tables/user_app_state.dart';
import 'package:paperless_mobile/core/database/tables/local_user_app_state.dart';
import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart';
import 'package:paperless_mobile/core/repository/label_repository.dart';
import 'package:paperless_mobile/core/database/tables/user_account.dart';
import 'package:paperless_mobile/features/paged_document_view/cubit/document_paging_bloc_mixin.dart';
import 'package:paperless_mobile/features/paged_document_view/cubit/paged_documents_state.dart';
import 'package:paperless_mobile/features/settings/model/view_type.dart';
@@ -24,14 +23,17 @@ class DocumentsCubit extends Cubit<DocumentsState> with DocumentPagingBlocMixin
@override
final DocumentChangedNotifier notifier;
final UserAppState _userState;
final LocalUserAppState _userState;
DocumentsCubit(
this.api,
this.notifier,
this._labelRepository,
this._userState,
) : super(DocumentsState(filter: _userState.currentDocumentFilter)) {
) : super(DocumentsState(
filter: _userState.currentDocumentFilter,
viewType: _userState.documentsPageViewType,
)) {
notifier.addListener(
this,
onUpdated: (document) {
@@ -103,16 +105,6 @@ class DocumentsCubit extends Cubit<DocumentsState> with DocumentPagingBlocMixin
return res;
}
@override
DocumentsState? fromJson(Map<String, dynamic> json) {
return DocumentsState.fromJson(json);
}
@override
Map<String, dynamic>? toJson(DocumentsState state) {
return state.toJson();
}
@override
Future<void> close() {
notifier.removeListener(this);
@@ -122,6 +114,8 @@ class DocumentsCubit extends Cubit<DocumentsState> with DocumentPagingBlocMixin
void setViewType(ViewType viewType) {
emit(state.copyWith(viewType: viewType));
_userState.documentsPageViewType = viewType;
_userState.save();
}
@override

View File

@@ -482,7 +482,7 @@ class _DocumentsPageState extends State<DocumentsPage> with SingleTickerProvider
try {
final correspondent = cubit.state.filter.correspondent;
if (correspondent is SetIdQueryParameter) {
if (correspondent.id == correspondentId) {
if (correspondentId == null || correspondent.id == correspondentId) {
cubit.updateCurrentFilter(
(filter) => filter.copyWith(correspondent: const IdQueryParameter.unset()),
);
@@ -502,7 +502,7 @@ class _DocumentsPageState extends State<DocumentsPage> with SingleTickerProvider
try {
final documentType = cubit.state.filter.documentType;
if (documentType is SetIdQueryParameter) {
if (documentType.id == documentTypeId) {
if (documentTypeId == null || documentType.id == documentTypeId) {
cubit.updateCurrentFilter(
(filter) => filter.copyWith(documentType: const IdQueryParameter.unset()),
);
@@ -522,7 +522,7 @@ class _DocumentsPageState extends State<DocumentsPage> with SingleTickerProvider
try {
final path = cubit.state.filter.documentType;
if (path is SetIdQueryParameter) {
if (path.id == pathId) {
if (pathId == null || path.id == pathId) {
cubit.updateCurrentFilter(
(filter) => filter.copyWith(storagePath: const IdQueryParameter.unset()),
);

View File

@@ -9,10 +9,10 @@ import 'package:fluttertoast/fluttertoast.dart';
import 'package:hive/hive.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
import 'package:paperless_mobile/core/bloc/paperless_server_information_cubit.dart';
import 'package:paperless_mobile/core/bloc/server_information_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/user_app_state.dart';
import 'package:paperless_mobile/core/database/tables/local_user_app_state.dart';
import 'package:paperless_mobile/core/global/constants.dart';
import 'package:paperless_mobile/core/repository/label_repository.dart';
import 'package:paperless_mobile/core/repository/saved_view_repository.dart';
@@ -30,7 +30,7 @@ import 'package:paperless_mobile/features/inbox/view/pages/inbox_page.dart';
import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart';
import 'package:paperless_mobile/features/labels/view/pages/labels_page.dart';
import 'package:paperless_mobile/features/login/cubit/authentication_cubit.dart';
import 'package:paperless_mobile/core/database/tables/user_account.dart';
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
import 'package:paperless_mobile/features/notifications/services/local_notification_service.dart';
import 'package:paperless_mobile/features/saved_view/cubit/saved_view_cubit.dart';
import 'package:paperless_mobile/features/sharing/share_intent_queue.dart';
@@ -62,15 +62,6 @@ class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
_listenForReceivedFiles();
});
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
_initializeData(context);
final userId =
Hive.box<GlobalSettings>(HiveBoxes.globalSettings).getValue()!.currentLoggedInUser;
@@ -78,7 +69,7 @@ class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
context.read(),
context.read(),
context.read(),
Hive.box<UserAppState>(HiveBoxes.userAppState).get(userId)!,
Hive.box<LocalUserAppState>(HiveBoxes.localUserAppState).get(userId)!,
)..reload();
_savedViewCubit = SavedViewCubit(
context.read(),
@@ -91,6 +82,14 @@ class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
context.read(),
);
_listenToInboxChanges();
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
_listenForReceivedFiles();
});
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
}
void _listenToInboxChanges() {
@@ -352,7 +351,7 @@ class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
Future.wait([
context.read<LabelRepository>().initialize(),
context.read<SavedViewRepository>().findAll(),
context.read<PaperlessServerInformationCubit>().updateInformtion(),
context.read<ServerInformationCubit>().updateInformation(),
]).onError<PaperlessServerException>((error, stackTrace) {
showErrorMessage(context, error, stackTrace);
throw error;

View File

@@ -0,0 +1,29 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/src/widgets/framework.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_mobile/core/bloc/server_information_cubit.dart';
import 'package:paperless_mobile/features/home/view/home_page.dart';
import 'package:paperless_mobile/features/tasks/cubit/task_status_cubit.dart';
class HomeRoute extends StatelessWidget {
const HomeRoute({super.key});
@override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider(
create: (context) => TaskStatusCubit(
context.read(),
),
),
BlocProvider<ServerInformationCubit>(
create: (context) => ServerInformationCubit(
context.read(),
)..updateInformation(),
),
],
child: HomePage(),
);
}
}

View File

@@ -145,7 +145,7 @@ class _LabelsPageState extends State<LabelsPage> with SingleTickerProviderStateM
LabelTabView<Correspondent>(
labels: context.watch<LabelCubit>().state.correspondents,
filterBuilder: (label) => DocumentFilter(
correspondent: IdQueryParameter.fromId(label.id),
correspondent: IdQueryParameter.fromId(label.id!),
pageSize: label.documentCount ?? 0,
),
onEdit: _openEditCorrespondentPage,
@@ -166,7 +166,7 @@ class _LabelsPageState extends State<LabelsPage> with SingleTickerProviderStateM
LabelTabView<DocumentType>(
labels: context.watch<LabelCubit>().state.documentTypes,
filterBuilder: (label) => DocumentFilter(
documentType: IdQueryParameter.fromId(label.id),
documentType: IdQueryParameter.fromId(label.id!),
pageSize: label.documentCount ?? 0,
),
onEdit: _openEditDocumentTypePage,
@@ -218,7 +218,7 @@ class _LabelsPageState extends State<LabelsPage> with SingleTickerProviderStateM
labels: context.watch<LabelCubit>().state.storagePaths,
onEdit: _openEditStoragePathPage,
filterBuilder: (label) => DocumentFilter(
storagePath: IdQueryParameter.fromId(label.id),
storagePath: IdQueryParameter.fromId(label.id!),
pageSize: label.documentCount ?? 0,
),
contentBuilder: (path) => Text(path.path),

View File

@@ -80,11 +80,12 @@ class _FullscreenLabelFormState<T extends Label> extends State<FullscreenLabelFo
final index = AutocompleteHighlightedOption.of(context);
final value = index.isNegative ? null : options.elementAt(index);
widget.onSubmit(
returnValue: IdQueryParameter.fromId(
value?.whenOrNull(
fromId: (id) => id,
),
));
returnValue: value?.maybeWhen(
fromId: (id) => IdQueryParameter.fromId(id),
orElse: () => const IdQueryParameter.unset(),
) ??
const IdQueryParameter.unset(),
);
},
autofocus: true,
style: theme.textTheme.bodyLarge?.apply(
@@ -167,7 +168,7 @@ class _FullscreenLabelFormState<T extends Label> extends State<FullscreenLabelFo
if (widget.initialValue == null) {
// If nothing is selected yet, show all options first.
for (final option in widget.options.values) {
yield IdQueryParameter.fromId(option.id);
yield IdQueryParameter.fromId(option.id!);
}
if (widget.showNotAssignedOption) {
yield const IdQueryParameter.notAssigned();
@@ -189,7 +190,7 @@ class _FullscreenLabelFormState<T extends Label> extends State<FullscreenLabelFo
if (initialValue is SetIdQueryParameter && option.id == initialValue.id) {
continue;
}
yield IdQueryParameter.fromId(option.id);
yield IdQueryParameter.fromId(option.id!);
}
}
} else {
@@ -198,7 +199,7 @@ class _FullscreenLabelFormState<T extends Label> extends State<FullscreenLabelFo
widget.options.values.where((e) => e.name.trim().toLowerCase().contains(normalizedQuery));
if (matches.isNotEmpty) {
for (final match in matches) {
yield IdQueryParameter.fromId(match.id);
yield IdQueryParameter.fromId(match.id!);
}
if (widget.showNotAssignedOption) {
yield const IdQueryParameter.notAssigned();
@@ -225,7 +226,7 @@ class _FullscreenLabelFormState<T extends Label> extends State<FullscreenLabelFo
unset: () => S.of(context)!.startTyping,
notAssigned: () => S.of(context)!.notAssigned,
anyAssigned: () => S.of(context)!.anyAssigned,
fromId: (id) => widget.options[id]!.name,
fromId: (id) => widget.options[id]?.name ?? S.of(context)!.startTyping,
);
}

View File

@@ -49,7 +49,7 @@ class LabelFormField<T extends Label> extends StatelessWidget {
unset: () => '',
notAssigned: () => S.of(context)!.notAssigned,
anyAssigned: () => S.of(context)!.anyAssigned,
fromId: (id) => options[id]!.name,
fromId: (id) => options[id]?.name,
) ??
'';
}
@@ -142,7 +142,7 @@ class LabelFormField<T extends Label> extends StatelessWidget {
child: ActionChip(
label: Text(suggestion.name),
onPressed: () => field.didChange(
IdQueryParameter.fromId(suggestion.id),
IdQueryParameter.fromId(suggestion.id!),
),
),
);

View File

@@ -5,7 +5,7 @@ import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/config/hive/hive_config.dart';
import 'package:paperless_mobile/features/linked_documents/cubit/linked_documents_cubit.dart';
import 'package:paperless_mobile/features/linked_documents/view/linked_documents_page.dart';
import 'package:paperless_mobile/core/database/tables/user_account.dart';
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
import 'package:paperless_mobile/core/database/tables/global_settings.dart';
import 'package:paperless_mobile/helpers/format_helpers.dart';

View File

@@ -7,25 +7,25 @@ import 'package:hive_flutter/adapters.dart';
import 'package:hydrated_bloc/hydrated_bloc.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/user_app_state.dart';
import 'package:paperless_mobile/core/database/tables/local_user_app_state.dart';
import 'package:paperless_mobile/core/interceptor/dio_http_error_interceptor.dart';
import 'package:paperless_mobile/core/repository/label_repository.dart';
import 'package:paperless_mobile/core/repository/saved_view_repository.dart';
import 'package:paperless_mobile/core/security/session_manager.dart';
import 'package:paperless_mobile/features/login/model/client_certificate.dart';
import 'package:paperless_mobile/features/login/model/login_form_credentials.dart';
import 'package:paperless_mobile/core/database/tables/user_account.dart';
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
import 'package:paperless_mobile/core/database/tables/user_credentials.dart';
import 'package:paperless_mobile/features/login/services/authentication_service.dart';
import 'package:paperless_mobile/core/database/tables/global_settings.dart';
import 'package:paperless_mobile/core/database/tables/user_settings.dart';
import 'package:paperless_mobile/core/database/tables/local_user_settings.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
part 'authentication_state.dart';
class AuthenticationCubit extends Cubit<AuthenticationState> {
final LocalAuthenticationService _localAuthService;
final PaperlessAuthenticationApi _authApi;
final SessionManager _dioWrapper;
final SessionManager _sessionManager;
final LabelRepository _labelRepository;
final SavedViewRepository _savedViewRepository;
final PaperlessServerStatsApi _serverStatsApi;
@@ -33,7 +33,7 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
AuthenticationCubit(
this._localAuthService,
this._authApi,
this._dioWrapper,
this._sessionManager,
this._labelRepository,
this._savedViewRepository,
this._serverStatsApi,
@@ -46,7 +46,7 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
}) async {
assert(credentials.username != null && credentials.password != null);
_dioWrapper.updateSettings(
_sessionManager.updateSettings(
baseUrl: serverUrl,
clientCertificate: clientCertificate,
);
@@ -54,13 +54,14 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
username: credentials.username!,
password: credentials.password!,
);
_dioWrapper.updateSettings(
_sessionManager.updateSettings(
baseUrl: serverUrl,
clientCertificate: clientCertificate,
authToken: token,
);
final userAccountBox = Hive.box<UserAccount>(HiveBoxes.userAccount);
final userStateBox = Hive.box<UserAppState>(HiveBoxes.userAppState);
final userAccountBox = Hive.box<LocalUserAccount>(HiveBoxes.localUserAccount);
final userStateBox = Hive.box<LocalUserAppState>(HiveBoxes.localUserAppState);
final userId = "${credentials.username}@$serverUrl";
@@ -72,9 +73,9 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
// Create user account
await userAccountBox.put(
userId,
UserAccount(
LocalUserAccount(
id: userId,
settings: UserSettings(),
settings: LocalUserSettings(),
serverUrl: serverUrl,
username: credentials.username!,
fullName: fullName,
@@ -84,7 +85,7 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
// Create user state
await userStateBox.put(
userId,
UserAppState(userId: userId),
LocalUserAppState(userId: userId),
);
// Save credentials in encrypted box
@@ -119,7 +120,7 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
if (globalSettings.currentLoggedInUser == userId) {
return;
}
final userAccountBox = Hive.box<UserAccount>(HiveBoxes.userAccount);
final userAccountBox = Hive.box<LocalUserAccount>(HiveBoxes.localUserAccount);
if (!userAccountBox.containsKey(userId)) {
debugPrint("User $userId not yet registered.");
@@ -148,7 +149,7 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
await _resetExternalState();
_dioWrapper.updateSettings(
_sessionManager.updateSettings(
authToken: credentials!.token,
clientCertificate: credentials.clientCertificate,
serverInformation: PaperlessServerInformationModel(),
@@ -178,8 +179,8 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
assert(credentials.password != null && credentials.username != null);
final userId = "${credentials.username}@$serverUrl";
final userAccountsBox = Hive.box<UserAccount>(HiveBoxes.userAccount);
final userStateBox = Hive.box<UserAppState>(HiveBoxes.userAppState);
final userAccountsBox = Hive.box<LocalUserAccount>(HiveBoxes.localUserAccount);
final userStateBox = Hive.box<LocalUserAppState>(HiveBoxes.localUserAppState);
if (userAccountsBox.containsKey(userId)) {
throw Exception("User already exists");
@@ -204,11 +205,11 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
await userAccountsBox.put(
userId,
UserAccount(
LocalUserAccount(
id: userId,
serverUrl: serverUrl,
username: credentials.username!,
settings: UserSettings(
settings: LocalUserSettings(
isBiometricAuthenticationEnabled: enableBiometricAuthentication,
),
fullName: fullName,
@@ -217,7 +218,7 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
await userStateBox.put(
userId,
UserAppState(
LocalUserAppState(
userId: userId,
),
);
@@ -236,15 +237,15 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
Future<void> removeAccount(String userId) async {
final globalSettings = Hive.box<GlobalSettings>(HiveBoxes.globalSettings).getValue()!;
final userAccountBox = Hive.box<UserAccount>(HiveBoxes.userAccount);
final userAccountBox = Hive.box<LocalUserAccount>(HiveBoxes.localUserAccount);
final userCredentialsBox = await _getUserCredentialsBox();
final userAppStateBox = Hive.box<UserAppState>(HiveBoxes.userAppState);
final userAppStateBox = Hive.box<LocalUserAppState>(HiveBoxes.localUserAppState);
final currentUser = globalSettings.currentLoggedInUser;
await userAccountBox.delete(userId);
await userAppStateBox.delete(userId);
await userCredentialsBox.delete(userId);
await userAccountBox.close();
await userCredentialsBox.close();
if (currentUser == userId) {
return logout();
@@ -262,7 +263,7 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
return;
}
final userAccount = Hive.box<UserAccount>(HiveBoxes.userAccount).get(userId)!;
final userAccount = Hive.box<LocalUserAccount>(HiveBoxes.localUserAccount).get(userId)!;
if (userAccount.settings.isBiometricAuthenticationEnabled) {
final localAuthSuccess =
@@ -280,7 +281,7 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
if (authentication == null) {
throw Exception("User should be authenticated but no authentication information was found.");
}
_dioWrapper.updateSettings(
_sessionManager.updateSettings(
clientCertificate: authentication.clientCertificate,
authToken: authentication.token,
baseUrl: userAccount.serverUrl,
@@ -321,13 +322,13 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
Future<Box<UserCredentials>> _getUserCredentialsBox() async {
final keyBytes = await _getEncryptedBoxKey();
return Hive.openBox<UserCredentials>(
HiveBoxes.userCredentials,
HiveBoxes.localUserCredentials,
encryptionCipher: HiveAesCipher(keyBytes),
);
}
Future<void> _resetExternalState() {
_dioWrapper.resetSettings();
_sessionManager.resetSettings();
return Future.wait([
HydratedBloc.storage.clear(),
_labelRepository.clear(),

View File

@@ -1,10 +1,5 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_form_builder/flutter_form_builder.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/type/types.dart';
import 'package:paperless_mobile/features/app_intro/application_intro_slideshow.dart';
import 'package:paperless_mobile/features/login/cubit/authentication_cubit.dart';
import 'package:paperless_mobile/features/login/model/client_certificate.dart';
import 'package:paperless_mobile/features/login/model/client_certificate_form_model.dart';
import 'package:paperless_mobile/features/login/model/login_form_credentials.dart';
@@ -12,8 +7,6 @@ import 'package:paperless_mobile/features/login/view/widgets/form_fields/client_
import 'package:paperless_mobile/features/login/view/widgets/form_fields/server_address_form_field.dart';
import 'package:paperless_mobile/features/login/view/widgets/form_fields/user_credentials_form_field.dart';
import 'package:paperless_mobile/features/login/view/widgets/login_pages/server_connection_page.dart';
import 'package:paperless_mobile/core/database/tables/global_settings.dart';
import 'package:paperless_mobile/helpers/message_helpers.dart';
import 'widgets/login_pages/server_login_page.dart';
import 'widgets/never_scrollable_scroll_behavior.dart';

View File

@@ -1,6 +1,10 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:hive/hive.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_app_state.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_details/cubit/saved_view_details_cubit.dart';
@@ -17,11 +21,9 @@ class SavedViewList extends StatelessWidget {
return BlocBuilder<SavedViewCubit, SavedViewState>(
builder: (context, state) {
return state.when(
initial: (correspondents, documentTypes, tags, storagePaths) =>
Container(),
loading: (correspondents, documentTypes, tags, storagePaths) =>
Center(
child: Text("Saved views loading..."),
initial: (correspondents, documentTypes, tags, storagePaths) => Container(),
loading: (correspondents, documentTypes, tags, storagePaths) => Center(
child: Text("Saved views loading..."), //TODO: INTL
),
loaded: (
savedViews,
@@ -33,9 +35,7 @@ class SavedViewList extends StatelessWidget {
if (savedViews.isEmpty) {
return SliverToBoxAdapter(
child: HintCard(
hintText: S
.of(context)!
.createViewsToQuicklyFilterYourDocuments,
hintText: S.of(context)!.createViewsToQuicklyFilterYourDocuments,
),
);
}
@@ -59,13 +59,17 @@ class SavedViewList extends StatelessWidget {
ctxt.read(),
ctxt.read(),
context.read(),
Hive.box<LocalUserAppState>(HiveBoxes.localUserAppState).get(
Hive.box<GlobalSettings>(HiveBoxes.globalSettings)
.getValue()!
.currentLoggedInUser!,
)!,
savedView: view,
),
),
],
child: SavedViewDetailsPage(
onDelete:
context.read<SavedViewCubit>().remove,
onDelete: context.read<SavedViewCubit>().remove,
),
),
),

View File

@@ -1,17 +1,16 @@
import 'package:hydrated_bloc/hydrated_bloc.dart';
import 'package:json_annotation/json_annotation.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/core/database/tables/local_user_app_state.dart';
import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart';
import 'package:paperless_mobile/core/repository/label_repository.dart';
import 'package:paperless_mobile/features/paged_document_view/cubit/paged_documents_state.dart';
import 'package:paperless_mobile/features/paged_document_view/cubit/document_paging_bloc_mixin.dart';
import 'package:paperless_mobile/features/settings/model/view_type.dart';
part 'saved_view_details_cubit.g.dart';
part 'saved_view_details_state.dart';
class SavedViewDetailsCubit extends HydratedCubit<SavedViewDetailsState>
with DocumentPagingBlocMixin {
class SavedViewDetailsCubit extends Cubit<SavedViewDetailsState> with DocumentPagingBlocMixin {
@override
final PaperlessDocumentsApi api;
@@ -22,10 +21,13 @@ class SavedViewDetailsCubit extends HydratedCubit<SavedViewDetailsState>
final SavedView savedView;
final LocalUserAppState _userState;
SavedViewDetailsCubit(
this.api,
this.notifier,
this._labelRepository, {
this._labelRepository,
this._userState, {
required this.savedView,
}) : super(
SavedViewDetailsState(
@@ -33,6 +35,7 @@ class SavedViewDetailsCubit extends HydratedCubit<SavedViewDetailsState>
documentTypes: _labelRepository.state.documentTypes,
tags: _labelRepository.state.tags,
storagePaths: _labelRepository.state.storagePaths,
viewType: _userState.savedViewsViewType,
),
) {
notifier.addListener(
@@ -58,16 +61,9 @@ class SavedViewDetailsCubit extends HydratedCubit<SavedViewDetailsState>
void setViewType(ViewType viewType) {
emit(state.copyWith(viewType: viewType));
}
@override
SavedViewDetailsState? fromJson(Map<String, dynamic> json) {
return SavedViewDetailsState.fromJson(json);
}
@override
Map<String, dynamic>? toJson(SavedViewDetailsState state) {
return state.toJson();
_userState
..savedViewsViewType = viewType
..save();
}
@override

View File

@@ -1,8 +1,6 @@
part of 'saved_view_details_cubit.dart';
@JsonSerializable(ignoreUnannotated: true)
class SavedViewDetailsState extends DocumentPagingState {
@JsonKey()
final ViewType viewType;
final Map<int, Correspondent> correspondents;
@@ -70,9 +68,4 @@ class SavedViewDetailsState extends DocumentPagingState {
storagePaths: storagePaths ?? this.storagePaths,
);
}
factory SavedViewDetailsState.fromJson(Map<String, dynamic> json) =>
_$SavedViewDetailsStateFromJson(json);
Map<String, dynamic> toJson() => _$SavedViewDetailsStateToJson(this);
}

View File

@@ -1,6 +1,15 @@
import 'package:hive/hive.dart';
import 'package:paperless_mobile/core/config/hive/hive_config.dart';
part 'view_type.g.dart';
@HiveType(typeId: HiveTypeIds.viewType)
enum ViewType {
@HiveField(0)
grid,
@HiveField(1)
list,
@HiveField(2)
detailed;
ViewType toggle() {

View File

@@ -9,7 +9,7 @@ import 'package:paperless_mobile/core/config/hive/hive_config.dart';
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
import 'package:paperless_mobile/features/login/cubit/authentication_cubit.dart';
import 'package:paperless_mobile/features/login/model/login_form_credentials.dart';
import 'package:paperless_mobile/core/database/tables/user_account.dart';
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
import 'package:paperless_mobile/features/login/view/login_page.dart';
import 'package:paperless_mobile/core/database/tables/global_settings.dart';
import 'package:paperless_mobile/features/settings/view/dialogs/switch_account_dialog.dart';
@@ -26,7 +26,7 @@ class ManageAccountsPage extends StatelessWidget {
return GlobalSettingsBuilder(
builder: (context, globalSettings) {
return ValueListenableBuilder(
valueListenable: Hive.box<UserAccount>(HiveBoxes.userAccount).listenable(),
valueListenable: Hive.box<LocalUserAccount>(HiveBoxes.localUserAccount).listenable(),
builder: (context, box, _) {
final userIds = box.keys.toList().cast<String>();
final otherAccounts = userIds
@@ -68,7 +68,7 @@ class ManageAccountsPage extends StatelessWidget {
title: Text(S.of(context)!.addAccount),
leading: const Icon(Icons.person_add),
onTap: () {
_onAddAccount(context);
_onAddAccount(context, globalSettings.currentLoggedInUser!);
},
),
],
@@ -82,7 +82,7 @@ class ManageAccountsPage extends StatelessWidget {
Widget _buildAccountTile(
BuildContext context,
String userId,
UserAccount account,
LocalUserAccount account,
GlobalSettings settings,
) {
final isLoggedIn = userId == settings.currentLoggedInUser;
@@ -147,16 +147,7 @@ class ManageAccountsPage extends StatelessWidget {
onSelected: (value) async {
if (value == 0) {
// Switch
final navigator = Navigator.of(context);
if (settings.currentLoggedInUser == userId) return;
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => const SwitchingAccountsPage(),
),
);
await context.read<AuthenticationCubit>().switchAccount(userId);
navigator.popUntil((route) => route.isFirst);
_onSwitchAccount(context, settings.currentLoggedInUser!, userId);
} else if (value == 1) {
// Remove
final shouldPop = userId == settings.currentLoggedInUser;
@@ -177,7 +168,7 @@ class ManageAccountsPage extends StatelessWidget {
return child;
}
Future<void> _onAddAccount(BuildContext context) async {
Future<void> _onAddAccount(BuildContext context, String currentUser) async {
final userId = await Navigator.push(
context,
MaterialPageRoute(
@@ -207,9 +198,21 @@ class ManageAccountsPage extends StatelessWidget {
) ??
false;
if (shoudSwitch) {
await context.read<AuthenticationCubit>().switchAccount(userId);
Navigator.pop(context);
_onSwitchAccount(context, currentUser, userId);
}
}
}
_onSwitchAccount(BuildContext context, String currentUser, String newUser) async {
final navigator = Navigator.of(context);
if (currentUser == newUser) return;
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => const SwitchingAccountsPage(),
),
);
await context.read<AuthenticationCubit>().switchAccount(newUser);
navigator.popUntil((route) => route.isFirst);
}
}

View File

@@ -1,9 +1,10 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:paperless_mobile/core/bloc/paperless_server_information_cubit.dart';
import 'package:paperless_mobile/core/bloc/paperless_server_information_state.dart';
import 'package:paperless_mobile/core/bloc/server_information_cubit.dart';
import 'package:paperless_mobile/core/bloc/server_information_state.dart';
import 'package:paperless_mobile/features/settings/view/pages/application_settings_page.dart';
import 'package:paperless_mobile/features/settings/view/pages/security_settings_page.dart';
import 'package:paperless_mobile/features/settings/view/widgets/user_settings_builder.dart';
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
class SettingsPage extends StatelessWidget {
@@ -15,23 +16,27 @@ class SettingsPage extends StatelessWidget {
appBar: AppBar(
title: Text(S.of(context)!.settings),
),
bottomNavigationBar:
BlocBuilder<PaperlessServerInformationCubit, PaperlessServerInformationState>(
builder: (context, state) {
final info = state.information!;
bottomNavigationBar: UserAccountBuilder(
builder: (context, user) {
assert(user != null);
final host = user!.serverUrl.replaceFirst(RegExp(r"https?://"), "");
return ListTile(
title: Text(
S.of(context)!.loggedInAs(info.username ?? 'unknown') + "@${info.host}",
S.of(context)!.loggedInAs(user.username) + "@$host",
style: Theme.of(context).textTheme.labelSmall,
textAlign: TextAlign.center,
),
subtitle: Text(
S.of(context)!.paperlessServerVersion +
' ' +
info.version.toString() +
' (API v${info.apiVersion})',
style: Theme.of(context).textTheme.labelSmall,
textAlign: TextAlign.center,
subtitle: BlocBuilder<ServerInformationCubit, ServerInformationState>(
builder: (context, state) {
return Text(
S.of(context)!.paperlessServerVersion +
' ' +
state.information!.version.toString() +
' (API v${state.information!.apiVersion})',
style: Theme.of(context).textTheme.labelSmall,
textAlign: TextAlign.center,
);
},
),
);
},

View File

@@ -1,9 +1,9 @@
import 'package:flutter/material.dart';
import 'package:paperless_mobile/core/database/tables/user_account.dart';
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
class UserAvatar extends StatelessWidget {
final String userId;
final UserAccount account;
final LocalUserAccount account;
const UserAvatar({
super.key,
required this.userId,

View File

@@ -1,14 +1,14 @@
import 'package:flutter/material.dart';
import 'package:hive_flutter/adapters.dart';
import 'package:paperless_mobile/core/config/hive/hive_config.dart';
import 'package:paperless_mobile/core/database/tables/user_account.dart';
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
import 'package:paperless_mobile/core/database/tables/global_settings.dart';
import 'package:paperless_mobile/core/database/tables/user_settings.dart';
import 'package:paperless_mobile/core/database/tables/local_user_settings.dart';
class UserAccountBuilder extends StatelessWidget {
final Widget Function(
BuildContext context,
UserAccount? settings,
LocalUserAccount? settings,
) builder;
const UserAccountBuilder({
@@ -18,8 +18,8 @@ class UserAccountBuilder extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ValueListenableBuilder<Box<UserAccount>>(
valueListenable: Hive.box<UserAccount>(HiveBoxes.userAccount).listenable(),
return ValueListenableBuilder<Box<LocalUserAccount>>(
valueListenable: Hive.box<LocalUserAccount>(HiveBoxes.localUserAccount).listenable(),
builder: (context, accountBox, _) {
final currentUser =
Hive.box<GlobalSettings>(HiveBoxes.globalSettings).getValue()!.currentLoggedInUser;

View File

@@ -23,7 +23,7 @@ class SimilarDocumentsCubit extends Cubit<SimilarDocumentsState> with DocumentPa
this.notifier,
this._labelRepository, {
required this.documentId,
}) : super(SimilarDocumentsState(filter: DocumentFilter())) {
}) : super(const SimilarDocumentsState(filter: DocumentFilter())) {
notifier.addListener(
this,
onDeleted: remove,

View File

@@ -18,9 +18,9 @@ import 'package:package_info_plus/package_info_plus.dart';
import 'package:paperless_api/paperless_api.dart';
import 'package:paperless_mobile/constants.dart';
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
import 'package:paperless_mobile/core/bloc/paperless_server_information_cubit.dart';
import 'package:paperless_mobile/core/bloc/server_information_cubit.dart';
import 'package:paperless_mobile/core/config/hive/hive_config.dart';
import 'package:paperless_mobile/core/database/tables/user_app_state.dart';
import 'package:paperless_mobile/core/database/tables/local_user_app_state.dart';
import 'package:paperless_mobile/core/interceptor/dio_http_error_interceptor.dart';
import 'package:paperless_mobile/core/interceptor/language_header.interceptor.dart';
import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart';
@@ -32,11 +32,12 @@ import 'package:paperless_mobile/core/service/dio_file_service.dart';
import 'package:paperless_mobile/core/type/types.dart';
import 'package:paperless_mobile/features/app_intro/application_intro_slideshow.dart';
import 'package:paperless_mobile/features/home/view/home_page.dart';
import 'package:paperless_mobile/features/home/view/home_route.dart';
import 'package:paperless_mobile/features/home/view/widget/verify_identity_page.dart';
import 'package:paperless_mobile/features/login/cubit/authentication_cubit.dart';
import 'package:paperless_mobile/features/login/model/client_certificate.dart';
import 'package:paperless_mobile/features/login/model/login_form_credentials.dart';
import 'package:paperless_mobile/core/database/tables/user_account.dart';
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
import 'package:paperless_mobile/features/login/services/authentication_service.dart';
import 'package:paperless_mobile/features/login/view/login_page.dart';
import 'package:paperless_mobile/features/notifications/services/local_notification_service.dart';
@@ -66,8 +67,8 @@ Future<void> _initHive() async {
// await getApplicationDocumentsDirectory().then((value) => value.delete(recursive: true));
registerHiveAdapters();
await Hive.openBox<UserAccount>(HiveBoxes.userAccount);
await Hive.openBox<UserAppState>(HiveBoxes.userAppState);
await Hive.openBox<LocalUserAccount>(HiveBoxes.localUserAccount);
await Hive.openBox<LocalUserAppState>(HiveBoxes.localUserAppState);
final globalSettingsBox = await Hive.openBox<GlobalSettings>(HiveBoxes.globalSettings);
if (!globalSettingsBox.hasValue) {
@@ -302,26 +303,8 @@ class _AuthenticationWrapperState extends State<AuthenticationWrapper> {
return BlocBuilder<AuthenticationCubit, AuthenticationState>(
builder: (context, authentication) {
if (authentication.isAuthenticated) {
return MultiBlocProvider(
// This key will cause the subtree to be remounted, which will again
// call the provider's create callback! Without this key, the blocs
// would not be recreated on account switch!
return HomeRoute(
key: ValueKey(authentication.userId),
providers: [
BlocProvider(
create: (context) => TaskStatusCubit(
context.read<PaperlessTasksApi>(),
),
),
BlocProvider<PaperlessServerInformationCubit>(
create: (context) => PaperlessServerInformationCubit(
context.read<PaperlessServerStatsApi>(),
),
),
],
child: HomePage(
key: ValueKey(authentication.userId),
),
);
} else if (authentication.showBiometricAuthenticationScreen) {
return const VerifyIdentityPage();

View File

@@ -14,7 +14,7 @@ class IdQueryParameter with _$IdQueryParameter {
@HiveType(typeId: PaperlessApiHiveTypeIds.anyAssignedIdQueryParameter)
const factory IdQueryParameter.anyAssigned() = AnyAssignedIdQueryParameter;
@HiveType(typeId: PaperlessApiHiveTypeIds.setIdQueryParameter)
const factory IdQueryParameter.fromId(@HiveField(0) int? id) = SetIdQueryParameter;
const factory IdQueryParameter.fromId(@HiveField(0) int id) = SetIdQueryParameter;
Map<String, String> toQueryParameter(String field) {
return when(

View File

@@ -38,7 +38,7 @@ mixin _$IdQueryParameter {
required TResult Function() unset,
required TResult Function() notAssigned,
required TResult Function() anyAssigned,
required TResult Function(@HiveField(0) int? id) fromId,
required TResult Function(@HiveField(0) int id) fromId,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
@@ -46,7 +46,7 @@ mixin _$IdQueryParameter {
TResult? Function()? unset,
TResult? Function()? notAssigned,
TResult? Function()? anyAssigned,
TResult? Function(@HiveField(0) int? id)? fromId,
TResult? Function(@HiveField(0) int id)? fromId,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
@@ -54,7 +54,7 @@ mixin _$IdQueryParameter {
TResult Function()? unset,
TResult Function()? notAssigned,
TResult Function()? anyAssigned,
TResult Function(@HiveField(0) int? id)? fromId,
TResult Function(@HiveField(0) int id)? fromId,
required TResult orElse(),
}) =>
throw _privateConstructorUsedError;
@@ -155,7 +155,7 @@ class _$UnsetIdQueryParameter extends UnsetIdQueryParameter {
required TResult Function() unset,
required TResult Function() notAssigned,
required TResult Function() anyAssigned,
required TResult Function(@HiveField(0) int? id) fromId,
required TResult Function(@HiveField(0) int id) fromId,
}) {
return unset();
}
@@ -166,7 +166,7 @@ class _$UnsetIdQueryParameter extends UnsetIdQueryParameter {
TResult? Function()? unset,
TResult? Function()? notAssigned,
TResult? Function()? anyAssigned,
TResult? Function(@HiveField(0) int? id)? fromId,
TResult? Function(@HiveField(0) int id)? fromId,
}) {
return unset?.call();
}
@@ -177,7 +177,7 @@ class _$UnsetIdQueryParameter extends UnsetIdQueryParameter {
TResult Function()? unset,
TResult Function()? notAssigned,
TResult Function()? anyAssigned,
TResult Function(@HiveField(0) int? id)? fromId,
TResult Function(@HiveField(0) int id)? fromId,
required TResult orElse(),
}) {
if (unset != null) {
@@ -293,7 +293,7 @@ class _$NotAssignedIdQueryParameter extends NotAssignedIdQueryParameter {
required TResult Function() unset,
required TResult Function() notAssigned,
required TResult Function() anyAssigned,
required TResult Function(@HiveField(0) int? id) fromId,
required TResult Function(@HiveField(0) int id) fromId,
}) {
return notAssigned();
}
@@ -304,7 +304,7 @@ class _$NotAssignedIdQueryParameter extends NotAssignedIdQueryParameter {
TResult? Function()? unset,
TResult? Function()? notAssigned,
TResult? Function()? anyAssigned,
TResult? Function(@HiveField(0) int? id)? fromId,
TResult? Function(@HiveField(0) int id)? fromId,
}) {
return notAssigned?.call();
}
@@ -315,7 +315,7 @@ class _$NotAssignedIdQueryParameter extends NotAssignedIdQueryParameter {
TResult Function()? unset,
TResult Function()? notAssigned,
TResult Function()? anyAssigned,
TResult Function(@HiveField(0) int? id)? fromId,
TResult Function(@HiveField(0) int id)? fromId,
required TResult orElse(),
}) {
if (notAssigned != null) {
@@ -431,7 +431,7 @@ class _$AnyAssignedIdQueryParameter extends AnyAssignedIdQueryParameter {
required TResult Function() unset,
required TResult Function() notAssigned,
required TResult Function() anyAssigned,
required TResult Function(@HiveField(0) int? id) fromId,
required TResult Function(@HiveField(0) int id) fromId,
}) {
return anyAssigned();
}
@@ -442,7 +442,7 @@ class _$AnyAssignedIdQueryParameter extends AnyAssignedIdQueryParameter {
TResult? Function()? unset,
TResult? Function()? notAssigned,
TResult? Function()? anyAssigned,
TResult? Function(@HiveField(0) int? id)? fromId,
TResult? Function(@HiveField(0) int id)? fromId,
}) {
return anyAssigned?.call();
}
@@ -453,7 +453,7 @@ class _$AnyAssignedIdQueryParameter extends AnyAssignedIdQueryParameter {
TResult Function()? unset,
TResult Function()? notAssigned,
TResult Function()? anyAssigned,
TResult Function(@HiveField(0) int? id)? fromId,
TResult Function(@HiveField(0) int id)? fromId,
required TResult orElse(),
}) {
if (anyAssigned != null) {
@@ -521,7 +521,7 @@ abstract class _$$SetIdQueryParameterCopyWith<$Res> {
$Res Function(_$SetIdQueryParameter) then) =
__$$SetIdQueryParameterCopyWithImpl<$Res>;
@useResult
$Res call({@HiveField(0) int? id});
$Res call({@HiveField(0) int id});
}
/// @nodoc
@@ -535,13 +535,13 @@ class __$$SetIdQueryParameterCopyWithImpl<$Res>
@pragma('vm:prefer-inline')
@override
$Res call({
Object? id = freezed,
Object? id = null,
}) {
return _then(_$SetIdQueryParameter(
freezed == id
null == id
? _value.id
: id // ignore: cast_nullable_to_non_nullable
as int?,
as int,
));
}
}
@@ -559,7 +559,7 @@ class _$SetIdQueryParameter extends SetIdQueryParameter {
@override
@HiveField(0)
final int? id;
final int id;
@JsonKey(name: 'runtimeType')
final String $type;
@@ -594,7 +594,7 @@ class _$SetIdQueryParameter extends SetIdQueryParameter {
required TResult Function() unset,
required TResult Function() notAssigned,
required TResult Function() anyAssigned,
required TResult Function(@HiveField(0) int? id) fromId,
required TResult Function(@HiveField(0) int id) fromId,
}) {
return fromId(id);
}
@@ -605,7 +605,7 @@ class _$SetIdQueryParameter extends SetIdQueryParameter {
TResult? Function()? unset,
TResult? Function()? notAssigned,
TResult? Function()? anyAssigned,
TResult? Function(@HiveField(0) int? id)? fromId,
TResult? Function(@HiveField(0) int id)? fromId,
}) {
return fromId?.call(id);
}
@@ -616,7 +616,7 @@ class _$SetIdQueryParameter extends SetIdQueryParameter {
TResult Function()? unset,
TResult Function()? notAssigned,
TResult Function()? anyAssigned,
TResult Function(@HiveField(0) int? id)? fromId,
TResult Function(@HiveField(0) int id)? fromId,
required TResult orElse(),
}) {
if (fromId != null) {
@@ -671,7 +671,7 @@ class _$SetIdQueryParameter extends SetIdQueryParameter {
}
abstract class SetIdQueryParameter extends IdQueryParameter {
const factory SetIdQueryParameter(@HiveField(0) final int? id) =
const factory SetIdQueryParameter(@HiveField(0) final int id) =
_$SetIdQueryParameter;
const SetIdQueryParameter._() : super._();
@@ -679,7 +679,7 @@ abstract class SetIdQueryParameter extends IdQueryParameter {
_$SetIdQueryParameter.fromJson;
@HiveField(0)
int? get id;
int get id;
@JsonKey(ignore: true)
_$$SetIdQueryParameterCopyWith<_$SetIdQueryParameter> get copyWith =>
throw _privateConstructorUsedError;

View File

@@ -7,6 +7,7 @@ import 'query_type.dart';
part 'text_query.g.dart';
//TODO: Realize with freezed...
@HiveType(typeId: PaperlessApiHiveTypeIds.textQuery)
@JsonSerializable()
class TextQuery extends Equatable {

View File

@@ -78,6 +78,7 @@ class PaperlessDocumentsApiImpl implements PaperlessDocumentsApi {
throw const PaperlessServerException(ErrorCode.documentUpdateFailed);
}
} on DioError catch (err) {
//TODO: Handle 403 permission errors for 1.14.0
throw err.error!;
}
}
@@ -86,8 +87,7 @@ class PaperlessDocumentsApiImpl implements PaperlessDocumentsApi {
Future<PagedSearchResult<DocumentModel>> findAll(
DocumentFilter filter,
) async {
final filterParams = filter.toQueryParameters()
..addAll({'truncate_content': "true"});
final filterParams = filter.toQueryParameters()..addAll({'truncate_content': "true"});
try {
final response = await client.get(
"/api/documents/",
@@ -137,9 +137,8 @@ class PaperlessDocumentsApiImpl implements PaperlessDocumentsApi {
try {
final response = await client.get(
getPreviewUrl(documentId),
options: Options(
responseType: ResponseType
.bytes), //TODO: Check if bytes or stream is required
options:
Options(responseType: ResponseType.bytes), //TODO: Check if bytes or stream is required
);
if (response.statusCode == 200) {
return response.data;
@@ -219,8 +218,7 @@ class PaperlessDocumentsApiImpl implements PaperlessDocumentsApi {
final response = await client.download(
"/api/documents/${document.id}/download/",
localFilePath,
onReceiveProgress: (count, total) =>
onProgressChanged?.call(count / total),
onReceiveProgress: (count, total) => onProgressChanged?.call(count / total),
queryParameters: {'original': original},
);
return response.data;
@@ -232,8 +230,7 @@ class PaperlessDocumentsApiImpl implements PaperlessDocumentsApi {
@override
Future<DocumentMetaData> getMetaData(DocumentModel document) async {
try {
final response =
await client.get("/api/documents/${document.id}/metadata/");
final response = await client.get("/api/documents/${document.id}/metadata/");
return compute(
DocumentMetaData.fromJson,
response.data as Map<String, dynamic>,
@@ -265,11 +262,9 @@ class PaperlessDocumentsApiImpl implements PaperlessDocumentsApi {
@override
Future<FieldSuggestions> findSuggestions(DocumentModel document) async {
try {
final response =
await client.get("/api/documents/${document.id}/suggestions/");
final response = await client.get("/api/documents/${document.id}/suggestions/");
if (response.statusCode == 200) {
return FieldSuggestions.fromJson(response.data)
.forDocumentId(document.id);
return FieldSuggestions.fromJson(response.data).forDocumentId(document.id);
}
throw const PaperlessServerException(ErrorCode.suggestionsQueryError);
} on DioError catch (err) {

View File

@@ -1,7 +1,4 @@
import 'dart:convert';
import 'package:dio/dio.dart';
import 'package:http/http.dart';
import 'package:paperless_api/src/models/paperless_server_exception.dart';
import 'package:paperless_api/src/models/paperless_server_information_model.dart';
import 'package:paperless_api/src/models/paperless_server_statistics_model.dart';
@@ -21,20 +18,14 @@ class PaperlessServerStatsApiImpl implements PaperlessServerStatsApi {
@override
Future<PaperlessServerInformationModel> getServerInformation() async {
final response = await client.get("/api/ui_settings/");
final response = await client.get("/api/");
final version =
response.headers[PaperlessServerInformationModel.versionHeader]?.first ?? 'unknown';
final apiVersion = int.tryParse(
response.headers[PaperlessServerInformationModel.apiVersionHeader]?.first ?? '1');
final String username = response.data['username'];
final String host = response.headers[PaperlessServerInformationModel.hostHeader]?.first ??
response.headers[PaperlessServerInformationModel.hostHeader]?.first ??
('${response.requestOptions.uri.host}:${response.requestOptions.uri.port}');
return PaperlessServerInformationModel(
username: username,
version: version,
apiVersion: apiVersion,
host: host,
);
}