feat: Fix hive errors, change provider hierarchy for blocs, translate strings

This commit is contained in:
Anton Stubenbord
2023-04-25 01:16:14 +02:00
parent 1f335119b3
commit 8c2a6928b4
34 changed files with 502 additions and 363 deletions

View File

@@ -54,12 +54,16 @@ class DocumentDetailsCubit extends Cubit<DocumentDetailsState> {
Future<void> loadSuggestions() async {
final suggestions = await _api.findSuggestions(state.document);
emit(state.copyWith(suggestions: suggestions));
if (!isClosed) {
emit(state.copyWith(suggestions: suggestions));
}
}
Future<void> loadMetaData() async {
final metaData = await _api.getMetaData(state.document);
emit(state.copyWith(metaData: metaData));
if (!isClosed) {
emit(state.copyWith(metaData: metaData));
}
}
Future<void> loadFullContent() async {
@@ -85,8 +89,8 @@ class DocumentDetailsCubit extends Cubit<DocumentDetailsState> {
_notifier.notifyUpdated(updatedDocument);
} else {
final int autoAsn = await _api.findNextAsn();
final updatedDocument = await _api
.update(document.copyWith(archiveSerialNumber: () => autoAsn));
final updatedDocument =
await _api.update(document.copyWith(archiveSerialNumber: () => autoAsn));
_notifier.notifyUpdated(updatedDocument);
}
}
@@ -97,8 +101,7 @@ class DocumentDetailsCubit extends Cubit<DocumentDetailsState> {
if (state.metaData == null) {
await loadMetaData();
}
final desc = FileDescription.fromPath(
state.metaData!.mediaFilename.replaceAll("/", " "));
final desc = FileDescription.fromPath(state.metaData!.mediaFilename.replaceAll("/", " "));
final fileName = "${desc.filename}.pdf";
final file = File("${cacheDir.path}/$fileName");
@@ -132,8 +135,7 @@ class DocumentDetailsCubit extends Cubit<DocumentDetailsState> {
await FileService.downloadsDirectory,
);
final desc = FileDescription.fromPath(
state.metaData!.mediaFilename
.replaceAll("/", " "), // Flatten directory structure
state.metaData!.mediaFilename.replaceAll("/", " "), // Flatten directory structure
);
if (!File(filePath).existsSync()) {
File(filePath).createSync();
@@ -198,8 +200,7 @@ class DocumentDetailsCubit extends Cubit<DocumentDetailsState> {
String _buildDownloadFilePath(bool original, Directory dir) {
final description = FileDescription.fromPath(
state.metaData!.mediaFilename
.replaceAll("/", " "), // Flatten directory structure
state.metaData!.mediaFilename.replaceAll("/", " "), // Flatten directory structure
);
final extension = original ? description.extension : 'pdf';
return "${dir.path}/${description.filename}.$extension";

View File

@@ -60,36 +60,28 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
Navigator.of(context)
.pop(context.read<DocumentDetailsCubit>().state.document);
Navigator.of(context).pop(context.read<DocumentDetailsCubit>().state.document);
return false;
},
child: DefaultTabController(
length: 4,
child: BlocListener<ConnectivityCubit, ConnectivityState>(
listenWhen: (previous, current) =>
!previous.isConnected && current.isConnected,
listenWhen: (previous, current) => !previous.isConnected && current.isConnected,
listener: (context, state) {
_loadMetaData();
setState(() {});
},
child: Scaffold(
extendBodyBehindAppBar: false,
floatingActionButtonLocation:
FloatingActionButtonLocation.endDocked,
floatingActionButtonLocation: FloatingActionButtonLocation.endDocked,
floatingActionButton: widget.allowEdit ? _buildEditButton() : null,
bottomNavigationBar: _buildBottomAppBar(),
body: NestedScrollView(
headerSliverBuilder: (context, innerBoxIsScrolled) => [
SliverOverlapAbsorber(
handle:
NestedScrollView.sliverOverlapAbsorberHandleFor(context),
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
sliver: SliverAppBar(
title: Text(context
.watch<DocumentDetailsCubit>()
.state
.document
.title),
title: Text(context.watch<DocumentDetailsCubit>().state.document.title),
leading: const BackButton(),
pinned: true,
forceElevated: innerBoxIsScrolled,
@@ -99,8 +91,7 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
background: Stack(
alignment: Alignment.topCenter,
children: [
BlocBuilder<DocumentDetailsCubit,
DocumentDetailsState>(
BlocBuilder<DocumentDetailsCubit, DocumentDetailsState>(
builder: (context, state) => Positioned.fill(
child: DocumentPreview(
document: state.document,
@@ -137,9 +128,7 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
child: Text(
S.of(context)!.overview,
style: TextStyle(
color: Theme.of(context)
.colorScheme
.onPrimaryContainer,
color: Theme.of(context).colorScheme.onPrimaryContainer,
),
),
),
@@ -147,9 +136,7 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
child: Text(
S.of(context)!.content,
style: TextStyle(
color: Theme.of(context)
.colorScheme
.onPrimaryContainer,
color: Theme.of(context).colorScheme.onPrimaryContainer,
),
),
),
@@ -157,9 +144,7 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
child: Text(
S.of(context)!.metaData,
style: TextStyle(
color: Theme.of(context)
.colorScheme
.onPrimaryContainer,
color: Theme.of(context).colorScheme.onPrimaryContainer,
),
),
),
@@ -167,9 +152,7 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
child: Text(
S.of(context)!.similarDocuments,
style: TextStyle(
color: Theme.of(context)
.colorScheme
.onPrimaryContainer,
color: Theme.of(context).colorScheme.onPrimaryContainer,
),
),
),
@@ -198,8 +181,7 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
CustomScrollView(
slivers: [
SliverOverlapInjector(
handle: NestedScrollView
.sliverOverlapAbsorberHandleFor(context),
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
),
DocumentOverviewWidget(
document: state.document,
@@ -215,8 +197,7 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
CustomScrollView(
slivers: [
SliverOverlapInjector(
handle: NestedScrollView
.sliverOverlapAbsorberHandleFor(context),
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
),
DocumentContentWidget(
isFullContentLoaded: state.isFullContentLoaded,
@@ -229,8 +210,7 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
CustomScrollView(
slivers: [
SliverOverlapInjector(
handle: NestedScrollView
.sliverOverlapAbsorberHandleFor(context),
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
),
DocumentMetaDataWidget(
document: state.document,
@@ -242,8 +222,7 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
controller: _pagingScrollController,
slivers: [
SliverOverlapInjector(
handle: NestedScrollView
.sliverOverlapAbsorberHandleFor(context),
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
),
SimilarDocumentsView(
pagingScrollController: _pagingScrollController,
@@ -301,9 +280,8 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
IconButton(
tooltip: S.of(context)!.deleteDocumentTooltip,
icon: const Icon(Icons.delete),
onPressed: widget.allowEdit && isConnected
? () => _onDelete(state.document)
: null,
onPressed:
widget.allowEdit && isConnected ? () => _onDelete(state.document) : null,
).paddedSymmetrically(horizontal: 4),
DocumentDownloadButton(
document: state.document,
@@ -313,8 +291,7 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
IconButton(
tooltip: S.of(context)!.previewTooltip,
icon: const Icon(Icons.visibility),
onPressed:
isConnected ? () => _onOpen(state.document) : null,
onPressed: isConnected ? () => _onOpen(state.document) : null,
).paddedOnly(right: 4.0),
IconButton(
tooltip: S.of(context)!.openInSystemViewer,
@@ -352,8 +329,7 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
),
],
child: BlocListener<DocumentEditCubit, DocumentEditState>(
listenWhen: (previous, current) =>
previous.document != current.document,
listenWhen: (previous, current) => previous.document != current.document,
listener: (context, state) {
cubit.replace(state.document);
},
@@ -373,8 +349,7 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
}
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.noAppToOpen) {
showGenericError(context, S.of(context)!.noAppToDisplayPDFFilesFound);
@@ -383,16 +358,14 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
showGenericError(context, translateError(context, ErrorCode.unknown));
}
if (status == ResultType.permissionDenied) {
showGenericError(
context, S.of(context)!.couldNotOpenFilePermissionDenied);
showGenericError(context, S.of(context)!.couldNotOpenFilePermissionDenied);
}
}
void _onDelete(DocumentModel document) async {
final delete = await showDialog(
context: context,
builder: (context) =>
DeleteDocumentConfirmationDialog(document: document),
builder: (context) => DeleteDocumentConfirmationDialog(document: document),
) ??
false;
if (delete) {
@@ -412,8 +385,7 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => DocumentView(
documentBytes:
context.read<PaperlessDocumentsApi>().getPreview(document.id),
documentBytes: context.read<PaperlessDocumentsApi>().getPreview(document.id),
),
),
);

View File

@@ -190,7 +190,7 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
allowCreation: true,
allowExclude: false,
initialValue: TagsQuery.ids(
include: state.document.tags,
include: state.document.tags.toList(),
),
).padded(),
if (_filteredSuggestions?.tags

View File

@@ -1,27 +1,32 @@
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/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:json_annotation/json_annotation.dart';
import 'package:paperless_mobile/features/paged_document_view/cubit/paged_documents_state.dart';
import 'package:paperless_mobile/core/database/tables/user_settings.dart';
import 'package:paperless_mobile/features/settings/model/view_type.dart';
part 'document_search_state.dart';
part 'document_search_cubit.g.dart';
part 'document_search_state.dart';
class DocumentSearchCubit extends HydratedCubit<DocumentSearchState> with DocumentPagingBlocMixin {
class DocumentSearchCubit extends Cubit<DocumentSearchState> with DocumentPagingBlocMixin {
@override
final PaperlessDocumentsApi api;
final LabelRepository _labelRepository;
@override
final DocumentChangedNotifier notifier;
DocumentSearchCubit(this.api, this.notifier, this._labelRepository)
: super(const DocumentSearchState()) {
final UserAppState _userAppState;
DocumentSearchCubit(
this.api,
this.notifier,
this._labelRepository,
this._userAppState,
) : super(DocumentSearchState(searchHistory: _userAppState.documentSearchHistory)) {
_labelRepository.addListener(
this,
onChanged: (labels) {
@@ -61,6 +66,9 @@ class DocumentSearchCubit extends HydratedCubit<DocumentSearchState> with Docume
],
),
);
_userAppState
..documentSearchHistory = state.searchHistory
..save();
}
void updateViewType(ViewType viewType) {
@@ -73,6 +81,9 @@ class DocumentSearchCubit extends HydratedCubit<DocumentSearchState> with Docume
searchHistory: state.searchHistory.whereNot((element) => element == entry).toList(),
),
);
_userAppState
..documentSearchHistory = state.searchHistory
..save();
}
Future<void> suggest(String query) async {
@@ -92,11 +103,13 @@ class DocumentSearchCubit extends HydratedCubit<DocumentSearchState> with Docume
}
void reset() {
emit(state.copyWith(
view: SearchView.suggestions,
suggestions: [],
isLoading: false,
));
emit(
state.copyWith(
view: SearchView.suggestions,
suggestions: [],
isLoading: false,
),
);
}
@override
@@ -106,16 +119,6 @@ class DocumentSearchCubit extends HydratedCubit<DocumentSearchState> with Docume
return super.close();
}
@override
DocumentSearchState? fromJson(Map<String, dynamic> json) {
return DocumentSearchState.fromJson(json);
}
@override
Map<String, dynamic>? toJson(DocumentSearchState state) {
return state.toJson();
}
@override
Future<void> onFilterUpdated(DocumentFilter filter) async {}
}

View File

@@ -3,6 +3,10 @@ import 'dart:async';
import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
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/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';
@@ -14,6 +18,8 @@ import 'package:paperless_mobile/routes/document_details_route.dart';
import 'dart:math' as math;
Future<void> showDocumentSearchPage(BuildContext context) {
final currentUser =
Hive.box<GlobalSettings>(HiveBoxes.globalSettings).getValue()!.currentLoggedInUser;
return Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => BlocProvider(
@@ -21,6 +27,7 @@ Future<void> showDocumentSearchPage(BuildContext context) {
context.read(),
context.read(),
context.read(),
Hive.box<UserAppState>(HiveBoxes.userAppState).get(currentUser)!,
),
child: const DocumentSearchPage(),
),
@@ -111,9 +118,8 @@ class _DocumentSearchPageState extends State<DocumentSearchPage> {
}
Widget _buildSuggestionsView(DocumentSearchState state) {
final suggestions = state.suggestions
.whereNot((element) => state.searchHistory.contains(element))
.toList();
final suggestions =
state.suggestions.whereNot((element) => state.searchHistory.contains(element)).toList();
final historyMatches = state.searchHistory
.where(
(element) => element.startsWith(query),
@@ -195,8 +201,7 @@ class _DocumentSearchPageState extends State<DocumentSearchPage> {
builder: (context, state) {
return ViewTypeSelectionWidget(
viewType: state.viewType,
onChanged: (type) =>
context.read<DocumentSearchCubit>().updateViewType(type),
onChanged: (type) => context.read<DocumentSearchCubit>().updateViewType(type),
);
},
)

View File

@@ -2,13 +2,11 @@ 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/paperless_server_information_state.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/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';
import 'package:paperless_mobile/core/database/tables/user_account.dart';
import 'package:paperless_mobile/features/settings/view/dialogs/account_settings_dialog.dart';
import 'package:paperless_mobile/features/settings/view/manage_accounts_page.dart';
import 'package:paperless_mobile/features/settings/view/widgets/global_settings_builder.dart';
import 'package:paperless_mobile/features/settings/view/widgets/user_avatar.dart';

View File

@@ -15,7 +15,7 @@ import 'package:paperless_mobile/features/settings/model/view_type.dart';
part 'documents_cubit.g.dart';
part 'documents_state.dart';
class DocumentsCubit extends HydratedCubit<DocumentsState> with DocumentPagingBlocMixin {
class DocumentsCubit extends Cubit<DocumentsState> with DocumentPagingBlocMixin {
@override
final PaperlessDocumentsApi api;

View File

@@ -461,8 +461,8 @@ class _DocumentsPageState extends State<DocumentsPage> with SingleTickerProvider
context.read<DocumentsCubit>().updateCurrentFilter(
(filter) => filter.copyWith(
tags: tagsQuery.copyWith(
include: tagsQuery.include.whereNot((id) => id == tagId),
exclude: tagsQuery.exclude.whereNot((id) => id == tagId)),
include: tagsQuery.include.whereNot((id) => id == tagId).toList(),
exclude: tagsQuery.exclude.whereNot((id) => id == tagId).toList()),
),
);
} else {

View File

@@ -11,6 +11,7 @@ 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/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/global/constants.dart';
import 'package:paperless_mobile/core/repository/label_repository.dart';
@@ -40,6 +41,8 @@ import 'package:paperless_mobile/helpers/message_helpers.dart';
import 'package:receive_sharing_intent/receive_sharing_intent.dart';
import 'package:responsive_builder/responsive_builder.dart';
/// Wrapper around all functionality for a logged in user.
/// Performs initialization logic.
class HomePage extends StatefulWidget {
const HomePage({Key? key}) : super(key: key);
@@ -50,14 +53,37 @@ class HomePage extends StatefulWidget {
class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
int _currentIndex = 0;
final DocumentScannerCubit _scannerCubit = DocumentScannerCubit();
late final DocumentsCubit _documentsCubit;
late final InboxCubit _inboxCubit;
late final SavedViewCubit _savedViewCubit;
late Timer _inboxTimer;
@override
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;
_documentsCubit = DocumentsCubit(
context.read(),
context.read(),
context.read(),
Hive.box<UserAppState>(HiveBoxes.userAppState).get(userId)!,
)..reload();
_savedViewCubit = SavedViewCubit(
context.read(),
context.read(),
)..reload();
_inboxCubit = InboxCubit(
context.read(),
context.read(),
@@ -65,14 +91,9 @@ class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
context.read(),
);
_listenToInboxChanges();
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
_listenForReceivedFiles();
});
}
void _listenToInboxChanges() {
_inboxCubit.refreshItemsInInboxCount();
_inboxTimer = Timer.periodic(const Duration(seconds: 10), (timer) {
if (!mounted) {
timer.cancel();
@@ -108,6 +129,8 @@ class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
WidgetsBinding.instance.removeObserver(this);
_inboxTimer.cancel();
_inboxCubit.close();
_documentsCubit.close();
_savedViewCubit.close();
super.dispose();
}
@@ -190,7 +213,8 @@ class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
@override
Widget build(BuildContext context) {
final userId = context.watch<AuthenticationCubit>().state.userId;
final userId =
Hive.box<GlobalSettings>(HiveBoxes.globalSettings).getValue()!.currentLoggedInUser!;
final destinations = [
RouteDescription(
icon: const Icon(Icons.description_outlined),
@@ -239,22 +263,10 @@ class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
];
final routes = <Widget>[
MultiBlocProvider(
key: ValueKey(userId),
// key: ValueKey(userId),
providers: [
BlocProvider(
create: (context) => DocumentsCubit(
context.read(),
context.read(),
context.read(),
Hive.box<UserAppState>(HiveBoxes.userAppState).get(userId)!,
)..reload(),
),
BlocProvider(
create: (context) => SavedViewCubit(
context.read(),
context.read(),
)..reload(),
),
BlocProvider.value(value: _documentsCubit),
BlocProvider.value(value: _savedViewCubit),
],
child: const DocumentsPage(),
),
@@ -263,7 +275,7 @@ class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
child: const ScannerPage(),
),
MultiBlocProvider(
key: ValueKey(userId),
// key: ValueKey(userId),
providers: [
BlocProvider(
create: (context) => LabelCubit(context.read()),

View File

@@ -97,7 +97,7 @@ class InboxCubit extends HydratedCubit<InboxState> with DocumentPagingBlocMixin
updateFilter(
filter: DocumentFilter(
sortField: SortField.added,
tags: TagsQuery.ids(include: inboxTags),
tags: TagsQuery.ids(include: inboxTags.toList()),
),
);
}
@@ -127,7 +127,7 @@ class InboxCubit extends HydratedCubit<InboxState> with DocumentPagingBlocMixin
updateFilter(
filter: DocumentFilter(
sortField: SortField.added,
tags: TagsQuery.ids(include: inboxTags),
tags: TagsQuery.ids(include: inboxTags.toList()),
),
);
}

View File

@@ -121,15 +121,15 @@ class TagsFormField extends StatelessWidget {
final tag = options[id]!;
return QueryTagChip(
onDeleted: () => field.didChange(formValue.copyWith(
include: formValue.include.whereNot((element) => element == id),
exclude: formValue.exclude.whereNot((element) => element == id),
include: formValue.include.whereNot((element) => element == id).toList(),
exclude: formValue.exclude.whereNot((element) => element == id).toList(),
)),
onSelected: allowExclude
? () {
if (formValue.include.contains(id)) {
field.didChange(
formValue.copyWith(
include: formValue.include.whereNot((element) => element == id),
include: formValue.include.whereNot((element) => element == id).toList(),
exclude: [...formValue.exclude, id],
),
);
@@ -137,7 +137,7 @@ class TagsFormField extends StatelessWidget {
field.didChange(
formValue.copyWith(
include: [...formValue.include, id],
exclude: formValue.exclude.whereNot((element) => element == id),
exclude: formValue.exclude.whereNot((element) => element == id).toList(),
),
);
}
@@ -171,7 +171,7 @@ class TagsFormField extends StatelessWidget {
return QueryTagChip(
onDeleted: () {
final updatedQuery = query.copyWith(
tagIds: query.tagIds.whereNot((element) => element == e),
tagIds: query.tagIds.whereNot((element) => element == e).toList(),
);
if (updatedQuery.tagIds.isEmpty) {
field.didChange(const TagsQuery.ids());

View File

@@ -59,7 +59,6 @@ class LabelItem<T extends Label> extends StatelessWidget {
context.read(),
context.read(),
context.read(),
Hive.box<UserAccount>(HiveBoxes.userAccount).get(currentUser)!,
),
child: const LinkedDocumentsPage(),
),

View File

@@ -3,7 +3,6 @@ import 'package:json_annotation/json_annotation.dart';
import 'package:paperless_api/paperless_api.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/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';
@@ -21,15 +20,11 @@ class LinkedDocumentsCubit extends HydratedCubit<LinkedDocumentsState>
final LabelRepository _labelRepository;
@override
// TODO: implement account
final UserAccount account;
LinkedDocumentsCubit(
DocumentFilter filter,
this.api,
this.notifier,
this._labelRepository,
this.account,
) : super(LinkedDocumentsState(filter: filter)) {
updateFilter(filter: filter);
_labelRepository.addListener(

View File

@@ -143,8 +143,9 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
debugPrint("Invalid authentication for $userId");
return;
}
final credentials = credentialsBox.get(userId);
await credentialsBox.close();
await _resetExternalState();
_dioWrapper.updateSettings(
@@ -154,9 +155,10 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
baseUrl: account.serverUrl,
);
await _reloadRepositories();
globalSettings.currentLoggedInUser = userId;
await globalSettings.save();
await _reloadRepositories();
emit(
AuthenticationState(
isAuthenticated: true,

View File

@@ -65,7 +65,7 @@ class _ServerConnectionPageState extends State<ServerConnectionPage> {
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
TextButton(
child: Text("Test connection"), //TODO: INTL
child: Text(S.of(context)!.testConnection),
onPressed: _updateReachability,
),
FilledButton(

View File

@@ -1,114 +0,0 @@
import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
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/bloc/paperless_server_information_cubit.dart';
import 'package:paperless_mobile/core/bloc/paperless_server_information_state.dart';
import 'package:paperless_mobile/core/config/hive/hive_config.dart';
import 'package:paperless_mobile/core/widgets/hint_card.dart';
import 'package:paperless_mobile/extensions/flutter_extensions.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/features/settings/view/widgets/global_settings_builder.dart';
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
import 'package:paperless_mobile/helpers/message_helpers.dart';
class AccountSettingsDialog extends StatelessWidget {
const AccountSettingsDialog({super.key});
@override
Widget build(BuildContext context) {
return GlobalSettingsBuilder(builder: (context, globalSettings) {
return AlertDialog(
insetPadding: EdgeInsets.symmetric(horizontal: 24, vertical: 32),
scrollable: true,
contentPadding: EdgeInsets.zero,
title: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(S.of(context)!.account),
const CloseButton(),
],
),
content: BlocBuilder<PaperlessServerInformationCubit, PaperlessServerInformationState>(
builder: (context, state) {
return Column(
children: [
ValueListenableBuilder(
valueListenable: Hive.box<UserAccount>(HiveBoxes.userAccount).listenable(),
builder: (context, box, _) {
// final currentUser = globalSettings.currentLoggedInUser;
final currentUser = null;
final accountIds =
box.keys.whereNot((element) => element == currentUser).toList();
final accounts = accountIds.map((id) => box.get(id)!).toList();
return ExpansionTile(
leading: CircleAvatar(
child: Text(state.information?.userInitials ?? ''),
),
title: Text(state.information?.username ?? ''),
subtitle: Text(state.information?.host ?? ''),
children:
accounts.map((account) => _buildAccountTile(account, true)).toList(),
);
},
),
ListTile(
dense: true,
leading: const Icon(Icons.person_add_rounded),
title: Text(S.of(context)!.addAnotherAccount),
onTap: () {},
),
const Divider(),
FilledButton(
style: ButtonStyle(
backgroundColor: MaterialStatePropertyAll(
Theme.of(context).colorScheme.error,
),
),
child: Text(
S.of(context)!.disconnect,
style: TextStyle(
color: Theme.of(context).colorScheme.onError,
),
),
onPressed: () async {
await _onLogout(context);
Navigator.of(context).maybePop();
},
).padded(16),
],
);
},
),
);
});
}
Future<void> _onLogout(BuildContext context) async {
try {
await context.read<AuthenticationCubit>().logout();
await HydratedBloc.storage.clear();
} on PaperlessServerException catch (error, stackTrace) {
showErrorMessage(context, error, stackTrace);
}
}
Widget _buildAccountTile(UserAccount account, bool isActive) {
return ListTile(
selected: isActive,
title: Text(account.username),
subtitle: Text(account.serverUrl),
leading: CircleAvatar(
child: Text((account.fullName ?? account.username)
.split(" ")
.take(2)
.map((e) => e.substring(0, 1))
.map((e) => e.toUpperCase())
.join(" ")),
),
);
}
}

View File

@@ -4,25 +4,19 @@ import 'package:paperless_mobile/core/widgets/dialog_utils/dialog_confirm_button
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
class SwitchAccountDialog extends StatelessWidget {
final String username;
final String serverUrl;
const SwitchAccountDialog({
super.key,
required this.username,
required this.serverUrl,
});
const SwitchAccountDialog({super.key});
@override
Widget build(BuildContext context) {
return AlertDialog(
title: Text("Switch account"),
content: Text("Do you want to switch to $serverUrl and log in as $username?"),
title: Text(S.of(context)!.switchAccountTitle),
content: Text(S.of(context)!.switchToNewAccount),
actions: [
const DialogCancelButton(),
DialogConfirmButton(
style: DialogConfirmButtonStyle.danger,
label: S.of(context)!.continueLabel, //TODO: INTL change labels
style: DialogConfirmButtonStyle.normal,
label: S.of(context)!.switchAccount,
),
DialogCancelButton(),
],
);
}

View File

@@ -42,7 +42,7 @@ class ManageAccountsPage extends StatelessWidget {
alignment: Alignment.centerLeft,
child: CloseButton(),
),
Center(child: Text("Accounts")),
Center(child: Text(S.of(context)!.accounts)),
],
), //TODO: INTL
shape: RoundedRectangleBorder(
@@ -65,7 +65,7 @@ class ManageAccountsPage extends StatelessWidget {
),
const Divider(),
ListTile(
title: const Text("Add account"),
title: Text(S.of(context)!.addAccount),
leading: const Icon(Icons.person_add),
onTap: () {
_onAddAccount(context);
@@ -113,17 +113,17 @@ class ManageAccountsPage extends StatelessWidget {
itemBuilder: (context) {
return [
if (!isLoggedIn)
const PopupMenuItem(
PopupMenuItem(
child: ListTile(
title: Text("Switch"), //TODO: INTL
title: Text(S.of(context)!.switchAccount),
leading: Icon(Icons.switch_account_rounded),
),
value: 0,
),
if (!isLoggedIn)
const PopupMenuItem(
PopupMenuItem(
child: ListTile(
title: Text("Remove"), // TODO: INTL
title: Text(S.of(context)!.remove),
leading: Icon(
Icons.person_remove,
color: Colors.red,
@@ -132,9 +132,9 @@ class ManageAccountsPage extends StatelessWidget {
value: 1,
)
else
const PopupMenuItem(
PopupMenuItem(
child: ListTile(
title: Text("Logout"), // TODO: INTL
title: Text(S.of(context)!.logout),
leading: Icon(
Icons.person_remove,
color: Colors.red,
@@ -177,12 +177,12 @@ class ManageAccountsPage extends StatelessWidget {
return child;
}
Future<void> _onAddAccount(BuildContext context) {
return Navigator.push(
Future<void> _onAddAccount(BuildContext context) async {
final userId = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => LoginPage(
titleString: "Add account", //TODO: INTL
titleString: S.of(context)!.addAccount,
onSubmit: (context, username, password, serverUrl, clientCertificate) async {
final userId = await context.read<AuthenticationCubit>().addAccount(
credentials: LoginFormCredentials(
@@ -194,19 +194,22 @@ class ManageAccountsPage extends StatelessWidget {
//TODO: Ask user whether to enable biometric authentication
enableBiometricAuthentication: false,
);
final shoudSwitch = await showDialog(
context: context,
builder: (context) =>
SwitchAccountDialog(username: username, serverUrl: serverUrl),
) ??
false;
if (shoudSwitch) {
context.read<AuthenticationCubit>().switchAccount(userId);
}
Navigator.of(context).pop<String?>(userId);
},
submitText: "Add account", //TODO: INTL
submitText: S.of(context)!.addAccount,
),
),
);
if (userId != null) {
final shoudSwitch = await showDialog<bool>(
context: context,
builder: (context) => const SwitchAccountDialog(),
) ??
false;
if (shoudSwitch) {
await context.read<AuthenticationCubit>().switchAccount(userId);
Navigator.pop(context);
}
}
}
}

View File

@@ -15,12 +15,8 @@ class ApplicationSettingsPage extends StatelessWidget {
actions: [
Padding(
padding: const EdgeInsets.all(16.0),
child: Tooltip(
triggerMode: TooltipTriggerMode.tap,
message: "These settings apply to all accounts", //TODO: INTL
child: Icon(Icons.info_outline),
),
),
child: const Icon(Icons.public),
)
],
),
body: ListView(

View File

@@ -13,12 +13,8 @@ class SecuritySettingsPage extends StatelessWidget {
actions: [
Padding(
padding: const EdgeInsets.all(16.0),
child: Tooltip(
triggerMode: TooltipTriggerMode.tap,
message: "These settings apply to the current user only", //TODO: INTL
child: Icon(Icons.info_outline),
),
),
child: const Icon(Icons.person_outline),
)
],
),
body: ListView(

View File

@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
class SwitchingAccountsPage extends StatelessWidget {
const SwitchingAccountsPage({super.key});
@@ -6,17 +7,19 @@ class SwitchingAccountsPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
return false;
},
onWillPop: () async => false,
child: Material(
child: Center(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
CircularProgressIndicator(),
Text("Switching accounts. Please wait..."),
const CircularProgressIndicator(),
const SizedBox(height: 16),
Text(
S.of(context)!.switchingAccountsPleaseWait,
style: Theme.of(context).textTheme.labelLarge,
),
],
),
),

View File

@@ -1,10 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:hive_flutter/adapters.dart';
import 'package:paperless_mobile/core/config/hive/hive_config.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/features/settings/view/widgets/user_settings_builder.dart';
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
import 'package:provider/provider.dart';