mirror of
https://github.com/Xevion/paperless-mobile.git
synced 2025-12-07 16:07:53 -06:00
WIP - started implementing quick search
This commit is contained in:
7
lib/constants.dart
Normal file
7
lib/constants.dart
Normal file
@@ -0,0 +1,7 @@
|
||||
import 'package:device_info_plus/device_info_plus.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
|
||||
// Globally accessible variables which are definitely initialized after main().
|
||||
late final PackageInfo packageInfo;
|
||||
late final AndroidDeviceInfo? androidInfo;
|
||||
late final IosDeviceInfo? iosInfo;
|
||||
@@ -8,7 +8,7 @@ import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/bloc/document_status_cubit.dart';
|
||||
import 'package:paperless_mobile/core/model/document_processing_status.dart';
|
||||
import 'package:paperless_mobile/features/login/model/authentication_information.dart';
|
||||
import 'package:paperless_mobile/util.dart';
|
||||
import 'package:paperless_mobile/constants.dart';
|
||||
import 'package:web_socket_channel/io.dart';
|
||||
|
||||
abstract class StatusService {
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:encrypted_shared_preferences/encrypted_shared_preferences.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:paperless_mobile/core/type/types.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/features/settings/model/application_settings_state.dart';
|
||||
|
||||
abstract class LocalVault {
|
||||
Future<void> storeAuthenticationInformation(AuthenticationInformation auth);
|
||||
Future<AuthenticationInformation?> loadAuthenticationInformation();
|
||||
Future<ClientCertificate?> loadCertificate();
|
||||
Future<bool> storeApplicationSettings(ApplicationSettingsState settings);
|
||||
Future<ApplicationSettingsState?> loadApplicationSettings();
|
||||
Future<void> clear();
|
||||
}
|
||||
|
||||
class LocalVaultImpl implements LocalVault {
|
||||
static const applicationSettingsKey = "applicationSettings";
|
||||
static const authenticationKey = "authentication";
|
||||
|
||||
final EncryptedSharedPreferences sharedPreferences;
|
||||
|
||||
LocalVaultImpl(this.sharedPreferences);
|
||||
|
||||
@override
|
||||
Future<void> storeAuthenticationInformation(
|
||||
AuthenticationInformation auth,
|
||||
) async {
|
||||
await sharedPreferences.setString(
|
||||
authenticationKey,
|
||||
jsonEncode(auth.toJson()),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<AuthenticationInformation?> loadAuthenticationInformation() async {
|
||||
if ((await sharedPreferences.getString(authenticationKey)).isEmpty) {
|
||||
return null;
|
||||
}
|
||||
return AuthenticationInformation.fromJson(
|
||||
jsonDecode(await sharedPreferences.getString(authenticationKey)),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<ClientCertificate?> loadCertificate() async {
|
||||
return loadAuthenticationInformation()
|
||||
.then((value) => value?.clientCertificate);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> storeApplicationSettings(ApplicationSettingsState settings) {
|
||||
return sharedPreferences.setString(
|
||||
applicationSettingsKey,
|
||||
jsonEncode(settings.toJson()),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<ApplicationSettingsState?> loadApplicationSettings() async {
|
||||
final settings = await sharedPreferences.getString(applicationSettingsKey);
|
||||
if (settings.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
return compute(
|
||||
ApplicationSettingsState.fromJson,
|
||||
jsonDecode(settings) as JSON,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> clear() {
|
||||
return sharedPreferences.clear();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:paperless_mobile/features/settings/model/color_scheme_option.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
|
||||
String translateColorSchemeOption(
|
||||
BuildContext context, ColorSchemeOption option) {
|
||||
switch (option) {
|
||||
case ColorSchemeOption.classic:
|
||||
return S.of(context).colorSchemeOptionClassic;
|
||||
case ColorSchemeOption.dynamic:
|
||||
return S.of(context).colorSchemeOptionDynamic;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'package:hydrated_bloc/hydrated_bloc.dart';
|
||||
import 'package:paperless_mobile/features/login/bloc/authentication_state.dart';
|
||||
import 'package:paperless_mobile/features/settings/model/application_settings_state.dart';
|
||||
import 'package:paperless_mobile/features/settings/bloc/application_settings_state.dart';
|
||||
|
||||
extension AddressableHydratedStorage on Storage {
|
||||
ApplicationSettingsState get settings {
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import 'dart:developer';
|
||||
import 'dart:io';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/service/file_service.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
@@ -49,14 +51,18 @@ class DocumentDetailsCubit extends Cubit<DocumentDetailsState> {
|
||||
}
|
||||
|
||||
Future<ResultType> openDocumentInSystemViewer() async {
|
||||
final downloadDir = await FileService.temporaryDirectory;
|
||||
final cacheDir = await FileService.temporaryDirectory;
|
||||
|
||||
final metaData = await _api.getMetaData(state.document);
|
||||
final docBytes = await _api.download(state.document);
|
||||
File f = File('${downloadDir.path}/${metaData.mediaFilename}');
|
||||
f.createSync(recursive: true);
|
||||
f.writeAsBytesSync(docBytes);
|
||||
return OpenFilex.open(f.path, type: "application/pdf")
|
||||
.then((value) => value.type);
|
||||
final bytes = await _api.download(state.document);
|
||||
|
||||
final file = File('${cacheDir.path}/${metaData.mediaFilename}')
|
||||
..createSync(recursive: true)
|
||||
..writeAsBytesSync(bytes);
|
||||
|
||||
return OpenFilex.open(file.path, type: "application/pdf").then(
|
||||
(value) => value.type,
|
||||
);
|
||||
}
|
||||
|
||||
void replaceDocument(DocumentModel document) {
|
||||
|
||||
@@ -26,6 +26,7 @@ import 'package:paperless_mobile/features/labels/tags/view/widgets/tags_widget.d
|
||||
import 'package:paperless_mobile/features/labels/view/widgets/label_text.dart';
|
||||
import 'package:paperless_mobile/features/similar_documents/cubit/similar_documents_cubit.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
import 'package:paperless_mobile/helpers/format_helpers.dart';
|
||||
import 'package:paperless_mobile/helpers/message_helpers.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
@@ -556,15 +557,6 @@ class _DocumentDetailsPageState extends State<DocumentDetailsPage> {
|
||||
);
|
||||
}
|
||||
|
||||
static String formatBytes(int bytes, int decimals) {
|
||||
if (bytes <= 0) return "0 B";
|
||||
const suffixes = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
|
||||
var i = (log(bytes) / log(1024)).floor();
|
||||
return ((bytes / pow(1024, i)).toStringAsFixed(decimals)) +
|
||||
' ' +
|
||||
suffixes[i];
|
||||
}
|
||||
|
||||
Widget _buildSimilarDocumentsView() {
|
||||
return const SimilarDocumentsView();
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import 'package:paperless_mobile/features/documents/view/widgets/documents_empty
|
||||
import 'package:paperless_mobile/features/documents/view/widgets/list/document_list_item.dart';
|
||||
import 'package:paperless_mobile/features/similar_documents/cubit/similar_documents_cubit.dart';
|
||||
import 'package:paperless_mobile/helpers/message_helpers.dart';
|
||||
import 'package:paperless_mobile/util.dart';
|
||||
import 'package:paperless_mobile/constants.dart';
|
||||
|
||||
class SimilarDocumentsView extends StatefulWidget {
|
||||
const SimilarDocumentsView({super.key});
|
||||
|
||||
@@ -6,7 +6,7 @@ import 'package:paperless_mobile/core/service/file_service.dart';
|
||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
import 'package:paperless_mobile/helpers/message_helpers.dart';
|
||||
import 'package:paperless_mobile/util.dart';
|
||||
import 'package:paperless_mobile/constants.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class DocumentDownloadButton extends StatefulWidget {
|
||||
@@ -48,20 +48,24 @@ class _DocumentDownloadButtonState extends State<DocumentDownloadButton> {
|
||||
return;
|
||||
}
|
||||
setState(() => _isDownloadPending = true);
|
||||
final service = context.read<PaperlessDocumentsApi>();
|
||||
try {
|
||||
final bytes =
|
||||
await context.read<PaperlessDocumentsApi>().download(document);
|
||||
final bytes = await service.download(document);
|
||||
final meta = await service.getMetaData(document);
|
||||
final Directory dir = await FileService.downloadsDirectory;
|
||||
String filePath = "${dir.path}/${document.originalFileName}";
|
||||
//TODO: Add replacement mechanism here (ask user if file should be replaced if exists)
|
||||
await File(filePath).writeAsBytes(bytes);
|
||||
String filePath = "${dir.path}/${meta.mediaFilename}";
|
||||
final createdFile = File(filePath);
|
||||
createdFile.createSync(recursive: true);
|
||||
createdFile.writeAsBytesSync(bytes);
|
||||
showSnackBar(context, S.of(context).documentDownloadSuccessMessage);
|
||||
} on PaperlessServerException catch (error, stackTrace) {
|
||||
showErrorMessage(context, error, stackTrace);
|
||||
} catch (error) {
|
||||
showGenericError(context, error);
|
||||
} finally {
|
||||
setState(() => _isDownloadPending = false);
|
||||
if (mounted) {
|
||||
setState(() => _isDownloadPending = false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:hydrated_bloc/hydrated_bloc.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_api/src/modules/documents_api/paperless_documents_api.dart';
|
||||
import 'package:paperless_mobile/features/paged_document_view/documents_paging_mixin.dart';
|
||||
|
||||
import 'document_search_state.dart';
|
||||
|
||||
class DocumentSearchCubit extends HydratedCubit<DocumentSearchState>
|
||||
with DocumentsPagingMixin {
|
||||
DocumentSearchCubit(this.api) : super(const DocumentSearchState());
|
||||
|
||||
@override
|
||||
final PaperlessDocumentsApi api;
|
||||
|
||||
Future<void> updateResults(String query) async {
|
||||
await updateFilter(
|
||||
filter: state.filter.copyWith(query: TextQuery.titleAndContent(query)),
|
||||
);
|
||||
emit(state.copyWith(searchHistory: [query, ...state.searchHistory]));
|
||||
}
|
||||
|
||||
Future<void> updateSuggestions(String query) async {
|
||||
final suggestions = await api.autocomplete(query);
|
||||
emit(state.copyWith(suggestions: suggestions));
|
||||
}
|
||||
|
||||
@override
|
||||
DocumentSearchState? fromJson(Map<String, dynamic> json) {
|
||||
return DocumentSearchState.fromJson(json);
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, dynamic>? toJson(DocumentSearchState state) {
|
||||
return state.toJson();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/features/paged_document_view/model/documents_paged_state.dart';
|
||||
|
||||
part 'document_search_state.g.dart';
|
||||
|
||||
|
||||
|
||||
@JsonSerializable(ignoreUnannotated: true)
|
||||
class DocumentSearchState extends DocumentsPagedState {
|
||||
@JsonKey()
|
||||
final List<String> searchHistory;
|
||||
|
||||
final List<String> suggestions;
|
||||
|
||||
const DocumentSearchState({
|
||||
this.searchHistory = const [],
|
||||
this.suggestions = const [],
|
||||
super.filter,
|
||||
super.hasLoaded,
|
||||
super.isLoading,
|
||||
super.value,
|
||||
});
|
||||
|
||||
@override
|
||||
List<Object> get props => [
|
||||
hasLoaded,
|
||||
isLoading,
|
||||
filter,
|
||||
value,
|
||||
searchHistory,
|
||||
suggestions,
|
||||
];
|
||||
|
||||
@override
|
||||
DocumentSearchState copyWithPaged({
|
||||
bool? hasLoaded,
|
||||
bool? isLoading,
|
||||
List<PagedSearchResult<DocumentModel>>? value,
|
||||
DocumentFilter? filter,
|
||||
}) {
|
||||
return copyWith(
|
||||
hasLoaded: hasLoaded,
|
||||
isLoading: isLoading,
|
||||
filter: filter,
|
||||
value: value,
|
||||
);
|
||||
}
|
||||
|
||||
DocumentSearchState copyWith({
|
||||
List<String>? searchHistory,
|
||||
bool? hasLoaded,
|
||||
bool? isLoading,
|
||||
List<PagedSearchResult<DocumentModel>>? value,
|
||||
DocumentFilter? filter,
|
||||
List<String>? suggestions,
|
||||
}) {
|
||||
return DocumentSearchState(
|
||||
value: value ?? this.value,
|
||||
filter: filter ?? this.filter,
|
||||
hasLoaded: hasLoaded ?? this.hasLoaded,
|
||||
isLoading: isLoading ?? this.isLoading,
|
||||
searchHistory: searchHistory ?? this.searchHistory,
|
||||
suggestions: suggestions ?? this.suggestions,
|
||||
);
|
||||
}
|
||||
|
||||
factory DocumentSearchState.fromJson(Map<String, dynamic> json) =>
|
||||
_$DocumentSearchStateFromJson(json);
|
||||
|
||||
Map<String, dynamic> toJson() => _$DocumentSearchStateToJson(this);
|
||||
}
|
||||
|
||||
class
|
||||
106
lib/features/document_search/document_search_delegate.dart
Normal file
106
lib/features/document_search/document_search_delegate.dart
Normal file
@@ -0,0 +1,106 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/repository/provider/label_repositories_provider.dart';
|
||||
import 'package:paperless_mobile/core/widgets/documents_list_loading_widget.dart';
|
||||
import 'package:paperless_mobile/features/document_details/bloc/document_details_cubit.dart';
|
||||
import 'package:paperless_mobile/features/document_details/view/pages/document_details_page.dart';
|
||||
import 'package:paperless_mobile/features/document_search/cubit/document_search_cubit.dart';
|
||||
import 'package:paperless_mobile/features/document_search/cubit/document_search_state.dart';
|
||||
import 'package:paperless_mobile/features/documents/view/widgets/list/document_list_item.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class DocumentSearchDelegate extends SearchDelegate<DocumentModel> {
|
||||
DocumentSearchDelegate({
|
||||
required String hintText,
|
||||
required super.searchFieldStyle,
|
||||
}) : super(
|
||||
searchFieldLabel: hintText,
|
||||
keyboardType: TextInputType.text,
|
||||
textInputAction: TextInputAction.search,
|
||||
);
|
||||
|
||||
@override
|
||||
Widget buildLeading(BuildContext context) => const BackButton();
|
||||
|
||||
@override
|
||||
Widget buildSuggestions(BuildContext context) {
|
||||
BlocBuilder<DocumentSearchCubit, DocumentSearchState>(
|
||||
builder: (context, state) {
|
||||
if (!state.hasLoaded && state.isLoading) {
|
||||
return const DocumentsListLoadingWidget();
|
||||
}
|
||||
return ListView.builder(itemBuilder: (context, index) => ListTile(
|
||||
title: Text(snapshot.data![index]),
|
||||
onTap: () {
|
||||
query = snapshot.data![index];
|
||||
super.showResults(context);
|
||||
},
|
||||
),);
|
||||
},
|
||||
)
|
||||
return FutureBuilder(
|
||||
future: context.read<PaperlessDocumentsApi>().autocomplete(query),
|
||||
builder: (context, snapshot) {
|
||||
if (!snapshot.hasData) {
|
||||
return const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
);
|
||||
}
|
||||
return ListView.builder(
|
||||
itemCount: snapshot.data!.length,
|
||||
itemBuilder: (context, index) => ListTile(
|
||||
title: Text(snapshot.data![index]),
|
||||
onTap: () {
|
||||
query = snapshot.data![index];
|
||||
super.showResults(context);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget buildResults(BuildContext context) {
|
||||
return FutureBuilder(
|
||||
future: context
|
||||
.read<PaperlessDocumentsApi>()
|
||||
.findAll(DocumentFilter(query: TextQuery.titleAndContent(query))),
|
||||
builder: (context, snapshot) {
|
||||
if (!snapshot.hasData) {
|
||||
return const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
);
|
||||
}
|
||||
final documents = snapshot.data!.results;
|
||||
return ListView.builder(
|
||||
itemBuilder: (context, index) => DocumentListItem(
|
||||
document: documents[index],
|
||||
onTap: (document) {
|
||||
Navigator.push<DocumentModel?>(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => BlocProvider(
|
||||
create: (context) => DocumentDetailsCubit(
|
||||
context.read<PaperlessDocumentsApi>(),
|
||||
document,
|
||||
),
|
||||
child: const LabelRepositoriesProvider(
|
||||
child: DocumentDetailsPage(
|
||||
isLabelClickable: false,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
List<Widget> buildActions(BuildContext context) => <Widget>[];
|
||||
}
|
||||
@@ -8,7 +8,6 @@ import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||
import 'package:paperless_mobile/core/repository/state/impl/correspondent_repository_state.dart';
|
||||
import 'package:paperless_mobile/core/repository/state/impl/document_type_repository_state.dart';
|
||||
import 'package:paperless_mobile/core/repository/state/impl/tag_repository_state.dart';
|
||||
import 'package:paperless_mobile/core/store/local_vault.dart';
|
||||
|
||||
part 'document_upload_state.dart';
|
||||
|
||||
@@ -24,7 +23,6 @@ class DocumentUploadCubit extends Cubit<DocumentUploadState> {
|
||||
final List<StreamSubscription> _subs = [];
|
||||
|
||||
DocumentUploadCubit({
|
||||
required LocalVault localVault,
|
||||
required PaperlessDocumentsApi documentApi,
|
||||
required LabelRepository<Tag, TagRepositoryState> tagRepository,
|
||||
required LabelRepository<Correspondent, CorrespondentRepositoryState>
|
||||
|
||||
@@ -20,7 +20,7 @@ import 'package:paperless_mobile/features/labels/tags/view/widgets/tags_form_fie
|
||||
import 'package:paperless_mobile/features/labels/view/widgets/label_form_field.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
import 'package:paperless_mobile/helpers/message_helpers.dart';
|
||||
import 'package:paperless_mobile/util.dart';
|
||||
import 'package:paperless_mobile/constants.dart';
|
||||
|
||||
class DocumentUploadPreparationPage extends StatefulWidget {
|
||||
final Uint8List fileBytes;
|
||||
|
||||
@@ -21,7 +21,7 @@ import 'package:paperless_mobile/features/labels/tags/view/widgets/tags_form_fie
|
||||
import 'package:paperless_mobile/features/labels/view/widgets/label_form_field.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
import 'package:paperless_mobile/helpers/message_helpers.dart';
|
||||
import 'package:paperless_mobile/util.dart';
|
||||
import 'package:paperless_mobile/constants.dart';
|
||||
|
||||
class DocumentEditPage extends StatefulWidget {
|
||||
final FieldSuggestions suggestions;
|
||||
|
||||
@@ -8,6 +8,7 @@ import 'package:paperless_mobile/core/repository/provider/label_repositories_pro
|
||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/features/document_details/bloc/document_details_cubit.dart';
|
||||
import 'package:paperless_mobile/features/document_details/view/pages/document_details_page.dart';
|
||||
import 'package:paperless_mobile/features/document_search/document_search_delegate.dart';
|
||||
import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart';
|
||||
import 'package:paperless_mobile/features/documents/bloc/documents_state.dart';
|
||||
import 'package:paperless_mobile/features/documents/view/widgets/documents_empty_state.dart';
|
||||
@@ -22,12 +23,12 @@ import 'package:paperless_mobile/features/login/bloc/authentication_cubit.dart';
|
||||
import 'package:paperless_mobile/features/saved_view/cubit/saved_view_cubit.dart';
|
||||
import 'package:paperless_mobile/features/saved_view/view/saved_view_selection_widget.dart';
|
||||
import 'package:paperless_mobile/features/settings/bloc/application_settings_cubit.dart';
|
||||
import 'package:paperless_mobile/features/settings/model/application_settings_state.dart';
|
||||
import 'package:paperless_mobile/features/settings/bloc/application_settings_state.dart';
|
||||
import 'package:paperless_mobile/features/settings/model/view_type.dart';
|
||||
import 'package:paperless_mobile/features/tasks/cubit/task_status_cubit.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
import 'package:paperless_mobile/helpers/message_helpers.dart';
|
||||
import 'package:paperless_mobile/util.dart';
|
||||
import 'package:paperless_mobile/constants.dart';
|
||||
|
||||
class DocumentFilterIntent {
|
||||
final DocumentFilter? filter;
|
||||
@@ -148,9 +149,42 @@ class _DocumentsPageState extends State<DocumentsPage> {
|
||||
builder: (context, state) {
|
||||
if (state.selection.isEmpty) {
|
||||
return AppBar(
|
||||
title: Text(
|
||||
"${S.of(context).documentsPageTitle} (${_formatDocumentCount(state.count)})",
|
||||
title: TextField(
|
||||
onTap: () => showSearch(
|
||||
context: context,
|
||||
delegate: DocumentSearchDelegate(
|
||||
searchFieldStyle:
|
||||
Theme.of(context).textTheme.bodyLarge,
|
||||
hintText: "Search your documents",
|
||||
),
|
||||
),
|
||||
readOnly: true,
|
||||
decoration: InputDecoration(
|
||||
hintText: "Search your documents",
|
||||
hintStyle: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyLarge
|
||||
?.copyWith(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onSurfaceVariant),
|
||||
filled: true,
|
||||
fillColor: Theme.of(context).colorScheme.surface,
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(32),
|
||||
borderSide: BorderSide.none,
|
||||
),
|
||||
prefixIcon: IconButton(
|
||||
icon: const Icon(Icons.menu),
|
||||
onPressed: () {
|
||||
Scaffold.of(context).openDrawer();
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
// title: Text(
|
||||
// "${S.of(context).documentsPageTitle} (${_formatDocumentCount(state.count)})",
|
||||
// ),
|
||||
actions: [
|
||||
const SortDocumentsButton(),
|
||||
BlocBuilder<ApplicationSettingsCubit,
|
||||
@@ -182,6 +216,7 @@ class _DocumentsPageState extends State<DocumentsPage> {
|
||||
? const LinearProgressIndicator()
|
||||
: const SizedBox(height: 4.0),
|
||||
),
|
||||
automaticallyImplyLeading: false,
|
||||
);
|
||||
} else {
|
||||
return AppBar(
|
||||
|
||||
@@ -10,7 +10,7 @@ import 'package:paperless_mobile/features/edit_label/cubit/edit_label_cubit.dart
|
||||
import 'package:paperless_mobile/features/edit_label/view/label_form.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
import 'package:paperless_mobile/helpers/message_helpers.dart';
|
||||
import 'package:paperless_mobile/util.dart';
|
||||
import 'package:paperless_mobile/constants.dart';
|
||||
|
||||
class EditLabelPage<T extends Label> extends StatelessWidget {
|
||||
final T label;
|
||||
|
||||
@@ -7,7 +7,7 @@ import 'package:paperless_mobile/core/type/types.dart';
|
||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
import 'package:paperless_mobile/helpers/message_helpers.dart';
|
||||
import 'package:paperless_mobile/util.dart';
|
||||
import 'package:paperless_mobile/constants.dart';
|
||||
|
||||
class SubmitButtonConfig<T extends Label> {
|
||||
final Widget icon;
|
||||
|
||||
@@ -109,7 +109,6 @@ class _HomePageState extends State<HomePage> {
|
||||
MaterialPageRoute(
|
||||
builder: (context) => BlocProvider.value(
|
||||
value: DocumentUploadCubit(
|
||||
localVault: context.read(),
|
||||
documentApi: context.read(),
|
||||
tagRepository: context.read(),
|
||||
correspondentRepository: context.read(),
|
||||
|
||||
@@ -4,11 +4,13 @@ class RouteDescription {
|
||||
final String label;
|
||||
final Icon icon;
|
||||
final Icon selectedIcon;
|
||||
final Widget Function(Widget icon)? badgeBuilder;
|
||||
|
||||
RouteDescription({
|
||||
required this.label,
|
||||
required this.icon,
|
||||
required this.selectedIcon,
|
||||
this.badgeBuilder,
|
||||
});
|
||||
|
||||
NavigationDestination toNavigationDestination() {
|
||||
@@ -30,8 +32,8 @@ class RouteDescription {
|
||||
BottomNavigationBarItem toBottomNavigationBarItem() {
|
||||
return BottomNavigationBarItem(
|
||||
label: label,
|
||||
icon: icon,
|
||||
activeIcon: selectedIcon,
|
||||
icon: badgeBuilder?.call(icon) ?? icon,
|
||||
activeIcon: badgeBuilder?.call(selectedIcon) ?? selectedIcon,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ import 'package:paperless_mobile/core/repository/state/impl/correspondent_reposi
|
||||
import 'package:paperless_mobile/core/repository/state/impl/document_type_repository_state.dart';
|
||||
import 'package:paperless_mobile/core/repository/state/impl/storage_path_repository_state.dart';
|
||||
import 'package:paperless_mobile/core/repository/state/impl/tag_repository_state.dart';
|
||||
import 'package:paperless_mobile/core/store/local_vault.dart';
|
||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/features/inbox/bloc/inbox_cubit.dart';
|
||||
import 'package:paperless_mobile/features/inbox/view/pages/inbox_page.dart';
|
||||
@@ -21,7 +20,7 @@ import 'package:paperless_mobile/features/settings/bloc/application_settings_cub
|
||||
import 'package:paperless_mobile/features/settings/view/settings_page.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
import 'package:paperless_mobile/helpers/message_helpers.dart';
|
||||
import 'package:paperless_mobile/util.dart';
|
||||
import 'package:paperless_mobile/constants.dart';
|
||||
import 'package:url_launcher/link.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
|
||||
@@ -43,12 +42,9 @@ class AppDrawer extends StatefulWidget {
|
||||
// }
|
||||
|
||||
class _AppDrawerState extends State<AppDrawer> {
|
||||
late final Future<PackageInfo> _packageInfo;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_packageInfo = PackageInfo.fromPlatform();
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -120,162 +116,150 @@ class _AppDrawerState extends State<AppDrawer> {
|
||||
bottomRight: Radius.circular(16.0),
|
||||
),
|
||||
),
|
||||
child: Theme(
|
||||
data: Theme.of(context).copyWith(
|
||||
listTileTheme: ListTileThemeData(
|
||||
tileColor: Colors.transparent,
|
||||
),
|
||||
),
|
||||
child: ListView(
|
||||
children: [
|
||||
DrawerHeader(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 8,
|
||||
left: 8,
|
||||
bottom: 0,
|
||||
right: 8,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Image.asset(
|
||||
'assets/logos/paperless_logo_white.png',
|
||||
height: 32,
|
||||
width: 32,
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onPrimaryContainer,
|
||||
).paddedOnly(right: 8.0),
|
||||
Text(
|
||||
S.of(context).appTitleText,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.headlineSmall
|
||||
?.copyWith(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onPrimaryContainer,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Align(
|
||||
alignment: Alignment.bottomRight,
|
||||
child: BlocBuilder<PaperlessServerInformationCubit,
|
||||
PaperlessServerInformationState>(
|
||||
builder: (context, state) {
|
||||
if (!state.isLoaded) {
|
||||
return Container();
|
||||
}
|
||||
final info = state.information!;
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
ListTile(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
dense: true,
|
||||
title: Text(
|
||||
S
|
||||
.of(context)
|
||||
.appDrawerHeaderLoggedInAsText +
|
||||
(info.username ?? '?'),
|
||||
style:
|
||||
Theme.of(context).textTheme.bodyMedium,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
textAlign: TextAlign.end,
|
||||
maxLines: 1,
|
||||
),
|
||||
subtitle: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Text(
|
||||
state.information!.host ?? '',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyMedium,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
textAlign: TextAlign.end,
|
||||
maxLines: 1,
|
||||
),
|
||||
Text(
|
||||
'${S.of(context).serverInformationPaperlessVersionText} ${info.version} (API v${info.apiVersion})',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodySmall,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
textAlign: TextAlign.end,
|
||||
maxLines: 1,
|
||||
),
|
||||
],
|
||||
),
|
||||
isThreeLine: true,
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
child: ListView(
|
||||
children: [
|
||||
DrawerHeader(
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.secondaryContainer,
|
||||
),
|
||||
padding: const EdgeInsets.only(
|
||||
top: 8,
|
||||
left: 8,
|
||||
bottom: 0,
|
||||
right: 8,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Image.asset(
|
||||
'assets/logos/paperless_logo_white.png',
|
||||
height: 32,
|
||||
width: 32,
|
||||
color:
|
||||
Theme.of(context).colorScheme.onPrimaryContainer,
|
||||
).paddedOnly(right: 8.0),
|
||||
Text(
|
||||
S.of(context).appTitleText,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.headlineSmall
|
||||
?.copyWith(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onPrimaryContainer,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Align(
|
||||
alignment: Alignment.bottomRight,
|
||||
child: BlocBuilder<PaperlessServerInformationCubit,
|
||||
PaperlessServerInformationState>(
|
||||
builder: (context, state) {
|
||||
if (!state.isLoaded) {
|
||||
return Container();
|
||||
}
|
||||
final info = state.information!;
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
ListTile(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
dense: true,
|
||||
title: Text(
|
||||
S.of(context).appDrawerHeaderLoggedInAsText +
|
||||
(info.username ?? '?'),
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
textAlign: TextAlign.end,
|
||||
maxLines: 1,
|
||||
),
|
||||
subtitle: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Text(
|
||||
state.information!.host ?? '',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyMedium,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
textAlign: TextAlign.end,
|
||||
maxLines: 1,
|
||||
),
|
||||
Text(
|
||||
'${S.of(context).serverInformationPaperlessVersionText} ${info.version} (API v${info.apiVersion})',
|
||||
style:
|
||||
Theme.of(context).textTheme.bodySmall,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
textAlign: TextAlign.end,
|
||||
maxLines: 1,
|
||||
),
|
||||
],
|
||||
),
|
||||
isThreeLine: true,
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
...[
|
||||
ListTile(
|
||||
title: Text(S.of(context).bottomNavInboxPageLabel),
|
||||
leading: const Icon(Icons.inbox),
|
||||
onTap: () => _onOpenInbox(),
|
||||
shape: listtTileShape,
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.settings),
|
||||
shape: listtTileShape,
|
||||
title: Text(
|
||||
S.of(context).appDrawerSettingsLabel,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.primaryContainer,
|
||||
onTap: () => Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => BlocProvider.value(
|
||||
value: context.read<ApplicationSettingsCubit>(),
|
||||
child: const SettingsPage(),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
...[
|
||||
ListTile(
|
||||
title: Text(S.of(context).bottomNavInboxPageLabel),
|
||||
leading: const Icon(Icons.inbox),
|
||||
onTap: () => _onOpenInbox(),
|
||||
shape: listtTileShape,
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.settings),
|
||||
shape: listtTileShape,
|
||||
title: Text(
|
||||
S.of(context).appDrawerSettingsLabel,
|
||||
),
|
||||
onTap: () => Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => BlocProvider.value(
|
||||
value: context.read<ApplicationSettingsCubit>(),
|
||||
child: const SettingsPage(),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const Divider(
|
||||
indent: 16,
|
||||
endIndent: 16,
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.bug_report),
|
||||
title: Text(S.of(context).appDrawerReportBugLabel),
|
||||
onTap: () {
|
||||
launchUrlString(
|
||||
'https://github.com/astubenbord/paperless-mobile/issues/new');
|
||||
},
|
||||
shape: listtTileShape,
|
||||
),
|
||||
ListTile(
|
||||
title: Text(S.of(context).appDrawerAboutLabel),
|
||||
leading: Icon(Icons.info_outline_rounded),
|
||||
onTap: _onShowAboutDialog,
|
||||
shape: listtTileShape,
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.logout),
|
||||
title: Text(S.of(context).appDrawerLogoutLabel),
|
||||
shape: listtTileShape,
|
||||
onTap: () {
|
||||
_onLogout();
|
||||
},
|
||||
)
|
||||
],
|
||||
const Divider(
|
||||
indent: 16,
|
||||
endIndent: 16,
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.bug_report),
|
||||
title: Text(S.of(context).appDrawerReportBugLabel),
|
||||
onTap: () {
|
||||
launchUrlString(
|
||||
'https://github.com/astubenbord/paperless-mobile/issues/new');
|
||||
},
|
||||
shape: listtTileShape,
|
||||
),
|
||||
ListTile(
|
||||
title: Text(S.of(context).appDrawerAboutLabel),
|
||||
leading: Icon(Icons.info_outline_rounded),
|
||||
onTap: _onShowAboutDialog,
|
||||
shape: listtTileShape,
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.logout),
|
||||
title: Text(S.of(context).appDrawerLogoutLabel),
|
||||
shape: listtTileShape,
|
||||
onTap: () {
|
||||
_onLogout();
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -285,7 +269,6 @@ class _AppDrawerState extends State<AppDrawer> {
|
||||
void _onLogout() async {
|
||||
try {
|
||||
await context.read<AuthenticationCubit>().logout();
|
||||
await context.read<LocalVault>().clear();
|
||||
await context.read<ApplicationSettingsCubit>().clear();
|
||||
await context.read<LabelRepository<Tag, TagRepositoryState>>().clear();
|
||||
await context
|
||||
@@ -354,15 +337,14 @@ class _AppDrawerState extends State<AppDrawer> {
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onShowAboutDialog() async {
|
||||
final snapshot = await _packageInfo;
|
||||
void _onShowAboutDialog() {
|
||||
showAboutDialog(
|
||||
context: context,
|
||||
applicationIcon: const ImageIcon(
|
||||
AssetImage('assets/logos/paperless_logo_green.png'),
|
||||
),
|
||||
applicationName: 'Paperless Mobile',
|
||||
applicationVersion: snapshot.version + '+' + snapshot.buildNumber,
|
||||
applicationVersion: packageInfo.version + '+' + packageInfo.buildNumber,
|
||||
children: [
|
||||
Text(S.of(context).aboutDialogDevelopedByText('Anton Stubenbord')),
|
||||
Link(
|
||||
|
||||
@@ -14,7 +14,7 @@ import 'package:paperless_mobile/features/inbox/view/widgets/inbox_empty_widget.
|
||||
import 'package:paperless_mobile/features/inbox/view/widgets/inbox_item.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
import 'package:paperless_mobile/helpers/message_helpers.dart';
|
||||
import 'package:paperless_mobile/util.dart';
|
||||
import 'package:paperless_mobile/constants.dart';
|
||||
|
||||
class InboxPage extends StatefulWidget {
|
||||
const InboxPage({super.key});
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
import 'package:local_auth/local_auth.dart';
|
||||
import 'package:paperless_mobile/core/store/local_vault.dart';
|
||||
|
||||
class LocalAuthenticationService {
|
||||
final LocalVault localStore;
|
||||
final LocalAuthentication localAuthentication;
|
||||
|
||||
LocalAuthenticationService(
|
||||
this.localStore,
|
||||
this.localAuthentication,
|
||||
);
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import 'package:paperless_mobile/features/login/view/widgets/form_fields/user_cr
|
||||
import 'package:paperless_mobile/features/login/view/widgets/login_pages/server_connection_page.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
import 'package:paperless_mobile/helpers/message_helpers.dart';
|
||||
import 'package:paperless_mobile/util.dart';
|
||||
import 'package:paperless_mobile/constants.dart';
|
||||
|
||||
import 'widgets/never_scrollable_scroll_behavior.dart';
|
||||
import 'widgets/login_pages/server_login_page.dart';
|
||||
|
||||
@@ -6,7 +6,7 @@ import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/features/login/model/client_certificate.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
import 'package:paperless_mobile/util.dart';
|
||||
import 'package:paperless_mobile/constants.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
|
||||
import 'obscured_input_text_form_field.dart';
|
||||
|
||||
@@ -13,7 +13,7 @@ import 'package:paperless_mobile/features/saved_view/cubit/saved_view_state.dart
|
||||
import 'package:paperless_mobile/features/saved_view/view/add_saved_view_page.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
import 'package:paperless_mobile/helpers/message_helpers.dart';
|
||||
import 'package:paperless_mobile/util.dart';
|
||||
import 'package:paperless_mobile/constants.dart';
|
||||
import 'package:shimmer/shimmer.dart';
|
||||
|
||||
class SavedViewSelectionWidget extends StatelessWidget {
|
||||
|
||||
@@ -16,7 +16,6 @@ import 'package:paperless_mobile/core/repository/state/impl/correspondent_reposi
|
||||
import 'package:paperless_mobile/core/repository/state/impl/document_type_repository_state.dart';
|
||||
import 'package:paperless_mobile/core/repository/state/impl/tag_repository_state.dart';
|
||||
import 'package:paperless_mobile/core/service/file_service.dart';
|
||||
import 'package:paperless_mobile/core/store/local_vault.dart';
|
||||
import 'package:paperless_mobile/core/widgets/offline_banner.dart';
|
||||
import 'package:paperless_mobile/features/document_upload/cubit/document_upload_cubit.dart';
|
||||
import 'package:paperless_mobile/features/document_upload/view/document_upload_preparation_page.dart';
|
||||
@@ -148,7 +147,6 @@ class _ScannerPageState extends State<ScannerPage>
|
||||
builder: (_) => LabelRepositoriesProvider(
|
||||
child: BlocProvider(
|
||||
create: (context) => DocumentUploadCubit(
|
||||
localVault: context.read<LocalVault>(),
|
||||
documentApi: context.read<PaperlessDocumentsApi>(),
|
||||
correspondentRepository: context.read<
|
||||
LabelRepository<Correspondent,
|
||||
@@ -266,7 +264,6 @@ class _ScannerPageState extends State<ScannerPage>
|
||||
builder: (_) => LabelRepositoriesProvider(
|
||||
child: BlocProvider(
|
||||
create: (context) => DocumentUploadCubit(
|
||||
localVault: context.read<LocalVault>(),
|
||||
documentApi: context.read<PaperlessDocumentsApi>(),
|
||||
correspondentRepository: context.read<
|
||||
LabelRepository<Correspondent,
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hydrated_bloc/hydrated_bloc.dart';
|
||||
import 'package:paperless_mobile/features/login/services/authentication_service.dart';
|
||||
import 'package:paperless_mobile/features/settings/model/application_settings_state.dart';
|
||||
import 'package:paperless_mobile/features/settings/bloc/application_settings_state.dart';
|
||||
import 'package:paperless_mobile/features/settings/model/color_scheme_option.dart';
|
||||
import 'package:paperless_mobile/features/settings/model/view_type.dart';
|
||||
|
||||
class ApplicationSettingsCubit extends HydratedCubit<ApplicationSettingsState> {
|
||||
@@ -27,17 +28,23 @@ class ApplicationSettingsCubit extends HydratedCubit<ApplicationSettingsState> {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> setThemeMode(ThemeMode? selectedMode) async {
|
||||
void setThemeMode(ThemeMode? selectedMode) {
|
||||
final updatedSettings = state.copyWith(preferredThemeMode: selectedMode);
|
||||
_updateSettings(updatedSettings);
|
||||
}
|
||||
|
||||
Future<void> setViewType(ViewType viewType) async {
|
||||
void setViewType(ViewType viewType) {
|
||||
final updatedSettings = state.copyWith(preferredViewType: viewType);
|
||||
_updateSettings(updatedSettings);
|
||||
}
|
||||
|
||||
Future<void> _updateSettings(ApplicationSettingsState settings) async {
|
||||
void setColorSchemeOption(ColorSchemeOption schemeOption) {
|
||||
final updatedSettings =
|
||||
state.copyWith(preferredColorSchemeOption: schemeOption);
|
||||
_updateSettings(updatedSettings);
|
||||
}
|
||||
|
||||
void _updateSettings(ApplicationSettingsState settings) async {
|
||||
emit(settings);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
import 'package:paperless_mobile/core/type/types.dart';
|
||||
import 'package:paperless_mobile/features/settings/model/color_scheme_option.dart';
|
||||
import 'package:paperless_mobile/features/settings/model/view_type.dart';
|
||||
|
||||
part 'application_settings_state.g.dart';
|
||||
@@ -13,22 +13,21 @@ part 'application_settings_state.g.dart';
|
||||
@JsonSerializable()
|
||||
class ApplicationSettingsState {
|
||||
static final defaultSettings = ApplicationSettingsState(
|
||||
isLocalAuthenticationEnabled: false,
|
||||
preferredLocaleSubtag: Platform.localeName.split('_').first,
|
||||
preferredThemeMode: ThemeMode.system,
|
||||
preferredViewType: ViewType.list,
|
||||
);
|
||||
|
||||
final bool isLocalAuthenticationEnabled;
|
||||
final String preferredLocaleSubtag;
|
||||
final ThemeMode preferredThemeMode;
|
||||
final ViewType preferredViewType;
|
||||
final ColorSchemeOption preferredColorSchemeOption;
|
||||
|
||||
ApplicationSettingsState({
|
||||
required this.preferredLocaleSubtag,
|
||||
required this.preferredThemeMode,
|
||||
required this.isLocalAuthenticationEnabled,
|
||||
required this.preferredViewType,
|
||||
this.preferredThemeMode = ThemeMode.system,
|
||||
this.isLocalAuthenticationEnabled = false,
|
||||
this.preferredViewType = ViewType.list,
|
||||
this.preferredColorSchemeOption = ColorSchemeOption.dynamic,
|
||||
});
|
||||
|
||||
Map<String, dynamic> toJson() => _$ApplicationSettingsStateToJson(this);
|
||||
@@ -40,6 +39,7 @@ class ApplicationSettingsState {
|
||||
String? preferredLocaleSubtag,
|
||||
ThemeMode? preferredThemeMode,
|
||||
ViewType? preferredViewType,
|
||||
ColorSchemeOption? preferredColorSchemeOption,
|
||||
}) {
|
||||
return ApplicationSettingsState(
|
||||
isLocalAuthenticationEnabled:
|
||||
@@ -48,6 +48,8 @@ class ApplicationSettingsState {
|
||||
preferredLocaleSubtag ?? this.preferredLocaleSubtag,
|
||||
preferredThemeMode: preferredThemeMode ?? this.preferredThemeMode,
|
||||
preferredViewType: preferredViewType ?? this.preferredViewType,
|
||||
preferredColorSchemeOption:
|
||||
preferredColorSchemeOption ?? this.preferredColorSchemeOption,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -11,11 +11,16 @@ ApplicationSettingsState _$ApplicationSettingsStateFromJson(
|
||||
ApplicationSettingsState(
|
||||
preferredLocaleSubtag: json['preferredLocaleSubtag'] as String,
|
||||
preferredThemeMode:
|
||||
$enumDecode(_$ThemeModeEnumMap, json['preferredThemeMode']),
|
||||
$enumDecodeNullable(_$ThemeModeEnumMap, json['preferredThemeMode']) ??
|
||||
ThemeMode.system,
|
||||
isLocalAuthenticationEnabled:
|
||||
json['isLocalAuthenticationEnabled'] as bool,
|
||||
json['isLocalAuthenticationEnabled'] as bool? ?? false,
|
||||
preferredViewType:
|
||||
$enumDecode(_$ViewTypeEnumMap, json['preferredViewType']),
|
||||
$enumDecodeNullable(_$ViewTypeEnumMap, json['preferredViewType']) ??
|
||||
ViewType.list,
|
||||
preferredColorSchemeOption: $enumDecodeNullable(
|
||||
_$ColorSchemeOptionEnumMap, json['preferredColorSchemeOption']) ??
|
||||
ColorSchemeOption.dynamic,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$ApplicationSettingsStateToJson(
|
||||
@@ -25,6 +30,8 @@ Map<String, dynamic> _$ApplicationSettingsStateToJson(
|
||||
'preferredLocaleSubtag': instance.preferredLocaleSubtag,
|
||||
'preferredThemeMode': _$ThemeModeEnumMap[instance.preferredThemeMode]!,
|
||||
'preferredViewType': _$ViewTypeEnumMap[instance.preferredViewType]!,
|
||||
'preferredColorSchemeOption':
|
||||
_$ColorSchemeOptionEnumMap[instance.preferredColorSchemeOption]!,
|
||||
};
|
||||
|
||||
const _$ThemeModeEnumMap = {
|
||||
@@ -37,3 +44,8 @@ const _$ViewTypeEnumMap = {
|
||||
ViewType.grid: 'grid',
|
||||
ViewType.list: 'list',
|
||||
};
|
||||
|
||||
const _$ColorSchemeOptionEnumMap = {
|
||||
ColorSchemeOption.classic: 'classic',
|
||||
ColorSchemeOption.dynamic: 'dynamic',
|
||||
};
|
||||
4
lib/features/settings/model/color_scheme_option.dart
Normal file
4
lib/features/settings/model/color_scheme_option.dart
Normal file
@@ -0,0 +1,4 @@
|
||||
enum ColorSchemeOption {
|
||||
classic,
|
||||
dynamic;
|
||||
}
|
||||
@@ -1,7 +1,12 @@
|
||||
import 'dart:developer';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:paperless_mobile/features/settings/view/widgets/color_scheme_option_setting.dart';
|
||||
import 'package:paperless_mobile/features/settings/view/widgets/language_selection_setting.dart';
|
||||
import 'package:paperless_mobile/features/settings/view/widgets/theme_mode_setting.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
import 'package:paperless_mobile/constants.dart';
|
||||
|
||||
class ApplicationSettingsPage extends StatelessWidget {
|
||||
const ApplicationSettingsPage({super.key});
|
||||
@@ -16,6 +21,7 @@ class ApplicationSettingsPage extends StatelessWidget {
|
||||
children: const [
|
||||
LanguageSelectionSetting(),
|
||||
ThemeModeSetting(),
|
||||
ColorSchemeOptionSetting(),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:paperless_mobile/features/settings/view/widgets/clear_storage_setting.dart';
|
||||
import 'package:paperless_mobile/features/settings/view/widgets/clear_storage_settings.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
|
||||
class StorageSettingsPage extends StatelessWidget {
|
||||
@@ -13,7 +13,7 @@ class StorageSettingsPage extends StatelessWidget {
|
||||
),
|
||||
body: ListView(
|
||||
children: const [
|
||||
ClearStorageSetting(),
|
||||
ClearCacheSetting(),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
@@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_mobile/features/login/services/authentication_service.dart';
|
||||
import 'package:paperless_mobile/features/settings/bloc/application_settings_cubit.dart';
|
||||
import 'package:paperless_mobile/features/settings/model/application_settings_state.dart';
|
||||
import 'package:paperless_mobile/features/settings/bloc/application_settings_state.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_cache_manager/flutter_cache_manager.dart' as cm;
|
||||
import 'package:paperless_mobile/core/service/file_service.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class ClearStorageSetting extends StatelessWidget {
|
||||
const ClearStorageSetting({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListTile(
|
||||
title: Text("Clear data"),
|
||||
subtitle:
|
||||
Text("Remove downloaded files, scans and clear the cache's content"),
|
||||
onTap: () {
|
||||
context.read<cm.CacheManager>().emptyCache();
|
||||
FileService.clearUserData();
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_cache_manager/flutter_cache_manager.dart' as cm;
|
||||
import 'package:paperless_mobile/core/service/file_service.dart';
|
||||
import 'package:paperless_mobile/helpers/format_helpers.dart';
|
||||
import 'package:paperless_mobile/helpers/message_helpers.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class ClearCacheSetting extends StatelessWidget {
|
||||
const ClearCacheSetting({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListTile(
|
||||
title: Text("Clear downloaded files"), //TODO: INTL
|
||||
subtitle:
|
||||
Text("Deletes all files downloaded from this app."), //TODO: INTL
|
||||
onTap: () async {
|
||||
final dir = await FileService.downloadsDirectory;
|
||||
final deletedSize = _dirSize(dir);
|
||||
await dir.delete(recursive: true);
|
||||
// await context.read<cm.CacheManager>().emptyCache();
|
||||
showSnackBar(
|
||||
context,
|
||||
"Downloads successfully cleared, removed $deletedSize.",
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ClearDownloadsSetting extends StatelessWidget {
|
||||
const ClearDownloadsSetting({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListTile(
|
||||
title: Text("Clear downloads"), //TODO: INTL
|
||||
subtitle: Text(
|
||||
"Remove downloaded files, scans and clear the cache's content"), //TODO: INTL
|
||||
onTap: () {
|
||||
FileService.documentsDirectory;
|
||||
FileService.downloadsDirectory;
|
||||
context.read<cm.CacheManager>().emptyCache();
|
||||
FileService.clearUserData();
|
||||
//TODO: Show notification about clearing (include size?)
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
String _dirSize(Directory dir) {
|
||||
int totalSize = 0;
|
||||
try {
|
||||
if (dir.existsSync()) {
|
||||
dir
|
||||
.listSync(recursive: true, followLinks: false)
|
||||
.forEach((FileSystemEntity entity) {
|
||||
if (entity is File) {
|
||||
totalSize += entity.lengthSync();
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
print(e.toString());
|
||||
}
|
||||
|
||||
return formatBytes(totalSize, 2);
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_mobile/core/translation/color_scheme_option_localization_mapper.dart';
|
||||
import 'package:paperless_mobile/core/widgets/hint_card.dart';
|
||||
import 'package:paperless_mobile/features/login/services/authentication_service.dart';
|
||||
import 'package:paperless_mobile/features/settings/bloc/application_settings_cubit.dart';
|
||||
import 'package:paperless_mobile/features/settings/bloc/application_settings_state.dart';
|
||||
import 'package:paperless_mobile/features/settings/model/color_scheme_option.dart';
|
||||
import 'package:paperless_mobile/features/settings/view/widgets/radio_settings_dialog.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
import 'package:paperless_mobile/constants.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class ColorSchemeOptionSetting extends StatelessWidget {
|
||||
const ColorSchemeOptionSetting({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<ApplicationSettingsCubit, ApplicationSettingsState>(
|
||||
builder: (context, settings) {
|
||||
return ListTile(
|
||||
title: Text(S.of(context).settingsPageColorSchemeSettingLabel),
|
||||
subtitle: Text(
|
||||
translateColorSchemeOption(
|
||||
context,
|
||||
settings.preferredColorSchemeOption,
|
||||
),
|
||||
),
|
||||
onTap: () => showDialog(
|
||||
context: context,
|
||||
builder: (_) => RadioSettingsDialog<ColorSchemeOption>(
|
||||
titleText: S.of(context).settingsPageColorSchemeSettingLabel,
|
||||
descriptionText:
|
||||
S.of(context).settingsPageColorSchemeSettingDialogDescription,
|
||||
options: [
|
||||
RadioOption(
|
||||
value: ColorSchemeOption.classic,
|
||||
label: translateColorSchemeOption(
|
||||
context, ColorSchemeOption.classic),
|
||||
),
|
||||
RadioOption(
|
||||
value: ColorSchemeOption.dynamic,
|
||||
label: translateColorSchemeOption(
|
||||
context,
|
||||
ColorSchemeOption.dynamic,
|
||||
),
|
||||
),
|
||||
],
|
||||
footer: _isBelowAndroid12()
|
||||
? HintCard(
|
||||
hintText: S
|
||||
.of(context)
|
||||
.settingsPageColorSchemeSettingDynamicThemeingVersionMismatchWarning,
|
||||
hintIcon: Icons.warning_amber,
|
||||
)
|
||||
: null,
|
||||
initialValue: context
|
||||
.read<ApplicationSettingsCubit>()
|
||||
.state
|
||||
.preferredColorSchemeOption,
|
||||
),
|
||||
).then(
|
||||
(value) {
|
||||
if (value != null) {
|
||||
context
|
||||
.read<ApplicationSettingsCubit>()
|
||||
.setColorSchemeOption(value);
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
bool _isBelowAndroid12() {
|
||||
if (Platform.isAndroid) {
|
||||
final int version =
|
||||
int.tryParse(androidInfo!.version.release ?? '0') ?? 0;
|
||||
return version < 12;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_mobile/features/settings/bloc/application_settings_cubit.dart';
|
||||
import 'package:paperless_mobile/features/settings/model/application_settings_state.dart';
|
||||
import 'package:paperless_mobile/features/settings/bloc/application_settings_state.dart';
|
||||
import 'package:paperless_mobile/features/settings/view/widgets/radio_settings_dialog.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
|
||||
@@ -30,7 +30,7 @@ class _LanguageSelectionSettingState extends State<LanguageSelectionSetting> {
|
||||
onTap: () => showDialog(
|
||||
context: context,
|
||||
builder: (_) => RadioSettingsDialog<String>(
|
||||
title: Text(S.of(context).settingsPageLanguageSettingLabel),
|
||||
titleText: S.of(context).settingsPageLanguageSettingLabel,
|
||||
options: [
|
||||
RadioOption(
|
||||
value: 'en',
|
||||
@@ -54,8 +54,11 @@ class _LanguageSelectionSettingState extends State<LanguageSelectionSetting> {
|
||||
.state
|
||||
.preferredLocaleSubtag,
|
||||
),
|
||||
).then((value) =>
|
||||
context.read<ApplicationSettingsCubit>().setLocale(value)),
|
||||
).then((value) {
|
||||
if (value != null) {
|
||||
context.read<ApplicationSettingsCubit>().setLocale(value);
|
||||
}
|
||||
}),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -4,7 +4,9 @@ import 'package:paperless_mobile/generated/l10n.dart';
|
||||
class RadioSettingsDialog<T> extends StatefulWidget {
|
||||
final List<RadioOption<T>> options;
|
||||
final T initialValue;
|
||||
final Widget? title;
|
||||
final String? titleText;
|
||||
final String? descriptionText;
|
||||
final Widget? footer;
|
||||
final Widget? confirmButton;
|
||||
final Widget? cancelButton;
|
||||
|
||||
@@ -12,9 +14,11 @@ class RadioSettingsDialog<T> extends StatefulWidget {
|
||||
super.key,
|
||||
required this.options,
|
||||
required this.initialValue,
|
||||
this.title,
|
||||
this.titleText,
|
||||
this.confirmButton,
|
||||
this.cancelButton,
|
||||
this.descriptionText,
|
||||
this.footer,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -43,10 +47,16 @@ class _RadioSettingsDialogState<T> extends State<RadioSettingsDialog<T>> {
|
||||
onPressed: () => Navigator.pop(context, _groupValue),
|
||||
child: Text(S.of(context).genericActionOkLabel)),
|
||||
],
|
||||
title: widget.title,
|
||||
title: widget.titleText != null ? Text(widget.titleText!) : null,
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: widget.options.map(_buildOptionListTile).toList(),
|
||||
children: [
|
||||
if (widget.descriptionText != null)
|
||||
Text(widget.descriptionText!,
|
||||
style: Theme.of(context).textTheme.bodySmall),
|
||||
...widget.options.map(_buildOptionListTile),
|
||||
if (widget.footer != null) widget.footer!,
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:paperless_mobile/features/settings/bloc/application_settings_cubit.dart';
|
||||
import 'package:paperless_mobile/features/settings/model/application_settings_state.dart';
|
||||
import 'package:paperless_mobile/features/settings/bloc/application_settings_state.dart';
|
||||
import 'package:paperless_mobile/features/settings/view/widgets/radio_settings_dialog.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
|
||||
@@ -19,6 +19,11 @@ class ThemeModeSetting extends StatelessWidget {
|
||||
onTap: () => showDialog<ThemeMode>(
|
||||
context: context,
|
||||
builder: (_) => RadioSettingsDialog<ThemeMode>(
|
||||
titleText: S.of(context).settingsPageAppearanceSettingTitle,
|
||||
initialValue: context
|
||||
.read<ApplicationSettingsCubit>()
|
||||
.state
|
||||
.preferredThemeMode,
|
||||
options: [
|
||||
RadioOption(
|
||||
value: ThemeMode.system,
|
||||
@@ -38,14 +43,11 @@ class ThemeModeSetting extends StatelessWidget {
|
||||
S.of(context).settingsPageAppearanceSettingDarkThemeLabel,
|
||||
)
|
||||
],
|
||||
initialValue: context
|
||||
.read<ApplicationSettingsCubit>()
|
||||
.state
|
||||
.preferredThemeMode,
|
||||
title: Text(S.of(context).settingsPageAppearanceSettingTitle),
|
||||
),
|
||||
).then((value) {
|
||||
return context.read<ApplicationSettingsCubit>().setThemeMode(value);
|
||||
if (value != null) {
|
||||
context.read<ApplicationSettingsCubit>().setThemeMode(value);
|
||||
}
|
||||
}),
|
||||
);
|
||||
},
|
||||
|
||||
@@ -1,6 +1,15 @@
|
||||
import 'dart:math';
|
||||
|
||||
String formatMaxCount(int? count, [int maxCount = 99]) {
|
||||
if ((count ?? 0) > maxCount) {
|
||||
return "$maxCount+";
|
||||
}
|
||||
return (count ?? 0).toString().padLeft(maxCount.toString().length);
|
||||
}
|
||||
|
||||
String formatBytes(int bytes, int decimals) {
|
||||
if (bytes <= 0) return "0 B";
|
||||
const suffixes = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
|
||||
var i = (log(bytes) / log(1024)).floor();
|
||||
return ((bytes / pow(1024, i)).toStringAsFixed(decimals)) + ' ' + suffixes[i];
|
||||
}
|
||||
|
||||
@@ -611,5 +611,10 @@
|
||||
"verifyIdentityPageTitle": "Ověř svou identitu",
|
||||
"@verifyIdentityPageTitle": {},
|
||||
"verifyIdentityPageVerifyIdentityButtonLabel": "Ověřit identitu",
|
||||
"@verifyIdentityPageVerifyIdentityButtonLabel": {}
|
||||
"@verifyIdentityPageVerifyIdentityButtonLabel": {},
|
||||
"settingsPageColorSchemeSettingLabel": "Colors",
|
||||
"colorSchemeOptionClassic": "Classic",
|
||||
"colorSchemeOptionDznamic": "Dynamic",
|
||||
"settingsPageColorSchemeSettingDialogDescription": "Choose between the classic color scheme in Paperless inspired green or use the dynamic color scheme based on your system theme.",
|
||||
"settingsPageColorSchemeSettingDynamicThemeingVersionMismatchWarning": "Dynamic theming is only supported for devices running Android 12 and above. Using the 'dynamic' option might not have any effect depending on your OS implementation."
|
||||
}
|
||||
@@ -611,5 +611,10 @@
|
||||
"verifyIdentityPageTitle": "Verifiziere deine Identität",
|
||||
"@verifyIdentityPageTitle": {},
|
||||
"verifyIdentityPageVerifyIdentityButtonLabel": "Identität verifizieren",
|
||||
"@verifyIdentityPageVerifyIdentityButtonLabel": {}
|
||||
"@verifyIdentityPageVerifyIdentityButtonLabel": {},
|
||||
"settingsPageColorSchemeSettingLabel": "Colors",
|
||||
"colorSchemeOptionClassic": "Classic",
|
||||
"colorSchemeOptionDznamic": "Dynamic",
|
||||
"settingsPageColorSchemeSettingDialogDescription": "Choose between the classic color scheme in Paperless inspired green or use the dynamic color scheme based on your system theme.",
|
||||
"settingsPageColorSchemeSettingDynamicThemeingVersionMismatchWarning": "Dynamic theming is only supported for devices running Android 12 and above. Using the 'dynamic' option might not have any effect depending on your OS implementation."
|
||||
}
|
||||
@@ -611,5 +611,10 @@
|
||||
"verifyIdentityPageTitle": "Verify your identity",
|
||||
"@verifyIdentityPageTitle": {},
|
||||
"verifyIdentityPageVerifyIdentityButtonLabel": "Verify Identity",
|
||||
"@verifyIdentityPageVerifyIdentityButtonLabel": {}
|
||||
"@verifyIdentityPageVerifyIdentityButtonLabel": {},
|
||||
"settingsPageColorSchemeSettingLabel": "Colors",
|
||||
"colorSchemeOptionClassic": "Classic",
|
||||
"colorSchemeOptionDynamic": "Dynamic",
|
||||
"settingsPageColorSchemeSettingDialogDescription": "Choose between a classic color scheme inspired by a traditional Paperless green or use the dynamic color scheme based on your system theme.",
|
||||
"settingsPageColorSchemeSettingDynamicThemeingVersionMismatchWarning": "Dynamic theming is only supported for devices running Android 12 and above. Selecting the 'Dynamic' option might not have any effect depending on your OS implementation."
|
||||
}
|
||||
@@ -611,5 +611,10 @@
|
||||
"verifyIdentityPageTitle": "Kimliğinizi doğrulayın",
|
||||
"@verifyIdentityPageTitle": {},
|
||||
"verifyIdentityPageVerifyIdentityButtonLabel": "Kimliği Doğrula",
|
||||
"@verifyIdentityPageVerifyIdentityButtonLabel": {}
|
||||
"@verifyIdentityPageVerifyIdentityButtonLabel": {},
|
||||
"settingsPageColorSchemeSettingLabel": "Colors",
|
||||
"colorSchemeOptionClassic": "Classic",
|
||||
"colorSchemeOptionDznamic": "Dynamic",
|
||||
"settingsPageColorSchemeSettingDialogDescription": "Choose between the classic color scheme in Paperless inspired green or use the dynamic color scheme based on your system theme.",
|
||||
"settingsPageColorSchemeSettingDynamicThemeingVersionMismatchWarning": "Dynamic theming is only supported for devices running Android 12 and above. Using the 'dynamic' option might not have any effect depending on your OS implementation."
|
||||
}
|
||||
108
lib/main.dart
108
lib/main.dart
@@ -1,6 +1,8 @@
|
||||
import 'dart:developer';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||
import 'package:device_info_plus/device_info_plus.dart';
|
||||
import 'package:encrypted_shared_preferences/encrypted_shared_preferences.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
@@ -12,6 +14,7 @@ import 'package:hydrated_bloc/hydrated_bloc.dart';
|
||||
import 'package:intl/date_symbol_data_local.dart';
|
||||
import 'package:intl/intl_standalone.dart';
|
||||
import 'package:local_auth/local_auth.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/bloc/bloc_changes_observer.dart';
|
||||
import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart';
|
||||
@@ -33,7 +36,6 @@ import 'package:paperless_mobile/core/security/session_manager.dart';
|
||||
import 'package:paperless_mobile/core/service/connectivity_status_service.dart';
|
||||
import 'package:paperless_mobile/core/service/dio_file_service.dart';
|
||||
import 'package:paperless_mobile/core/service/file_service.dart';
|
||||
import 'package:paperless_mobile/core/store/local_vault.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/widget/verify_identity_page.dart';
|
||||
@@ -43,29 +45,36 @@ import 'package:paperless_mobile/features/login/services/authentication_service.
|
||||
import 'package:paperless_mobile/features/login/view/login_page.dart';
|
||||
import 'package:paperless_mobile/features/notifications/services/local_notification_service.dart';
|
||||
import 'package:paperless_mobile/features/settings/bloc/application_settings_cubit.dart';
|
||||
import 'package:paperless_mobile/features/settings/model/application_settings_state.dart';
|
||||
import 'package:paperless_mobile/features/settings/bloc/application_settings_state.dart';
|
||||
import 'package:paperless_mobile/features/sharing/share_intent_queue.dart';
|
||||
import 'package:paperless_mobile/features/tasks/cubit/task_status_cubit.dart';
|
||||
import 'package:paperless_mobile/generated/l10n.dart';
|
||||
import 'package:paperless_mobile/theme.dart';
|
||||
import 'package:paperless_mobile/constants.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:receive_sharing_intent/receive_sharing_intent.dart';
|
||||
import 'package:dynamic_color/dynamic_color.dart';
|
||||
|
||||
void main() async {
|
||||
Bloc.observer = BlocChangesObserver();
|
||||
final widgetsBinding = WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
await findSystemLocale();
|
||||
packageInfo = await PackageInfo.fromPlatform();
|
||||
if (Platform.isAndroid) {
|
||||
androidInfo = await DeviceInfoPlugin().androidInfo;
|
||||
}
|
||||
if (Platform.isIOS) {
|
||||
iosInfo = await DeviceInfoPlugin().iosInfo;
|
||||
}
|
||||
|
||||
// Initialize External dependencies
|
||||
final connectivity = Connectivity();
|
||||
final encryptedSharedPreferences = EncryptedSharedPreferences();
|
||||
final localAuthentication = LocalAuthentication();
|
||||
// Initialize other utility classes
|
||||
final connectivityStatusService = ConnectivityStatusServiceImpl(connectivity);
|
||||
final localVault = LocalVaultImpl(encryptedSharedPreferences);
|
||||
final localAuthService =
|
||||
LocalAuthenticationService(localVault, localAuthentication);
|
||||
final localAuthService = LocalAuthenticationService(localAuthentication);
|
||||
|
||||
final hiveDir = await getApplicationDocumentsDirectory();
|
||||
HydratedBloc.storage = await HydratedStorage.build(
|
||||
@@ -152,7 +161,6 @@ void main() async {
|
||||
),
|
||||
),
|
||||
),
|
||||
Provider<LocalVault>.value(value: localVault),
|
||||
Provider<ConnectivityStatusService>.value(
|
||||
value: connectivityStatusService,
|
||||
),
|
||||
@@ -207,22 +215,6 @@ class PaperlessMobileEntrypoint extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _PaperlessMobileEntrypointState extends State<PaperlessMobileEntrypoint> {
|
||||
final _lightTheme = ThemeData.from(
|
||||
colorScheme: ColorScheme.fromSeed(
|
||||
seedColor: Colors.lightGreen,
|
||||
brightness: Brightness.light,
|
||||
),
|
||||
useMaterial3: true,
|
||||
);
|
||||
|
||||
final _darkTheme = ThemeData.from(
|
||||
colorScheme: ColorScheme.fromSeed(
|
||||
seedColor: Colors.lightGreen,
|
||||
brightness: Brightness.dark,
|
||||
),
|
||||
useMaterial3: true,
|
||||
);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MultiBlocProvider(
|
||||
@@ -235,52 +227,36 @@ class _PaperlessMobileEntrypointState extends State<PaperlessMobileEntrypoint> {
|
||||
],
|
||||
child: BlocBuilder<ApplicationSettingsCubit, ApplicationSettingsState>(
|
||||
builder: (context, settings) {
|
||||
return MaterialApp(
|
||||
debugShowCheckedModeBanner: true,
|
||||
title: "Paperless Mobile",
|
||||
theme: _lightTheme.copyWith(
|
||||
inputDecorationTheme: InputDecorationTheme(
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
return DynamicColorBuilder(
|
||||
builder: (lightDynamic, darkDynamic) {
|
||||
return MaterialApp(
|
||||
debugShowCheckedModeBanner: true,
|
||||
title: "Paperless Mobile",
|
||||
theme: buildTheme(
|
||||
brightness: Brightness.light,
|
||||
dynamicScheme: lightDynamic,
|
||||
preferredColorScheme: settings.preferredColorSchemeOption,
|
||||
),
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 16.0,
|
||||
vertical: 16.0,
|
||||
darkTheme: buildTheme(
|
||||
brightness: Brightness.dark,
|
||||
dynamicScheme: darkDynamic,
|
||||
preferredColorScheme: settings.preferredColorSchemeOption,
|
||||
),
|
||||
),
|
||||
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
listTileTheme: const ListTileThemeData(
|
||||
tileColor: Colors.transparent,
|
||||
),
|
||||
),
|
||||
darkTheme: _darkTheme.copyWith(
|
||||
inputDecorationTheme: InputDecorationTheme(
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
themeMode: settings.preferredThemeMode,
|
||||
supportedLocales: S.delegate.supportedLocales,
|
||||
locale: Locale.fromSubtags(
|
||||
languageCode: settings.preferredLocaleSubtag,
|
||||
),
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 16.0,
|
||||
vertical: 16.0,
|
||||
),
|
||||
),
|
||||
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
listTileTheme: const ListTileThemeData(
|
||||
tileColor: Colors.transparent,
|
||||
),
|
||||
),
|
||||
themeMode: settings.preferredThemeMode,
|
||||
supportedLocales: S.delegate.supportedLocales,
|
||||
locale: Locale.fromSubtags(
|
||||
languageCode: settings.preferredLocaleSubtag,
|
||||
),
|
||||
localizationsDelegates: const [
|
||||
S.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
FormBuilderLocalizations.delegate,
|
||||
],
|
||||
home: const AuthenticationWrapper(),
|
||||
localizationsDelegates: const [
|
||||
S.delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
FormBuilderLocalizations.delegate,
|
||||
],
|
||||
home: const AuthenticationWrapper(),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
47
lib/theme.dart
Normal file
47
lib/theme.dart
Normal file
@@ -0,0 +1,47 @@
|
||||
import 'package:dynamic_color/dynamic_color.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:paperless_mobile/features/settings/model/color_scheme_option.dart';
|
||||
|
||||
const _classicThemeColorSeed = Colors.lightGreen;
|
||||
|
||||
const _defaultListTileTheme = ListTileThemeData(
|
||||
tileColor: Colors.transparent,
|
||||
);
|
||||
|
||||
final _defaultInputDecorationTheme = InputDecorationTheme(
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 16.0,
|
||||
vertical: 16.0,
|
||||
),
|
||||
);
|
||||
|
||||
ThemeData buildTheme({
|
||||
required Brightness brightness,
|
||||
required ColorSchemeOption preferredColorScheme,
|
||||
ColorScheme? dynamicScheme,
|
||||
}) {
|
||||
final classicScheme = ColorScheme.fromSeed(
|
||||
seedColor: _classicThemeColorSeed,
|
||||
brightness: brightness,
|
||||
);
|
||||
late ColorScheme colorScheme;
|
||||
switch (preferredColorScheme) {
|
||||
case ColorSchemeOption.classic:
|
||||
colorScheme = classicScheme;
|
||||
break;
|
||||
case ColorSchemeOption.dynamic:
|
||||
colorScheme = dynamicScheme ?? classicScheme;
|
||||
break;
|
||||
}
|
||||
return ThemeData.from(
|
||||
colorScheme: colorScheme.harmonized(),
|
||||
useMaterial3: true,
|
||||
).copyWith(
|
||||
inputDecorationTheme: _defaultInputDecorationTheme,
|
||||
listTileTheme: _defaultListTileTheme,
|
||||
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
);
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
|
||||
@@ -433,6 +433,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
dynamic_color:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: dynamic_color
|
||||
sha256: "37a15576f5a0bfd5555b613cf20ea3bd379607cf88d457374a16032f4e942174"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.5.4"
|
||||
edge_detection:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
||||
@@ -87,6 +87,7 @@ dependencies:
|
||||
flutter_staggered_grid_view: ^0.6.2
|
||||
responsive_builder: ^0.4.3
|
||||
open_filex: ^4.3.2
|
||||
dynamic_color: ^1.5.4
|
||||
|
||||
dev_dependencies:
|
||||
integration_test:
|
||||
|
||||
Reference in New Issue
Block a user