Skip to content

Commit 1b6d733

Browse files
committed
Added list item selector component
1 parent 4dcb81f commit 1b6d733

20 files changed

Lines changed: 403 additions & 235 deletions

l10n/app_en.arb

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -314,26 +314,23 @@
314314
"moveCategoryError_message": "Failed to move your album",
315315

316316

317-
"categoryPrivacy": "Album Privacy",
318-
"categoryPrivacy_subtitle": "Manage access permissions of {album_name} album",
317+
"categoryPrivacy": "Manage Permissions",
318+
"categoryPrivacy_subtitle": "Manage access permissions of \"{album_name}\".",
319319
"@categoryPrivacy_subtitle" : {
320320
"placeholders": {
321321
"album_name": {}
322322
}
323323
},
324324
"categoryPrivacyMode_public": "Public",
325-
"categoryPrivacyMode_publicMessage": "Every user can see this album",
325+
"categoryPrivacyMode_publicMessage": "Every user can see this album.",
326326
"categoryPrivacyMode_private": "Private",
327-
"categoryPrivacyMode_privateMessage": "Visitors must log in and have the necessary permissions to see this album",
327+
"categoryPrivacyMode_privateMessage": "Visitors must log in and have the necessary permissions to see this album.",
328328
"categoryPrivacyGroups": "Group permissions",
329329
"categoryPrivacyGroups_add": "Authorize groups",
330330
"categoryPrivacyUsers": "User permissions",
331-
"categoryPrivacyUsers_message": "{user_count} users automatically have permissions on this album because they are administrators :",
332-
"@categoryPrivacyUsers_message" : {
333-
"placeholders": {
334-
"user_count": {}
335-
}
336-
},
331+
"categoryPrivacyUsers_message": "To manage use permissions, go to your web administration.",
332+
"categoryPrivacyRecursive": "Apply to sub-albums",
333+
"categoryPrivacyRecursive_message": "After confirmation, all modifications will be applied to sub-albums.",
337334

338335

339336
"categorySelection_setThumbnail": "Please select the album which will use the photo {photo} as a thumbnail.",

lib/app.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ import 'package:piwigo_ng/services/app_providers.dart';
77
import 'package:piwigo_ng/services/preferences_service.dart';
88
import 'package:piwigo_ng/utils/overscroll_behavior.dart';
99
import 'package:piwigo_ng/utils/themes.dart';
10+
import 'package:piwigo_ng/views/album/album_page.dart';
1011
import 'package:piwigo_ng/views/album/album_privacy_page.dart';
11-
import 'package:piwigo_ng/views/album/album_view_page.dart';
1212
import 'package:piwigo_ng/views/album/root_album_page.dart';
1313
import 'package:piwigo_ng/views/authentication/login_page.dart';
1414
import 'package:piwigo_ng/views/authentication/login_settings_page.dart';

lib/components/buttons/piwigo_button.dart

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ class PiwigoButton extends StatelessWidget {
2323
final bool loading;
2424
final String text;
2525

26+
static const double buttonHeight = 56.0;
27+
2628
@override
2729
Widget build(BuildContext context) {
2830
final Color backgroundColor = disabled
@@ -35,9 +37,9 @@ class PiwigoButton extends StatelessWidget {
3537
duration: const Duration(milliseconds: 300),
3638
margin: margin,
3739
padding: padding,
38-
height: 56.0,
40+
height: buttonHeight,
3941
constraints: const BoxConstraints(
40-
minWidth: 56.0,
42+
minWidth: buttonHeight,
4143
maxWidth: Settings.modalMaxWidth,
4244
),
4345
decoration: BoxDecoration(

lib/components/cards/piwigo_chip.dart

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,25 @@ class SelectChip extends StatelessWidget {
2727
: Theme.of(context).textTheme.bodyMedium!.color,
2828
),
2929
backgroundColor: Theme.of(context).chipTheme.backgroundColor,
30-
checkmarkColor: AppColors.white,
30+
// checkmarkColor: AppColors.white,
3131
selected: selected,
32+
showCheckmark: false,
3233
onSelected: (_) => onTap?.call(),
33-
label: Text(label),
34+
label: Row(
35+
mainAxisSize: MainAxisSize.min,
36+
children: [
37+
Text(label),
38+
const SizedBox(width: 8.0),
39+
AnimatedRotation(
40+
duration: const Duration(milliseconds: 150),
41+
turns: selected ? 1 / 8 : .0,
42+
child: Icon(
43+
Icons.add,
44+
color: selected ? AppColors.white : Colors.green,
45+
),
46+
),
47+
],
48+
),
3449
);
3550
}
3651
}
@@ -54,14 +69,15 @@ class PiwigoChip extends StatelessWidget {
5469
return Chip(
5570
elevation: .0,
5671
side: BorderSide.none,
57-
backgroundColor: Theme.of(context).colorScheme.secondary,
58-
deleteIconColor: AppColors.white,
72+
backgroundColor:
73+
backgroundColor ?? Theme.of(context).colorScheme.secondary,
74+
deleteIconColor: foregroundColor ?? AppColors.white,
5975
label: Text(label),
6076
labelStyle: TextStyle(
6177
fontSize: 14,
62-
color: AppColors.white,
78+
color: foregroundColor ?? AppColors.white,
6379
),
64-
onDeleted: () => onRemove?.call(),
80+
onDeleted: onRemove,
6581
);
6682
}
6783
}
File renamed without changes.
File renamed without changes.
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:piwigo_ng/components/sections/form_section.dart';
3+
import 'package:piwigo_ng/utils/localizations.dart';
4+
5+
class SelectModelList<T> extends StatefulWidget {
6+
const SelectModelList({
7+
Key? key,
8+
this.selected = const [],
9+
this.unselected = const [],
10+
required this.itemBuilder,
11+
this.onSelect,
12+
this.onDeselect,
13+
}) : super(key: key);
14+
15+
final List<T> selected;
16+
final List<T> unselected;
17+
final Widget Function(T) itemBuilder;
18+
final int Function(T)? onSelect;
19+
final int Function(T)? onDeselect;
20+
21+
@override
22+
State<SelectModelList<T>> createState() => _SelectModelListState<T>();
23+
}
24+
25+
class _SelectModelListState<T> extends State<SelectModelList<T>> {
26+
final GlobalKey<AnimatedListState> _selectedListKey =
27+
GlobalKey<AnimatedListState>();
28+
final GlobalKey<AnimatedListState> _unselectedListKey =
29+
GlobalKey<AnimatedListState>();
30+
31+
void _onTapItem(T item) {
32+
if (widget.selected.contains(item)) {
33+
_onDeselect(item);
34+
} else {
35+
_onSelect(item);
36+
}
37+
}
38+
39+
void _onSelect(T item) async {
40+
int oldIndex = widget.unselected.indexOf(item);
41+
_unselectedListKey.currentState?.removeItem(
42+
oldIndex,
43+
(_, animation) => _buildItem(item, animation, false),
44+
duration: const Duration(milliseconds: 150),
45+
);
46+
47+
int? newIndex = widget.onSelect?.call(item);
48+
newIndex ??= widget.selected.indexOf(item);
49+
_selectedListKey.currentState?.insertItem(newIndex);
50+
}
51+
52+
void _onDeselect(T item) async {
53+
int oldIndex = widget.selected.indexOf(item);
54+
_selectedListKey.currentState?.removeItem(
55+
oldIndex,
56+
(_, animation) => _buildItem(item, animation, true),
57+
duration: const Duration(milliseconds: 150),
58+
);
59+
60+
int? newIndex = widget.onDeselect?.call(item);
61+
await Future.delayed(const Duration(milliseconds: 100));
62+
newIndex ??= widget.unselected.indexOf(item);
63+
_unselectedListKey.currentState?.insertItem(newIndex);
64+
}
65+
66+
@override
67+
Widget build(BuildContext context) {
68+
return Column(
69+
children: [
70+
_buildSelectedList,
71+
_buildUnselectedList,
72+
],
73+
);
74+
}
75+
76+
Widget get _buildSelectedList {
77+
return FormSection(
78+
title: appStrings.tagsHeader_selected,
79+
child: ClipRRect(
80+
borderRadius: BorderRadius.circular(15.0),
81+
child: Material(
82+
color: Theme.of(context).cardColor,
83+
child: AnimatedList(
84+
key: _selectedListKey,
85+
shrinkWrap: true,
86+
physics: const NeverScrollableScrollPhysics(),
87+
initialItemCount: widget.selected.length,
88+
itemBuilder: (context, index, animation) {
89+
T item = widget.selected[index];
90+
return _buildItem(item, animation, true);
91+
},
92+
),
93+
),
94+
),
95+
);
96+
}
97+
98+
Widget get _buildUnselectedList {
99+
return FormSection(
100+
title: appStrings.tagsHeader_notSelected,
101+
child: ClipRRect(
102+
borderRadius: BorderRadius.circular(15.0),
103+
child: Material(
104+
color: Theme.of(context).cardColor,
105+
child: AnimatedList(
106+
key: _unselectedListKey,
107+
shrinkWrap: true,
108+
physics: const NeverScrollableScrollPhysics(),
109+
initialItemCount: widget.unselected.length,
110+
itemBuilder: (context, index, animation) {
111+
T item = widget.unselected[index];
112+
return _buildItem(item, animation, false);
113+
},
114+
),
115+
),
116+
),
117+
);
118+
}
119+
120+
_buildItem(T item, Animation<double> animation, bool selected) {
121+
return SizeTransition(
122+
sizeFactor: animation,
123+
child: ListTile(
124+
key: ObjectKey(item),
125+
visualDensity: VisualDensity.compact,
126+
shape: Border(
127+
bottom: BorderSide(color: Theme.of(context).scaffoldBackgroundColor),
128+
),
129+
title: widget.itemBuilder(item),
130+
trailing: selected
131+
? Icon(Icons.remove_circle_outline, color: Colors.red)
132+
: Icon(Icons.add_circle_outline, color: Colors.green),
133+
onTap: () => _onTapItem(item),
134+
),
135+
);
136+
}
137+
}

0 commit comments

Comments
 (0)