mirror of
https://github.com/Xevion/paperless-mobile.git
synced 2025-12-06 01:15:44 -06:00
feat: Add query highlight fragments to detailed view, fix build
This commit is contained in:
@@ -5,6 +5,7 @@ import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart';
|
||||
import 'package:paperless_mobile/features/paged_document_view/cubit/document_paging_bloc_mixin.dart';
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
import 'package:paperless_mobile/features/paged_document_view/cubit/paged_documents_state.dart';
|
||||
import 'package:paperless_mobile/features/settings/model/view_type.dart';
|
||||
|
||||
part 'document_search_state.dart';
|
||||
|
||||
@@ -33,7 +34,7 @@ class DocumentSearchCubit extends HydratedCubit<DocumentSearchState>
|
||||
view: SearchView.results,
|
||||
));
|
||||
final searchFilter = DocumentFilter(
|
||||
query: TextQuery.titleAndContent(query),
|
||||
query: TextQuery.extended(query),
|
||||
);
|
||||
|
||||
await updateFilter(filter: searchFilter);
|
||||
@@ -48,6 +49,10 @@ class DocumentSearchCubit extends HydratedCubit<DocumentSearchState>
|
||||
);
|
||||
}
|
||||
|
||||
void updateViewType(ViewType viewType) {
|
||||
emit(state.copyWith(viewType: viewType));
|
||||
}
|
||||
|
||||
void removeHistoryEntry(String entry) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
|
||||
@@ -11,10 +11,13 @@ class DocumentSearchState extends DocumentPagingState {
|
||||
final List<String> searchHistory;
|
||||
final SearchView view;
|
||||
final List<String> suggestions;
|
||||
@JsonKey()
|
||||
final ViewType viewType;
|
||||
const DocumentSearchState({
|
||||
this.view = SearchView.suggestions,
|
||||
this.searchHistory = const [],
|
||||
this.suggestions = const [],
|
||||
this.viewType = ViewType.detailed,
|
||||
super.filter,
|
||||
super.hasLoaded,
|
||||
super.isLoading,
|
||||
@@ -27,6 +30,7 @@ class DocumentSearchState extends DocumentPagingState {
|
||||
searchHistory,
|
||||
suggestions,
|
||||
view,
|
||||
viewType,
|
||||
];
|
||||
|
||||
@override
|
||||
@@ -52,6 +56,7 @@ class DocumentSearchState extends DocumentPagingState {
|
||||
DocumentFilter? filter,
|
||||
List<String>? suggestions,
|
||||
SearchView? view,
|
||||
ViewType? viewType,
|
||||
}) {
|
||||
return DocumentSearchState(
|
||||
value: value ?? this.value,
|
||||
@@ -61,6 +66,7 @@ class DocumentSearchState extends DocumentPagingState {
|
||||
searchHistory: searchHistory ?? this.searchHistory,
|
||||
view: view ?? this.view,
|
||||
suggestions: suggestions ?? this.suggestions,
|
||||
viewType: viewType ?? this.viewType,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,8 @@ 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/settings/model/view_type.dart';
|
||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||
|
||||
import 'package:paperless_mobile/routes/document_details_route.dart';
|
||||
@@ -168,7 +170,7 @@ class _DocumentSearchPageState extends State<DocumentSearchPage> {
|
||||
alignment: Alignment.center,
|
||||
transform: Matrix4.rotationY(math.pi),
|
||||
child: IconButton(
|
||||
icon: Icon(Icons.arrow_outward),
|
||||
icon: const Icon(Icons.arrow_outward),
|
||||
onPressed: () {
|
||||
_queryController.text = '$suggestion ';
|
||||
_queryController.selection = TextSelection.fromPosition(
|
||||
@@ -181,9 +183,23 @@ class _DocumentSearchPageState extends State<DocumentSearchPage> {
|
||||
}
|
||||
|
||||
Widget _buildResultsView(DocumentSearchState state) {
|
||||
final header = Text(
|
||||
S.of(context)!.results,
|
||||
style: Theme.of(context).textTheme.labelSmall,
|
||||
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: [
|
||||
@@ -196,6 +212,7 @@ class _DocumentSearchPageState extends State<DocumentSearchPage> {
|
||||
)
|
||||
else
|
||||
SliverAdaptiveDocumentsView(
|
||||
viewType: state.viewType,
|
||||
documents: state.documents,
|
||||
hasInternetConnection: true,
|
||||
isLabelClickable: false,
|
||||
|
||||
@@ -146,6 +146,7 @@ class SliverAdaptiveDocumentsView extends AdaptiveDocumentsView {
|
||||
onDocumentTypeSelected: onDocumentTypeSelected,
|
||||
onStoragePathSelected: onStoragePathSelected,
|
||||
enableHeroAnimation: enableHeroAnimation,
|
||||
highlights: document.searchHit?.highlights,
|
||||
),
|
||||
);
|
||||
},
|
||||
|
||||
@@ -8,10 +8,13 @@ import 'package:paperless_mobile/features/documents/view/widgets/items/document_
|
||||
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/tags/view/widgets/tags_widget.dart';
|
||||
import 'package:flutter_html/flutter_html.dart';
|
||||
|
||||
class DocumentDetailedItem extends DocumentItem {
|
||||
final String? highlights;
|
||||
const DocumentDetailedItem({
|
||||
super.key,
|
||||
this.highlights,
|
||||
required super.document,
|
||||
required super.isSelected,
|
||||
required super.isSelectionActive,
|
||||
@@ -37,7 +40,9 @@ class DocumentDetailedItem extends DocumentItem {
|
||||
padding.bottom -
|
||||
kBottomNavigationBarHeight -
|
||||
kToolbarHeight;
|
||||
final maxHeight = min(500.0, availableHeight);
|
||||
final maxHeight = highlights != null
|
||||
? min(600.0, availableHeight)
|
||||
: min(500.0, availableHeight);
|
||||
return Card(
|
||||
child: InkWell(
|
||||
enableFeedback: true,
|
||||
@@ -52,90 +57,98 @@ class DocumentDetailedItem extends DocumentItem {
|
||||
onLongPress: () {
|
||||
onSelected?.call(document);
|
||||
},
|
||||
child: Stack(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
ConstrainedBox(
|
||||
constraints: BoxConstraints.tightFor(
|
||||
width: double.infinity,
|
||||
height: maxHeight / 2,
|
||||
),
|
||||
child: DocumentPreview(
|
||||
document: document,
|
||||
fit: BoxFit.cover,
|
||||
alignment: Alignment.topCenter,
|
||||
),
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
DateFormat.yMMMMd().format(document.created),
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodySmall
|
||||
?.apply(color: Theme.of(context).hintColor),
|
||||
),
|
||||
if (document.archiveSerialNumber != null)
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
'#${document.archiveSerialNumber}',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodySmall
|
||||
?.apply(color: Theme.of(context).hintColor),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
).paddedLTRB(8, 8, 8, 4),
|
||||
Text(
|
||||
document.title,
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
).paddedLTRB(8, 0, 8, 4),
|
||||
Row(
|
||||
children: [
|
||||
const Icon(
|
||||
Icons.person_outline,
|
||||
size: 16,
|
||||
).paddedOnly(right: 4.0),
|
||||
CorrespondentWidget(
|
||||
onSelected: onCorrespondentSelected,
|
||||
textStyle: Theme.of(context).textTheme.titleSmall?.apply(
|
||||
color:
|
||||
Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
correspondentId: document.correspondent,
|
||||
),
|
||||
],
|
||||
).paddedLTRB(8, 0, 8, 4),
|
||||
Row(
|
||||
children: [
|
||||
const Icon(
|
||||
Icons.description_outlined,
|
||||
size: 16,
|
||||
).paddedOnly(right: 4.0),
|
||||
DocumentTypeWidget(
|
||||
onSelected: onDocumentTypeSelected,
|
||||
textStyle: Theme.of(context).textTheme.titleSmall?.apply(
|
||||
color:
|
||||
Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
documentTypeId: document.documentType,
|
||||
),
|
||||
],
|
||||
).paddedLTRB(8, 0, 8, 4),
|
||||
TagsWidget(
|
||||
isMultiLine: false,
|
||||
tagIds: document.tags,
|
||||
).padded(),
|
||||
],
|
||||
ConstrainedBox(
|
||||
constraints: BoxConstraints.tightFor(
|
||||
width: double.infinity,
|
||||
height: maxHeight / 2,
|
||||
),
|
||||
child: DocumentPreview(
|
||||
document: document,
|
||||
fit: BoxFit.cover,
|
||||
alignment: Alignment.topCenter,
|
||||
),
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
DateFormat.yMMMMd().format(document.created),
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodySmall
|
||||
?.apply(color: Theme.of(context).hintColor),
|
||||
),
|
||||
if (document.archiveSerialNumber != null)
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
'#${document.archiveSerialNumber}',
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodySmall
|
||||
?.apply(color: Theme.of(context).hintColor),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
).paddedLTRB(8, 8, 8, 4),
|
||||
Text(
|
||||
document.title,
|
||||
style: Theme.of(context).textTheme.titleMedium,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
).paddedLTRB(8, 0, 8, 4),
|
||||
Row(
|
||||
children: [
|
||||
const Icon(
|
||||
Icons.person_outline,
|
||||
size: 16,
|
||||
).paddedOnly(right: 4.0),
|
||||
CorrespondentWidget(
|
||||
onSelected: onCorrespondentSelected,
|
||||
textStyle: Theme.of(context).textTheme.titleSmall?.apply(
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
correspondentId: document.correspondent,
|
||||
),
|
||||
],
|
||||
).paddedLTRB(8, 0, 8, 4),
|
||||
Row(
|
||||
children: [
|
||||
const Icon(
|
||||
Icons.description_outlined,
|
||||
size: 16,
|
||||
).paddedOnly(right: 4.0),
|
||||
DocumentTypeWidget(
|
||||
onSelected: onDocumentTypeSelected,
|
||||
textStyle: Theme.of(context).textTheme.titleSmall?.apply(
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
documentTypeId: document.documentType,
|
||||
),
|
||||
],
|
||||
).paddedLTRB(8, 0, 8, 4),
|
||||
TagsWidget(
|
||||
isMultiLine: false,
|
||||
tagIds: document.tags,
|
||||
).padded(),
|
||||
if (highlights != null)
|
||||
Html(
|
||||
data: '<p>${highlights!}</p>',
|
||||
style: {
|
||||
"span": Style(
|
||||
backgroundColor: Colors.yellow,
|
||||
color: Colors.black,
|
||||
),
|
||||
"p": Style(
|
||||
maxLines: 3,
|
||||
textOverflow: TextOverflow.ellipsis,
|
||||
),
|
||||
},
|
||||
).padded(),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -27,7 +27,9 @@ class ViewTypeSelectionWidget extends StatelessWidget {
|
||||
icon = Icons.article_outlined;
|
||||
break;
|
||||
}
|
||||
|
||||
return PopupMenuButton<ViewType>(
|
||||
position: PopupMenuPosition.under,
|
||||
initialValue: viewType,
|
||||
icon: Icon(icon),
|
||||
itemBuilder: (context) => [
|
||||
|
||||
16
pubspec.lock
16
pubspec.lock
@@ -564,6 +564,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.7.0"
|
||||
flutter_html:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_html
|
||||
sha256: "342c7908f0a67bcec62b6e0f7cf23e23bafe7f64693665dd35be98d5e783bdfd"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.0-alpha.6"
|
||||
flutter_keyboard_visibility:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1005,6 +1013,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.1"
|
||||
numerus:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: numerus
|
||||
sha256: "436759d84f233b40107d0cc31cfa92d24e0960afeb2e506be70926d4cddffd9e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
octo_image:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
name: paperless_mobile
|
||||
description: Application to conveniently scan and share documents with a paperless-ng
|
||||
description:
|
||||
Application to conveniently scan and share documents with a paperless-ng
|
||||
server.
|
||||
|
||||
# The following line prevents the package from being accidentally published to
|
||||
@@ -19,7 +20,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
|
||||
version: 2.0.7+24
|
||||
|
||||
environment:
|
||||
sdk: '>=2.19.0 <4.0.0'
|
||||
sdk: ">=2.19.0 <4.0.0"
|
||||
|
||||
# Dependencies specify other packages that your package needs in order to work.
|
||||
# To automatically upgrade your package dependencies to the latest versions
|
||||
@@ -88,7 +89,7 @@ dependencies:
|
||||
open_filex: ^4.3.2
|
||||
flutter_displaymode: ^0.5.0
|
||||
dynamic_color: ^1.5.4
|
||||
|
||||
flutter_html: ^3.0.0-alpha.6
|
||||
|
||||
dev_dependencies:
|
||||
integration_test:
|
||||
@@ -147,7 +148,6 @@ flutter:
|
||||
# For details regarding fonts from package dependencies,
|
||||
# see https://flutter.dev/custom-fonts/#from-packages
|
||||
|
||||
|
||||
flutter_native_splash:
|
||||
image: assets/logos/paperless_logo_green.png
|
||||
color: "#f9f9f9"
|
||||
|
||||
@@ -4,6 +4,6 @@ pushd packages/paperless_api
|
||||
flutter pub get
|
||||
flutter pub run build_runner build --delete-conflicting-outputs
|
||||
popd
|
||||
flutter pub get
|
||||
flutter packages pub get
|
||||
flutter gen-l10n
|
||||
flutter pub run build_runner build --delete-conflicting-outputs
|
||||
|
||||
Reference in New Issue
Block a user