mirror of
https://gitlab.com/nkming2/nc-photos.git
synced 2025-03-30 03:51:36 +02:00
Refactoring: rewrite acccount settings
This commit is contained in:
parent
a17a0432c4
commit
4bde517813
14 changed files with 941 additions and 403 deletions
app/lib
controller
widget
|
@ -1,5 +1,6 @@
|
||||||
import 'package:kiwi/kiwi.dart';
|
import 'package:kiwi/kiwi.dart';
|
||||||
import 'package:nc_photos/account.dart';
|
import 'package:nc_photos/account.dart';
|
||||||
|
import 'package:nc_photos/controller/account_pref_controller.dart';
|
||||||
import 'package:nc_photos/controller/collections_controller.dart';
|
import 'package:nc_photos/controller/collections_controller.dart';
|
||||||
import 'package:nc_photos/controller/server_controller.dart';
|
import 'package:nc_photos/controller/server_controller.dart';
|
||||||
import 'package:nc_photos/di_container.dart';
|
import 'package:nc_photos/di_container.dart';
|
||||||
|
@ -11,6 +12,8 @@ class AccountController {
|
||||||
_collectionsController = null;
|
_collectionsController = null;
|
||||||
_serverController?.dispose();
|
_serverController?.dispose();
|
||||||
_serverController = null;
|
_serverController = null;
|
||||||
|
_accountPrefController?.dispose();
|
||||||
|
_accountPrefController = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
Account get account => _account!;
|
Account get account => _account!;
|
||||||
|
@ -27,7 +30,13 @@ class AccountController {
|
||||||
account: _account!,
|
account: _account!,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
AccountPrefController get accountPrefController =>
|
||||||
|
_accountPrefController ??= AccountPrefController(
|
||||||
|
account: _account!,
|
||||||
|
);
|
||||||
|
|
||||||
Account? _account;
|
Account? _account;
|
||||||
CollectionsController? _collectionsController;
|
CollectionsController? _collectionsController;
|
||||||
ServerController? _serverController;
|
ServerController? _serverController;
|
||||||
|
AccountPrefController? _accountPrefController;
|
||||||
}
|
}
|
||||||
|
|
79
app/lib/controller/account_pref_controller.dart
Normal file
79
app/lib/controller/account_pref_controller.dart
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
import 'package:logging/logging.dart';
|
||||||
|
import 'package:nc_photos/account.dart';
|
||||||
|
import 'package:nc_photos/pref.dart';
|
||||||
|
import 'package:np_codegen/np_codegen.dart';
|
||||||
|
import 'package:rxdart/rxdart.dart';
|
||||||
|
|
||||||
|
part 'account_pref_controller.g.dart';
|
||||||
|
|
||||||
|
@npLog
|
||||||
|
class AccountPrefController {
|
||||||
|
AccountPrefController({
|
||||||
|
required this.account,
|
||||||
|
}) : _accountPref = AccountPref.of(account);
|
||||||
|
|
||||||
|
void dispose() {}
|
||||||
|
|
||||||
|
ValueStream<bool> get isEnableFaceRecognitionApp =>
|
||||||
|
_enableFaceRecognitionAppController.stream;
|
||||||
|
|
||||||
|
Future<void> setEnableFaceRecognitionApp(bool value) async {
|
||||||
|
final backup = _enableFaceRecognitionAppController.value;
|
||||||
|
_enableFaceRecognitionAppController.add(value);
|
||||||
|
try {
|
||||||
|
if (!await _accountPref.setEnableFaceRecognitionApp(value)) {
|
||||||
|
throw StateError("Unknown error");
|
||||||
|
}
|
||||||
|
} catch (e, stackTrace) {
|
||||||
|
_log.severe("[setEnableFaceRecognitionApp] Failed setting preference", e,
|
||||||
|
stackTrace);
|
||||||
|
_enableFaceRecognitionAppController
|
||||||
|
..addError(e, stackTrace)
|
||||||
|
..add(backup);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ValueStream<String> get shareFolder => _shareFolderController.stream;
|
||||||
|
|
||||||
|
Future<void> setShareFolder(String value) async {
|
||||||
|
final backup = _shareFolderController.value;
|
||||||
|
_shareFolderController.add(value);
|
||||||
|
try {
|
||||||
|
if (!await _accountPref.setShareFolder(value)) {
|
||||||
|
throw StateError("Unknown error");
|
||||||
|
}
|
||||||
|
} catch (e, stackTrace) {
|
||||||
|
_log.severe("[setShareFolder] Failed setting preference", e, stackTrace);
|
||||||
|
_shareFolderController
|
||||||
|
..addError(e, stackTrace)
|
||||||
|
..add(backup);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ValueStream<String?> get accountLabel => _accountLabelController.stream;
|
||||||
|
|
||||||
|
Future<void> setAccountLabel(String? value) async {
|
||||||
|
final backup = _accountLabelController.value;
|
||||||
|
_accountLabelController.add(value);
|
||||||
|
try {
|
||||||
|
if (!await _accountPref.setAccountLabel(value)) {
|
||||||
|
throw StateError("Unknown error");
|
||||||
|
}
|
||||||
|
} catch (e, stackTrace) {
|
||||||
|
_log.severe("[setAccountLabel] Failed setting preference", e, stackTrace);
|
||||||
|
_accountLabelController
|
||||||
|
..addError(e, stackTrace)
|
||||||
|
..add(backup);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final Account account;
|
||||||
|
|
||||||
|
final AccountPref _accountPref;
|
||||||
|
late final _enableFaceRecognitionAppController =
|
||||||
|
BehaviorSubject.seeded(_accountPref.isEnableFaceRecognitionAppOr(true));
|
||||||
|
late final _shareFolderController =
|
||||||
|
BehaviorSubject.seeded(_accountPref.getShareFolderOr(""));
|
||||||
|
late final _accountLabelController =
|
||||||
|
BehaviorSubject.seeded(_accountPref.getAccountLabel());
|
||||||
|
}
|
15
app/lib/controller/account_pref_controller.g.dart
Normal file
15
app/lib/controller/account_pref_controller.g.dart
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'account_pref_controller.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// NpLogGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
extension _$AccountPrefControllerNpLog on AccountPrefController {
|
||||||
|
// ignore: unused_element
|
||||||
|
Logger get _log => log;
|
||||||
|
|
||||||
|
static final log =
|
||||||
|
Logger("controller.account_pref_controller.AccountPrefController");
|
||||||
|
}
|
|
@ -29,6 +29,7 @@ import 'package:nc_photos/toast.dart';
|
||||||
import 'package:nc_photos/url_launcher_util.dart';
|
import 'package:nc_photos/url_launcher_util.dart';
|
||||||
import 'package:nc_photos/widget/home.dart';
|
import 'package:nc_photos/widget/home.dart';
|
||||||
import 'package:nc_photos/widget/settings.dart';
|
import 'package:nc_photos/widget/settings.dart';
|
||||||
|
import 'package:nc_photos/widget/settings/account_settings.dart';
|
||||||
import 'package:nc_photos/widget/sign_in.dart';
|
import 'package:nc_photos/widget/sign_in.dart';
|
||||||
import 'package:np_codegen/np_codegen.dart';
|
import 'package:np_codegen/np_codegen.dart';
|
||||||
import 'package:to_string/to_string.dart';
|
import 'package:to_string/to_string.dart';
|
||||||
|
@ -354,11 +355,7 @@ class _AccountSettingsView extends StatelessWidget {
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Navigator.of(context)
|
Navigator.of(context)
|
||||||
..pop()
|
..pop()
|
||||||
..pushNamed(
|
..pushNamed(AccountSettings.routeName);
|
||||||
AccountSettingsWidget.routeName,
|
|
||||||
arguments: AccountSettingsWidgetArguments(
|
|
||||||
context.read<_Bloc>().activeAccount),
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
import 'package:cached_network_image/cached_network_image.dart';
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:nc_photos/account.dart';
|
import 'package:nc_photos/account.dart';
|
||||||
import 'package:nc_photos/api/api_util.dart' as api_util;
|
import 'package:nc_photos/api/api_util.dart' as api_util;
|
||||||
import 'package:nc_photos/pref.dart';
|
import 'package:nc_photos/controller/account_controller.dart';
|
||||||
|
import 'package:nc_photos/stream_util.dart';
|
||||||
import 'package:nc_photos/theme.dart';
|
import 'package:nc_photos/theme.dart';
|
||||||
import 'package:nc_photos/widget/account_picker_dialog.dart';
|
import 'package:nc_photos/widget/account_picker_dialog.dart';
|
||||||
import 'package:nc_photos/widget/app_bar_circular_progress_indicator.dart';
|
import 'package:nc_photos/widget/app_bar_circular_progress_indicator.dart';
|
||||||
|
@ -21,8 +23,7 @@ class HomeSliverAppBar extends StatelessWidget {
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final accountLabel = AccountPref.of(account).getAccountLabel();
|
|
||||||
return TranslucentSliverAppBar(
|
return TranslucentSliverAppBar(
|
||||||
title: InkWell(
|
title: InkWell(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
|
@ -31,31 +32,7 @@ class HomeSliverAppBar extends StatelessWidget {
|
||||||
builder: (_) => const AccountPickerDialog(),
|
builder: (_) => const AccountPickerDialog(),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
child: AppBarTitleContainer(
|
child: _TitleView(account: account),
|
||||||
title: Row(
|
|
||||||
children: [
|
|
||||||
account.scheme == "http"
|
|
||||||
? Icon(
|
|
||||||
Icons.no_encryption_outlined,
|
|
||||||
color: Theme.of(context).colorScheme.error,
|
|
||||||
size: 16,
|
|
||||||
)
|
|
||||||
: Icon(
|
|
||||||
Icons.https,
|
|
||||||
color: Theme.of(context).colorScheme.primary,
|
|
||||||
size: 16,
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: Text(
|
|
||||||
accountLabel ?? account.address,
|
|
||||||
maxLines: 1,
|
|
||||||
overflow: TextOverflow.clip,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
subtitle: accountLabel == null ? Text(account.username2) : null,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
scrolledUnderBackgroundColor:
|
scrolledUnderBackgroundColor:
|
||||||
Theme.of(context).homeNavigationBarBackgroundColor,
|
Theme.of(context).homeNavigationBarBackgroundColor,
|
||||||
|
@ -100,6 +77,47 @@ class HomeSliverAppBar extends StatelessWidget {
|
||||||
final bool isShowProgressIcon;
|
final bool isShowProgressIcon;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _TitleView extends StatelessWidget {
|
||||||
|
const _TitleView({
|
||||||
|
required this.account,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return ValueStreamBuilder<String?>(
|
||||||
|
stream:
|
||||||
|
context.read<AccountController>().accountPrefController.accountLabel,
|
||||||
|
builder: (context, snapshot) => AppBarTitleContainer(
|
||||||
|
title: Row(
|
||||||
|
children: [
|
||||||
|
account.scheme == "http"
|
||||||
|
? Icon(
|
||||||
|
Icons.no_encryption_outlined,
|
||||||
|
color: Theme.of(context).colorScheme.error,
|
||||||
|
size: 16,
|
||||||
|
)
|
||||||
|
: Icon(
|
||||||
|
Icons.https,
|
||||||
|
color: Theme.of(context).colorScheme.primary,
|
||||||
|
size: 16,
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
snapshot.data ?? account.address,
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.clip,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
subtitle: snapshot.data == null ? Text(account.username2) : null,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final Account account;
|
||||||
|
}
|
||||||
|
|
||||||
class _ProfileIconView extends StatelessWidget {
|
class _ProfileIconView extends StatelessWidget {
|
||||||
const _ProfileIconView({
|
const _ProfileIconView({
|
||||||
required this.account,
|
required this.account,
|
||||||
|
|
|
@ -34,6 +34,7 @@ import 'package:nc_photos/widget/places_browser.dart';
|
||||||
import 'package:nc_photos/widget/result_viewer.dart';
|
import 'package:nc_photos/widget/result_viewer.dart';
|
||||||
import 'package:nc_photos/widget/root_picker.dart';
|
import 'package:nc_photos/widget/root_picker.dart';
|
||||||
import 'package:nc_photos/widget/settings.dart';
|
import 'package:nc_photos/widget/settings.dart';
|
||||||
|
import 'package:nc_photos/widget/settings/account_settings.dart';
|
||||||
import 'package:nc_photos/widget/settings/language_settings.dart';
|
import 'package:nc_photos/widget/settings/language_settings.dart';
|
||||||
import 'package:nc_photos/widget/setup.dart';
|
import 'package:nc_photos/widget/setup.dart';
|
||||||
import 'package:nc_photos/widget/share_folder_picker.dart';
|
import 'package:nc_photos/widget/share_folder_picker.dart';
|
||||||
|
@ -177,6 +178,7 @@ class _WrappedAppState extends State<_WrappedApp>
|
||||||
),
|
),
|
||||||
CollectionPicker.routeName: CollectionPicker.buildRoute,
|
CollectionPicker.routeName: CollectionPicker.buildRoute,
|
||||||
LanguageSettings.routeName: LanguageSettings.buildRoute,
|
LanguageSettings.routeName: LanguageSettings.buildRoute,
|
||||||
|
AccountSettings.routeName: AccountSettings.buildRoute,
|
||||||
};
|
};
|
||||||
|
|
||||||
Route<dynamic>? _onGenerateRoute(RouteSettings settings) {
|
Route<dynamic>? _onGenerateRoute(RouteSettings settings) {
|
||||||
|
@ -198,7 +200,6 @@ class _WrappedAppState extends State<_WrappedApp>
|
||||||
route ??= _handleSharingBrowserRoute(settings);
|
route ??= _handleSharingBrowserRoute(settings);
|
||||||
route ??= _handleSharedFileViewerRoute(settings);
|
route ??= _handleSharedFileViewerRoute(settings);
|
||||||
route ??= _handleAlbumShareOutlierBrowserRoute(settings);
|
route ??= _handleAlbumShareOutlierBrowserRoute(settings);
|
||||||
route ??= _handleAccountSettingsRoute(settings);
|
|
||||||
route ??= _handleShareFolderPickerRoute(settings);
|
route ??= _handleShareFolderPickerRoute(settings);
|
||||||
route ??= _handleEnhancedPhotoBrowserRoute(settings);
|
route ??= _handleEnhancedPhotoBrowserRoute(settings);
|
||||||
route ??= _handleLocalFileViewerRoute(settings);
|
route ??= _handleLocalFileViewerRoute(settings);
|
||||||
|
@ -427,20 +428,6 @@ class _WrappedAppState extends State<_WrappedApp>
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
Route<dynamic>? _handleAccountSettingsRoute(RouteSettings settings) {
|
|
||||||
try {
|
|
||||||
if (settings.name == AccountSettingsWidget.routeName &&
|
|
||||||
settings.arguments != null) {
|
|
||||||
final args = settings.arguments as AccountSettingsWidgetArguments;
|
|
||||||
return AccountSettingsWidget.buildRoute(args);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
_log.severe(
|
|
||||||
"[_handleAccountSettingsRoute] Failed while handling route", e);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
Route<dynamic>? _handleShareFolderPickerRoute(RouteSettings settings) {
|
Route<dynamic>? _handleShareFolderPickerRoute(RouteSettings settings) {
|
||||||
try {
|
try {
|
||||||
if (settings.name == ShareFolderPicker.routeName &&
|
if (settings.name == ShareFolderPicker.routeName &&
|
||||||
|
|
|
@ -7,6 +7,7 @@ import 'package:nc_photos/account.dart';
|
||||||
import 'package:nc_photos/api/api_util.dart' as api_util;
|
import 'package:nc_photos/api/api_util.dart' as api_util;
|
||||||
import 'package:nc_photos/app_localizations.dart';
|
import 'package:nc_photos/app_localizations.dart';
|
||||||
import 'package:nc_photos/bloc/search_landing.dart';
|
import 'package:nc_photos/bloc/search_landing.dart';
|
||||||
|
import 'package:nc_photos/controller/account_controller.dart';
|
||||||
import 'package:nc_photos/di_container.dart';
|
import 'package:nc_photos/di_container.dart';
|
||||||
import 'package:nc_photos/entity/collection/builder.dart';
|
import 'package:nc_photos/entity/collection/builder.dart';
|
||||||
import 'package:nc_photos/entity/person.dart';
|
import 'package:nc_photos/entity/person.dart';
|
||||||
|
@ -14,7 +15,6 @@ import 'package:nc_photos/exception.dart';
|
||||||
import 'package:nc_photos/exception_util.dart' as exception_util;
|
import 'package:nc_photos/exception_util.dart' as exception_util;
|
||||||
import 'package:nc_photos/help_utils.dart' as help_util;
|
import 'package:nc_photos/help_utils.dart' as help_util;
|
||||||
import 'package:nc_photos/k.dart' as k;
|
import 'package:nc_photos/k.dart' as k;
|
||||||
import 'package:nc_photos/pref.dart';
|
|
||||||
import 'package:nc_photos/snack_bar_manager.dart';
|
import 'package:nc_photos/snack_bar_manager.dart';
|
||||||
import 'package:nc_photos/theme.dart';
|
import 'package:nc_photos/theme.dart';
|
||||||
import 'package:nc_photos/url_launcher_util.dart';
|
import 'package:nc_photos/url_launcher_util.dart';
|
||||||
|
@ -82,7 +82,11 @@ class _SearchLandingState extends State<SearchLanding> {
|
||||||
Widget _buildContent(BuildContext context, SearchLandingBlocState state) {
|
Widget _buildContent(BuildContext context, SearchLandingBlocState state) {
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
if (AccountPref.of(widget.account).isEnableFaceRecognitionAppOr())
|
if (context
|
||||||
|
.read<AccountController>()
|
||||||
|
.accountPrefController
|
||||||
|
.isEnableFaceRecognitionApp
|
||||||
|
.value)
|
||||||
..._buildPeopleSection(context, state),
|
..._buildPeopleSection(context, state),
|
||||||
..._buildLocationSection(context, state),
|
..._buildLocationSection(context, state),
|
||||||
ListTile(
|
ListTile(
|
||||||
|
|
|
@ -8,7 +8,6 @@ import 'package:nc_photos/app_localizations.dart';
|
||||||
import 'package:nc_photos/controller/pref_controller.dart';
|
import 'package:nc_photos/controller/pref_controller.dart';
|
||||||
import 'package:nc_photos/debug_util.dart';
|
import 'package:nc_photos/debug_util.dart';
|
||||||
import 'package:nc_photos/event/event.dart';
|
import 'package:nc_photos/event/event.dart';
|
||||||
import 'package:nc_photos/exception_util.dart' as exception_util;
|
|
||||||
import 'package:nc_photos/k.dart' as k;
|
import 'package:nc_photos/k.dart' as k;
|
||||||
import 'package:nc_photos/language_util.dart' as language_util;
|
import 'package:nc_photos/language_util.dart' as language_util;
|
||||||
import 'package:nc_photos/mobile/platform.dart'
|
import 'package:nc_photos/mobile/platform.dart'
|
||||||
|
@ -23,15 +22,12 @@ import 'package:nc_photos/stream_util.dart';
|
||||||
import 'package:nc_photos/url_launcher_util.dart';
|
import 'package:nc_photos/url_launcher_util.dart';
|
||||||
import 'package:nc_photos/widget/fancy_option_picker.dart';
|
import 'package:nc_photos/widget/fancy_option_picker.dart';
|
||||||
import 'package:nc_photos/widget/gps_map.dart';
|
import 'package:nc_photos/widget/gps_map.dart';
|
||||||
import 'package:nc_photos/widget/home.dart';
|
|
||||||
import 'package:nc_photos/widget/list_tile_center_leading.dart';
|
import 'package:nc_photos/widget/list_tile_center_leading.dart';
|
||||||
import 'package:nc_photos/widget/root_picker.dart';
|
|
||||||
import 'package:nc_photos/widget/settings/developer_settings.dart';
|
import 'package:nc_photos/widget/settings/developer_settings.dart';
|
||||||
import 'package:nc_photos/widget/settings/expert_settings.dart';
|
import 'package:nc_photos/widget/settings/expert_settings.dart';
|
||||||
import 'package:nc_photos/widget/settings/language_settings.dart';
|
import 'package:nc_photos/widget/settings/language_settings.dart';
|
||||||
|
import 'package:nc_photos/widget/settings/settings_list_caption.dart';
|
||||||
import 'package:nc_photos/widget/settings/theme_settings.dart';
|
import 'package:nc_photos/widget/settings/theme_settings.dart';
|
||||||
import 'package:nc_photos/widget/share_folder_picker.dart';
|
|
||||||
import 'package:nc_photos/widget/simple_input_dialog.dart';
|
|
||||||
import 'package:nc_photos/widget/stateful_slider.dart';
|
import 'package:nc_photos/widget/stateful_slider.dart';
|
||||||
import 'package:np_codegen/np_codegen.dart';
|
import 'package:np_codegen/np_codegen.dart';
|
||||||
import 'package:screen_brightness/screen_brightness.dart';
|
import 'package:screen_brightness/screen_brightness.dart';
|
||||||
|
@ -199,7 +195,9 @@ class _SettingsState extends State<Settings> {
|
||||||
label: "Developer options",
|
label: "Developer options",
|
||||||
builder: () => const DeveloperSettings(),
|
builder: () => const DeveloperSettings(),
|
||||||
),
|
),
|
||||||
_buildCaption(context, L10n.global().settingsAboutSectionTitle),
|
SettingsListCaption(
|
||||||
|
label: L10n.global().settingsAboutSectionTitle,
|
||||||
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
title: Text(L10n.global().settingsVersionTitle),
|
title: Text(L10n.global().settingsVersionTitle),
|
||||||
subtitle: const Text(k.versionStr),
|
subtitle: const Text(k.versionStr),
|
||||||
|
@ -410,334 +408,6 @@ class _SettingsState extends State<Settings> {
|
||||||
static const String _translationUrl = "https://bit.ly/3NwmdSw";
|
static const String _translationUrl = "https://bit.ly/3NwmdSw";
|
||||||
}
|
}
|
||||||
|
|
||||||
class AccountSettingsWidgetArguments {
|
|
||||||
const AccountSettingsWidgetArguments(this.account);
|
|
||||||
|
|
||||||
final Account account;
|
|
||||||
}
|
|
||||||
|
|
||||||
class AccountSettingsWidget extends StatefulWidget {
|
|
||||||
static const routeName = "/account-settings";
|
|
||||||
|
|
||||||
static Route buildRoute(AccountSettingsWidgetArguments args) =>
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) => AccountSettingsWidget.fromArgs(args),
|
|
||||||
);
|
|
||||||
|
|
||||||
const AccountSettingsWidget({
|
|
||||||
Key? key,
|
|
||||||
required this.account,
|
|
||||||
}) : super(key: key);
|
|
||||||
|
|
||||||
AccountSettingsWidget.fromArgs(AccountSettingsWidgetArguments args,
|
|
||||||
{Key? key})
|
|
||||||
: this(
|
|
||||||
key: key,
|
|
||||||
account: args.account,
|
|
||||||
);
|
|
||||||
|
|
||||||
@override
|
|
||||||
createState() => _AccountSettingsState();
|
|
||||||
|
|
||||||
final Account account;
|
|
||||||
}
|
|
||||||
|
|
||||||
@npLog
|
|
||||||
class _AccountSettingsState extends State<AccountSettingsWidget> {
|
|
||||||
@override
|
|
||||||
initState() {
|
|
||||||
super.initState();
|
|
||||||
_account = widget.account;
|
|
||||||
|
|
||||||
final settings = AccountPref.of(_account);
|
|
||||||
_isEnableFaceRecognitionApp = settings.isEnableFaceRecognitionAppOr();
|
|
||||||
_shareFolder = settings.getShareFolderOr();
|
|
||||||
_label = settings.getAccountLabel();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
build(BuildContext context) {
|
|
||||||
return Scaffold(
|
|
||||||
body: Builder(
|
|
||||||
builder: (context) => _buildContent(context),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildContent(BuildContext context) {
|
|
||||||
return WillPopScope(
|
|
||||||
onWillPop: () async => !_shouldReload,
|
|
||||||
child: CustomScrollView(
|
|
||||||
slivers: [
|
|
||||||
SliverAppBar(
|
|
||||||
pinned: true,
|
|
||||||
title: Text(L10n.global().settingsAccountTitle),
|
|
||||||
leading: _shouldReload
|
|
||||||
? IconButton(
|
|
||||||
icon: const Icon(Icons.check),
|
|
||||||
tooltip: L10n.global().doneButtonTooltip,
|
|
||||||
onPressed: () => _onDonePressed(context),
|
|
||||||
)
|
|
||||||
: null,
|
|
||||||
),
|
|
||||||
SliverList(
|
|
||||||
delegate: SliverChildListDelegate(
|
|
||||||
[
|
|
||||||
ListTile(
|
|
||||||
title: Text(L10n.global().settingsAccountLabelTitle),
|
|
||||||
subtitle: Text(
|
|
||||||
_label ?? L10n.global().settingsAccountLabelDescription),
|
|
||||||
onTap: () => _onLabelPressed(context),
|
|
||||||
),
|
|
||||||
ListTile(
|
|
||||||
title: Text(L10n.global().settingsIncludedFoldersTitle),
|
|
||||||
subtitle: Text(_account.roots.map((e) => "/$e").join("; ")),
|
|
||||||
onTap: _onIncludedFoldersPressed,
|
|
||||||
),
|
|
||||||
ListTile(
|
|
||||||
title: Text(L10n.global().settingsShareFolderTitle),
|
|
||||||
subtitle: Text("/$_shareFolder"),
|
|
||||||
onTap: () => _onShareFolderPressed(context),
|
|
||||||
),
|
|
||||||
_buildCaption(
|
|
||||||
context, L10n.global().settingsServerAppSectionTitle),
|
|
||||||
SwitchListTile(
|
|
||||||
title: const Text("Face Recognition"),
|
|
||||||
value: _isEnableFaceRecognitionApp,
|
|
||||||
onChanged: _onEnableFaceRecognitionAppChanged,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _onDonePressed(BuildContext context) {
|
|
||||||
Navigator.of(context).pushNamedAndRemoveUntil(
|
|
||||||
Home.routeName,
|
|
||||||
(route) => false,
|
|
||||||
arguments: HomeArguments(_account),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _onLabelPressed(BuildContext context) async {
|
|
||||||
final result = await showDialog<String>(
|
|
||||||
context: context,
|
|
||||||
builder: (context) => SimpleInputDialog(
|
|
||||||
titleText: L10n.global().settingsAccountLabelTitle,
|
|
||||||
buttonText: MaterialLocalizations.of(context).okButtonLabel,
|
|
||||||
initialText: _label ?? "",
|
|
||||||
));
|
|
||||||
if (result == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (result.isEmpty) {
|
|
||||||
return _setLabel(null);
|
|
||||||
} else {
|
|
||||||
return _setLabel(result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _onIncludedFoldersPressed() async {
|
|
||||||
try {
|
|
||||||
final result = await Navigator.of(context).pushNamed<Account>(
|
|
||||||
RootPicker.routeName,
|
|
||||||
arguments: RootPickerArguments(_account));
|
|
||||||
if (result == null) {
|
|
||||||
// user canceled
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// we've got a good account
|
|
||||||
if (result == _account) {
|
|
||||||
// no changes, do nothing
|
|
||||||
_log.fine("[_onIncludedFoldersPressed] No changes");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
final accounts = Pref().getAccounts3()!;
|
|
||||||
if (accounts.contains(result)) {
|
|
||||||
// conflict with another account. This normally won't happen because
|
|
||||||
// the app passwords are unique to each entry, but just in case
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
SnackBarManager().showSnackBar(SnackBar(
|
|
||||||
content: Text(L10n.global().editAccountConflictFailureNotification),
|
|
||||||
duration: k.snackBarDurationNormal,
|
|
||||||
));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final index = accounts.indexOf(_account);
|
|
||||||
if (index < 0) {
|
|
||||||
_log.shout("[_onIncludedFoldersPressed] Account not found: $_account");
|
|
||||||
SnackBarManager().showSnackBar(SnackBar(
|
|
||||||
content: Text(L10n.global().writePreferenceFailureNotification),
|
|
||||||
duration: k.snackBarDurationNormal,
|
|
||||||
));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
accounts[index] = result;
|
|
||||||
if (!await Pref().setAccounts3(accounts)) {
|
|
||||||
SnackBarManager().showSnackBar(SnackBar(
|
|
||||||
content: Text(L10n.global().writePreferenceFailureNotification),
|
|
||||||
duration: k.snackBarDurationNormal,
|
|
||||||
));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setState(() {
|
|
||||||
_account = result;
|
|
||||||
_shouldReload = true;
|
|
||||||
});
|
|
||||||
} catch (e, stackTrace) {
|
|
||||||
_log.shout("[_onIncludedFoldersPressed] Exception", e, stackTrace);
|
|
||||||
SnackBarManager().showSnackBar(SnackBar(
|
|
||||||
content: Text(exception_util.toUserString(e)),
|
|
||||||
duration: k.snackBarDurationNormal,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _onShareFolderPressed(BuildContext context) async {
|
|
||||||
final path = await showDialog<String>(
|
|
||||||
context: context,
|
|
||||||
builder: (_) => _ShareFolderDialog(
|
|
||||||
account: widget.account,
|
|
||||||
initialValue: _shareFolder,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
if (path == null || path == _shareFolder) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
return _setShareFolder(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _onEnableFaceRecognitionAppChanged(bool value) async {
|
|
||||||
_log.info("[_onEnableFaceRecognitionAppChanged] New value: $value");
|
|
||||||
final oldValue = _isEnableFaceRecognitionApp;
|
|
||||||
setState(() {
|
|
||||||
_isEnableFaceRecognitionApp = value;
|
|
||||||
});
|
|
||||||
if (!await AccountPref.of(_account).setEnableFaceRecognitionApp(value)) {
|
|
||||||
_log.severe("[_onEnableFaceRecognitionAppChanged] Failed writing pref");
|
|
||||||
SnackBarManager().showSnackBar(SnackBar(
|
|
||||||
content: Text(L10n.global().writePreferenceFailureNotification),
|
|
||||||
duration: k.snackBarDurationNormal,
|
|
||||||
));
|
|
||||||
setState(() {
|
|
||||||
_isEnableFaceRecognitionApp = oldValue;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _setLabel(String? value) async {
|
|
||||||
_log.info("[_setLabel] New value: $value");
|
|
||||||
final oldValue = _label;
|
|
||||||
setState(() {
|
|
||||||
_label = value;
|
|
||||||
});
|
|
||||||
if (!await AccountPref.of(_account).setAccountLabel(value)) {
|
|
||||||
_log.severe("[_setLabel] Failed writing pref");
|
|
||||||
SnackBarManager().showSnackBar(SnackBar(
|
|
||||||
content: Text(L10n.global().writePreferenceFailureNotification),
|
|
||||||
duration: k.snackBarDurationNormal,
|
|
||||||
));
|
|
||||||
setState(() {
|
|
||||||
_label = oldValue;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _setShareFolder(String value) async {
|
|
||||||
_log.info("[_setShareFolder] New value: $value");
|
|
||||||
final oldValue = _shareFolder;
|
|
||||||
setState(() {
|
|
||||||
_shareFolder = value;
|
|
||||||
});
|
|
||||||
if (!await AccountPref.of(_account).setShareFolder(value)) {
|
|
||||||
_log.severe("[_setShareFolder] Failed writing pref");
|
|
||||||
SnackBarManager().showSnackBar(SnackBar(
|
|
||||||
content: Text(L10n.global().writePreferenceFailureNotification),
|
|
||||||
duration: k.snackBarDurationNormal,
|
|
||||||
));
|
|
||||||
setState(() {
|
|
||||||
_shareFolder = oldValue;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool _shouldReload = false;
|
|
||||||
late Account _account;
|
|
||||||
late bool _isEnableFaceRecognitionApp;
|
|
||||||
late String _shareFolder;
|
|
||||||
late String? _label;
|
|
||||||
}
|
|
||||||
|
|
||||||
class _ShareFolderDialog extends StatefulWidget {
|
|
||||||
const _ShareFolderDialog({
|
|
||||||
Key? key,
|
|
||||||
required this.account,
|
|
||||||
required this.initialValue,
|
|
||||||
}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
createState() => _ShareFolderDialogState();
|
|
||||||
|
|
||||||
final Account account;
|
|
||||||
final String initialValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
class _ShareFolderDialogState extends State<_ShareFolderDialog> {
|
|
||||||
@override
|
|
||||||
build(BuildContext context) {
|
|
||||||
return AlertDialog(
|
|
||||||
title: Text(L10n.global().settingsShareFolderDialogTitle),
|
|
||||||
content: Form(
|
|
||||||
key: _formKey,
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
Text(L10n.global().settingsShareFolderDialogDescription),
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
InkWell(
|
|
||||||
onTap: _onTextFieldPressed,
|
|
||||||
child: TextFormField(
|
|
||||||
enabled: false,
|
|
||||||
controller: _controller,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
actions: [
|
|
||||||
TextButton(
|
|
||||||
onPressed: _onOkPressed,
|
|
||||||
child: Text(MaterialLocalizations.of(context).okButtonLabel),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _onTextFieldPressed() async {
|
|
||||||
final pick = await Navigator.of(context).pushNamed<String>(
|
|
||||||
ShareFolderPicker.routeName,
|
|
||||||
arguments: ShareFolderPickerArguments(widget.account, _path));
|
|
||||||
if (pick != null) {
|
|
||||||
_path = pick;
|
|
||||||
_controller.text = "/$pick";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _onOkPressed() {
|
|
||||||
Navigator.of(context).pop(_path);
|
|
||||||
}
|
|
||||||
|
|
||||||
final _formKey = GlobalKey<FormState>();
|
|
||||||
late final _controller =
|
|
||||||
TextEditingController(text: "/${widget.initialValue}");
|
|
||||||
late String _path = widget.initialValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
class _PhotosSettings extends StatefulWidget {
|
class _PhotosSettings extends StatefulWidget {
|
||||||
const _PhotosSettings({
|
const _PhotosSettings({
|
||||||
Key? key,
|
Key? key,
|
||||||
|
@ -1527,17 +1197,5 @@ class _MiscSettingsState extends State<_MiscSettings> {
|
||||||
late bool _isDoubleTapExit;
|
late bool _isDoubleTapExit;
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildCaption(BuildContext context, String label) {
|
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.fromLTRB(16, 16, 16, 8),
|
|
||||||
child: Text(
|
|
||||||
label,
|
|
||||||
style: Theme.of(context).textTheme.titleMedium!.copyWith(
|
|
||||||
color: Theme.of(context).colorScheme.primary,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// final _enabledExperiments = [
|
// final _enabledExperiments = [
|
||||||
// ];
|
// ];
|
||||||
|
|
|
@ -13,13 +13,6 @@ extension _$_SettingsStateNpLog on _SettingsState {
|
||||||
static final log = Logger("widget.settings._SettingsState");
|
static final log = Logger("widget.settings._SettingsState");
|
||||||
}
|
}
|
||||||
|
|
||||||
extension _$_AccountSettingsStateNpLog on _AccountSettingsState {
|
|
||||||
// ignore: unused_element
|
|
||||||
Logger get _log => log;
|
|
||||||
|
|
||||||
static final log = Logger("widget.settings._AccountSettingsState");
|
|
||||||
}
|
|
||||||
|
|
||||||
extension _$_PhotosSettingsStateNpLog on _PhotosSettingsState {
|
extension _$_PhotosSettingsStateNpLog on _PhotosSettingsState {
|
||||||
// ignore: unused_element
|
// ignore: unused_element
|
||||||
Logger get _log => log;
|
Logger get _log => log;
|
||||||
|
|
158
app/lib/widget/settings/account/bloc.dart
Normal file
158
app/lib/widget/settings/account/bloc.dart
Normal file
|
@ -0,0 +1,158 @@
|
||||||
|
part of '../account_settings.dart';
|
||||||
|
|
||||||
|
@npLog
|
||||||
|
class _Bloc extends Bloc<_Event, _State> {
|
||||||
|
_Bloc({
|
||||||
|
required DiContainer container,
|
||||||
|
required Account account,
|
||||||
|
required this.accountPrefController,
|
||||||
|
}) : _c = container,
|
||||||
|
super(_State.init(
|
||||||
|
account: account,
|
||||||
|
label: accountPrefController.accountLabel.value,
|
||||||
|
shareFolder: accountPrefController.shareFolder.value,
|
||||||
|
isEnableFaceRecognitionApp:
|
||||||
|
accountPrefController.isEnableFaceRecognitionApp.value,
|
||||||
|
)) {
|
||||||
|
on<_SetLabel>(_onSetLabel);
|
||||||
|
on<_OnUpdateLabel>(_onOnUpdateLabel);
|
||||||
|
_subscriptions.add(accountPrefController.accountLabel.listen(
|
||||||
|
(event) {
|
||||||
|
add(_OnUpdateLabel(event));
|
||||||
|
},
|
||||||
|
onError: (e, stackTrace) {
|
||||||
|
add(_SetError(_WritePrefError(e, stackTrace)));
|
||||||
|
},
|
||||||
|
));
|
||||||
|
|
||||||
|
on<_SetAccount>(_onSetAccount);
|
||||||
|
on<_OnUpdateAccount>(_onOnUpdateAccount);
|
||||||
|
|
||||||
|
on<_SetShareFolder>(_onSetShareFolder);
|
||||||
|
on<_OnUpdateShareFolder>(_onOnUpdateShareFolder);
|
||||||
|
_subscriptions.add(accountPrefController.shareFolder.listen(
|
||||||
|
(event) {
|
||||||
|
add(_OnUpdateShareFolder(event));
|
||||||
|
},
|
||||||
|
onError: (e, stackTrace) {
|
||||||
|
add(_SetError(_WritePrefError(e, stackTrace)));
|
||||||
|
},
|
||||||
|
));
|
||||||
|
|
||||||
|
on<_SetEnableFaceRecognitionApp>(_onSetEnableFaceRecognitionApp);
|
||||||
|
on<_OnUpdateEnableFaceRecognitionApp>(_onOnUpdateEnableFaceRecognitionApp);
|
||||||
|
_subscriptions.add(accountPrefController.isEnableFaceRecognitionApp.listen(
|
||||||
|
(event) {
|
||||||
|
add(_OnUpdateEnableFaceRecognitionApp(event));
|
||||||
|
},
|
||||||
|
onError: (e, stackTrace) {
|
||||||
|
add(_SetError(_WritePrefError(e, stackTrace)));
|
||||||
|
},
|
||||||
|
));
|
||||||
|
|
||||||
|
on<_SetError>(_onSetError);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> close() {
|
||||||
|
for (final s in _subscriptions) {
|
||||||
|
unawaited(s.cancel());
|
||||||
|
}
|
||||||
|
return super.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onError(Object error, StackTrace stackTrace) {
|
||||||
|
// we need this to prevent onError being triggered recursively
|
||||||
|
if (!isClosed && !_isHandlingError) {
|
||||||
|
_isHandlingError = true;
|
||||||
|
try {
|
||||||
|
add(_SetError(error, stackTrace));
|
||||||
|
} catch (_) {}
|
||||||
|
_isHandlingError = false;
|
||||||
|
}
|
||||||
|
super.onError(error, stackTrace);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onSetLabel(_SetLabel ev, Emitter<_State> emit) {
|
||||||
|
_log.info(ev);
|
||||||
|
accountPrefController.setAccountLabel(ev.label);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onOnUpdateLabel(_OnUpdateLabel ev, Emitter<_State> emit) {
|
||||||
|
_log.info(ev);
|
||||||
|
emit(state.copyWith(label: ev.label));
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _onSetAccount(_SetAccount ev, Emitter<_State> emit) async {
|
||||||
|
_log.info(ev);
|
||||||
|
emit(state.copyWith(
|
||||||
|
account: ev.account,
|
||||||
|
shouldReload: true,
|
||||||
|
));
|
||||||
|
final revert = state.account;
|
||||||
|
try {
|
||||||
|
final accounts = _c.pref.getAccounts3()!;
|
||||||
|
if (accounts.contains(ev.account)) {
|
||||||
|
// conflict with another account. This normally won't happen because
|
||||||
|
// the app passwords are unique to each entry, but just in case
|
||||||
|
throw const _AccountConflictError();
|
||||||
|
}
|
||||||
|
|
||||||
|
final index = accounts.indexWhere((a) => a.id == ev.account.id);
|
||||||
|
if (index < 0) {
|
||||||
|
_log.shout("[_onSetAccount] Account not found: ${ev.account}");
|
||||||
|
throw const _WritePrefError();
|
||||||
|
}
|
||||||
|
|
||||||
|
accounts[index] = ev.account;
|
||||||
|
if (!await _c.pref.setAccounts3(accounts)) {
|
||||||
|
_log.severe("[_onSetAccount] Failed while setAccounts3: ${ev.account}");
|
||||||
|
throw const _WritePrefError();
|
||||||
|
}
|
||||||
|
} catch (_) {
|
||||||
|
emit(state.copyWith(account: revert));
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onOnUpdateAccount(_OnUpdateAccount ev, Emitter<_State> emit) {
|
||||||
|
_log.info(ev);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onSetShareFolder(_SetShareFolder ev, Emitter<_State> emit) {
|
||||||
|
_log.info(ev);
|
||||||
|
accountPrefController.setShareFolder(ev.shareFolder);
|
||||||
|
emit(state.copyWith(shouldReload: true));
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onOnUpdateShareFolder(_OnUpdateShareFolder ev, Emitter<_State> emit) {
|
||||||
|
_log.info(ev);
|
||||||
|
emit(state.copyWith(shareFolder: ev.shareFolder));
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onSetEnableFaceRecognitionApp(
|
||||||
|
_SetEnableFaceRecognitionApp ev, Emitter<_State> emit) {
|
||||||
|
_log.info(ev);
|
||||||
|
accountPrefController
|
||||||
|
.setEnableFaceRecognitionApp(ev.isEnableFaceRecognitionApp);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onOnUpdateEnableFaceRecognitionApp(
|
||||||
|
_OnUpdateEnableFaceRecognitionApp ev, Emitter<_State> emit) {
|
||||||
|
_log.info(ev);
|
||||||
|
emit(state.copyWith(
|
||||||
|
isEnableFaceRecognitionApp: ev.isEnableFaceRecognitionApp));
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onSetError(_SetError ev, Emitter<_State> emit) {
|
||||||
|
_log.info(ev);
|
||||||
|
emit(state.copyWith(error: ExceptionEvent(ev.error, ev.stackTrace)));
|
||||||
|
}
|
||||||
|
|
||||||
|
final DiContainer _c;
|
||||||
|
final AccountPrefController accountPrefController;
|
||||||
|
|
||||||
|
final _subscriptions = <StreamSubscription>[];
|
||||||
|
var _isHandlingError = false;
|
||||||
|
}
|
148
app/lib/widget/settings/account/state_event.dart
Normal file
148
app/lib/widget/settings/account/state_event.dart
Normal file
|
@ -0,0 +1,148 @@
|
||||||
|
part of '../account_settings.dart';
|
||||||
|
|
||||||
|
@genCopyWith
|
||||||
|
@toString
|
||||||
|
class _State {
|
||||||
|
const _State({
|
||||||
|
required this.account,
|
||||||
|
required this.shouldReload,
|
||||||
|
required this.label,
|
||||||
|
required this.shareFolder,
|
||||||
|
required this.isEnableFaceRecognitionApp,
|
||||||
|
this.error,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory _State.init({
|
||||||
|
required Account account,
|
||||||
|
required String? label,
|
||||||
|
required String shareFolder,
|
||||||
|
required bool isEnableFaceRecognitionApp,
|
||||||
|
}) {
|
||||||
|
return _State(
|
||||||
|
shouldReload: false,
|
||||||
|
account: account,
|
||||||
|
label: label,
|
||||||
|
shareFolder: shareFolder,
|
||||||
|
isEnableFaceRecognitionApp: isEnableFaceRecognitionApp,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => _$toString();
|
||||||
|
|
||||||
|
final bool shouldReload;
|
||||||
|
final Account account;
|
||||||
|
final String? label;
|
||||||
|
final String shareFolder;
|
||||||
|
final bool isEnableFaceRecognitionApp;
|
||||||
|
|
||||||
|
final ExceptionEvent? error;
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AccountConflictError implements Exception {
|
||||||
|
const _AccountConflictError();
|
||||||
|
}
|
||||||
|
|
||||||
|
@toString
|
||||||
|
class _WritePrefError implements Exception {
|
||||||
|
const _WritePrefError([this.error, this.stackTrace]);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => _$toString();
|
||||||
|
|
||||||
|
final Error? error;
|
||||||
|
final StackTrace? stackTrace;
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class _Event {}
|
||||||
|
|
||||||
|
@toString
|
||||||
|
class _SetLabel implements _Event {
|
||||||
|
const _SetLabel(this.label);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => _$toString();
|
||||||
|
|
||||||
|
final String? label;
|
||||||
|
}
|
||||||
|
|
||||||
|
@toString
|
||||||
|
class _OnUpdateLabel implements _Event {
|
||||||
|
const _OnUpdateLabel(this.label);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => _$toString();
|
||||||
|
|
||||||
|
final String? label;
|
||||||
|
}
|
||||||
|
|
||||||
|
@toString
|
||||||
|
class _SetAccount implements _Event {
|
||||||
|
const _SetAccount(this.account);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => _$toString();
|
||||||
|
|
||||||
|
final Account account;
|
||||||
|
}
|
||||||
|
|
||||||
|
@toString
|
||||||
|
class _OnUpdateAccount implements _Event {
|
||||||
|
const _OnUpdateAccount(this.account);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => _$toString();
|
||||||
|
|
||||||
|
final Account account;
|
||||||
|
}
|
||||||
|
|
||||||
|
@toString
|
||||||
|
class _SetShareFolder implements _Event {
|
||||||
|
const _SetShareFolder(this.shareFolder);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => _$toString();
|
||||||
|
|
||||||
|
final String shareFolder;
|
||||||
|
}
|
||||||
|
|
||||||
|
@toString
|
||||||
|
class _OnUpdateShareFolder implements _Event {
|
||||||
|
const _OnUpdateShareFolder(this.shareFolder);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => _$toString();
|
||||||
|
|
||||||
|
final String shareFolder;
|
||||||
|
}
|
||||||
|
|
||||||
|
@toString
|
||||||
|
class _SetEnableFaceRecognitionApp implements _Event {
|
||||||
|
const _SetEnableFaceRecognitionApp(this.isEnableFaceRecognitionApp);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => _$toString();
|
||||||
|
|
||||||
|
final bool isEnableFaceRecognitionApp;
|
||||||
|
}
|
||||||
|
|
||||||
|
@toString
|
||||||
|
class _OnUpdateEnableFaceRecognitionApp implements _Event {
|
||||||
|
const _OnUpdateEnableFaceRecognitionApp(this.isEnableFaceRecognitionApp);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => _$toString();
|
||||||
|
|
||||||
|
final bool isEnableFaceRecognitionApp;
|
||||||
|
}
|
||||||
|
|
||||||
|
@toString
|
||||||
|
class _SetError implements _Event {
|
||||||
|
const _SetError(this.error, [this.stackTrace]);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => _$toString();
|
||||||
|
|
||||||
|
final Object error;
|
||||||
|
final StackTrace? stackTrace;
|
||||||
|
}
|
294
app/lib/widget/settings/account_settings.dart
Normal file
294
app/lib/widget/settings/account_settings.dart
Normal file
|
@ -0,0 +1,294 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:copy_with/copy_with.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:kiwi/kiwi.dart';
|
||||||
|
import 'package:logging/logging.dart';
|
||||||
|
import 'package:nc_photos/account.dart';
|
||||||
|
import 'package:nc_photos/app_localizations.dart';
|
||||||
|
import 'package:nc_photos/controller/account_controller.dart';
|
||||||
|
import 'package:nc_photos/controller/account_pref_controller.dart';
|
||||||
|
import 'package:nc_photos/di_container.dart';
|
||||||
|
import 'package:nc_photos/exception_event.dart';
|
||||||
|
import 'package:nc_photos/exception_util.dart' as exception_util;
|
||||||
|
import 'package:nc_photos/k.dart' as k;
|
||||||
|
import 'package:nc_photos/snack_bar_manager.dart';
|
||||||
|
import 'package:nc_photos/widget/home.dart';
|
||||||
|
import 'package:nc_photos/widget/page_visibility_mixin.dart';
|
||||||
|
import 'package:nc_photos/widget/root_picker.dart';
|
||||||
|
import 'package:nc_photos/widget/settings/settings_list_caption.dart';
|
||||||
|
import 'package:nc_photos/widget/share_folder_picker.dart';
|
||||||
|
import 'package:nc_photos/widget/simple_input_dialog.dart';
|
||||||
|
import 'package:np_codegen/np_codegen.dart';
|
||||||
|
import 'package:to_string/to_string.dart';
|
||||||
|
|
||||||
|
part 'account/bloc.dart';
|
||||||
|
part 'account/state_event.dart';
|
||||||
|
part 'account_settings.g.dart';
|
||||||
|
|
||||||
|
// typedef _BlocBuilder = BlocBuilder<_Bloc, _State>;
|
||||||
|
typedef _BlocListener = BlocListener<_Bloc, _State>;
|
||||||
|
typedef _BlocSelector<T> = BlocSelector<_Bloc, _State, T>;
|
||||||
|
|
||||||
|
class AccountSettings extends StatelessWidget {
|
||||||
|
static const routeName = "/account-settings";
|
||||||
|
|
||||||
|
static Route buildRoute() => MaterialPageRoute(
|
||||||
|
builder: (_) => const AccountSettings(),
|
||||||
|
);
|
||||||
|
|
||||||
|
const AccountSettings({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final accountController = context.read<AccountController>();
|
||||||
|
return BlocProvider(
|
||||||
|
create: (_) => _Bloc(
|
||||||
|
container: KiwiContainer().resolve(),
|
||||||
|
account: accountController.account,
|
||||||
|
accountPrefController: accountController.accountPrefController,
|
||||||
|
),
|
||||||
|
child: const _WrappedAccountSettings(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _WrappedAccountSettings extends StatefulWidget {
|
||||||
|
const _WrappedAccountSettings();
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<StatefulWidget> createState() => _WrappedDeveloperSettingsState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@npLog
|
||||||
|
class _WrappedDeveloperSettingsState extends State<_WrappedAccountSettings>
|
||||||
|
with RouteAware, PageVisibilityMixin {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
body: MultiBlocListener(
|
||||||
|
listeners: [
|
||||||
|
_BlocListener(
|
||||||
|
listenWhen: (previous, current) => previous.error != current.error,
|
||||||
|
listener: (context, state) {
|
||||||
|
if (state.error != null && isPageVisible()) {
|
||||||
|
final String errorMsg;
|
||||||
|
if (state.error is _AccountConflictError) {
|
||||||
|
errorMsg =
|
||||||
|
L10n.global().editAccountConflictFailureNotification;
|
||||||
|
} else if (state.error is _WritePrefError) {
|
||||||
|
errorMsg = L10n.global().writePreferenceFailureNotification;
|
||||||
|
} else {
|
||||||
|
errorMsg = exception_util.toUserString(state.error!.error);
|
||||||
|
}
|
||||||
|
SnackBarManager().showSnackBar(SnackBar(
|
||||||
|
content: Text(errorMsg),
|
||||||
|
duration: k.snackBarDurationNormal,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
child: WillPopScope(
|
||||||
|
onWillPop: () async => !context.read<_Bloc>().state.shouldReload,
|
||||||
|
child: CustomScrollView(
|
||||||
|
slivers: [
|
||||||
|
SliverAppBar(
|
||||||
|
pinned: true,
|
||||||
|
title: Text(L10n.global().settingsAccountTitle),
|
||||||
|
leading: _BlocSelector<bool>(
|
||||||
|
selector: (state) => state.shouldReload,
|
||||||
|
builder: (_, state) =>
|
||||||
|
state ? const _DoneButton() : const BackButton(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SliverList(
|
||||||
|
delegate: SliverChildListDelegate(
|
||||||
|
[
|
||||||
|
_BlocSelector<String?>(
|
||||||
|
selector: (state) => state.label,
|
||||||
|
builder: (context, state) => ListTile(
|
||||||
|
title: Text(L10n.global().settingsAccountLabelTitle),
|
||||||
|
subtitle: Text(state ??
|
||||||
|
L10n.global().settingsAccountLabelDescription),
|
||||||
|
onTap: () => _onLabelPressed(context),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
_BlocSelector<Account>(
|
||||||
|
selector: (state) => state.account,
|
||||||
|
builder: (context, state) => ListTile(
|
||||||
|
title: Text(L10n.global().settingsIncludedFoldersTitle),
|
||||||
|
subtitle:
|
||||||
|
Text(state.roots.map((e) => "/$e").join("; ")),
|
||||||
|
onTap: () => _onIncludedFoldersPressed(context),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
_BlocSelector<String>(
|
||||||
|
selector: (state) => state.shareFolder,
|
||||||
|
builder: (context, state) => ListTile(
|
||||||
|
title: Text(L10n.global().settingsShareFolderTitle),
|
||||||
|
subtitle: Text("/$state"),
|
||||||
|
onTap: () => _onShareFolderPressed(context),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SettingsListCaption(
|
||||||
|
label: L10n.global().settingsServerAppSectionTitle,
|
||||||
|
),
|
||||||
|
_BlocSelector<bool>(
|
||||||
|
selector: (state) => state.isEnableFaceRecognitionApp,
|
||||||
|
builder: (context, state) => SwitchListTile(
|
||||||
|
title: const Text("Face Recognition"),
|
||||||
|
value: state,
|
||||||
|
onChanged: (value) =>
|
||||||
|
_onEnableFaceRecognitionAppChanged(context, value),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _onLabelPressed(BuildContext context) async {
|
||||||
|
final bloc = context.read<_Bloc>();
|
||||||
|
final result = await showDialog<String>(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => SimpleInputDialog(
|
||||||
|
titleText: L10n.global().settingsAccountLabelTitle,
|
||||||
|
buttonText: MaterialLocalizations.of(context).okButtonLabel,
|
||||||
|
initialText: bloc.state.label ?? "",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (!context.mounted || result == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
context.read<_Bloc>().add(_SetLabel(result.isEmpty ? null : result));
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _onIncludedFoldersPressed(BuildContext context) async {
|
||||||
|
final bloc = context.read<_Bloc>();
|
||||||
|
final result = await Navigator.of(context).pushNamed<Account>(
|
||||||
|
RootPicker.routeName,
|
||||||
|
arguments: RootPickerArguments(bloc.state.account),
|
||||||
|
);
|
||||||
|
if (result == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (result == bloc.state.account) {
|
||||||
|
// no changes, do nothing
|
||||||
|
_log.fine("[_onIncludedFoldersPressed] No changes");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
context.read<_Bloc>().add(_SetAccount(result));
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _onShareFolderPressed(BuildContext context) async {
|
||||||
|
final bloc = context.read<_Bloc>();
|
||||||
|
final result = await showDialog<String>(
|
||||||
|
context: context,
|
||||||
|
builder: (_) => _ShareFolderDialog(
|
||||||
|
account: bloc.state.account,
|
||||||
|
initialValue: bloc.state.shareFolder,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (!context.mounted || result == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
context.read<_Bloc>().add(_SetShareFolder(result));
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onEnableFaceRecognitionAppChanged(BuildContext context, bool value) {
|
||||||
|
context.read<_Bloc>().add(_SetEnableFaceRecognitionApp(value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DoneButton extends StatelessWidget {
|
||||||
|
const _DoneButton();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return IconButton(
|
||||||
|
icon: const Icon(Icons.check),
|
||||||
|
tooltip: L10n.global().doneButtonTooltip,
|
||||||
|
onPressed: () {
|
||||||
|
final newAccount = context.read<_Bloc>().state.account;
|
||||||
|
context.read<AccountController>().setCurrentAccount(newAccount);
|
||||||
|
Navigator.of(context).pushNamedAndRemoveUntil(
|
||||||
|
Home.routeName,
|
||||||
|
(_) => false,
|
||||||
|
arguments: HomeArguments(newAccount),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ShareFolderDialog extends StatefulWidget {
|
||||||
|
const _ShareFolderDialog({
|
||||||
|
required this.account,
|
||||||
|
required this.initialValue,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<StatefulWidget> createState() => _ShareFolderDialogState();
|
||||||
|
|
||||||
|
final Account account;
|
||||||
|
final String initialValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ShareFolderDialogState extends State<_ShareFolderDialog> {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return AlertDialog(
|
||||||
|
title: Text(L10n.global().settingsShareFolderDialogTitle),
|
||||||
|
content: Form(
|
||||||
|
key: _formKey,
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Text(L10n.global().settingsShareFolderDialogDescription),
|
||||||
|
const SizedBox(height: 8),
|
||||||
|
InkWell(
|
||||||
|
onTap: _onTextFieldPressed,
|
||||||
|
child: TextFormField(
|
||||||
|
enabled: false,
|
||||||
|
controller: _controller,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: _onOkPressed,
|
||||||
|
child: Text(MaterialLocalizations.of(context).okButtonLabel),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _onTextFieldPressed() async {
|
||||||
|
final pick = await Navigator.of(context).pushNamed<String>(
|
||||||
|
ShareFolderPicker.routeName,
|
||||||
|
arguments: ShareFolderPickerArguments(widget.account, _path),
|
||||||
|
);
|
||||||
|
if (pick != null) {
|
||||||
|
_path = pick;
|
||||||
|
_controller.text = "/$pick";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onOkPressed() {
|
||||||
|
Navigator.of(context).pop(_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
final _formKey = GlobalKey<FormState>();
|
||||||
|
late final _controller =
|
||||||
|
TextEditingController(text: "/${widget.initialValue}");
|
||||||
|
late String _path = widget.initialValue;
|
||||||
|
}
|
155
app/lib/widget/settings/account_settings.g.dart
Normal file
155
app/lib/widget/settings/account_settings.g.dart
Normal file
|
@ -0,0 +1,155 @@
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'account_settings.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// CopyWithLintRuleGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
// ignore_for_file: library_private_types_in_public_api, duplicate_ignore
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// CopyWithGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
abstract class $_StateCopyWithWorker {
|
||||||
|
_State call(
|
||||||
|
{bool? shouldReload,
|
||||||
|
Account? account,
|
||||||
|
String? label,
|
||||||
|
String? shareFolder,
|
||||||
|
bool? isEnableFaceRecognitionApp,
|
||||||
|
ExceptionEvent? error});
|
||||||
|
}
|
||||||
|
|
||||||
|
class _$_StateCopyWithWorkerImpl implements $_StateCopyWithWorker {
|
||||||
|
_$_StateCopyWithWorkerImpl(this.that);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_State call(
|
||||||
|
{dynamic shouldReload,
|
||||||
|
dynamic account,
|
||||||
|
dynamic label = copyWithNull,
|
||||||
|
dynamic shareFolder,
|
||||||
|
dynamic isEnableFaceRecognitionApp,
|
||||||
|
dynamic error = copyWithNull}) {
|
||||||
|
return _State(
|
||||||
|
shouldReload: shouldReload as bool? ?? that.shouldReload,
|
||||||
|
account: account as Account? ?? that.account,
|
||||||
|
label: label == copyWithNull ? that.label : label as String?,
|
||||||
|
shareFolder: shareFolder as String? ?? that.shareFolder,
|
||||||
|
isEnableFaceRecognitionApp: isEnableFaceRecognitionApp as bool? ??
|
||||||
|
that.isEnableFaceRecognitionApp,
|
||||||
|
error: error == copyWithNull ? that.error : error as ExceptionEvent?);
|
||||||
|
}
|
||||||
|
|
||||||
|
final _State that;
|
||||||
|
}
|
||||||
|
|
||||||
|
extension $_StateCopyWith on _State {
|
||||||
|
$_StateCopyWithWorker get copyWith => _$copyWith;
|
||||||
|
$_StateCopyWithWorker get _$copyWith => _$_StateCopyWithWorkerImpl(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// NpLogGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
extension _$_WrappedDeveloperSettingsStateNpLog
|
||||||
|
on _WrappedDeveloperSettingsState {
|
||||||
|
// ignore: unused_element
|
||||||
|
Logger get _log => log;
|
||||||
|
|
||||||
|
static final log =
|
||||||
|
Logger("widget.settings.account_settings._WrappedDeveloperSettingsState");
|
||||||
|
}
|
||||||
|
|
||||||
|
extension _$_BlocNpLog on _Bloc {
|
||||||
|
// ignore: unused_element
|
||||||
|
Logger get _log => log;
|
||||||
|
|
||||||
|
static final log = Logger("widget.settings.account_settings._Bloc");
|
||||||
|
}
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// ToStringGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
extension _$_StateToString on _State {
|
||||||
|
String _$toString() {
|
||||||
|
// ignore: unnecessary_string_interpolations
|
||||||
|
return "_State {shouldReload: $shouldReload, account: $account, label: $label, shareFolder: $shareFolder, isEnableFaceRecognitionApp: $isEnableFaceRecognitionApp, error: $error}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension _$_WritePrefErrorToString on _WritePrefError {
|
||||||
|
String _$toString() {
|
||||||
|
// ignore: unnecessary_string_interpolations
|
||||||
|
return "_WritePrefError {error: $error, stackTrace: $stackTrace}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension _$_SetLabelToString on _SetLabel {
|
||||||
|
String _$toString() {
|
||||||
|
// ignore: unnecessary_string_interpolations
|
||||||
|
return "_SetLabel {label: $label}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension _$_OnUpdateLabelToString on _OnUpdateLabel {
|
||||||
|
String _$toString() {
|
||||||
|
// ignore: unnecessary_string_interpolations
|
||||||
|
return "_OnUpdateLabel {label: $label}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension _$_SetAccountToString on _SetAccount {
|
||||||
|
String _$toString() {
|
||||||
|
// ignore: unnecessary_string_interpolations
|
||||||
|
return "_SetAccount {account: $account}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension _$_OnUpdateAccountToString on _OnUpdateAccount {
|
||||||
|
String _$toString() {
|
||||||
|
// ignore: unnecessary_string_interpolations
|
||||||
|
return "_OnUpdateAccount {account: $account}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension _$_SetShareFolderToString on _SetShareFolder {
|
||||||
|
String _$toString() {
|
||||||
|
// ignore: unnecessary_string_interpolations
|
||||||
|
return "_SetShareFolder {shareFolder: $shareFolder}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension _$_OnUpdateShareFolderToString on _OnUpdateShareFolder {
|
||||||
|
String _$toString() {
|
||||||
|
// ignore: unnecessary_string_interpolations
|
||||||
|
return "_OnUpdateShareFolder {shareFolder: $shareFolder}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension _$_SetEnableFaceRecognitionAppToString
|
||||||
|
on _SetEnableFaceRecognitionApp {
|
||||||
|
String _$toString() {
|
||||||
|
// ignore: unnecessary_string_interpolations
|
||||||
|
return "_SetEnableFaceRecognitionApp {isEnableFaceRecognitionApp: $isEnableFaceRecognitionApp}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension _$_OnUpdateEnableFaceRecognitionAppToString
|
||||||
|
on _OnUpdateEnableFaceRecognitionApp {
|
||||||
|
String _$toString() {
|
||||||
|
// ignore: unnecessary_string_interpolations
|
||||||
|
return "_OnUpdateEnableFaceRecognitionApp {isEnableFaceRecognitionApp: $isEnableFaceRecognitionApp}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension _$_SetErrorToString on _SetError {
|
||||||
|
String _$toString() {
|
||||||
|
// ignore: unnecessary_string_interpolations
|
||||||
|
return "_SetError {error: $error, stackTrace: $stackTrace}";
|
||||||
|
}
|
||||||
|
}
|
23
app/lib/widget/settings/settings_list_caption.dart
Normal file
23
app/lib/widget/settings/settings_list_caption.dart
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class SettingsListCaption extends StatelessWidget {
|
||||||
|
const SettingsListCaption({
|
||||||
|
super.key,
|
||||||
|
required this.label,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(16, 16, 16, 8),
|
||||||
|
child: Text(
|
||||||
|
label,
|
||||||
|
style: Theme.of(context).textTheme.titleMedium!.copyWith(
|
||||||
|
color: Theme.of(context).colorScheme.primary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final String label;
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue