Skip to content

Commit abfbafa

Browse files
committed
Added favorites page
1 parent e68fca6 commit abfbafa

8 files changed

Lines changed: 500 additions & 28 deletions

File tree

l10n/app_en.arb

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,8 @@
199199
"uploadList_uploading": "Uploading",
200200
"uploadList_history": "History",
201201

202+
"uploadList_empty": "Empty",
203+
202204

203205
"setDefaultCategory_select": "Please select an album or sub-album which will become the new root album.",
204206
"setDefaultCategory_title": "Default Album",
@@ -368,8 +370,8 @@
368370
"imageOptions_edit": "Edit",
369371
"imageOptions_download": "Download",
370372
"imageOptions_share": "Share",
371-
"imageOptions_addFavorites": "Add favorites",
372-
"imageOptions_removeFavorites": "Remove favorites",
373+
"imageOptions_addFavorites": "Add to favorites",
374+
"imageOptions_removeFavorites": "Remove from favorites",
373375
"imageOptions_setAlbumImage": "Set Album Thumbnail",
374376

375377

lib/api/images.dart

Lines changed: 53 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ Future<ApiResult<ImageModel>> getImage(int imageId) async {
4747
return ApiResult(error: ApiErrors.error);
4848
}
4949

50-
Future<ApiResult<List<ImageModel>>> fetchImages(int albumID, int page) async {
50+
Future<ApiResult<List<ImageModel>>> fetchImages(int albumID, [int page = 0]) async {
5151
Map<String, dynamic> queries = {
5252
'format': 'json',
5353
'method': 'pwg.categories.getImages',
@@ -61,7 +61,7 @@ Future<ApiResult<List<ImageModel>>> fetchImages(int albumID, int page) async {
6161
Response response = await ApiClient.get(queryParameters: queries);
6262

6363
if (response.statusCode == 200) {
64-
var jsonImages = json.decode(response.data)["result"]["images"];
64+
var jsonImages = json.decode(response.data)['result']['images'];
6565
List<ImageModel> images = List<ImageModel>.from(
6666
jsonImages.map((image) => ImageModel.fromJson(image)),
6767
);
@@ -78,10 +78,12 @@ Future<ApiResult<List<ImageModel>>> fetchImages(int albumID, int page) async {
7878

7979
Future<ApiResult<Map>> searchImages(String searchQuery, [int page = 0]) async {
8080
Map<String, dynamic> query = {
81-
"format": "json",
82-
"method": "pwg.images.search",
83-
"query": searchQuery,
84-
"page": page,
81+
'format': 'json',
82+
'method': 'pwg.images.search',
83+
'query': searchQuery,
84+
'order': Preferences.getImageSort.value,
85+
'per_page': Settings.defaultElementPerPage,
86+
'page': page,
8587
};
8688

8789
try {
@@ -91,17 +93,17 @@ Future<ApiResult<Map>> searchImages(String searchQuery, [int page = 0]) async {
9193
final Map<String, dynamic> result = json.decode(response.data);
9294
if (result['err'] == 1002) {
9395
return ApiResult<Map>(data: {
94-
"total_count": 0,
95-
"images": [],
96+
'total_count': 0,
97+
'images': [],
9698
});
9799
}
98-
final jsonImages = result["result"]["images"];
100+
final jsonImages = result['result']['images'];
99101
List<ImageModel> images = List<ImageModel>.from(
100102
jsonImages.map((image) => ImageModel.fromJson(image)),
101103
);
102104
return ApiResult<Map>(data: {
103-
"total_count": result["result"]["paging"]["total_count"],
104-
"images": images,
105+
'total_count': result['result']['paging']['total_count'],
106+
'images': images,
105107
});
106108
}
107109
} on DioError catch (e) {
@@ -112,6 +114,46 @@ Future<ApiResult<Map>> searchImages(String searchQuery, [int page = 0]) async {
112114
return ApiResult(error: ApiErrors.searchImagesError);
113115
}
114116

117+
Future<ApiResult<Map>> fetchFavorites([int page = 0]) async {
118+
Map<String, dynamic> query = {
119+
'format': 'json',
120+
'method': 'pwg.users.favorites.getList',
121+
'order': Preferences.getImageSort.value,
122+
'per_page': Settings.defaultElementPerPage,
123+
'page': page,
124+
};
125+
126+
try {
127+
Response response = await ApiClient.get(queryParameters: query);
128+
129+
if (response.statusCode == 200) {
130+
final Map<String, dynamic> result = json.decode(response.data);
131+
if (result['stat'] == 'fail') {
132+
return ApiResult<Map>(data: {
133+
'total_count': 0,
134+
'images': [],
135+
});
136+
}
137+
final jsonImages = result['result']['images'];
138+
List<ImageModel> images = List<ImageModel>.from(
139+
jsonImages.map((image) {
140+
image['is_favorite'] = true;
141+
return ImageModel.fromJson(image);
142+
}),
143+
);
144+
return ApiResult<Map>(data: {
145+
'total_count': result['result']['paging']['count'],
146+
'images': images,
147+
});
148+
}
149+
} on DioError catch (e) {
150+
debugPrint('Fetch favorites: ${e.message}');
151+
} on Error catch (e) {
152+
debugPrint('Fetch favorites: ${e.stackTrace}');
153+
}
154+
return ApiResult(error: ApiErrors.error);
155+
}
156+
115157
Future<bool> _requestPermissions() async {
116158
var permission = await Permission.storage.status;
117159
if (permission != PermissionStatus.granted) {

lib/app.dart

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import 'package:piwigo_ng/views/album/album_view_page.dart';
1111
import 'package:piwigo_ng/views/album/root_album_view_page.dart';
1212
import 'package:piwigo_ng/views/authentication/login_view_page.dart';
1313
import 'package:piwigo_ng/views/image/edit_image_page.dart';
14+
import 'package:piwigo_ng/views/image/image_favorites_page.dart';
1415
import 'package:piwigo_ng/views/image/image_search_view_page.dart';
1516
import 'package:piwigo_ng/views/image/image_view_page.dart';
1617
import 'package:piwigo_ng/views/settings/privacy_policy_view_page.dart';
@@ -116,8 +117,15 @@ Route<dynamic> generateRoute(RouteSettings settings) {
116117
settings: settings,
117118
);
118119
case ImageSearchViewPage.routeName:
119-
return SlideUpPageRoute(
120-
page: ImageSearchViewPage(
120+
return MaterialPageRoute(
121+
builder: (_) => ImageSearchViewPage(
122+
isAdmin: arguments['isAdmin'] ?? isAdmin,
123+
),
124+
settings: settings,
125+
);
126+
case ImageFavoritesPage.routeName:
127+
return MaterialPageRoute(
128+
builder: (_) => ImageFavoritesPage(
121129
isAdmin: arguments['isAdmin'] ?? isAdmin,
122130
),
123131
settings: settings,

lib/components/appbars/root_search_app_bar.dart

Lines changed: 65 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
import 'package:flutter/material.dart';
2+
import 'package:piwigo_ng/components/notification_dot.dart';
3+
import 'package:piwigo_ng/components/popup_list_item.dart';
4+
import 'package:piwigo_ng/services/preferences_service.dart';
5+
import 'package:piwigo_ng/services/upload_notifier.dart';
26
import 'package:piwigo_ng/utils/localizations.dart';
7+
import 'package:piwigo_ng/views/image/image_favorites_page.dart';
8+
import 'package:piwigo_ng/views/upload/upload_status_page.dart';
9+
import 'package:provider/provider.dart';
310

411
import '../../views/settings/settings_view_page.dart';
512
import '../fields/app_field.dart';
@@ -72,7 +79,7 @@ class _RootSearchAppBarState extends State<RootSearchAppBar> {
7279
child: GestureDetector(
7380
onTap: widget.onSearch,
7481
child: const Hero(
75-
tag: 'search-bar',
82+
tag: '<search-bar>',
7683
child: Material(
7784
color: Colors.transparent,
7885
child: IgnorePointer(
@@ -87,11 +94,7 @@ class _RootSearchAppBarState extends State<RootSearchAppBar> {
8794
),
8895
),
8996
actions: [
90-
SizedBox(width: 16.0),
91-
// IconButton(
92-
// onPressed: () {},
93-
// icon: const Icon(Icons.more_vert),
94-
// ),
97+
_popupMenu,
9598
],
9699
expandedHeight: _expandedHeight,
97100
flexibleSpace: FlexibleSpaceBar(
@@ -107,4 +110,60 @@ class _RootSearchAppBarState extends State<RootSearchAppBar> {
107110
),
108111
);
109112
}
113+
114+
Widget get _popupMenu {
115+
return Stack(
116+
alignment: Alignment.center,
117+
children: [
118+
PopupMenuButton(
119+
position: PopupMenuPosition.under,
120+
itemBuilder: (context) => [
121+
PopupMenuItem(
122+
onTap: () => Future.delayed(
123+
const Duration(seconds: 0),
124+
() => Navigator.of(context).pushNamed(UploadStatusPage.routeName),
125+
),
126+
child: Stack(
127+
children: [
128+
PopupListItem(
129+
icon: Icons.upload,
130+
text: appStrings.uploadSection_queue,
131+
),
132+
Positioned(
133+
top: 14.0,
134+
left: 0.0,
135+
child: Consumer<UploadNotifier>(builder: (context, uploadNotifier, child) {
136+
return NotificationDot(
137+
isShown: uploadNotifier.uploadList.isNotEmpty,
138+
);
139+
}),
140+
),
141+
],
142+
),
143+
),
144+
if (Preferences.getUserStatus != 'guest')
145+
PopupMenuItem(
146+
onTap: () => Future.delayed(
147+
const Duration(seconds: 0),
148+
() => Navigator.of(context).pushNamed(ImageFavoritesPage.routeName),
149+
),
150+
child: PopupListItem(
151+
icon: Icons.favorite,
152+
text: appStrings.categoryDiscoverFavorites_title,
153+
),
154+
),
155+
],
156+
),
157+
Positioned(
158+
top: 12.0,
159+
left: 12.0,
160+
child: Consumer<UploadNotifier>(builder: (context, uploadNotifier, child) {
161+
return NotificationDot(
162+
isShown: uploadNotifier.uploadList.isNotEmpty,
163+
);
164+
}),
165+
),
166+
],
167+
);
168+
}
110169
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:piwigo_ng/utils/resources.dart';
3+
4+
class NotificationDot extends StatelessWidget {
5+
const NotificationDot({Key? key, this.isShown = false}) : super(key: key);
6+
7+
final bool isShown;
8+
9+
@override
10+
Widget build(BuildContext context) {
11+
return AnimatedScale(
12+
duration: const Duration(milliseconds: 300),
13+
curve: Curves.ease,
14+
scale: isShown ? 1.0 : 0.0,
15+
child: CircleAvatar(
16+
radius: 4.0,
17+
backgroundColor: AppColors.error,
18+
),
19+
);
20+
}
21+
}

0 commit comments

Comments
 (0)