mirror of
https://github.com/Xevion/paperless-mobile.git
synced 2025-12-10 08:07:59 -06:00
feat:Update search
This commit is contained in:
@@ -7,18 +7,12 @@ enum SearchView {
|
|||||||
|
|
||||||
@JsonSerializable(ignoreUnannotated: true)
|
@JsonSerializable(ignoreUnannotated: true)
|
||||||
class DocumentSearchState extends DocumentPagingState {
|
class DocumentSearchState extends DocumentPagingState {
|
||||||
@JsonKey()
|
|
||||||
final List<String> searchHistory;
|
final List<String> searchHistory;
|
||||||
final SearchView view;
|
final SearchView view;
|
||||||
final List<String> suggestions;
|
final List<String> suggestions;
|
||||||
@JsonKey()
|
@JsonKey()
|
||||||
final ViewType viewType;
|
final ViewType viewType;
|
||||||
|
|
||||||
final Map<int, Correspondent> correspondents;
|
|
||||||
final Map<int, DocumentType> documentTypes;
|
|
||||||
final Map<int, Tag> tags;
|
|
||||||
final Map<int, StoragePath> storagePaths;
|
|
||||||
|
|
||||||
const DocumentSearchState({
|
const DocumentSearchState({
|
||||||
this.view = SearchView.suggestions,
|
this.view = SearchView.suggestions,
|
||||||
this.searchHistory = const [],
|
this.searchHistory = const [],
|
||||||
@@ -28,10 +22,6 @@ class DocumentSearchState extends DocumentPagingState {
|
|||||||
super.hasLoaded,
|
super.hasLoaded,
|
||||||
super.isLoading,
|
super.isLoading,
|
||||||
super.value,
|
super.value,
|
||||||
this.correspondents = const {},
|
|
||||||
this.documentTypes = const {},
|
|
||||||
this.tags = const {},
|
|
||||||
this.storagePaths = const {},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -41,10 +31,6 @@ class DocumentSearchState extends DocumentPagingState {
|
|||||||
suggestions,
|
suggestions,
|
||||||
view,
|
view,
|
||||||
viewType,
|
viewType,
|
||||||
correspondents,
|
|
||||||
documentTypes,
|
|
||||||
tags,
|
|
||||||
storagePaths,
|
|
||||||
];
|
];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -85,10 +71,6 @@ class DocumentSearchState extends DocumentPagingState {
|
|||||||
view: view ?? this.view,
|
view: view ?? this.view,
|
||||||
suggestions: suggestions ?? this.suggestions,
|
suggestions: suggestions ?? this.suggestions,
|
||||||
viewType: viewType ?? this.viewType,
|
viewType: viewType ?? this.viewType,
|
||||||
correspondents: correspondents ?? this.correspondents,
|
|
||||||
documentTypes: documentTypes ?? this.documentTypes,
|
|
||||||
tags: tags ?? this.tags,
|
|
||||||
storagePaths: storagePaths ?? this.storagePaths,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
211
lib/features/document_search/view/document_search_bar.dart
Normal file
211
lib/features/document_search/view/document_search_bar.dart
Normal file
@@ -0,0 +1,211 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'dart:math' as math;
|
||||||
|
|
||||||
|
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:paperless_mobile/core/config/hive/hive_config.dart';
|
||||||
|
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
||||||
|
import 'package:paperless_mobile/core/navigation/push_routes.dart';
|
||||||
|
import 'package:paperless_mobile/core/repository/label_repository.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';
|
||||||
|
import 'package:paperless_mobile/features/documents/view/widgets/adaptive_documents_view.dart';
|
||||||
|
import 'package:paperless_mobile/features/documents/view/widgets/selection/view_type_selection_widget.dart';
|
||||||
|
import 'package:paperless_mobile/features/home/view/model/api_version.dart';
|
||||||
|
import 'package:paperless_mobile/features/settings/model/view_type.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';
|
||||||
|
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
class DocumentSearchBar extends StatefulWidget {
|
||||||
|
const DocumentSearchBar({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<DocumentSearchBar> createState() => _DocumentSearchBarState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DocumentSearchBarState extends State<DocumentSearchBar> {
|
||||||
|
Timer? _debounceTimer;
|
||||||
|
|
||||||
|
final _controller = SearchController();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_controller.addListener(() {
|
||||||
|
_debounceTimer?.cancel();
|
||||||
|
_debounceTimer = Timer(const Duration(milliseconds: 500), () {
|
||||||
|
context.read<DocumentSearchCubit>().suggest(query);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
late final DocumentSearchCubit _searchCubit;
|
||||||
|
String get query => _controller.text;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didChangeDependencies() {
|
||||||
|
super.didChangeDependencies();
|
||||||
|
_searchCubit = context.watch<DocumentSearchCubit>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return SearchAnchor.bar(
|
||||||
|
searchController: _controller,
|
||||||
|
barLeading: IconButton(
|
||||||
|
icon: const Icon(Icons.menu),
|
||||||
|
onPressed: Scaffold.of(context).openDrawer,
|
||||||
|
),
|
||||||
|
barHintText: S.of(context)!.searchDocuments,
|
||||||
|
barTrailing: [
|
||||||
|
IconButton(
|
||||||
|
icon: GlobalSettingsBuilder(
|
||||||
|
builder: (context, settings) {
|
||||||
|
return ValueListenableBuilder(
|
||||||
|
valueListenable:
|
||||||
|
Hive.box<LocalUserAccount>(HiveBoxes.localUserAccount).listenable(),
|
||||||
|
builder: (context, box, _) {
|
||||||
|
final account = box.get(settings.currentLoggedInUser!)!;
|
||||||
|
return UserAvatar(
|
||||||
|
userId: settings.currentLoggedInUser!,
|
||||||
|
account: account,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
final apiVersion = context.read<ApiVersion>();
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => Provider.value(
|
||||||
|
value: apiVersion,
|
||||||
|
child: const ManageAccountsPage(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
suggestionsBuilder: (context, controller) {
|
||||||
|
switch (_searchCubit.state.view) {
|
||||||
|
case SearchView.suggestions:
|
||||||
|
return _buildSuggestionItems(_searchCubit.state);
|
||||||
|
case SearchView.results:
|
||||||
|
// TODO: Handle this case.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Iterable<Widget> _buildSuggestionItems(DocumentSearchState state) sync* {
|
||||||
|
final suggestions =
|
||||||
|
state.suggestions.whereNot((element) => state.searchHistory.contains(element));
|
||||||
|
final historyMatches = state.searchHistory.where((element) => element.startsWith(query));
|
||||||
|
for (var match in historyMatches.take(5)) {
|
||||||
|
yield ListTile(
|
||||||
|
title: Text(match),
|
||||||
|
leading: const Icon(Icons.history),
|
||||||
|
onLongPress: () => _onDeleteHistoryEntry(match),
|
||||||
|
onTap: () => _selectSuggestion(match),
|
||||||
|
trailing: _buildInsertSuggestionButton(match),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var suggestion in suggestions) {
|
||||||
|
yield ListTile(
|
||||||
|
title: Text(suggestion),
|
||||||
|
leading: const Icon(Icons.search),
|
||||||
|
onTap: () => _selectSuggestion(suggestion),
|
||||||
|
trailing: _buildInsertSuggestionButton(suggestion),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onDeleteHistoryEntry(String entry) async {
|
||||||
|
final shouldRemove = await showDialog<bool>(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => RemoveHistoryEntryDialog(entry: entry),
|
||||||
|
) ??
|
||||||
|
false;
|
||||||
|
if (shouldRemove) {
|
||||||
|
context.read<DocumentSearchCubit>().removeHistoryEntry(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildInsertSuggestionButton(String suggestion) {
|
||||||
|
return Transform(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
transform: Matrix4.rotationY(math.pi),
|
||||||
|
child: IconButton(
|
||||||
|
icon: const Icon(Icons.arrow_outward),
|
||||||
|
onPressed: () {
|
||||||
|
_controller.text = '$suggestion ';
|
||||||
|
_controller.selection = TextSelection.fromPosition(
|
||||||
|
TextPosition(offset: _controller.text.length),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildResultsView(DocumentSearchState state) {
|
||||||
|
final header = Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
S.of(context)!.results,
|
||||||
|
style: Theme.of(context).textTheme.bodySmall,
|
||||||
|
),
|
||||||
|
BlocBuilder<DocumentSearchCubit, DocumentSearchState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
return ViewTypeSelectionWidget(
|
||||||
|
viewType: state.viewType,
|
||||||
|
onChanged: (type) => context.read<DocumentSearchCubit>().updateViewType(type),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
],
|
||||||
|
).padded();
|
||||||
|
return CustomScrollView(
|
||||||
|
slivers: [
|
||||||
|
SliverToBoxAdapter(child: header),
|
||||||
|
if (state.hasLoaded && !state.isLoading && state.documents.isEmpty)
|
||||||
|
SliverToBoxAdapter(
|
||||||
|
child: Center(
|
||||||
|
child: Text(S.of(context)!.noMatchesFound),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
else
|
||||||
|
SliverAdaptiveDocumentsView(
|
||||||
|
viewType: state.viewType,
|
||||||
|
documents: state.documents,
|
||||||
|
hasInternetConnection: true,
|
||||||
|
isLabelClickable: false,
|
||||||
|
isLoading: state.isLoading,
|
||||||
|
hasLoaded: state.hasLoaded,
|
||||||
|
enableHeroAnimation: false,
|
||||||
|
onTap: (document) {
|
||||||
|
pushDocumentDetailsRoute(
|
||||||
|
context,
|
||||||
|
document: document,
|
||||||
|
isLabelClickable: false,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _selectSuggestion(String suggestion) {
|
||||||
|
_controller.text = suggestion;
|
||||||
|
context.read<DocumentSearchCubit>().search(suggestion);
|
||||||
|
FocusScope.of(context).unfocus();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,7 +13,6 @@ import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
|||||||
|
|
||||||
import 'dart:math' as math;
|
import 'dart:math' as math;
|
||||||
|
|
||||||
|
|
||||||
class DocumentSearchPage extends StatefulWidget {
|
class DocumentSearchPage extends StatefulWidget {
|
||||||
const DocumentSearchPage({super.key});
|
const DocumentSearchPage({super.key});
|
||||||
|
|
||||||
@@ -211,10 +210,6 @@ class _DocumentSearchPageState extends State<DocumentSearchPage> {
|
|||||||
isLabelClickable: false,
|
isLabelClickable: false,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
correspondents: state.correspondents,
|
|
||||||
documentTypes: state.documentTypes,
|
|
||||||
tags: state.tags,
|
|
||||||
storagePaths: state.storagePaths,
|
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,10 +1,15 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:hive_flutter/adapters.dart';
|
import 'package:hive_flutter/adapters.dart';
|
||||||
import 'package:paperless_mobile/core/config/hive/hive_config.dart';
|
import 'package:paperless_mobile/core/config/hive/hive_config.dart';
|
||||||
|
import 'package:paperless_mobile/core/database/tables/global_settings.dart';
|
||||||
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
import 'package:paperless_mobile/core/database/tables/local_user_account.dart';
|
||||||
|
import 'package:paperless_mobile/core/database/tables/local_user_app_state.dart';
|
||||||
import 'package:paperless_mobile/core/delegate/customizable_sliver_persistent_header_delegate.dart';
|
import 'package:paperless_mobile/core/delegate/customizable_sliver_persistent_header_delegate.dart';
|
||||||
import 'package:paperless_mobile/core/navigation/push_routes.dart';
|
import 'package:paperless_mobile/core/navigation/push_routes.dart';
|
||||||
import 'package:paperless_mobile/core/widgets/material/search/m3_search_bar.dart' as s;
|
import 'package:paperless_mobile/core/widgets/material/search/m3_search_bar.dart' as s;
|
||||||
|
import 'package:paperless_mobile/features/document_search/cubit/document_search_cubit.dart';
|
||||||
|
import 'package:paperless_mobile/features/document_search/view/document_search_bar.dart';
|
||||||
import 'package:paperless_mobile/features/home/view/model/api_version.dart';
|
import 'package:paperless_mobile/features/home/view/model/api_version.dart';
|
||||||
import 'package:paperless_mobile/features/settings/view/manage_accounts_page.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/global_settings_builder.dart';
|
||||||
@@ -23,6 +28,8 @@ class SliverSearchBar extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final currentUser =
|
||||||
|
Hive.box<GlobalSettings>(HiveBoxes.globalSettings).getValue()!.currentLoggedInUser;
|
||||||
return SliverPersistentHeader(
|
return SliverPersistentHeader(
|
||||||
floating: floating,
|
floating: floating,
|
||||||
pinned: pinned,
|
pinned: pinned,
|
||||||
@@ -30,45 +37,56 @@ class SliverSearchBar extends StatelessWidget {
|
|||||||
minExtent: kToolbarHeight,
|
minExtent: kToolbarHeight,
|
||||||
maxExtent: kToolbarHeight,
|
maxExtent: kToolbarHeight,
|
||||||
child: Container(
|
child: Container(
|
||||||
margin: const EdgeInsets.symmetric(horizontal: 8.0),
|
margin: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||||
child: s.SearchBar(
|
child: BlocProvider(
|
||||||
height: kToolbarHeight,
|
create: (context) => DocumentSearchCubit(
|
||||||
supportingText: S.of(context)!.searchDocuments,
|
context.read(),
|
||||||
onTap: () => pushDocumentSearchPage(context),
|
context.read(),
|
||||||
leadingIcon: IconButton(
|
context.read(),
|
||||||
icon: const Icon(Icons.menu),
|
Hive.box<LocalUserAppState>(HiveBoxes.localUserAppState).get(currentUser)!,
|
||||||
onPressed: Scaffold.of(context).openDrawer,
|
|
||||||
),
|
|
||||||
trailingIcon: IconButton(
|
|
||||||
icon: GlobalSettingsBuilder(
|
|
||||||
builder: (context, settings) {
|
|
||||||
return ValueListenableBuilder(
|
|
||||||
valueListenable:
|
|
||||||
Hive.box<LocalUserAccount>(HiveBoxes.localUserAccount).listenable(),
|
|
||||||
builder: (context, box, _) {
|
|
||||||
final account = box.get(settings.currentLoggedInUser!)!;
|
|
||||||
return UserAvatar(
|
|
||||||
userId: settings.currentLoggedInUser!,
|
|
||||||
account: account,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
onPressed: () {
|
|
||||||
final apiVersion = context.read<ApiVersion>();
|
|
||||||
showDialog(
|
|
||||||
context: context,
|
|
||||||
builder: (context) => Provider.value(
|
|
||||||
value: apiVersion,
|
|
||||||
child: const ManageAccountsPage(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
|
child: const DocumentSearchBar(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
s.SearchBar _buildOld(BuildContext context) {
|
||||||
|
return s.SearchBar(
|
||||||
|
height: kToolbarHeight,
|
||||||
|
supportingText: S.of(context)!.searchDocuments,
|
||||||
|
onTap: () => pushDocumentSearchPage(context),
|
||||||
|
leadingIcon: IconButton(
|
||||||
|
icon: const Icon(Icons.menu),
|
||||||
|
onPressed: Scaffold.of(context).openDrawer,
|
||||||
|
),
|
||||||
|
trailingIcon: IconButton(
|
||||||
|
icon: GlobalSettingsBuilder(
|
||||||
|
builder: (context, settings) {
|
||||||
|
return ValueListenableBuilder(
|
||||||
|
valueListenable: Hive.box<LocalUserAccount>(HiveBoxes.localUserAccount).listenable(),
|
||||||
|
builder: (context, box, _) {
|
||||||
|
final account = box.get(settings.currentLoggedInUser!)!;
|
||||||
|
return UserAvatar(
|
||||||
|
userId: settings.currentLoggedInUser!,
|
||||||
|
account: account,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
final apiVersion = context.read<ApiVersion>();
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => Provider.value(
|
||||||
|
value: apiVersion,
|
||||||
|
child: const ManageAccountsPage(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -333,10 +333,6 @@ class _DocumentsPageState extends State<DocumentsPage> with SingleTickerProvider
|
|||||||
isLabelClickable: true,
|
isLabelClickable: true,
|
||||||
isLoading: state.isLoading,
|
isLoading: state.isLoading,
|
||||||
selectedDocumentIds: state.selectedIds,
|
selectedDocumentIds: state.selectedIds,
|
||||||
correspondents: state.correspondents,
|
|
||||||
documentTypes: state.documentTypes,
|
|
||||||
tags: state.tags,
|
|
||||||
storagePaths: state.storagePaths,
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -24,11 +24,6 @@ abstract class AdaptiveDocumentsView extends StatelessWidget {
|
|||||||
final void Function(int? id)? onDocumentTypeSelected;
|
final void Function(int? id)? onDocumentTypeSelected;
|
||||||
final void Function(int? id)? onStoragePathSelected;
|
final void Function(int? id)? onStoragePathSelected;
|
||||||
|
|
||||||
final Map<int, Correspondent> correspondents;
|
|
||||||
final Map<int, DocumentType> documentTypes;
|
|
||||||
final Map<int, Tag> tags;
|
|
||||||
final Map<int, StoragePath> storagePaths;
|
|
||||||
|
|
||||||
bool get showLoadingPlaceholder => !hasLoaded && isLoading;
|
bool get showLoadingPlaceholder => !hasLoaded && isLoading;
|
||||||
|
|
||||||
const AdaptiveDocumentsView({
|
const AdaptiveDocumentsView({
|
||||||
@@ -47,10 +42,6 @@ abstract class AdaptiveDocumentsView extends StatelessWidget {
|
|||||||
required this.isLoading,
|
required this.isLoading,
|
||||||
required this.hasLoaded,
|
required this.hasLoaded,
|
||||||
this.enableHeroAnimation = true,
|
this.enableHeroAnimation = true,
|
||||||
required this.correspondents,
|
|
||||||
required this.documentTypes,
|
|
||||||
required this.tags,
|
|
||||||
required this.storagePaths,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
AdaptiveDocumentsView.fromPagedState(
|
AdaptiveDocumentsView.fromPagedState(
|
||||||
@@ -67,10 +58,6 @@ abstract class AdaptiveDocumentsView extends StatelessWidget {
|
|||||||
required this.hasInternetConnection,
|
required this.hasInternetConnection,
|
||||||
this.viewType = ViewType.list,
|
this.viewType = ViewType.list,
|
||||||
this.selectedDocumentIds = const [],
|
this.selectedDocumentIds = const [],
|
||||||
required this.correspondents,
|
|
||||||
required this.documentTypes,
|
|
||||||
required this.tags,
|
|
||||||
required this.storagePaths,
|
|
||||||
}) : documents = state.documents,
|
}) : documents = state.documents,
|
||||||
isLoading = state.isLoading,
|
isLoading = state.isLoading,
|
||||||
hasLoaded = state.hasLoaded;
|
hasLoaded = state.hasLoaded;
|
||||||
@@ -93,10 +80,6 @@ class SliverAdaptiveDocumentsView extends AdaptiveDocumentsView {
|
|||||||
super.enableHeroAnimation,
|
super.enableHeroAnimation,
|
||||||
required super.isLoading,
|
required super.isLoading,
|
||||||
required super.hasLoaded,
|
required super.hasLoaded,
|
||||||
required super.correspondents,
|
|
||||||
required super.documentTypes,
|
|
||||||
required super.tags,
|
|
||||||
required super.storagePaths,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -132,10 +115,6 @@ class SliverAdaptiveDocumentsView extends AdaptiveDocumentsView {
|
|||||||
onDocumentTypeSelected: onDocumentTypeSelected,
|
onDocumentTypeSelected: onDocumentTypeSelected,
|
||||||
onStoragePathSelected: onStoragePathSelected,
|
onStoragePathSelected: onStoragePathSelected,
|
||||||
enableHeroAnimation: enableHeroAnimation,
|
enableHeroAnimation: enableHeroAnimation,
|
||||||
correspondents: correspondents,
|
|
||||||
documentTypes: documentTypes,
|
|
||||||
storagePaths: storagePaths,
|
|
||||||
tags: tags,
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -165,10 +144,6 @@ class SliverAdaptiveDocumentsView extends AdaptiveDocumentsView {
|
|||||||
onStoragePathSelected: onStoragePathSelected,
|
onStoragePathSelected: onStoragePathSelected,
|
||||||
enableHeroAnimation: enableHeroAnimation,
|
enableHeroAnimation: enableHeroAnimation,
|
||||||
highlights: document.searchHit?.highlights,
|
highlights: document.searchHit?.highlights,
|
||||||
correspondents: correspondents,
|
|
||||||
documentTypes: documentTypes,
|
|
||||||
storagePaths: storagePaths,
|
|
||||||
tags: tags,
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -201,10 +176,6 @@ class SliverAdaptiveDocumentsView extends AdaptiveDocumentsView {
|
|||||||
onDocumentTypeSelected: onDocumentTypeSelected,
|
onDocumentTypeSelected: onDocumentTypeSelected,
|
||||||
onStoragePathSelected: onStoragePathSelected,
|
onStoragePathSelected: onStoragePathSelected,
|
||||||
enableHeroAnimation: enableHeroAnimation,
|
enableHeroAnimation: enableHeroAnimation,
|
||||||
correspondents: correspondents,
|
|
||||||
documentTypes: documentTypes,
|
|
||||||
storagePaths: storagePaths,
|
|
||||||
tags: tags,
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -230,10 +201,6 @@ class DefaultAdaptiveDocumentsView extends AdaptiveDocumentsView {
|
|||||||
super.selectedDocumentIds,
|
super.selectedDocumentIds,
|
||||||
super.viewType,
|
super.viewType,
|
||||||
super.enableHeroAnimation = true,
|
super.enableHeroAnimation = true,
|
||||||
required super.correspondents,
|
|
||||||
required super.documentTypes,
|
|
||||||
required super.tags,
|
|
||||||
required super.storagePaths,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -272,10 +239,6 @@ class DefaultAdaptiveDocumentsView extends AdaptiveDocumentsView {
|
|||||||
onDocumentTypeSelected: onDocumentTypeSelected,
|
onDocumentTypeSelected: onDocumentTypeSelected,
|
||||||
onStoragePathSelected: onStoragePathSelected,
|
onStoragePathSelected: onStoragePathSelected,
|
||||||
enableHeroAnimation: enableHeroAnimation,
|
enableHeroAnimation: enableHeroAnimation,
|
||||||
correspondents: correspondents,
|
|
||||||
documentTypes: documentTypes,
|
|
||||||
storagePaths: storagePaths,
|
|
||||||
tags: tags,
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -306,10 +269,6 @@ class DefaultAdaptiveDocumentsView extends AdaptiveDocumentsView {
|
|||||||
onDocumentTypeSelected: onDocumentTypeSelected,
|
onDocumentTypeSelected: onDocumentTypeSelected,
|
||||||
onStoragePathSelected: onStoragePathSelected,
|
onStoragePathSelected: onStoragePathSelected,
|
||||||
enableHeroAnimation: enableHeroAnimation,
|
enableHeroAnimation: enableHeroAnimation,
|
||||||
correspondents: correspondents,
|
|
||||||
documentTypes: documentTypes,
|
|
||||||
storagePaths: storagePaths,
|
|
||||||
tags: tags,
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -344,10 +303,6 @@ class DefaultAdaptiveDocumentsView extends AdaptiveDocumentsView {
|
|||||||
onDocumentTypeSelected: onDocumentTypeSelected,
|
onDocumentTypeSelected: onDocumentTypeSelected,
|
||||||
onStoragePathSelected: onStoragePathSelected,
|
onStoragePathSelected: onStoragePathSelected,
|
||||||
enableHeroAnimation: enableHeroAnimation,
|
enableHeroAnimation: enableHeroAnimation,
|
||||||
correspondents: correspondents,
|
|
||||||
documentTypes: documentTypes,
|
|
||||||
storagePaths: storagePaths,
|
|
||||||
tags: tags,
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -3,12 +3,14 @@ import 'dart:math';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_html/flutter_html.dart';
|
import 'package:flutter_html/flutter_html.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||||
import 'package:paperless_mobile/features/documents/view/widgets/document_preview.dart';
|
import 'package:paperless_mobile/features/documents/view/widgets/document_preview.dart';
|
||||||
import 'package:paperless_mobile/features/documents/view/widgets/items/document_item.dart';
|
import 'package:paperless_mobile/features/documents/view/widgets/items/document_item.dart';
|
||||||
import 'package:paperless_mobile/features/labels/correspondent/view/widgets/correspondent_widget.dart';
|
import 'package:paperless_mobile/features/labels/correspondent/view/widgets/correspondent_widget.dart';
|
||||||
import 'package:paperless_mobile/features/labels/document_type/view/widgets/document_type_widget.dart';
|
import 'package:paperless_mobile/features/labels/document_type/view/widgets/document_type_widget.dart';
|
||||||
import 'package:paperless_mobile/features/labels/tags/view/widgets/tags_widget.dart';
|
import 'package:paperless_mobile/features/labels/tags/view/widgets/tags_widget.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
class DocumentDetailedItem extends DocumentItem {
|
class DocumentDetailedItem extends DocumentItem {
|
||||||
final String? highlights;
|
final String? highlights;
|
||||||
@@ -26,10 +28,6 @@ class DocumentDetailedItem extends DocumentItem {
|
|||||||
super.onStoragePathSelected,
|
super.onStoragePathSelected,
|
||||||
super.onTagSelected,
|
super.onTagSelected,
|
||||||
super.onTap,
|
super.onTap,
|
||||||
required super.tags,
|
|
||||||
required super.correspondents,
|
|
||||||
required super.documentTypes,
|
|
||||||
required super.storagePaths,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -116,7 +114,8 @@ class DocumentDetailedItem extends DocumentItem {
|
|||||||
textStyle: Theme.of(context).textTheme.titleSmall?.apply(
|
textStyle: Theme.of(context).textTheme.titleSmall?.apply(
|
||||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
),
|
),
|
||||||
correspondent: correspondents[document.correspondent],
|
correspondent:
|
||||||
|
context.watch<LabelRepository>().state.correspondents[document.correspondent],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
).paddedLTRB(8, 0, 8, 4),
|
).paddedLTRB(8, 0, 8, 4),
|
||||||
@@ -131,13 +130,16 @@ class DocumentDetailedItem extends DocumentItem {
|
|||||||
textStyle: Theme.of(context).textTheme.titleSmall?.apply(
|
textStyle: Theme.of(context).textTheme.titleSmall?.apply(
|
||||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
),
|
),
|
||||||
documentType: documentTypes[document.documentType],
|
documentType:
|
||||||
|
context.watch<LabelRepository>().state.documentTypes[document.documentType],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
).paddedLTRB(8, 0, 8, 4),
|
).paddedLTRB(8, 0, 8, 4),
|
||||||
TagsWidget(
|
TagsWidget(
|
||||||
isMultiLine: false,
|
isMultiLine: false,
|
||||||
tags: document.tags.map((e) => tags[e]!).toList(),
|
tags: document.tags
|
||||||
|
.map((e) => context.watch<LabelRepository>().state.tags[e]!)
|
||||||
|
.toList(),
|
||||||
).padded(),
|
).padded(),
|
||||||
if (highlights != null)
|
if (highlights != null)
|
||||||
Html(
|
Html(
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||||
import 'package:paperless_mobile/features/documents/view/widgets/document_preview.dart';
|
import 'package:paperless_mobile/features/documents/view/widgets/document_preview.dart';
|
||||||
import 'package:paperless_mobile/features/documents/view/widgets/items/document_item.dart';
|
import 'package:paperless_mobile/features/documents/view/widgets/items/document_item.dart';
|
||||||
import 'package:paperless_mobile/features/labels/correspondent/view/widgets/correspondent_widget.dart';
|
import 'package:paperless_mobile/features/labels/correspondent/view/widgets/correspondent_widget.dart';
|
||||||
import 'package:paperless_mobile/features/labels/document_type/view/widgets/document_type_widget.dart';
|
import 'package:paperless_mobile/features/labels/document_type/view/widgets/document_type_widget.dart';
|
||||||
import 'package:paperless_mobile/features/labels/tags/view/widgets/tags_widget.dart';
|
import 'package:paperless_mobile/features/labels/tags/view/widgets/tags_widget.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
class DocumentGridItem extends DocumentItem {
|
class DocumentGridItem extends DocumentItem {
|
||||||
const DocumentGridItem({
|
const DocumentGridItem({
|
||||||
@@ -20,10 +22,6 @@ class DocumentGridItem extends DocumentItem {
|
|||||||
super.onTagSelected,
|
super.onTagSelected,
|
||||||
super.onTap,
|
super.onTap,
|
||||||
required super.enableHeroAnimation,
|
required super.enableHeroAnimation,
|
||||||
required super.tags,
|
|
||||||
required super.correspondents,
|
|
||||||
required super.documentTypes,
|
|
||||||
required super.storagePaths,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -32,9 +30,8 @@ class DocumentGridItem extends DocumentItem {
|
|||||||
padding: const EdgeInsets.all(8.0),
|
padding: const EdgeInsets.all(8.0),
|
||||||
child: Card(
|
child: Card(
|
||||||
elevation: 1.0,
|
elevation: 1.0,
|
||||||
color: isSelected
|
color:
|
||||||
? Theme.of(context).colorScheme.inversePrimary
|
isSelected ? Theme.of(context).colorScheme.inversePrimary : Theme.of(context).cardColor,
|
||||||
: Theme.of(context).cardColor,
|
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
onTap: _onTap,
|
onTap: _onTap,
|
||||||
@@ -57,10 +54,16 @@ class DocumentGridItem extends DocumentItem {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
CorrespondentWidget(
|
CorrespondentWidget(
|
||||||
correspondent: correspondents[document.correspondent],
|
correspondent: context
|
||||||
|
.watch<LabelRepository>()
|
||||||
|
.state
|
||||||
|
.correspondents[document.correspondent],
|
||||||
),
|
),
|
||||||
DocumentTypeWidget(
|
DocumentTypeWidget(
|
||||||
documentType: documentTypes[document.documentType],
|
documentType: context
|
||||||
|
.watch<LabelRepository>()
|
||||||
|
.state
|
||||||
|
.documentTypes[document.documentType],
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
document.title,
|
document.title,
|
||||||
@@ -70,7 +73,9 @@ class DocumentGridItem extends DocumentItem {
|
|||||||
),
|
),
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
TagsWidget(
|
TagsWidget(
|
||||||
tags: document.tags.map((e) => tags[e]!).toList(),
|
tags: document.tags
|
||||||
|
.map((e) => context.watch<LabelRepository>().state.tags[e]!)
|
||||||
|
.toList(),
|
||||||
isMultiLine: false,
|
isMultiLine: false,
|
||||||
onTagSelected: onTagSelected,
|
onTagSelected: onTagSelected,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -10,11 +10,6 @@ abstract class DocumentItem extends StatelessWidget {
|
|||||||
final bool isLabelClickable;
|
final bool isLabelClickable;
|
||||||
final bool enableHeroAnimation;
|
final bool enableHeroAnimation;
|
||||||
|
|
||||||
final Map<int, Tag> tags;
|
|
||||||
final Map<int, Correspondent> correspondents;
|
|
||||||
final Map<int, DocumentType> documentTypes;
|
|
||||||
final Map<int, StoragePath> storagePaths;
|
|
||||||
|
|
||||||
final void Function(int tagId)? onTagSelected;
|
final void Function(int tagId)? onTagSelected;
|
||||||
final void Function(int? correspondentId)? onCorrespondentSelected;
|
final void Function(int? correspondentId)? onCorrespondentSelected;
|
||||||
final void Function(int? documentTypeId)? onDocumentTypeSelected;
|
final void Function(int? documentTypeId)? onDocumentTypeSelected;
|
||||||
@@ -33,9 +28,5 @@ abstract class DocumentItem extends StatelessWidget {
|
|||||||
this.onDocumentTypeSelected,
|
this.onDocumentTypeSelected,
|
||||||
this.onStoragePathSelected,
|
this.onStoragePathSelected,
|
||||||
required this.enableHeroAnimation,
|
required this.enableHeroAnimation,
|
||||||
required this.tags,
|
|
||||||
required this.correspondents,
|
|
||||||
required this.documentTypes,
|
|
||||||
required this.storagePaths,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||||
import 'package:paperless_mobile/features/documents/view/widgets/document_preview.dart';
|
import 'package:paperless_mobile/features/documents/view/widgets/document_preview.dart';
|
||||||
import 'package:paperless_mobile/features/documents/view/widgets/items/document_item.dart';
|
import 'package:paperless_mobile/features/documents/view/widgets/items/document_item.dart';
|
||||||
import 'package:paperless_mobile/features/labels/correspondent/view/widgets/correspondent_widget.dart';
|
import 'package:paperless_mobile/features/labels/correspondent/view/widgets/correspondent_widget.dart';
|
||||||
import 'package:paperless_mobile/features/labels/tags/view/widgets/tags_widget.dart';
|
import 'package:paperless_mobile/features/labels/tags/view/widgets/tags_widget.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
class DocumentListItem extends DocumentItem {
|
class DocumentListItem extends DocumentItem {
|
||||||
static const _a4AspectRatio = 1 / 1.4142;
|
static const _a4AspectRatio = 1 / 1.4142;
|
||||||
@@ -21,14 +23,11 @@ class DocumentListItem extends DocumentItem {
|
|||||||
super.onTagSelected,
|
super.onTagSelected,
|
||||||
super.onTap,
|
super.onTap,
|
||||||
super.enableHeroAnimation = true,
|
super.enableHeroAnimation = true,
|
||||||
required super.tags,
|
|
||||||
required super.correspondents,
|
|
||||||
required super.documentTypes,
|
|
||||||
required super.storagePaths,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final labels = context.watch<LabelRepository>().state;
|
||||||
return Material(
|
return Material(
|
||||||
child: ListTile(
|
child: ListTile(
|
||||||
dense: true,
|
dense: true,
|
||||||
@@ -46,7 +45,10 @@ class DocumentListItem extends DocumentItem {
|
|||||||
absorbing: isSelectionActive,
|
absorbing: isSelectionActive,
|
||||||
child: CorrespondentWidget(
|
child: CorrespondentWidget(
|
||||||
isClickable: isLabelClickable,
|
isClickable: isLabelClickable,
|
||||||
correspondent: correspondents[document.correspondent],
|
correspondent: context
|
||||||
|
.watch<LabelRepository>()
|
||||||
|
.state
|
||||||
|
.correspondents[document.correspondent],
|
||||||
onSelected: onCorrespondentSelected,
|
onSelected: onCorrespondentSelected,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -62,8 +64,8 @@ class DocumentListItem extends DocumentItem {
|
|||||||
child: TagsWidget(
|
child: TagsWidget(
|
||||||
isClickable: isLabelClickable,
|
isClickable: isLabelClickable,
|
||||||
tags: document.tags
|
tags: document.tags
|
||||||
.where((e) => tags.containsKey(e))
|
.where((e) => labels.tags.containsKey(e))
|
||||||
.map((e) => tags[e]!)
|
.map((e) => labels.tags[e]!)
|
||||||
.toList(),
|
.toList(),
|
||||||
isMultiLine: false,
|
isMultiLine: false,
|
||||||
onTagSelected: (id) => onTagSelected?.call(id),
|
onTagSelected: (id) => onTagSelected?.call(id),
|
||||||
@@ -78,15 +80,12 @@ class DocumentListItem extends DocumentItem {
|
|||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
text: TextSpan(
|
text: TextSpan(
|
||||||
text: DateFormat.yMMMd().format(document.created),
|
text: DateFormat.yMMMd().format(document.created),
|
||||||
style: Theme.of(context)
|
style: Theme.of(context).textTheme.labelSmall?.apply(color: Colors.grey),
|
||||||
.textTheme
|
|
||||||
.labelSmall
|
|
||||||
?.apply(color: Colors.grey),
|
|
||||||
children: document.documentType != null
|
children: document.documentType != null
|
||||||
? [
|
? [
|
||||||
const TextSpan(text: '\u30FB'),
|
const TextSpan(text: '\u30FB'),
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: documentTypes[document.documentType]?.name,
|
text: labels.documentTypes[document.documentType]?.name,
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
: null,
|
: null,
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import 'package:paperless_mobile/features/linked_documents/cubit/linked_document
|
|||||||
import 'package:paperless_mobile/features/paged_document_view/view/document_paging_view_mixin.dart';
|
import 'package:paperless_mobile/features/paged_document_view/view/document_paging_view_mixin.dart';
|
||||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||||
|
|
||||||
|
|
||||||
class LinkedDocumentsPage extends StatefulWidget {
|
class LinkedDocumentsPage extends StatefulWidget {
|
||||||
const LinkedDocumentsPage({super.key});
|
const LinkedDocumentsPage({super.key});
|
||||||
|
|
||||||
@@ -58,10 +57,6 @@ class _LinkedDocumentsPageState extends State<LinkedDocumentsPage>
|
|||||||
isLabelClickable: false,
|
isLabelClickable: false,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
correspondents: state.correspondents,
|
|
||||||
documentTypes: state.documentTypes,
|
|
||||||
storagePaths: state.storagePaths,
|
|
||||||
tags: state.tags,
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -83,10 +83,6 @@ class _SavedViewDetailsPageState extends State<SavedViewDetailsPage>
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
viewType: state.viewType,
|
viewType: state.viewType,
|
||||||
correspondents: state.correspondents,
|
|
||||||
documentTypes: state.documentTypes,
|
|
||||||
tags: state.tags,
|
|
||||||
storagePaths: state.storagePaths,
|
|
||||||
),
|
),
|
||||||
if (state.hasLoaded && state.isLoading)
|
if (state.hasLoaded && state.isLoading)
|
||||||
const SliverToBoxAdapter(
|
const SliverToBoxAdapter(
|
||||||
|
|||||||
@@ -66,10 +66,6 @@ class _SimilarDocumentsViewState extends State<SimilarDocumentsView>
|
|||||||
isLabelClickable: false,
|
isLabelClickable: false,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
correspondents: state.correspondents,
|
|
||||||
documentTypes: state.documentTypes,
|
|
||||||
tags: state.tags,
|
|
||||||
storagePaths: state.storagePaths,
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -29,3 +29,4 @@ export 'permissions/user_permissions.dart';
|
|||||||
export 'permissions/inherited_permissions.dart';
|
export 'permissions/inherited_permissions.dart';
|
||||||
export 'group_model.dart';
|
export 'group_model.dart';
|
||||||
export 'user_model.dart';
|
export 'user_model.dart';
|
||||||
|
export 'permissions/user_permission_extension.dart';
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import 'package:paperless_api/paperless_api.dart';
|
||||||
|
|
||||||
|
extension UserPermissionExtension on UserModel {
|
||||||
|
bool hasPermission(PermissionAction action, PermissionTarget target) {
|
||||||
|
return map(
|
||||||
|
v3: (user) {
|
||||||
|
final permission = [action.value, target.value].join("_");
|
||||||
|
return user.userPermissions.any((element) => element == permission) ||
|
||||||
|
user.inheritedPermissions.any((element) => element.split(".").last == permission);
|
||||||
|
},
|
||||||
|
v2: (_) => true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,7 +3,6 @@
|
|||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:paperless_api/config/hive/hive_type_ids.dart';
|
import 'package:paperless_api/config/hive/hive_type_ids.dart';
|
||||||
import 'package:paperless_api/src/models/permissions/user_permissions.dart';
|
|
||||||
|
|
||||||
part 'user_model.freezed.dart';
|
part 'user_model.freezed.dart';
|
||||||
part 'user_model.g.dart';
|
part 'user_model.g.dart';
|
||||||
@@ -42,29 +41,17 @@ class UserModel with _$UserModel {
|
|||||||
String? get fullName => map(
|
String? get fullName => map(
|
||||||
v2: (value) => value.displayName,
|
v2: (value) => value.displayName,
|
||||||
v3: (value) {
|
v3: (value) {
|
||||||
if (value.firstName == null && value.lastName == null) {
|
bool hasFirstName = value.firstName?.trim().isNotEmpty ?? false;
|
||||||
|
bool hasLastName = value.lastName?.trim().isNotEmpty ?? false;
|
||||||
|
if (hasFirstName && hasLastName) {
|
||||||
|
return "${value.firstName!} ${value.lastName!}";
|
||||||
|
} else if (hasFirstName) {
|
||||||
|
return value.firstName!;
|
||||||
|
} else if (hasLastName) {
|
||||||
|
return value.lastName;
|
||||||
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (value.firstName == null) {
|
|
||||||
return value.lastName;
|
|
||||||
}
|
|
||||||
return "${value.firstName!} ${value.lastName ?? ''}";
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
bool hasPermission(PermissionAction action, PermissionTarget target) {
|
|
||||||
return map(
|
|
||||||
v3: (value) {
|
|
||||||
if (value.isSuperuser) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
final permissionIdentifier = "${action.value}_${target.value}";
|
|
||||||
return value.userPermissions.contains(permissionIdentifier);
|
|
||||||
},
|
|
||||||
v2: (value) {
|
|
||||||
// In previous versions, all users have all permissions.
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,6 @@ subprojects {
|
|||||||
project.evaluationDependsOn(':app')
|
project.evaluationDependsOn(':app')
|
||||||
}
|
}
|
||||||
|
|
||||||
task clean(type: Delete) {
|
tasks.register("clean", Delete) {
|
||||||
delete rootProject.buildDir
|
delete rootProject.buildDir
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
|
|
||||||
import 'package:camera/camera.dart';
|
import 'package:camera/camera.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:image/image.dart' as imglib;
|
import 'package:image/image.dart' as imglib;
|
||||||
|
import 'package:camerawesome/camerawesome_plugin.dart';
|
||||||
|
import 'package:path_provider/path_provider.dart';
|
||||||
|
|
||||||
late final List<CameraDescription> cameras;
|
late final List<CameraDescription> cameras;
|
||||||
void main() async {
|
void main() async {
|
||||||
@@ -19,27 +20,9 @@ class EdgeDetectionApp extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _EdgeDetectionAppState extends State<EdgeDetectionApp> {
|
class _EdgeDetectionAppState extends State<EdgeDetectionApp> {
|
||||||
CameraImage? _image;
|
|
||||||
late final CameraController _controller;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
|
||||||
() async {
|
|
||||||
_controller = CameraController(
|
|
||||||
cameras
|
|
||||||
.where(
|
|
||||||
(element) => element.lensDirection == CameraLensDirection.back)
|
|
||||||
.first,
|
|
||||||
ResolutionPreset.low,
|
|
||||||
enableAudio: false,
|
|
||||||
);
|
|
||||||
await _controller.initialize();
|
|
||||||
_controller.startImageStream((image) {
|
|
||||||
setState(() => _image = image);
|
|
||||||
});
|
|
||||||
}();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Uint8List concatenatePlanes(List<Plane> planes) {
|
Uint8List concatenatePlanes(List<Plane> planes) {
|
||||||
@@ -68,8 +51,7 @@ class _EdgeDetectionAppState extends State<EdgeDetectionApp> {
|
|||||||
// Fill image buffer with plane[0] from YUV420_888
|
// Fill image buffer with plane[0] from YUV420_888
|
||||||
for (int x = 0; x < width; x++) {
|
for (int x = 0; x < width; x++) {
|
||||||
for (int y = 0; y < height; y++) {
|
for (int y = 0; y < height; y++) {
|
||||||
final int uvIndex =
|
final int uvIndex = uvPixelStride * (x / 2).floor() + uvRowStride * (y / 2).floor();
|
||||||
uvPixelStride * (x / 2).floor() + uvRowStride * (y / 2).floor();
|
|
||||||
final int index = y * width + x;
|
final int index = y * width + x;
|
||||||
|
|
||||||
final yp = image.planes[0].bytes[index];
|
final yp = image.planes[0].bytes[index];
|
||||||
@@ -77,9 +59,7 @@ class _EdgeDetectionAppState extends State<EdgeDetectionApp> {
|
|||||||
final vp = image.planes[2].bytes[uvIndex];
|
final vp = image.planes[2].bytes[uvIndex];
|
||||||
// Calculate pixel color
|
// Calculate pixel color
|
||||||
int r = (yp + vp * 1436 / 1024 - 179).round().clamp(0, 255);
|
int r = (yp + vp * 1436 / 1024 - 179).round().clamp(0, 255);
|
||||||
int g = (yp - up * 46549 / 131072 + 44 - vp * 93604 / 131072 + 91)
|
int g = (yp - up * 46549 / 131072 + 44 - vp * 93604 / 131072 + 91).round().clamp(0, 255);
|
||||||
.round()
|
|
||||||
.clamp(0, 255);
|
|
||||||
int b = (yp + up * 1814 / 1024 - 227).round().clamp(0, 255);
|
int b = (yp + up * 1814 / 1024 - 227).round().clamp(0, 255);
|
||||||
// color: 0x FF FF FF FF
|
// color: 0x FF FF FF FF
|
||||||
// A B G R
|
// A B G R
|
||||||
@@ -100,10 +80,22 @@ class _EdgeDetectionAppState extends State<EdgeDetectionApp> {
|
|||||||
visualDensity: VisualDensity.adaptivePlatformDensity,
|
visualDensity: VisualDensity.adaptivePlatformDensity,
|
||||||
),
|
),
|
||||||
home: Scaffold(
|
home: Scaffold(
|
||||||
body: Center(
|
body: CameraAwesomeBuilder.awesome(
|
||||||
child: _image != null
|
saveConfig: SaveConfig.photo(
|
||||||
? convertYUV420toImageColor(_image!)
|
pathBuilder: () =>
|
||||||
: const Placeholder(),
|
getApplicationDocumentsDirectory().then((value) => "${value.path}/test.jpg"),
|
||||||
|
),
|
||||||
|
onImageForAnalysis: (image) async {},
|
||||||
|
imageAnalysisConfig: AnalysisConfig(
|
||||||
|
// Android specific options
|
||||||
|
androidOptions: const AndroidAnalysisOptions.yuv420(
|
||||||
|
// Target width (CameraX will chose the closest resolution to this width)
|
||||||
|
width: 250,
|
||||||
|
),
|
||||||
|
// Wether to start automatically the analysis (true by default)
|
||||||
|
autoStart: true,
|
||||||
|
// Max frames per second, null for no limit (default)
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -65,6 +65,22 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.3.1+3"
|
version: "0.3.1+3"
|
||||||
|
camerawesome:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: camerawesome
|
||||||
|
sha256: "0fd4ad7cf85ced7c3018edbe5a66d8c4b630ada7120b02c01961db3ea36f52ce"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.4.0"
|
||||||
|
carousel_slider:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: carousel_slider
|
||||||
|
sha256: "9c695cc963bf1d04a47bd6021f68befce8970bcd61d24938e1fb0918cf5d9c42"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.2.1"
|
||||||
characters:
|
characters:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -89,6 +105,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.17.1"
|
version: "1.17.1"
|
||||||
|
colorfilter_generator:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: colorfilter_generator
|
||||||
|
sha256: ccc2995e440b1d828d55d99150e7cad64624f3cb4a1e235000de3f93cf10d35c
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.0.8"
|
||||||
convert:
|
convert:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -216,6 +240,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.2.0"
|
version: "0.2.0"
|
||||||
|
matrix2d:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: matrix2d
|
||||||
|
sha256: "188718dd3bc2a31e372cfd0791b0f77f4f13ea76164147342cc378d9132949e7"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.4"
|
||||||
meta:
|
meta:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -335,6 +367,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.2.1"
|
version: "3.2.1"
|
||||||
|
rxdart:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: rxdart
|
||||||
|
sha256: "0c7c0cedd93788d996e33041ffecda924cc54389199cde4e6a34b440f50044cb"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.27.7"
|
||||||
sky_engine:
|
sky_engine:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description: flutter
|
description: flutter
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ environment:
|
|||||||
flutter: ">=2.5.0"
|
flutter: ">=2.5.0"
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
|
camerawesome: ^1.4.0
|
||||||
ffi: ^2.0.1
|
ffi: ^2.0.1
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
|||||||
Reference in New Issue
Block a user