nc-photos/app/lib/widget/settings.dart

542 lines
17 KiB
Dart
Raw Normal View History

2022-07-28 18:59:26 +02:00
import 'dart:async';
2021-04-10 06:28:12 +02:00
import 'package:flutter/material.dart';
2023-06-06 15:39:58 +02:00
import 'package:flutter_bloc/flutter_bloc.dart';
2021-04-10 06:28:12 +02:00
import 'package:logging/logging.dart';
import 'package:nc_photos/account.dart';
2021-07-25 07:00:38 +02:00
import 'package:nc_photos/app_localizations.dart';
2023-06-06 15:39:58 +02:00
import 'package:nc_photos/controller/pref_controller.dart';
import 'package:nc_photos/debug_util.dart';
2023-07-17 09:35:45 +02:00
import 'package:nc_photos/entity/pref.dart';
2021-04-10 06:28:12 +02:00
import 'package:nc_photos/k.dart' as k;
2021-06-23 10:15:25 +02:00
import 'package:nc_photos/language_util.dart' as language_util;
2022-04-08 21:16:10 +02:00
import 'package:nc_photos/mobile/platform.dart'
if (dart.library.html) 'package:nc_photos/web/platform.dart' as platform;
import 'package:nc_photos/platform/features.dart' as features;
2022-04-08 21:16:10 +02:00
import 'package:nc_photos/platform/notification.dart';
2021-04-10 06:28:12 +02:00
import 'package:nc_photos/snack_bar_manager.dart';
2023-06-06 15:39:58 +02:00
import 'package:nc_photos/stream_util.dart';
2022-07-08 11:38:24 +02:00
import 'package:nc_photos/url_launcher_util.dart';
import 'package:nc_photos/widget/list_tile_center_leading.dart';
2023-08-04 21:12:29 +02:00
import 'package:nc_photos/widget/settings/collection_settings.dart';
2023-05-29 18:55:10 +02:00
import 'package:nc_photos/widget/settings/developer_settings.dart';
import 'package:nc_photos/widget/settings/expert_settings.dart';
2023-06-06 15:39:58 +02:00
import 'package:nc_photos/widget/settings/language_settings.dart';
2023-07-27 17:14:50 +02:00
import 'package:nc_photos/widget/settings/metadata_settings.dart';
2023-08-09 16:45:32 +02:00
import 'package:nc_photos/widget/settings/misc_settings.dart';
2023-07-27 18:51:57 +02:00
import 'package:nc_photos/widget/settings/photos_settings.dart';
2023-06-10 12:44:02 +02:00
import 'package:nc_photos/widget/settings/settings_list_caption.dart';
import 'package:nc_photos/widget/settings/theme_settings.dart';
2023-08-04 21:11:41 +02:00
import 'package:nc_photos/widget/settings/viewer_settings.dart';
import 'package:nc_photos/widget/stateful_slider.dart';
2022-12-16 16:01:04 +01:00
import 'package:np_codegen/np_codegen.dart';
import 'package:tuple/tuple.dart';
2021-04-10 06:28:12 +02:00
2022-12-16 16:01:04 +01:00
part 'settings.g.dart';
2021-04-10 06:28:12 +02:00
class SettingsArguments {
SettingsArguments(this.account);
final Account account;
}
class Settings extends StatefulWidget {
static const routeName = "/settings";
2021-07-23 22:05:57 +02:00
static Route buildRoute(SettingsArguments args) => MaterialPageRoute(
builder: (context) => Settings.fromArgs(args),
);
2021-09-15 08:58:06 +02:00
const Settings({
2021-07-23 22:05:57 +02:00
Key? key,
required this.account,
2021-04-10 06:28:12 +02:00
}) : super(key: key);
2021-07-23 22:05:57 +02:00
Settings.fromArgs(SettingsArguments args, {Key? key})
2021-04-10 06:28:12 +02:00
: this(
2021-09-15 08:58:06 +02:00
key: key,
2021-04-10 06:28:12 +02:00
account: args.account,
);
@override
createState() => _SettingsState();
final Account account;
}
2022-12-16 16:01:04 +01:00
@npLog
2021-04-10 06:28:12 +02:00
class _SettingsState extends State<Settings> {
@override
2023-07-27 18:53:40 +02:00
Widget build(BuildContext context) {
2022-11-12 10:55:33 +01:00
return Scaffold(
body: Builder(
builder: (context) => _buildContent(context),
2021-04-10 06:28:12 +02:00
),
);
}
Widget _buildContent(BuildContext context) {
final translator = L10n.global().translator;
2021-04-10 06:28:12 +02:00
return CustomScrollView(
slivers: [
SliverAppBar(
pinned: true,
title: Text(L10n.global().settingsWidgetTitle),
2021-04-10 06:28:12 +02:00
),
SliverList(
delegate: SliverChildListDelegate(
[
2023-06-06 15:39:58 +02:00
ValueStreamBuilder<language_util.AppLanguage>(
stream: context.read<PrefController>().language,
builder: (context, snapshot) => ListTile(
leading: const ListTileCenterLeading(
child: Icon(Icons.translate_outlined),
),
title: Text(L10n.global().settingsLanguageTitle),
subtitle: Text(snapshot.requireData.nativeName),
onTap: () {
Navigator.of(context).pushNamed(LanguageSettings.routeName);
},
2022-08-08 08:35:37 +02:00
),
2021-06-23 10:15:25 +02:00
),
2023-07-27 17:17:04 +02:00
_SubPageItem(
2023-07-27 17:16:14 +02:00
leading: const Icon(Icons.palette_outlined),
label: L10n.global().settingsThemeTitle,
description: L10n.global().settingsThemeDescription,
2023-07-27 17:17:04 +02:00
pageBuilder: () => const ThemeSettings(),
2023-07-27 17:16:14 +02:00
),
2023-07-27 17:17:04 +02:00
_SubPageItem(
2023-07-27 17:14:50 +02:00
leading: const Icon(Icons.local_offer_outlined),
label: L10n.global().settingsMetadataTitle,
2023-07-27 17:17:04 +02:00
pageBuilder: () => const MetadataSettings(),
2021-04-10 06:28:12 +02:00
),
2023-07-27 17:17:04 +02:00
_SubPageItem(
2022-11-12 10:55:33 +01:00
leading: const Icon(Icons.image_outlined),
2022-09-05 05:41:00 +02:00
label: L10n.global().photosTabLabel,
description: L10n.global().settingsPhotosDescription,
2023-07-27 18:51:57 +02:00
pageBuilder: () => const PhotosSettings(),
2022-09-05 05:41:00 +02:00
),
2023-07-27 17:17:04 +02:00
_SubPageItem(
2023-07-27 17:16:14 +02:00
leading: const Icon(Icons.grid_view_outlined),
label: L10n.global().collectionsTooltip,
2023-08-04 21:12:29 +02:00
pageBuilder: () => const CollectionSettings(),
2021-11-17 15:41:11 +01:00
),
2023-07-27 17:17:04 +02:00
_SubPageItem(
2022-11-12 10:55:33 +01:00
leading: const Icon(Icons.view_carousel_outlined),
2022-09-05 06:53:50 +02:00
label: L10n.global().settingsViewerTitle,
description: L10n.global().settingsViewerDescription,
2023-08-04 21:11:41 +02:00
pageBuilder: () => const ViewerSettings(),
2021-10-04 15:53:03 +02:00
),
2022-06-09 20:35:46 +02:00
if (features.isSupportEnhancement)
2023-07-27 17:17:04 +02:00
_SubPageItem(
2022-11-12 10:55:33 +01:00
leading: const Icon(Icons.auto_fix_high_outlined),
2022-09-10 09:13:14 +02:00
label: L10n.global().settingsImageEditTitle,
description: L10n.global().settingsImageEditDescription,
2023-07-27 17:17:04 +02:00
pageBuilder: () => const EnhancementSettings(),
),
2023-07-27 17:17:04 +02:00
_SubPageItem(
2022-11-12 10:55:33 +01:00
leading: const Icon(Icons.emoji_symbols_outlined),
label: L10n.global().settingsMiscellaneousTitle,
2023-08-09 16:45:32 +02:00
pageBuilder: () => const MiscSettings(),
),
2023-05-17 19:42:08 +02:00
// if (_enabledExperiments.isNotEmpty)
2023-07-27 17:17:04 +02:00
// _SubPageItem(
2023-05-17 19:42:08 +02:00
// leading: const Icon(Icons.science_outlined),
// label: L10n.global().settingsExperimentalTitle,
// description: L10n.global().settingsExperimentalDescription,
2023-07-27 17:17:04 +02:00
// pageBuilder: () => _ExperimentalSettings(),
2023-05-17 19:42:08 +02:00
// ),
2023-07-27 17:17:04 +02:00
_SubPageItem(
leading: const Icon(Icons.warning_amber),
label: L10n.global().settingsExpertTitle,
2023-07-27 17:17:04 +02:00
pageBuilder: () => const ExpertSettings(),
),
if (_isShowDevSettings)
2023-07-27 17:17:04 +02:00
_SubPageItem(
2022-11-12 10:55:33 +01:00
leading: const Icon(Icons.code_outlined),
label: "Developer options",
2023-07-27 17:17:04 +02:00
pageBuilder: () => const DeveloperSettings(),
),
2023-06-10 12:44:02 +02:00
SettingsListCaption(
label: L10n.global().settingsAboutSectionTitle,
),
2021-04-10 06:28:12 +02:00
ListTile(
title: Text(L10n.global().settingsVersionTitle),
2021-04-16 10:45:17 +02:00
subtitle: const Text(k.versionStr),
onTap: () {
if (!_isShowDevSettings && --_devSettingsUnlockCount <= 0) {
setState(() {
_isShowDevSettings = true;
});
}
2023-05-10 18:51:45 +02:00
},
),
2021-04-10 06:28:12 +02:00
ListTile(
title: Text(L10n.global().settingsSourceCodeTitle),
2021-09-02 10:33:35 +02:00
onTap: () {
launch(_sourceRepo);
2021-04-10 06:28:12 +02:00
},
),
2021-06-10 15:49:17 +02:00
ListTile(
title: Text(L10n.global().settingsBugReportTitle),
2021-06-10 15:49:17 +02:00
onTap: () {
launch(_bugReportUrl);
},
),
SwitchListTile(
title: Text(L10n.global().settingsCaptureLogsTitle),
subtitle: Text(L10n.global().settingsCaptureLogsDescription),
value: LogCapturer().isEnable,
onChanged: (value) => _onCaptureLogChanged(context, value),
),
if (translator.isNotEmpty)
ListTile(
title: Text(L10n.global().settingsTranslatorTitle),
subtitle: Text(translator),
onTap: () {
launch(_translationUrl);
},
)
else
ListTile(
2021-09-02 10:33:35 +02:00
title: const Text("Improve translation"),
subtitle: const Text("Help translating to your language"),
onTap: () {
launch(_translationUrl);
},
),
2021-04-10 06:28:12 +02:00
],
),
),
],
);
}
2022-07-28 20:45:43 +02:00
Future<void> _onCaptureLogChanged(BuildContext context, bool value) async {
if (value) {
final result = await showDialog<bool>(
context: context,
2022-11-12 10:55:33 +01:00
builder: (context) => AlertDialog(
content: Text(L10n.global().captureLogDetails),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop(true);
},
child: Text(L10n.global().enableButtonLabel),
),
],
),
);
if (result == true) {
setState(() {
LogCapturer().start();
});
}
} else {
if (LogCapturer().isEnable) {
setState(() {
LogCapturer().stop().then((result) {
_onLogSaveSuccessful(result);
});
});
}
}
}
2022-04-08 21:16:10 +02:00
Future<void> _onLogSaveSuccessful(dynamic result) async {
final nm = platform.NotificationManager();
try {
await nm.notify(LogSaveSuccessfulNotification(result));
} catch (e, stacktrace) {
_log.shout("[_onLogSaveSuccessful] Failed showing platform notification",
e, stacktrace);
}
}
var _devSettingsUnlockCount = 3;
var _isShowDevSettings = false;
static const String _sourceRepo = "https://bit.ly/3LQerBv";
static const String _bugReportUrl = "https://bit.ly/3NANrr7";
static const String _translationUrl = "https://bit.ly/3NwmdSw";
2021-09-02 10:33:35 +02:00
}
2023-07-27 17:17:04 +02:00
class _SubPageItem extends StatelessWidget {
const _SubPageItem({
this.leading,
required this.label,
this.description,
required this.pageBuilder,
});
@override
Widget build(BuildContext context) {
return ListTile(
leading: leading == null ? null : ListTileCenterLeading(child: leading!),
title: Text(label),
subtitle: description == null ? null : Text(description!),
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => pageBuilder(),
),
);
},
);
}
final Widget? leading;
final String label;
final String? description;
final Widget Function() pageBuilder;
}
class EnhancementSettings extends StatefulWidget {
static const routeName = "/enhancement-settings";
static Route buildRoute() => MaterialPageRoute(
builder: (_) => const EnhancementSettings(),
);
const EnhancementSettings({
Key? key,
}) : super(key: key);
@override
createState() => _EnhancementSettingsState();
}
2022-12-16 16:01:04 +01:00
@npLog
class _EnhancementSettingsState extends State<EnhancementSettings> {
@override
initState() {
super.initState();
_maxWidth = Pref().getEnhanceMaxWidthOr();
_maxHeight = Pref().getEnhanceMaxHeightOr();
2022-09-10 09:13:14 +02:00
_isSaveEditResultToServer = Pref().isSaveEditResultToServerOr();
}
@override
build(BuildContext context) {
2022-11-12 10:55:33 +01:00
return Scaffold(
body: Builder(
builder: (context) => _buildContent(context),
),
);
}
Widget _buildContent(BuildContext context) {
return CustomScrollView(
slivers: [
SliverAppBar(
pinned: true,
2022-09-10 09:13:14 +02:00
title: Text(L10n.global().settingsImageEditTitle),
),
SliverList(
delegate: SliverChildListDelegate(
[
2022-09-10 09:13:14 +02:00
SwitchListTile(
title: Text(
L10n.global().settingsImageEditSaveResultsToServerTitle),
subtitle: Text(_isSaveEditResultToServer
? L10n.global()
.settingsImageEditSaveResultsToServerTrueDescription
: L10n.global()
.settingsImageEditSaveResultsToServerFalseDescription),
value: _isSaveEditResultToServer,
onChanged: _onSaveEditResultToServerChanged,
),
ListTile(
2022-09-10 09:13:14 +02:00
title: Text(L10n.global().settingsEnhanceMaxResolutionTitle2),
subtitle: Text("${_maxWidth}x$_maxHeight"),
onTap: () => _onMaxResolutionTap(context),
),
],
),
),
],
);
}
Future<void> _onMaxResolutionTap(BuildContext context) async {
var width = _maxWidth;
var height = _maxHeight;
final result = await showDialog<bool>(
context: context,
2022-11-12 10:55:33 +01:00
builder: (_) => AlertDialog(
title: Text(L10n.global().settingsEnhanceMaxResolutionTitle2),
content: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(L10n.global().settingsEnhanceMaxResolutionDescription),
const SizedBox(height: 16),
_EnhanceResolutionSlider(
initialWidth: _maxWidth,
initialHeight: _maxHeight,
onChanged: (value) {
width = value.item1;
height = value.item2;
},
2022-11-12 10:55:33 +01:00
)
],
),
2022-11-12 10:55:33 +01:00
actions: <Widget>[
TextButton(
onPressed: () {
Navigator.of(context).pop(true);
},
child: Text(MaterialLocalizations.of(context).okButtonLabel),
),
],
),
);
if (result != true || (width == _maxWidth && height == _maxHeight)) {
return;
}
2022-07-28 18:59:26 +02:00
unawaited(_setMaxResolution(width, height));
}
Future<void> _setMaxResolution(int width, int height) async {
_log.info(
"[_setMaxResolution] ${_maxWidth}x$_maxHeight -> ${width}x$height");
final oldWidth = _maxWidth;
final oldHeight = _maxHeight;
setState(() {
_maxWidth = width;
_maxHeight = height;
});
if (!await Pref().setEnhanceMaxWidth(width) ||
!await Pref().setEnhanceMaxHeight(height)) {
_log.severe("[_setMaxResolution] Failed writing pref");
SnackBarManager().showSnackBar(SnackBar(
content: Text(L10n.global().writePreferenceFailureNotification),
duration: k.snackBarDurationNormal,
));
await Pref().setEnhanceMaxWidth(oldWidth);
setState(() {
_maxWidth = oldWidth;
_maxHeight = oldHeight;
});
}
}
2022-09-10 09:13:14 +02:00
Future<void> _onSaveEditResultToServerChanged(bool value) async {
final oldValue = _isSaveEditResultToServer;
setState(() {
_isSaveEditResultToServer = value;
});
if (!await Pref().setSaveEditResultToServer(value)) {
_log.severe("[_onSaveEditResultToServerChanged] Failed writing pref");
SnackBarManager().showSnackBar(SnackBar(
content: Text(L10n.global().writePreferenceFailureNotification),
duration: k.snackBarDurationNormal,
));
setState(() {
_isSaveEditResultToServer = oldValue;
});
}
}
late int _maxWidth;
late int _maxHeight;
2022-09-10 09:13:14 +02:00
late bool _isSaveEditResultToServer;
}
class _EnhanceResolutionSlider extends StatefulWidget {
const _EnhanceResolutionSlider({
Key? key,
required this.initialWidth,
required this.initialHeight,
this.onChanged,
}) : super(key: key);
@override
createState() => _EnhanceResolutionSliderState();
final int initialWidth;
final int initialHeight;
final ValueChanged<Tuple2<int, int>>? onChanged;
}
class _EnhanceResolutionSliderState extends State<_EnhanceResolutionSlider> {
@override
initState() {
super.initState();
_width = widget.initialWidth;
_height = widget.initialHeight;
}
@override
build(BuildContext context) {
return Column(
children: [
Align(
alignment: Alignment.center,
child: Text("${_width}x$_height"),
),
StatefulSlider(
initialValue: resolutionToSliderValue(_width).toDouble(),
min: -3,
max: 3,
divisions: 6,
onChangeEnd: (value) async {
final resolution = sliderValueToResolution(value.toInt());
setState(() {
_width = resolution.item1;
_height = resolution.item2;
});
widget.onChanged?.call(resolution);
},
),
],
);
}
static Tuple2<int, int> sliderValueToResolution(int value) {
switch (value) {
case -3:
return const Tuple2(1024, 768);
case -2:
return const Tuple2(1280, 960);
case -1:
return const Tuple2(1600, 1200);
case 1:
return const Tuple2(2560, 1920);
case 2:
return const Tuple2(3200, 2400);
case 3:
return const Tuple2(4096, 3072);
default:
return const Tuple2(2048, 1536);
}
}
static int resolutionToSliderValue(int width) {
switch (width) {
case 1024:
return -3;
case 1280:
return -2;
case 1600:
return -1;
case 2560:
return 1;
case 3200:
return 2;
case 4096:
return 3;
default:
return 0;
}
}
late int _width;
late int _height;
}
2023-05-17 19:42:08 +02:00
// final _enabledExperiments = [
// ];