machine_learning_settings_page.dart 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. import "dart:async";
  2. import "package:flutter/material.dart";
  3. import "package:intl/intl.dart";
  4. import "package:photos/core/event_bus.dart";
  5. import 'package:photos/events/embedding_updated_event.dart';
  6. import "package:photos/generated/l10n.dart";
  7. import "package:photos/services/semantic_search/semantic_search_service.dart";
  8. import "package:photos/theme/ente_theme.dart";
  9. import "package:photos/ui/common/loading_widget.dart";
  10. import "package:photos/ui/components/buttons/icon_button_widget.dart";
  11. import "package:photos/ui/components/captioned_text_widget.dart";
  12. import "package:photos/ui/components/menu_item_widget/menu_item_widget.dart";
  13. import "package:photos/ui/components/menu_section_description_widget.dart";
  14. import "package:photos/ui/components/menu_section_title.dart";
  15. import "package:photos/ui/components/title_bar_title_widget.dart";
  16. import "package:photos/ui/components/title_bar_widget.dart";
  17. import "package:photos/ui/components/toggle_switch_widget.dart";
  18. import "package:photos/utils/local_settings.dart";
  19. class MachineLearningSettingsPage extends StatefulWidget {
  20. const MachineLearningSettingsPage({super.key});
  21. @override
  22. State<MachineLearningSettingsPage> createState() =>
  23. _MachineLearningSettingsPageState();
  24. }
  25. class _MachineLearningSettingsPageState
  26. extends State<MachineLearningSettingsPage> {
  27. @override
  28. Widget build(BuildContext context) {
  29. return Scaffold(
  30. body: CustomScrollView(
  31. primary: false,
  32. slivers: <Widget>[
  33. TitleBarWidget(
  34. flexibleSpaceTitle: TitleBarTitleWidget(
  35. title: S.of(context).machineLearning,
  36. ),
  37. actionIcons: [
  38. IconButtonWidget(
  39. icon: Icons.close_outlined,
  40. iconButtonType: IconButtonType.secondary,
  41. onTap: () {
  42. Navigator.pop(context);
  43. Navigator.pop(context);
  44. Navigator.pop(context);
  45. },
  46. ),
  47. ],
  48. ),
  49. SliverList(
  50. delegate: SliverChildBuilderDelegate(
  51. (delegateBuildContext, index) {
  52. return Padding(
  53. padding: const EdgeInsets.symmetric(horizontal: 16),
  54. child: Padding(
  55. padding: const EdgeInsets.symmetric(vertical: 20),
  56. child: Column(
  57. mainAxisSize: MainAxisSize.min,
  58. children: [
  59. _getMagicSearchSettings(context),
  60. ],
  61. ),
  62. ),
  63. );
  64. },
  65. childCount: 1,
  66. ),
  67. ),
  68. ],
  69. ),
  70. );
  71. }
  72. Widget _getMagicSearchSettings(BuildContext context) {
  73. final colorScheme = getEnteColorScheme(context);
  74. final hasEnabled = LocalSettings.instance.hasEnabledMagicSearch();
  75. return Column(
  76. children: [
  77. MenuItemWidget(
  78. captionedTextWidget: CaptionedTextWidget(
  79. title: S.of(context).magicSearch,
  80. ),
  81. menuItemColor: colorScheme.fillFaint,
  82. trailingWidget: ToggleSwitchWidget(
  83. value: () => LocalSettings.instance.hasEnabledMagicSearch(),
  84. onChanged: () async {
  85. await LocalSettings.instance.setShouldEnableMagicSearch(
  86. !LocalSettings.instance.hasEnabledMagicSearch(),
  87. );
  88. if (LocalSettings.instance.hasEnabledMagicSearch()) {
  89. SemanticSearchService.instance.sync();
  90. } else {
  91. await SemanticSearchService.instance.clearQueue();
  92. }
  93. setState(() {});
  94. },
  95. ),
  96. singleBorderRadius: 8,
  97. alignCaptionedTextToLeft: true,
  98. isGestureDetectorDisabled: true,
  99. ),
  100. const SizedBox(
  101. height: 4,
  102. ),
  103. MenuSectionDescriptionWidget(
  104. content: S.of(context).magicSearchDescription,
  105. ),
  106. const SizedBox(
  107. height: 12,
  108. ),
  109. hasEnabled
  110. ? Column(
  111. children: [
  112. const MagicSearchIndexStatsWidget(),
  113. const SizedBox(
  114. height: 12,
  115. ),
  116. MenuItemWidget(
  117. leadingIcon: Icons.delete_sweep_outlined,
  118. captionedTextWidget: CaptionedTextWidget(
  119. title: S.of(context).clearIndexes,
  120. ),
  121. menuItemColor: getEnteColorScheme(context).fillFaint,
  122. singleBorderRadius: 8,
  123. alwaysShowSuccessState: true,
  124. onTap: () async {
  125. await SemanticSearchService.instance.clearIndexes();
  126. if (mounted) {
  127. setState(() => {});
  128. }
  129. },
  130. ),
  131. ],
  132. )
  133. : const SizedBox.shrink(),
  134. ],
  135. );
  136. }
  137. }
  138. class MagicSearchIndexStatsWidget extends StatefulWidget {
  139. const MagicSearchIndexStatsWidget({
  140. super.key,
  141. });
  142. @override
  143. State<MagicSearchIndexStatsWidget> createState() =>
  144. _MagicSearchIndexStatsWidgetState();
  145. }
  146. class _MagicSearchIndexStatsWidgetState
  147. extends State<MagicSearchIndexStatsWidget> {
  148. IndexStatus? _status;
  149. late StreamSubscription<EmbeddingUpdatedEvent> _eventSubscription;
  150. @override
  151. void initState() {
  152. super.initState();
  153. _eventSubscription =
  154. Bus.instance.on<EmbeddingUpdatedEvent>().listen((event) {
  155. _fetchIndexStatus();
  156. });
  157. _fetchIndexStatus();
  158. }
  159. void _fetchIndexStatus() {
  160. SemanticSearchService.instance.getIndexStatus().then((status) {
  161. _status = status;
  162. setState(() {});
  163. });
  164. }
  165. @override
  166. void dispose() {
  167. super.dispose();
  168. _eventSubscription.cancel();
  169. }
  170. @override
  171. Widget build(BuildContext context) {
  172. if (_status == null) {
  173. return const EnteLoadingWidget();
  174. }
  175. return Column(
  176. children: [
  177. Row(
  178. children: [
  179. MenuSectionTitle(title: S.of(context).status),
  180. Expanded(child: Container()),
  181. _status!.pendingItems > 0
  182. ? EnteLoadingWidget(
  183. color: getEnteColorScheme(context).fillMuted,
  184. )
  185. : const SizedBox.shrink(),
  186. ],
  187. ),
  188. MenuItemWidget(
  189. captionedTextWidget: CaptionedTextWidget(
  190. title: S.of(context).indexedItems,
  191. ),
  192. trailingWidget: Text(
  193. NumberFormat().format(_status!.indexedItems),
  194. style: Theme.of(context).textTheme.bodySmall,
  195. ),
  196. singleBorderRadius: 8,
  197. alignCaptionedTextToLeft: true,
  198. isGestureDetectorDisabled: true,
  199. // Setting a key here to ensure trailingWidget is refreshed
  200. key: ValueKey("indexed_items_" + _status!.indexedItems.toString()),
  201. ),
  202. MenuItemWidget(
  203. captionedTextWidget: CaptionedTextWidget(
  204. title: S.of(context).pendingItems,
  205. ),
  206. trailingWidget: Text(
  207. NumberFormat().format(_status!.pendingItems),
  208. style: Theme.of(context).textTheme.bodySmall,
  209. ),
  210. singleBorderRadius: 8,
  211. alignCaptionedTextToLeft: true,
  212. isGestureDetectorDisabled: true,
  213. // Setting a key here to ensure trailingWidget is refreshed
  214. key: ValueKey("pending_items_" + _status!.pendingItems.toString()),
  215. ),
  216. ],
  217. );
  218. }
  219. }