edit.dart 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. import '../../config/routes.dart';
  2. import '../../state/app_state.dart';
  3. import 'package:flutter/material.dart';
  4. import '/l10n/app_localizations.dart';
  5. import 'package:provider/provider.dart';
  6. import './edit_list_item.dart' show EditListItem;
  7. /// Android edit page.
  8. class AndroidEditPage extends StatefulWidget {
  9. const AndroidEditPage({super.key});
  10. @override
  11. State<AndroidEditPage> createState() => _EditPageState();
  12. }
  13. class _EditPageState extends State<AndroidEditPage> {
  14. List<BaseItemType>? items;
  15. @override
  16. void initState() {
  17. super.initState();
  18. WidgetsBinding.instance.addPostFrameCallback((_) {
  19. setState(() =>
  20. items = List<BaseItemType>.from(context.read<AppState>().items!));
  21. });
  22. }
  23. // Handles reorder of elements
  24. void _handleReorder(int oldIndex, int newIndex) {
  25. setState(() {
  26. if (newIndex > oldIndex) {
  27. newIndex -= 1;
  28. }
  29. items!.insert(newIndex, items!.removeAt(oldIndex));
  30. _refreshHide();
  31. });
  32. }
  33. // Hide bottom bar
  34. final ValueNotifier<bool> _hideTrash = ValueNotifier(true);
  35. // Whether to hide save icon
  36. final ValueNotifier<bool> _hideSave = ValueNotifier(true);
  37. // List of selected items (to be removed)
  38. final List<String> _pendingRemovalList = [];
  39. // Handles item check
  40. void addRemovalItem(String id) {
  41. _pendingRemovalList.add(id);
  42. _refreshHide();
  43. }
  44. // Handles item uncheck
  45. void removeRemovalItem(String id) {
  46. _pendingRemovalList.remove(id);
  47. _refreshHide();
  48. }
  49. // Remove items with given ids
  50. void removeItems(List<String> itemIDs) {
  51. items!.removeWhere((item) => itemIDs.contains(item.id));
  52. }
  53. // Refreshes value of _hide
  54. void _refreshHide() {
  55. _hideTrash.value = _pendingRemovalList.isEmpty;
  56. _hideSave.value =
  57. !Provider.of<AppState>(context, listen: false).itemsChanged(items);
  58. }
  59. // Remove dialog
  60. void showRemoveDialog(BuildContext context) {
  61. showDialog(
  62. context: context,
  63. builder: (BuildContext context) {
  64. return AlertDialog(
  65. title: Text(AppLocalizations.of(context)!.removeAccounts),
  66. content: Text(AppLocalizations.of(context)!.removeConfirmation),
  67. actions: [
  68. TextButton(
  69. child: Text(AppLocalizations.of(context)!.no),
  70. onPressed: () {
  71. Navigator.of(context).pop();
  72. }),
  73. TextButton(
  74. child: Text(AppLocalizations.of(context)!.yes,
  75. style: const TextStyle(color: Colors.red)),
  76. onPressed: () {
  77. setState(() {
  78. removeItems(_pendingRemovalList);
  79. _pendingRemovalList.clear();
  80. _refreshHide();
  81. });
  82. Navigator.of(context).pop();
  83. },
  84. )
  85. ],
  86. );
  87. },
  88. );
  89. }
  90. // Handle history pop
  91. void _popCallback(bool didPop) async {
  92. if (didPop) return;
  93. // No change to items
  94. if (!Provider.of<AppState>(context, listen: false).itemsChanged(items)) {
  95. Navigator.of(context).pop();
  96. return;
  97. }
  98. await showDialog(
  99. context: context,
  100. builder: (BuildContext context) {
  101. return AlertDialog(
  102. title: Text(AppLocalizations.of(context)!.exitEditTitle),
  103. content: Text(AppLocalizations.of(context)!.exitEditInfo),
  104. actions: [
  105. TextButton(
  106. child: Text(AppLocalizations.of(context)!.no),
  107. onPressed: () {
  108. Navigator.of(context).pop();
  109. },
  110. ),
  111. TextButton(
  112. child: Text(AppLocalizations.of(context)!.yes,
  113. style: const TextStyle(color: Colors.red)),
  114. onPressed: () {
  115. Navigator.of(context)
  116. .popUntil(ModalRoute.withName(AppRoutes.home));
  117. },
  118. )
  119. ],
  120. );
  121. },
  122. );
  123. }
  124. @override
  125. Widget build(BuildContext context) {
  126. return PopScope(
  127. canPop: false,
  128. onPopInvoked: _popCallback,
  129. child: Theme(
  130. data: Theme.of(context).copyWith(
  131. bottomSheetTheme: const BottomSheetThemeData(
  132. surfaceTintColor: Colors.transparent,
  133. ),
  134. ),
  135. child: Scaffold(
  136. appBar: AppBar(
  137. title: Text(AppLocalizations.of(context)!.editTitle),
  138. actions: <Widget>[
  139. // Save
  140. ValueListenableBuilder<bool>(
  141. valueListenable: _hideSave,
  142. builder: (context, hideSave, child) {
  143. return hideSave
  144. ? Container()
  145. : IconButton(
  146. icon: const Icon(Icons.check),
  147. tooltip: AppLocalizations.of(context)!.save,
  148. onPressed: () {
  149. if (items != null) {
  150. Provider.of<AppState>(context, listen: false)
  151. .replaceItems(items!);
  152. }
  153. Navigator.pop(context);
  154. },
  155. );
  156. },
  157. )
  158. ],
  159. ),
  160. body: items == null
  161. ? Center(child: Text(AppLocalizations.of(context)!.loading))
  162. : Container(
  163. margin: const EdgeInsets.all(10),
  164. child: ReorderableListView(
  165. padding: const EdgeInsets.all(0),
  166. scrollDirection: Axis.vertical,
  167. onReorder: _handleReorder,
  168. children: items!
  169. .map((item) => EditListItem(
  170. item, addRemovalItem, removeRemovalItem,
  171. key: Key(item.id)))
  172. .toList())),
  173. // Delete button
  174. bottomSheet: ValueListenableBuilder<bool>(
  175. valueListenable: _hideTrash,
  176. builder: (context, value, child) {
  177. return value
  178. ? const SizedBox.shrink()
  179. : Row(
  180. mainAxisSize: MainAxisSize.max,
  181. mainAxisAlignment: MainAxisAlignment.spaceBetween,
  182. children: <Widget>[
  183. Expanded(
  184. child: TextButton(
  185. onPressed: () {
  186. showRemoveDialog(context);
  187. },
  188. style: TextButton.styleFrom(
  189. shape: const RoundedRectangleBorder(
  190. borderRadius:
  191. BorderRadius.all(Radius.circular(2)),
  192. ),
  193. padding: const EdgeInsets.symmetric(vertical: 10),
  194. side: BorderSide.none,
  195. ),
  196. child: Text(
  197. AppLocalizations.of(context)!.removeAccounts,
  198. style: const TextStyle(color: Colors.redAccent),
  199. ),
  200. ),
  201. ),
  202. ],
  203. );
  204. },
  205. ),
  206. ),
  207. ),
  208. );
  209. }
  210. }