mirror of
https://github.com/Xevion/paperless-mobile.git
synced 2025-12-06 21:15:50 -06:00
feat: Implement new tag form field
This commit is contained in:
@@ -54,6 +54,8 @@ PODS:
|
||||
- FMDB (2.7.5):
|
||||
- FMDB/standard (= 2.7.5)
|
||||
- FMDB/standard (2.7.5)
|
||||
- in_app_review (0.2.0):
|
||||
- Flutter
|
||||
- integration_test (0.0.1):
|
||||
- Flutter
|
||||
- local_auth_ios (0.0.1):
|
||||
@@ -99,16 +101,17 @@ DEPENDENCIES:
|
||||
- flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`)
|
||||
- flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`)
|
||||
- fluttertoast (from `.symlinks/plugins/fluttertoast/ios`)
|
||||
- in_app_review (from `.symlinks/plugins/in_app_review/ios`)
|
||||
- integration_test (from `.symlinks/plugins/integration_test/ios`)
|
||||
- local_auth_ios (from `.symlinks/plugins/local_auth_ios/ios`)
|
||||
- open_filex (from `.symlinks/plugins/open_filex/ios`)
|
||||
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
|
||||
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
|
||||
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/ios`)
|
||||
- pdfx (from `.symlinks/plugins/pdfx/ios`)
|
||||
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
|
||||
- receive_sharing_intent (from `.symlinks/plugins/receive_sharing_intent/ios`)
|
||||
- share_plus (from `.symlinks/plugins/share_plus/ios`)
|
||||
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
|
||||
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/ios`)
|
||||
- sqflite (from `.symlinks/plugins/sqflite/ios`)
|
||||
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
||||
|
||||
@@ -142,6 +145,8 @@ EXTERNAL SOURCES:
|
||||
:path: ".symlinks/plugins/flutter_native_splash/ios"
|
||||
fluttertoast:
|
||||
:path: ".symlinks/plugins/fluttertoast/ios"
|
||||
in_app_review:
|
||||
:path: ".symlinks/plugins/in_app_review/ios"
|
||||
integration_test:
|
||||
:path: ".symlinks/plugins/integration_test/ios"
|
||||
local_auth_ios:
|
||||
@@ -151,7 +156,7 @@ EXTERNAL SOURCES:
|
||||
package_info_plus:
|
||||
:path: ".symlinks/plugins/package_info_plus/ios"
|
||||
path_provider_foundation:
|
||||
:path: ".symlinks/plugins/path_provider_foundation/darwin"
|
||||
:path: ".symlinks/plugins/path_provider_foundation/ios"
|
||||
pdfx:
|
||||
:path: ".symlinks/plugins/pdfx/ios"
|
||||
permission_handler_apple:
|
||||
@@ -161,7 +166,7 @@ EXTERNAL SOURCES:
|
||||
share_plus:
|
||||
:path: ".symlinks/plugins/share_plus/ios"
|
||||
shared_preferences_foundation:
|
||||
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
|
||||
:path: ".symlinks/plugins/shared_preferences_foundation/ios"
|
||||
sqflite:
|
||||
:path: ".symlinks/plugins/sqflite/ios"
|
||||
url_launcher_ios:
|
||||
@@ -180,6 +185,7 @@ SPEC CHECKSUMS:
|
||||
flutter_native_splash: 52501b97d1c0a5f898d687f1646226c1f93c56ef
|
||||
fluttertoast: eb263d302cc92e04176c053d2385237e9f43fad0
|
||||
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
|
||||
in_app_review: 318597b3a06c22bb46dc454d56828c85f444f99d
|
||||
integration_test: a1e7d09bd98eca2fc37aefd79d4f41ad37bdbbe5
|
||||
local_auth_ios: 0d333dde7780f669e66f19d2ff6005f3ea84008d
|
||||
open_filex: 6e26e659846ec990262224a12ef1c528bb4edbe4
|
||||
@@ -195,7 +201,7 @@ SPEC CHECKSUMS:
|
||||
sqflite: 6d358c025f5b867b29ed92fc697fd34924e11904
|
||||
SwiftyGif: 6c3eafd0ce693cad58bb63d2b2fb9bacb8552780
|
||||
Toast: 91b396c56ee72a5790816f40d3a94dd357abc196
|
||||
url_launcher_ios: ae1517e5e344f5544fb090b079e11f399dfbe4d2
|
||||
url_launcher_ios: fb12c43172927bb5cf75aeebd073f883801f1993
|
||||
WeScan: fed582f6c38014d529afb5aa9ffd1bad38fc72b7
|
||||
|
||||
PODFILE CHECKSUM: 7daa35cc908d9fba025075df27cb57a1ba1ebf13
|
||||
|
||||
@@ -15,6 +15,7 @@ import 'package:paperless_mobile/features/document_edit/cubit/document_edit_cubi
|
||||
import 'package:paperless_mobile/features/edit_label/view/impl/add_correspondent_page.dart';
|
||||
import 'package:paperless_mobile/features/edit_label/view/impl/add_document_type_page.dart';
|
||||
import 'package:paperless_mobile/features/edit_label/view/impl/add_storage_path_page.dart';
|
||||
import 'package:paperless_mobile/features/labels/tags/view/widgets/tag_query_form_field.dart';
|
||||
import 'package:paperless_mobile/features/labels/tags/view/widgets/tags_form_field.dart';
|
||||
import 'package:paperless_mobile/features/labels/view/widgets/label_form_field.dart';
|
||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||
@@ -57,7 +58,6 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<DocumentEditCubit, DocumentEditState>(
|
||||
builder: (context, state) {
|
||||
log("Updated state. correspondents have ${state.correspondents.length} items.");
|
||||
return DefaultTabController(
|
||||
length: 2,
|
||||
child: Scaffold(
|
||||
@@ -207,57 +207,48 @@ class _DocumentEditPageState extends State<DocumentEditPage> {
|
||||
],
|
||||
).padded(),
|
||||
// Tag form field
|
||||
TagFormField(
|
||||
initialValue: IdsTagsQuery.included(
|
||||
state.document.tags.toList()),
|
||||
notAssignedSelectable: false,
|
||||
anyAssignedSelectable: false,
|
||||
excludeAllowed: false,
|
||||
TagQueryFormField(
|
||||
options: state.tags,
|
||||
name: fkTags,
|
||||
selectableOptions: state.tags,
|
||||
suggestions: (_filteredSuggestions?.tags.toSet() ??
|
||||
{})
|
||||
allowOnlySelection: true,
|
||||
allowCreation: true,
|
||||
allowExclude: false,
|
||||
initialValue: IdsTagsQuery.included(
|
||||
state.document.tags,
|
||||
),
|
||||
).padded(),
|
||||
if (_filteredSuggestions?.tags
|
||||
.toSet()
|
||||
.difference(state.document.tags.toSet())
|
||||
.isNotEmpty
|
||||
? _buildSuggestionsSkeleton<int>(
|
||||
.isNotEmpty ??
|
||||
false)
|
||||
_buildSuggestionsSkeleton<int>(
|
||||
suggestions:
|
||||
(_filteredSuggestions?.tags.toSet() ??
|
||||
{}),
|
||||
(_filteredSuggestions?.tags.toSet() ?? {}),
|
||||
itemBuilder: (context, itemData) {
|
||||
final tag = state.tags[itemData]!;
|
||||
return ActionChip(
|
||||
label: Text(
|
||||
tag.name,
|
||||
style:
|
||||
TextStyle(color: tag.textColor),
|
||||
style: TextStyle(color: tag.textColor),
|
||||
),
|
||||
backgroundColor: tag.color,
|
||||
onPressed: () {
|
||||
final currentTags = _formKey
|
||||
.currentState
|
||||
?.fields[fkTags]
|
||||
?.value as TagsQuery;
|
||||
final currentTags = _formKey.currentState
|
||||
?.fields[fkTags]?.value as TagsQuery;
|
||||
if (currentTags is IdsTagsQuery) {
|
||||
_formKey
|
||||
.currentState?.fields[fkTags]
|
||||
?.didChange(
|
||||
(IdsTagsQuery.fromIds({
|
||||
...currentTags.ids,
|
||||
itemData
|
||||
})));
|
||||
_formKey.currentState?.fields[fkTags]
|
||||
?.didChange((IdsTagsQuery.fromIds(
|
||||
{...currentTags.ids, itemData})));
|
||||
} else {
|
||||
_formKey
|
||||
.currentState?.fields[fkTags]
|
||||
?.didChange(
|
||||
(IdsTagsQuery.fromIds(
|
||||
_formKey.currentState?.fields[fkTags]
|
||||
?.didChange((IdsTagsQuery.fromIds(
|
||||
{itemData})));
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
)
|
||||
: null,
|
||||
).padded(),
|
||||
),
|
||||
// Prevent tags from being hidden by fab
|
||||
const SizedBox(height: 64),
|
||||
],
|
||||
|
||||
@@ -194,9 +194,12 @@ class _DocumentFilterFormState extends State<DocumentFilterForm> {
|
||||
|
||||
Widget _buildTagsFormField() {
|
||||
return TagQueryFormField(
|
||||
allowExclude: false,
|
||||
options: widget.tags,
|
||||
name: DocumentModel.tagsKey,
|
||||
initialValue: widget.initialFilter.tags,
|
||||
allowOnlySelection: false,
|
||||
allowCreation: false,
|
||||
);
|
||||
return TagFormField(
|
||||
name: DocumentModel.tagsKey,
|
||||
|
||||
@@ -1,16 +1,321 @@
|
||||
import 'package:flutter/src/widgets/framework.dart';
|
||||
import 'package:flutter/src/widgets/placeholder.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/features/edit_label/view/impl/add_tag_page.dart';
|
||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||
|
||||
class FullscreenTagsForm extends StatefulWidget {
|
||||
const FullscreenTagsForm({super.key});
|
||||
final TagsQuery? initialValue;
|
||||
final Map<int, Tag> options;
|
||||
final void Function({TagsQuery? returnValue}) onSubmit;
|
||||
final bool allowOnlySelection;
|
||||
final bool allowCreation;
|
||||
final bool allowExclude;
|
||||
|
||||
const FullscreenTagsForm({
|
||||
super.key,
|
||||
this.initialValue,
|
||||
required this.options,
|
||||
required this.onSubmit,
|
||||
required this.allowOnlySelection,
|
||||
required this.allowCreation,
|
||||
required this.allowExclude,
|
||||
});
|
||||
|
||||
@override
|
||||
State<FullscreenTagsForm> createState() => _FullscreenTagsFormState();
|
||||
}
|
||||
|
||||
class _FullscreenTagsFormState extends State<FullscreenTagsForm> {
|
||||
late bool _showClearIcon = false;
|
||||
final _textEditingController = TextEditingController();
|
||||
final _focusNode = FocusNode();
|
||||
late List<Tag> _options;
|
||||
|
||||
List<int> _include = [];
|
||||
List<int> _exclude = [];
|
||||
|
||||
bool _anyAssigned = false;
|
||||
bool _notAssigned = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_options = widget.options.values.toList();
|
||||
final value = widget.initialValue;
|
||||
if (value is IdsTagsQuery) {
|
||||
_include = value.includedIds.toList();
|
||||
_exclude = value.excludedIds.toList();
|
||||
} else if (value is AnyAssignedTagsQuery) {
|
||||
_include = value.tagIds.toList();
|
||||
_anyAssigned = true;
|
||||
} else if (value is OnlyNotAssignedTagsQuery) {
|
||||
_notAssigned = true;
|
||||
}
|
||||
_textEditingController.addListener(() => setState(() {
|
||||
_showClearIcon = _textEditingController.text.isNotEmpty;
|
||||
}));
|
||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
||||
//Delay keyboard popup to ensure open animation is finished before.
|
||||
Future.delayed(
|
||||
const Duration(milliseconds: 200),
|
||||
() => _focusNode.requestFocus(),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const Placeholder();
|
||||
final theme = Theme.of(context);
|
||||
return Scaffold(
|
||||
floatingActionButton: widget.allowCreation
|
||||
? FloatingActionButton(
|
||||
onPressed: _onAddTag,
|
||||
child: Icon(Icons.add),
|
||||
)
|
||||
: null,
|
||||
appBar: AppBar(
|
||||
backgroundColor: theme.colorScheme.surface,
|
||||
toolbarHeight: 72,
|
||||
leading: BackButton(
|
||||
color: theme.colorScheme.onSurface,
|
||||
),
|
||||
title: TextFormField(
|
||||
focusNode: _focusNode,
|
||||
controller: _textEditingController,
|
||||
autofocus: true,
|
||||
style: theme.textTheme.bodyLarge?.apply(
|
||||
color: theme.colorScheme.onSurface,
|
||||
),
|
||||
decoration: InputDecoration(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
hintStyle: theme.textTheme.bodyLarge?.apply(
|
||||
color: theme.colorScheme.onSurfaceVariant,
|
||||
),
|
||||
icon: const Icon(Icons.label_outline),
|
||||
hintText: S.of(context)!.startTyping,
|
||||
border: InputBorder.none,
|
||||
),
|
||||
textInputAction: TextInputAction.done,
|
||||
),
|
||||
actions: [
|
||||
if (_showClearIcon)
|
||||
IconButton(
|
||||
icon: const Icon(Icons.clear),
|
||||
onPressed: () {
|
||||
_textEditingController.clear();
|
||||
},
|
||||
),
|
||||
IconButton(
|
||||
tooltip: S.of(context)!.done,
|
||||
icon: const Icon(Icons.done),
|
||||
onPressed: () {
|
||||
if (widget.allowOnlySelection) {
|
||||
widget.onSubmit(returnValue: IdsTagsQuery.included(_include));
|
||||
return;
|
||||
}
|
||||
late final TagsQuery query;
|
||||
if (_notAssigned) {
|
||||
query = const OnlyNotAssignedTagsQuery();
|
||||
} else if (_anyAssigned) {
|
||||
query = AnyAssignedTagsQuery(tagIds: _include);
|
||||
} else {
|
||||
query = IdsTagsQuery([
|
||||
for (var id in _include) IncludeTagIdQuery(id),
|
||||
for (var id in _exclude) ExcludeTagIdQuery(id),
|
||||
]);
|
||||
}
|
||||
widget.onSubmit(returnValue: query);
|
||||
},
|
||||
),
|
||||
],
|
||||
bottom: PreferredSize(
|
||||
preferredSize: !widget.allowOnlySelection
|
||||
? const Size.fromHeight(32)
|
||||
: const Size.fromHeight(1),
|
||||
child: Column(
|
||||
children: [
|
||||
Divider(color: theme.colorScheme.outline),
|
||||
if (!widget.allowOnlySelection)
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: SegmentedButton<bool>(
|
||||
segments: [
|
||||
ButtonSegment(
|
||||
enabled: isSegmentedButtonEnabled,
|
||||
value: false,
|
||||
label: const Text("All"), //TODO: INTL
|
||||
),
|
||||
ButtonSegment(
|
||||
enabled: isSegmentedButtonEnabled,
|
||||
value: true,
|
||||
label: Text(S.of(context)!.anyAssigned),
|
||||
),
|
||||
],
|
||||
multiSelectionEnabled: false,
|
||||
emptySelectionAllowed: true,
|
||||
onSelectionChanged: (value) {
|
||||
setState(() {
|
||||
_anyAssigned = value.first;
|
||||
});
|
||||
},
|
||||
selected: {_anyAssigned},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
body: Builder(
|
||||
builder: (context) {
|
||||
final options = _buildOptions(_textEditingController.text);
|
||||
return Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: ListView.builder(
|
||||
padding: EdgeInsets.zero,
|
||||
shrinkWrap: true,
|
||||
itemCount: options.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
return options.elementAt(index);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _onAddTag() async {
|
||||
final createdTag = await Navigator.of(context).push<Tag?>(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => AddTagPage(
|
||||
initialValue: _textEditingController.text,
|
||||
),
|
||||
),
|
||||
);
|
||||
_textEditingController.clear();
|
||||
if (createdTag != null) {
|
||||
setState(() {
|
||||
_options.add(createdTag);
|
||||
_toggleSelection(createdTag.id!);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
bool get isSegmentedButtonEnabled {
|
||||
return _exclude.isEmpty && _include.length > 1;
|
||||
}
|
||||
|
||||
Widget _buildNotAssignedOption() {
|
||||
return ListTile(
|
||||
title: Text(S.of(context)!.notAssigned),
|
||||
trailing: _notAssigned ? const Icon(Icons.done) : null,
|
||||
onTap: () {
|
||||
setState(() {
|
||||
_notAssigned = !_notAssigned;
|
||||
_include = [];
|
||||
_exclude = [];
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
///
|
||||
/// Filters the options passed to this widget by the current [query] and
|
||||
/// adds not-/any assigned options
|
||||
///
|
||||
Iterable<Widget> _buildOptions(String query) sync* {
|
||||
final normalizedQuery = query.trim().toLowerCase();
|
||||
|
||||
if (!widget.allowOnlySelection &&
|
||||
S.of(context)!.notAssigned.toLowerCase().contains(normalizedQuery)) {
|
||||
yield _buildNotAssignedOption();
|
||||
}
|
||||
|
||||
var matches = _options
|
||||
.where((e) => e.name.trim().toLowerCase().contains(normalizedQuery));
|
||||
if (matches.isEmpty && widget.allowCreation) {
|
||||
yield Text(S.of(context)!.noItemsFound);
|
||||
yield TextButton(
|
||||
child: Text(S.of(context)!.addTag),
|
||||
onPressed: _onAddTag,
|
||||
);
|
||||
}
|
||||
for (final tag in matches) {
|
||||
yield SelectableTagWidget(
|
||||
tag: tag,
|
||||
excluded: _exclude.contains(tag.id),
|
||||
selected: _include.contains(tag.id),
|
||||
onTap: () => _toggleSelection(tag.id!),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _toggleSelection(int id) {
|
||||
if (widget.allowOnlySelection || widget.allowExclude) {
|
||||
if (_include.contains(id)) {
|
||||
setState(() => _include.remove(id));
|
||||
} else {
|
||||
setState(() => _include.add(id));
|
||||
}
|
||||
} else {
|
||||
if (_include.contains(id)) {
|
||||
setState(() {
|
||||
_notAssigned = false;
|
||||
_anyAssigned = false;
|
||||
_include.remove(id);
|
||||
_exclude.add(id);
|
||||
});
|
||||
} else if (_exclude.contains(id)) {
|
||||
setState(() {
|
||||
_notAssigned = false;
|
||||
_exclude.remove(id);
|
||||
});
|
||||
} else {
|
||||
setState(() {
|
||||
_notAssigned = false;
|
||||
_include.add(id);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SelectableTagWidget extends StatelessWidget {
|
||||
final Tag tag;
|
||||
final bool selected;
|
||||
final bool excluded;
|
||||
final VoidCallback onTap;
|
||||
|
||||
const SelectableTagWidget({
|
||||
super.key,
|
||||
required this.tag,
|
||||
required this.excluded,
|
||||
required this.selected,
|
||||
required this.onTap,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListTile(
|
||||
title: Text(tag.name),
|
||||
trailing: excluded
|
||||
? const Icon(Icons.close)
|
||||
: (selected ? const Icon(Icons.done) : null),
|
||||
leading: CircleAvatar(
|
||||
backgroundColor: tag.color,
|
||||
child: (tag.isInboxTag ?? false)
|
||||
? Icon(
|
||||
Icons.inbox,
|
||||
color: tag.textColor,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
onTap: onTap,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,53 +1,58 @@
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:animations/animations.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/core/workarounds/colored_chip.dart';
|
||||
import 'package:paperless_mobile/features/labels/tags/view/widgets/fullscreen_tags_form.dart';
|
||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||
|
||||
class TagQueryFormField extends StatelessWidget {
|
||||
final String name;
|
||||
final Map<int, Tag> options;
|
||||
final TagsQuery? initialValue;
|
||||
final bool allowOnlySelection;
|
||||
final bool allowCreation;
|
||||
final bool allowExclude;
|
||||
|
||||
const TagQueryFormField({
|
||||
super.key,
|
||||
required this.options,
|
||||
this.initialValue,
|
||||
required this.name,
|
||||
required this.allowOnlySelection,
|
||||
required this.allowCreation,
|
||||
required this.allowExclude,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
log(initialValue.toString());
|
||||
|
||||
return FormBuilderField<TagsQuery?>(
|
||||
initialValue: initialValue,
|
||||
builder: (field) {
|
||||
final values = _generateOptions(context, field.value, field).toList();
|
||||
final isEmpty = (field.value is IdsTagsQuery &&
|
||||
(field.value as IdsTagsQuery).ids.isEmpty) ||
|
||||
field.value == null;
|
||||
final values = _generateOptions(context, field.value, field).toList();
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => Dialog.fullscreen(
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text("Test"),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
bool anyAssigned = field.value is AnyAssignedTagsQuery;
|
||||
return OpenContainer<TagsQuery>(
|
||||
middleColor: Theme.of(context).colorScheme.background,
|
||||
closedColor: Theme.of(context).colorScheme.background,
|
||||
openColor: Theme.of(context).colorScheme.background,
|
||||
closedShape: InputBorder.none,
|
||||
openElevation: 0,
|
||||
closedElevation: 0,
|
||||
closedBuilder: (context, openForm) => Container(
|
||||
margin: const EdgeInsets.only(top: 6),
|
||||
child: GestureDetector(
|
||||
onTap: openForm,
|
||||
child: InputDecorator(
|
||||
isEmpty: isEmpty,
|
||||
decoration: InputDecoration(
|
||||
contentPadding: const EdgeInsets.all(12),
|
||||
labelText: S.of(context)!.tags,
|
||||
labelText:
|
||||
'${S.of(context)!.tags}${anyAssigned ? ' (${S.of(context)!.anyAssigned})' : ''}',
|
||||
prefixIcon: const Icon(Icons.label_outline),
|
||||
),
|
||||
child: SizedBox(
|
||||
@@ -60,6 +65,20 @@ class TagQueryFormField extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
),
|
||||
)),
|
||||
openBuilder: (context, closeForm) => FullscreenTagsForm(
|
||||
options: options,
|
||||
onSubmit: closeForm,
|
||||
initialValue: field.value,
|
||||
allowOnlySelection: allowOnlySelection,
|
||||
allowCreation: allowCreation,
|
||||
allowExclude: allowExclude,
|
||||
),
|
||||
onClosed: (data) {
|
||||
if (data != null) {
|
||||
field.didChange(data);
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
name: name,
|
||||
@@ -80,7 +99,9 @@ class TagQueryFormField extends StatelessWidget {
|
||||
} else if (query is OnlyNotAssignedTagsQuery) {
|
||||
yield _buildNotAssignedTagWidget(context, field);
|
||||
} else if (query is AnyAssignedTagsQuery) {
|
||||
yield _buildAnyAssignedTagWidget(context, field);
|
||||
for (final e in query.tagIds) {
|
||||
yield _buildAnyAssignedTagWidget(context, e, field, query);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,7 +115,9 @@ class TagQueryFormField extends StatelessWidget {
|
||||
final tag = options[e.id]!;
|
||||
return QueryTagChip(
|
||||
onDeleted: () => field.didChange(formValue.withIdsRemoved([e.id])),
|
||||
onSelected: () => field.didChange(formValue.withIdQueryToggled(e.id)),
|
||||
onSelected: allowExclude
|
||||
? () => field.didChange(formValue.withIdQueryToggled(e.id))
|
||||
: null,
|
||||
exclude: e is ExcludeTagIdQuery,
|
||||
backgroundColor: tag.color,
|
||||
foregroundColor: tag.textColor,
|
||||
@@ -116,13 +139,24 @@ class TagQueryFormField extends StatelessWidget {
|
||||
}
|
||||
|
||||
Widget _buildAnyAssignedTagWidget(
|
||||
BuildContext context, FormFieldState<TagsQuery?> field) {
|
||||
BuildContext context,
|
||||
int e,
|
||||
FormFieldState<TagsQuery?> field,
|
||||
AnyAssignedTagsQuery query,
|
||||
) {
|
||||
return QueryTagChip(
|
||||
onDeleted: () => field.didChange(const IdsTagsQuery()),
|
||||
onDeleted: () {
|
||||
final updatedQuery = query.withRemoved([e]);
|
||||
if (updatedQuery.tagIds.isEmpty) {
|
||||
field.didChange(const IdsTagsQuery());
|
||||
} else {
|
||||
field.didChange(updatedQuery);
|
||||
}
|
||||
},
|
||||
exclude: false,
|
||||
backgroundColor: Colors.grey,
|
||||
foregroundColor: Colors.black,
|
||||
labelText: S.of(context)!.anyAssigned,
|
||||
backgroundColor: options[e]!.color,
|
||||
foregroundColor: options[e]!.textColor,
|
||||
labelText: options[e]!.name,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:animations/animations.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||
@@ -11,8 +9,6 @@ import 'package:paperless_mobile/core/repository/label_repository.dart';
|
||||
import 'package:paperless_mobile/core/workarounds/colored_chip.dart';
|
||||
import 'package:paperless_mobile/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/features/edit_label/view/impl/add_tag_page.dart';
|
||||
import 'package:paperless_mobile/features/labels/tags/view/widgets/fullscreen_tags_form.dart';
|
||||
import 'package:paperless_mobile/features/labels/tags/view/widgets/tag_widget.dart';
|
||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||
|
||||
class TagFormField extends StatefulWidget {
|
||||
|
||||
@@ -81,7 +81,7 @@ class LabelFormField<T extends Label> extends StatelessWidget {
|
||||
openElevation: 0,
|
||||
closedElevation: 0,
|
||||
closedBuilder: (context, openForm) => Container(
|
||||
margin: const EdgeInsets.only(top: 4),
|
||||
margin: const EdgeInsets.only(top: 6),
|
||||
child: TextField(
|
||||
controller: controller,
|
||||
onTap: openForm,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
import 'tags_query.dart';
|
||||
@@ -22,6 +23,16 @@ class AnyAssignedTagsQuery extends TagsQuery {
|
||||
@override
|
||||
List<Object?> get props => [tagIds];
|
||||
|
||||
AnyAssignedTagsQuery withRemoved(Iterable<int> ids) {
|
||||
return AnyAssignedTagsQuery(
|
||||
tagIds: tagIds.toSet().difference(ids.toSet()).toList(),
|
||||
);
|
||||
}
|
||||
|
||||
AnyAssignedTagsQuery withAdded(Iterable<int> ids) {
|
||||
return AnyAssignedTagsQuery(tagIds: [...tagIds, ...ids]);
|
||||
}
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() => _$AnyAssignedTagsQueryToJson(this);
|
||||
|
||||
|
||||
0
scripts/install_dependencies.sh
Normal file → Executable file
0
scripts/install_dependencies.sh
Normal file → Executable file
Reference in New Issue
Block a user