Skip to content

Commit 3c0c146

Browse files
committed
feat: fixed select tag dialog
1 parent 76cb77f commit 3c0c146

6 files changed

Lines changed: 204 additions & 688 deletions

File tree

lib/components/appbars/root_search_app_bar.dart

Lines changed: 11 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import 'package:flutter/material.dart';
2-
import 'package:piwigo_ng/components/dialogs/tags_dialogs.dart';
2+
import 'package:piwigo_ng/components/modals/open_tag_modal.dart';
33
import 'package:piwigo_ng/components/notification_dot.dart';
44
import 'package:piwigo_ng/components/popup_list_item.dart';
55
import 'package:piwigo_ng/services/preferences_service.dart';
@@ -41,9 +41,7 @@ class _RootSearchAppBarState extends State<RootSearchAppBar> {
4141
if (widget.scrollController.offset > _expandedHeight * _opacityScale) {
4242
return 0.0;
4343
}
44-
return (_expandedHeight * _opacityScale -
45-
widget.scrollController.offset) /
46-
(_expandedHeight * _opacityScale);
44+
return (_expandedHeight * _opacityScale - widget.scrollController.offset) / (_expandedHeight * _opacityScale);
4745
}
4846
return 1.0;
4947
}
@@ -59,8 +57,7 @@ class _RootSearchAppBarState extends State<RootSearchAppBar> {
5957
}
6058

6159
// In case 0%-100% of the expanded height is viewed
62-
double scrollDelta =
63-
(_expandedHeight - widget.scrollController.offset) / _expandedHeight;
60+
double scrollDelta = (_expandedHeight - widget.scrollController.offset) / _expandedHeight;
6461
double scrollPercent = (scrollDelta * 2 - 1);
6562
return (1 - scrollPercent) * delta * basePadding + basePadding;
6663
}
@@ -72,8 +69,7 @@ class _RootSearchAppBarState extends State<RootSearchAppBar> {
7269
Widget build(BuildContext context) {
7370
return SliverAppBar(
7471
leading: IconButton(
75-
onPressed: () =>
76-
Navigator.of(context).pushNamed(SettingsPage.routeName),
72+
onPressed: () => Navigator.of(context).pushNamed(SettingsPage.routeName),
7773
icon: const Icon(Icons.settings),
7874
),
7975
pinned: true,
@@ -91,7 +87,6 @@ class _RootSearchAppBarState extends State<RootSearchAppBar> {
9187
child: AppField(
9288
padding: const EdgeInsets.symmetric(vertical: 8.0),
9389
prefix: Icon(Icons.search),
94-
hint: "Search...",
9590
),
9691
),
9792
),
@@ -126,8 +121,7 @@ class _RootSearchAppBarState extends State<RootSearchAppBar> {
126121
PopupMenuItem(
127122
onTap: () => Future.delayed(
128123
const Duration(seconds: 0),
129-
() =>
130-
Navigator.of(context).pushNamed(UploadStatusPage.routeName),
124+
() => Navigator.of(context).pushNamed(UploadStatusPage.routeName),
131125
),
132126
child: Stack(
133127
children: [
@@ -138,8 +132,7 @@ class _RootSearchAppBarState extends State<RootSearchAppBar> {
138132
Positioned(
139133
top: 14.0,
140134
left: 0.0,
141-
child: Consumer<UploadNotifier>(
142-
builder: (context, uploadNotifier, child) {
135+
child: Consumer<UploadNotifier>(builder: (context, uploadNotifier, child) {
143136
return NotificationDot(
144137
isShown: uploadNotifier.uploadList.isNotEmpty,
145138
);
@@ -149,8 +142,9 @@ class _RootSearchAppBarState extends State<RootSearchAppBar> {
149142
),
150143
),
151144
PopupMenuItem(
152-
onTap: () => (
153-
showChooseTagSheet(context)
145+
onTap: () => Future.delayed(
146+
const Duration(seconds: 0),
147+
() => showOpenTagModal(context),
154148
),
155149
child: PopupListItem(
156150
icon: Icons.local_offer_outlined,
@@ -161,8 +155,7 @@ class _RootSearchAppBarState extends State<RootSearchAppBar> {
161155
PopupMenuItem(
162156
onTap: () => Future.delayed(
163157
const Duration(seconds: 0),
164-
() => Navigator.of(context)
165-
.pushNamed(ImageFavoritesPage.routeName),
158+
() => Navigator.of(context).pushNamed(ImageFavoritesPage.routeName),
166159
),
167160
child: PopupListItem(
168161
icon: Icons.favorite,
@@ -174,8 +167,7 @@ class _RootSearchAppBarState extends State<RootSearchAppBar> {
174167
Positioned(
175168
top: 12.0,
176169
left: 12.0,
177-
child: Consumer<UploadNotifier>(
178-
builder: (context, uploadNotifier, child) {
170+
child: Consumer<UploadNotifier>(builder: (context, uploadNotifier, child) {
179171
return NotificationDot(
180172
isShown: uploadNotifier.uploadList.isNotEmpty,
181173
);
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
3+
import 'package:piwigo_ng/components/fields/app_field.dart';
4+
import 'package:piwigo_ng/models/tag_model.dart';
5+
import 'package:piwigo_ng/network/api_error.dart';
6+
import 'package:piwigo_ng/network/tags.dart';
7+
import 'package:piwigo_ng/utils/localizations.dart';
8+
import 'package:piwigo_ng/views/image/image_tags_page.dart';
9+
10+
class OpenTagModal extends StatefulWidget {
11+
const OpenTagModal({super.key});
12+
13+
@override
14+
_OpenTagModalState createState() => _OpenTagModalState();
15+
}
16+
17+
class _OpenTagModalState extends State<OpenTagModal> {
18+
final ScrollController _scrollController = ScrollController();
19+
late final Future _tagsFuture;
20+
21+
String _searchQuery = '';
22+
List<TagModel>? _tagList;
23+
24+
@override
25+
void initState() {
26+
super.initState();
27+
_tagsFuture = _onRefresh();
28+
}
29+
30+
@override
31+
void dispose() {
32+
_scrollController.dispose();
33+
super.dispose();
34+
}
35+
36+
Future<void> _onRefresh() async {
37+
try {
38+
final ApiResponse<List<TagModel>> result = await getTags();
39+
if (!result.hasData) return;
40+
setState(() {
41+
_tagList = result.data!.where((tag) => tag.counter > 0).toList()..sort((a, b) => a.name.compareTo(b.name));
42+
});
43+
} catch (e) {
44+
setState(() {
45+
_tagList = null;
46+
});
47+
}
48+
}
49+
50+
void _onSelectTag(TagModel tag) {
51+
Navigator.of(context).pop();
52+
Navigator.of(context).pushNamed(
53+
ImageTagsPage.routeName,
54+
arguments: {
55+
'tag': tag,
56+
},
57+
);
58+
}
59+
60+
@override
61+
Widget build(BuildContext context) {
62+
return Scaffold(
63+
backgroundColor: Colors.transparent,
64+
appBar: AppBar(
65+
shape: const RoundedRectangleBorder(
66+
borderRadius: BorderRadius.vertical(
67+
top: Radius.circular(15.0),
68+
),
69+
),
70+
elevation: 0.0,
71+
scrolledUnderElevation: 5.0,
72+
leading: IconButton(
73+
icon: Icon(Icons.close),
74+
onPressed: () => Navigator.of(context).pop(),
75+
),
76+
title: Text(appStrings.tags),
77+
),
78+
body: ListView(
79+
controller: ModalScrollController.of(context),
80+
physics: const AlwaysScrollableScrollPhysics(),
81+
children: [
82+
Padding(
83+
padding: const EdgeInsets.symmetric(
84+
horizontal: 16.0,
85+
vertical: 8.0,
86+
),
87+
child: AppField(
88+
padding: const EdgeInsets.symmetric(vertical: 8.0),
89+
prefix: Icon(Icons.search),
90+
onChanged: (query) => setState(() {
91+
_searchQuery = query;
92+
}),
93+
),
94+
),
95+
FutureBuilder(
96+
future: _tagsFuture,
97+
builder: (context, snapshot) {
98+
switch (snapshot.connectionState) {
99+
case ConnectionState.done:
100+
return _buildTagList();
101+
default:
102+
return Center(
103+
child: CircularProgressIndicator(),
104+
);
105+
}
106+
},
107+
),
108+
],
109+
),
110+
);
111+
}
112+
113+
Widget _buildTagList() {
114+
if (_tagList == null) {
115+
return Center(
116+
child: Text(appStrings.coreDataFetch_TagError),
117+
);
118+
}
119+
120+
List<TagModel> tags =
121+
_tagList!.where((tag) => tag.name.toLowerCase().contains(_searchQuery.toLowerCase())).toList();
122+
if (tags.isEmpty) {
123+
return Center(
124+
child: Text(appStrings.none),
125+
);
126+
}
127+
128+
return Column(
129+
children: tags.map((tag) => _buildItem(tag)).toList(),
130+
);
131+
}
132+
133+
Widget _buildItem(TagModel tag) => ListTile(
134+
visualDensity: VisualDensity.compact,
135+
shape: Border(
136+
bottom: BorderSide(color: Theme.of(context).scaffoldBackgroundColor),
137+
),
138+
title: Text(tag.name),
139+
trailing: Text(appStrings.imageCount(tag.counter)),
140+
onTap: () => _onSelectTag(tag),
141+
);
142+
}
143+
144+
Future<int?> showOpenTagModal(BuildContext context) async {
145+
return showMaterialModalBottomSheet<int>(
146+
context: context,
147+
enableDrag: false,
148+
shape: RoundedRectangleBorder(
149+
borderRadius: BorderRadius.vertical(top: Radius.circular(30.0)),
150+
),
151+
backgroundColor: Theme.of(context).scaffoldBackgroundColor,
152+
builder: (context) => OpenTagModal(),
153+
);
154+
}

lib/components/modals/select_tags_modal.dart

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,10 @@ class _SelectTagsModalState extends State<SelectTagsModal> {
4141
super.dispose();
4242
}
4343

44-
List<TagModel> get _unselectedTagList =>
45-
_tagList!.where((t) => !_selectedTagList.contains(t)).toList();
44+
List<TagModel> get _unselectedTagList => _tagList!.where((t) => !_selectedTagList.contains(t)).toList();
4645

4746
Future<void> _onRefresh() async {
48-
final ApiResponse<List<TagModel>> result = await getTags();
47+
final ApiResponse<List<TagModel>> result = await getAdminTags();
4948
if (!result.hasData) return;
5049
setState(() {
5150
_tagList = result.data!;

lib/network/tags.dart

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,31 @@ import 'package:piwigo_ng/services/preferences_service.dart';
99
import 'api_client.dart';
1010

1111
Future<ApiResponse<List<TagModel>>> getTags() async {
12+
Map<String, String> queries = {
13+
'format': 'json',
14+
'method': 'pwg.tags.getList',
15+
};
16+
17+
Response response = await ApiClient.get(queryParameters: queries);
18+
19+
try {
20+
if (response.statusCode == 200) {
21+
var data = json.decode(response.data);
22+
if (data['stat'] == 'fail') {
23+
return ApiResponse(error: ApiErrors.error);
24+
}
25+
List<TagModel> tags = data['result']['tags'].map<TagModel>((tag) => TagModel.fromJson(tag)).toList();
26+
return ApiResponse(data: tags);
27+
}
28+
} on DioError catch (e) {
29+
debugPrint('Get tags: ${e.message}');
30+
} on Error catch (e) {
31+
debugPrint('Get tags: $e\n${e.stackTrace}');
32+
}
33+
return ApiResponse(error: ApiErrors.error);
34+
}
35+
36+
Future<ApiResponse<List<TagModel>>> getAdminTags() async {
1237
Map<String, String> queries = {
1338
'format': 'json',
1439
'method': 'pwg.tags.getAdminList',
@@ -22,9 +47,7 @@ Future<ApiResponse<List<TagModel>>> getTags() async {
2247
if (data['stat'] == 'fail') {
2348
return ApiResponse(error: ApiErrors.error);
2449
}
25-
List<TagModel> tags = data['result']['tags']
26-
.map<TagModel>((tag) => TagModel.fromJson(tag))
27-
.toList();
50+
List<TagModel> tags = data['result']['tags'].map<TagModel>((tag) => TagModel.fromJson(tag)).toList();
2851
return ApiResponse(data: tags);
2952
}
3053
} on DioError catch (e) {
@@ -72,10 +95,7 @@ Future<dynamic> editTag(int tagId, String tagName) async {
7295
"new_name": tagName,
7396
'pwg_token': appPreferences.getString('PWG_TOKEN'),
7497
});
75-
Response response = await ApiClient.post(
76-
data: formData,
77-
queryParameters: queries
78-
);
98+
Response response = await ApiClient.post(data: formData, queryParameters: queries);
7999

80100
try {
81101
if (response.statusCode == 200) {
@@ -91,4 +111,4 @@ Future<dynamic> editTag(int tagId, String tagName) async {
91111
debugPrint('Get tags: $e\n${e.stackTrace}');
92112
}
93113
return ApiResponse(error: ApiErrors.error);
94-
}
114+
}

0 commit comments

Comments
 (0)