Skip to content

Commit 57cf09d

Browse files
committed
implemented search feature
1 parent 367f2ca commit 57cf09d

3 files changed

Lines changed: 188 additions & 142 deletions

File tree

lib/views/RootCategoryViewPage.dart

Lines changed: 152 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import 'dart:async';
22

3+
import 'package:auto_size_text/auto_size_text.dart';
34
import 'package:flutter/material.dart';
45

56
import 'package:piwigo_ng/api/API.dart';
@@ -13,6 +14,9 @@ import 'package:piwigo_ng/views/SettingsViewPage.dart';
1314
import 'package:piwigo_ng/views/components/appbars.dart';
1415
import 'package:piwigo_ng/views/components/dialogs/dialogs.dart';
1516

17+
import '../api/SearchAPI.dart';
18+
import 'ImageViewPage.dart';
19+
1620
class RootCategoryViewPage extends StatefulWidget {
1721
final bool isAdmin;
1822

@@ -24,11 +28,21 @@ class _RootCategoryViewPageState extends State<RootCategoryViewPage> with Single
2428
String _rootCategory;
2529
TextEditingController _searchController = TextEditingController();
2630
ScrollController _scrollController = ScrollController();
31+
bool _isSearching = false;
32+
33+
Future<Map<String,dynamic>> _albumsFuture;
34+
Future<Map<String,dynamic>> _imagesFuture;
2735

2836
@override
2937
void initState() {
3038
super.initState();
3139
_rootCategory = "0";
40+
_searchController.addListener(() {
41+
_getData();
42+
setState(() {
43+
_isSearching = _searchController.text.length > 0;
44+
});
45+
});
3246
WidgetsBinding.instance.addPostFrameCallback((_) {
3347
API.uploader = Uploader(context);
3448
});
@@ -38,35 +52,89 @@ class _RootCategoryViewPageState extends State<RootCategoryViewPage> with Single
3852
super.dispose();
3953
}
4054

55+
_getData() {
56+
_albumsFuture = fetchAlbums(_rootCategory);
57+
_imagesFuture = searchAlbums(_searchController.text);
58+
}
59+
4160
@override
4261
Widget build(BuildContext context) {
4362
ThemeData _theme = Theme.of(context);
4463
return Scaffold(
45-
resizeToAvoidBottomInset: true,
46-
body: NestedScrollView(
47-
controller: _scrollController,
48-
headerSliverBuilder: (context, innerBoxScrolled) => [
49-
// AppBarExpandable(
50-
AppBarExpandableSearch(
51-
scrollController: _scrollController,
52-
textController: _searchController,
53-
leading: IconButton(
54-
onPressed: () {
55-
Navigator.of(context).push(
56-
MaterialPageRoute(builder: (context) => SettingsPage()),
57-
);
58-
},
59-
icon: Icon(Icons.settings, color: _theme.iconTheme.color),
60-
),
61-
title: appStrings(context).tabBar_albums,
62-
),
63-
],
64-
body: GestureDetector(
65-
onTap: () {
66-
FocusScope.of(context).unfocus();
64+
body: GestureDetector(
65+
onTap: () {
66+
FocusScope.of(context).unfocus();
67+
},
68+
child: NestedScrollView(
69+
controller: _scrollController,
70+
headerSliverBuilder: (context, bool) {
71+
return [
72+
AppBarExpandable(
73+
scrollController: _scrollController,
74+
leading: IconButton(
75+
onPressed: () {
76+
Navigator.of(context).push(
77+
MaterialPageRoute(builder: (context) => SettingsPage()),
78+
);
79+
},
80+
icon: Icon(Icons.settings, color: _theme.iconTheme.color),
81+
),
82+
title: appStrings(context).tabBar_albums,
83+
),
84+
AppBarExpandableSearch(
85+
textController: _searchController,
86+
),
87+
];
6788
},
68-
child: FutureBuilder<Map<String,dynamic>>(
69-
future: fetchAlbums(_rootCategory), // Albums of the list
89+
body: Builder(builder: (context) {
90+
if(_isSearching) {
91+
return FutureBuilder<Map<String,dynamic>>(
92+
future: _imagesFuture, // Albums of the list
93+
builder: (BuildContext context, AsyncSnapshot imagesSnapshot) {
94+
if(imagesSnapshot.hasData){
95+
if(imagesSnapshot.data['stat'] == 'fail') {
96+
return Center(
97+
child: Text(appStrings(context).categoryImageList_noDataError),
98+
);
99+
}
100+
var images = imagesSnapshot.data['result']['images'];
101+
var nbImages = images.length;
102+
return RefreshIndicator(
103+
displacement: 20,
104+
notificationPredicate: (notification) {
105+
return notification.metrics.atEdge;
106+
},
107+
onRefresh: () {
108+
setState(() {
109+
print("refresh");
110+
});
111+
return Future.delayed(Duration(milliseconds: 1000));
112+
},
113+
child: SingleChildScrollView(
114+
physics: NeverScrollableScrollPhysics(),
115+
child: Column(
116+
children: [
117+
_imageGrid(images),
118+
Center(
119+
child: Container(
120+
padding: EdgeInsets.all(10),
121+
child: Text(appStrings(context).imageCount(nbImages), style: TextStyle(fontSize: 20, color: _theme.textTheme.bodyText2.color, fontWeight: FontWeight.w300)),
122+
),
123+
),
124+
],
125+
),
126+
),
127+
);
128+
} else {
129+
return Center(
130+
child: CircularProgressIndicator(),
131+
);
132+
}
133+
},
134+
);
135+
}
136+
return FutureBuilder<Map<String,dynamic>>(
137+
future: _albumsFuture, // Albums of the list
70138
builder: (BuildContext context, AsyncSnapshot albumSnapshot) {
71139
if(albumSnapshot.hasData){
72140
if(albumSnapshot.data['stat'] == 'fail') {
@@ -93,6 +161,7 @@ class _RootCategoryViewPageState extends State<RootCategoryViewPage> with Single
93161
return Future.delayed(Duration(milliseconds: 1000));
94162
},
95163
child: SingleChildScrollView(
164+
physics: NeverScrollableScrollPhysics(),
96165
child: Column(
97166
children: [
98167
_albumGrid(albums),
@@ -111,11 +180,12 @@ class _RootCategoryViewPageState extends State<RootCategoryViewPage> with Single
111180
child: CircularProgressIndicator(),
112181
);
113182
}
114-
}
115-
),
183+
},
184+
);
185+
}),
116186
),
117187
),
118-
floatingActionButton: widget.isAdmin? FloatingActionButton(
188+
floatingActionButton: widget.isAdmin ? FloatingActionButton(
119189
onPressed: () {
120190
showDialog(
121191
context: context,
@@ -129,7 +199,7 @@ class _RootCategoryViewPageState extends State<RootCategoryViewPage> with Single
129199
});
130200
},
131201
child: Icon(Icons.create_new_folder, color: _theme.primaryColorLight, size: 30),
132-
) : Container(),
202+
) : null,
133203
);
134204
}
135205

@@ -156,4 +226,58 @@ class _RootCategoryViewPageState extends State<RootCategoryViewPage> with Single
156226
},
157227
);
158228
}
229+
Widget _imageGrid(dynamic images) {
230+
return GridView.builder(
231+
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
232+
crossAxisCount: getImageCrossAxisCount(context),
233+
mainAxisSpacing: 3.0,
234+
crossAxisSpacing: 3.0,
235+
),
236+
padding: EdgeInsets.symmetric(horizontal: 5),
237+
itemCount: images.length,
238+
shrinkWrap: true,
239+
physics: NeverScrollableScrollPhysics(),
240+
itemBuilder: (BuildContext context, int index) {
241+
var image = images[index];
242+
return InkWell(
243+
onTap: () {
244+
Navigator.of(context).push(
245+
MaterialPageRoute(builder: (context) => ImageViewPage(
246+
images: images,
247+
index: index,
248+
isAdmin: widget.isAdmin,
249+
)),
250+
).whenComplete(() => setState(() {}));
251+
},
252+
child: Stack(
253+
alignment: Alignment.center,
254+
children: [
255+
Container(
256+
width: double.infinity,
257+
height: double.infinity,
258+
child: Image.network(images[index]["derivatives"][API.prefs.getString('thumbnail_size')]["url"],
259+
fit: BoxFit.cover,
260+
),
261+
),
262+
API.prefs.getBool('show_thumbnail_title')? Align(
263+
alignment: Alignment.bottomCenter,
264+
child: Container(
265+
width: double.infinity,
266+
color: Color(0x80ffffff),
267+
child: AutoSizeText('${image['name']}',
268+
overflow: TextOverflow.ellipsis,
269+
maxLines: 1,
270+
style: TextStyle(fontSize: 12),
271+
maxFontSize: 14, minFontSize: 7,
272+
textAlign: TextAlign.center,
273+
),
274+
),
275+
) : Center(),
276+
],
277+
),
278+
);
279+
},
280+
);
281+
}
282+
159283
}

lib/views/components/appbars.dart

Lines changed: 18 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,8 @@ class _AppBarExpandableState extends State<AppBarExpandable> {
6767

6868

6969
class AppBarExpandableSearch extends StatefulWidget {
70-
const AppBarExpandableSearch({Key key, this.leading, this.title = '', this.actions, this.scrollController, this.textController}) : super(key: key);
70+
const AppBarExpandableSearch({Key key, this.leading, this.title = '', this.actions, this.textController}) : super(key: key);
7171

72-
final ScrollController scrollController;
7372
final TextEditingController textController;
7473
final Widget leading;
7574
final String title;
@@ -81,106 +80,26 @@ class AppBarExpandableSearch extends StatefulWidget {
8180

8281
class _AppBarExpandableSearchState extends State<AppBarExpandableSearch> {
8382

84-
85-
@override
86-
initState() {
87-
widget.scrollController.addListener(() => refresh());
88-
super.initState();
89-
}
90-
91-
void refresh() {
92-
setState(() {
93-
94-
});
95-
}
96-
97-
double horizontalTitlePadding(height) {
98-
99-
if (height <= 100) {
100-
return 50;
101-
}
102-
103-
const kBasePadding = 10.0;
104-
const kMultiplier = 0.7;
105-
const kExpandedHeight = 120;
106-
107-
if (widget.scrollController.hasClients) {
108-
109-
if (widget.scrollController.offset > (kExpandedHeight - kToolbarHeight)) {
110-
return (kExpandedHeight - kToolbarHeight) * kMultiplier +
111-
kBasePadding;
112-
}
113-
114-
return (widget.scrollController.offset) * kMultiplier + kBasePadding;
115-
}
116-
117-
return kBasePadding;
118-
}
119-
120-
double verticalTitlePadding(height) {
121-
122-
if (height <= 100) {
123-
return 15.0;
124-
}
125-
126-
const kBasePadding = 15.0;
127-
const kMultiplier = 0.11;
128-
const kExpandedHeight = 20;
129-
130-
if (widget.scrollController.hasClients) {
131-
132-
return kBasePadding - (widget.scrollController.offset) * kMultiplier;
133-
}
134-
135-
return kBasePadding;
136-
}
137-
13883
@override
13984
Widget build(BuildContext context) {
14085
return SliverAppBar(
141-
pinned: true,
142-
expandedHeight: 150,
143-
backgroundColor: Theme.of(context).appBarTheme.backgroundColor,
144-
leading: widget.leading ?? SizedBox(),
145-
actions: widget.actions ?? [],
146-
flexibleSpace: LayoutBuilder(builder: (context, constraints) {
147-
// print(constraints.biggest.height);
148-
Widget flexibleTitle;
149-
Widget textTitle = Container(
150-
child: Text(widget.title),
151-
padding: EdgeInsets.symmetric(
152-
vertical: verticalTitlePadding(constraints.biggest.height),
153-
horizontal: horizontalTitlePadding(constraints.biggest.height)
154-
),
155-
);
156-
157-
if (constraints.biggest.height > 100) {
158-
flexibleTitle = Column(
159-
children: [
160-
textTitle,
161-
TextFieldSearch(
162-
controller: widget.textController,
163-
hint: "Search...", // TODO Localization
164-
margin: EdgeInsets.symmetric(horizontal: 10),
165-
)
166-
],
167-
crossAxisAlignment: CrossAxisAlignment.start,
168-
mainAxisSize: MainAxisSize.min,
169-
170-
);
171-
172-
} else {
173-
flexibleTitle = textTitle;
174-
}
175-
return FlexibleSpaceBar(
176-
title: flexibleTitle,
177-
titlePadding: EdgeInsets.symmetric(
178-
vertical: 0,
179-
horizontal: 0
180-
),
181-
);
182-
183-
})
86+
expandedHeight: 30,
87+
toolbarHeight: 0,
88+
floating: true,
89+
snap: true,
90+
pinned: true,
91+
backgroundColor: Theme.of(context).appBarTheme.backgroundColor,
92+
leading: widget.leading ?? SizedBox(),
93+
actions: widget.actions ?? [],
94+
flexibleSpace: FlexibleSpaceBar(
95+
background: Center(
96+
child: TextFieldSearch(
97+
controller: widget.textController,
98+
hint: "Search...", // TODO Localization
99+
margin: EdgeInsets.symmetric(horizontal: 10),
100+
),
101+
),
102+
),
184103
);
185104
}
186105
}

0 commit comments

Comments
 (0)