blob: f3ec480376893c0f46b9178ab04e21e765a77214 [file] [log] [blame]
// Copyright 2019 The Flutter team. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:collection';
import "package:collection/collection.dart";
import 'package:flutter/material.dart';
import 'package:flutter_localized_countries/flutter_localized_countries.dart';
import 'package:gallery/constants.dart';
import 'package:gallery/data/gallery_options.dart';
import 'package:gallery/l10n/gallery_localizations.dart';
import 'package:gallery/layout/adaptive.dart';
import 'package:gallery/pages/about.dart' as about;
import 'package:gallery/pages/backdrop.dart';
import 'package:gallery/pages/home.dart';
import 'package:gallery/pages/settings_list_item.dart';
import 'package:url_launcher/url_launcher.dart';
enum _ExpandableSetting {
textScale,
textDirection,
locale,
platform,
theme,
}
class SettingsPage extends StatefulWidget {
@override
_SettingsPageState createState() => _SettingsPageState();
}
class _SettingsPageState extends State<SettingsPage> {
_ExpandableSetting expandedSettingId;
Map<String, String> _localeNativeNames;
void onTapSetting(_ExpandableSetting settingId) {
setState(() {
if (expandedSettingId == settingId) {
expandedSettingId = null;
} else {
expandedSettingId = settingId;
}
});
}
@override
void initState() {
super.initState();
LocaleNamesLocalizationsDelegate().getLocaleNativeNames().then(
(data) => setState(
() {
_localeNativeNames = data;
},
),
);
}
/// Given a [Locale], returns a [DisplayOption] with its native name for a
/// title and its name in the currently selected locale for a subtitle. If the
/// native name can't be determined, it is omitted. If the locale can't be
/// determined, the locale code is used.
DisplayOption _getLocaleDisplayOption(BuildContext context, Locale locale) {
// TODO: gsw, fil, and es_419 aren't in flutter_localized_countries' dataset
final localeCode = locale.toString();
final localeName = LocaleNames.of(context).nameOf(localeCode);
if (localeName != null) {
final localeNativeName =
_localeNativeNames != null ? _localeNativeNames[localeCode] : null;
return localeNativeName != null
? DisplayOption(localeNativeName, subtitle: localeName)
: DisplayOption(localeName);
} else {
switch (localeCode) {
case 'gsw':
return DisplayOption('Schwiizertüütsch', subtitle: 'Swiss German');
case 'fil':
return DisplayOption('Filipino', subtitle: 'Filipino');
case 'es_419':
return DisplayOption(
'español (Latinoamérica)',
subtitle: 'Spanish (Latin America)',
);
}
}
return DisplayOption(localeCode);
}
/// Create a sorted — by native name – map of supported locales to their
/// intended display string, with a system option as the first element.
LinkedHashMap<Locale, DisplayOption> _getLocaleOptions() {
var localeOptions = LinkedHashMap.of({
systemLocaleOption: DisplayOption(
GalleryLocalizations.of(context).settingsSystemDefault +
(deviceLocale != null
? ' - ${_getLocaleDisplayOption(context, deviceLocale).title}'
: ''),
),
});
var supportedLocales =
List<Locale>.from(GalleryLocalizations.supportedLocales);
supportedLocales.removeWhere((locale) => locale == deviceLocale);
final displayLocales = Map<Locale, DisplayOption>.fromIterable(
supportedLocales,
value: (dynamic locale) =>
_getLocaleDisplayOption(context, locale as Locale),
).entries.toList()
..sort((l1, l2) => compareAsciiUpperCase(l1.value.title, l2.value.title));
localeOptions.addAll(LinkedHashMap.fromEntries(displayLocales));
return localeOptions;
}
@override
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
final options = GalleryOptions.of(context);
final isDesktop = isDisplayDesktop(context);
return Material(
color: colorScheme.secondaryVariant,
child: Padding(
padding: isDesktop
? EdgeInsets.zero
: EdgeInsets.only(bottom: galleryHeaderHeight),
// Remove ListView top padding as it is already accounted for.
child: MediaQuery.removePadding(
removeTop: isDesktop,
context: context,
child: ListView(
children: [
SizedBox(height: firstHeaderDesktopTopPadding),
Focus(
focusNode:
InheritedBackdropFocusNodes.of(context).frontLayerFocusNode,
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 32),
child: ExcludeSemantics(
child: Header(
color: Theme.of(context).colorScheme.onSurface,
text: GalleryLocalizations.of(context).settingsTitle,
),
),
),
),
SettingsListItem<double>(
title: GalleryLocalizations.of(context).settingsTextScaling,
selectedOption: options.textScaleFactor(
context,
useSentinel: true,
),
options: LinkedHashMap.of({
systemTextScaleFactorOption: DisplayOption(
GalleryLocalizations.of(context).settingsSystemDefault,
),
0.8: DisplayOption(
GalleryLocalizations.of(context).settingsTextScalingSmall,
),
1.0: DisplayOption(
GalleryLocalizations.of(context).settingsTextScalingNormal,
),
2.0: DisplayOption(
GalleryLocalizations.of(context).settingsTextScalingLarge,
),
3.0: DisplayOption(
GalleryLocalizations.of(context).settingsTextScalingHuge,
),
}),
onOptionChanged: (newTextScale) => GalleryOptions.update(
context,
options.copyWith(textScaleFactor: newTextScale),
),
onTapSetting: () => onTapSetting(_ExpandableSetting.textScale),
isExpanded: expandedSettingId == _ExpandableSetting.textScale,
),
SettingsListItem<CustomTextDirection>(
title: GalleryLocalizations.of(context).settingsTextDirection,
selectedOption: options.customTextDirection,
options: LinkedHashMap.of({
CustomTextDirection.localeBased: DisplayOption(
GalleryLocalizations.of(context)
.settingsTextDirectionLocaleBased,
),
CustomTextDirection.ltr: DisplayOption(
GalleryLocalizations.of(context).settingsTextDirectionLTR,
),
CustomTextDirection.rtl: DisplayOption(
GalleryLocalizations.of(context).settingsTextDirectionRTL,
),
}),
onOptionChanged: (newTextDirection) => GalleryOptions.update(
context,
options.copyWith(customTextDirection: newTextDirection),
),
onTapSetting: () =>
onTapSetting(_ExpandableSetting.textDirection),
isExpanded:
expandedSettingId == _ExpandableSetting.textDirection,
),
SettingsListItem<Locale>(
title: GalleryLocalizations.of(context).settingsLocale,
selectedOption: options.locale == deviceLocale
? systemLocaleOption
: options.locale,
options: _getLocaleOptions(),
onOptionChanged: (newLocale) {
if (newLocale == systemLocaleOption) {
newLocale = deviceLocale;
}
GalleryOptions.update(
context,
options.copyWith(locale: newLocale),
);
},
onTapSetting: () => onTapSetting(_ExpandableSetting.locale),
isExpanded: expandedSettingId == _ExpandableSetting.locale,
),
SettingsListItem<TargetPlatform>(
title:
GalleryLocalizations.of(context).settingsPlatformMechanics,
selectedOption: options.platform,
options: LinkedHashMap.of({
TargetPlatform.android: DisplayOption(
GalleryLocalizations.of(context).settingsPlatformAndroid,
),
TargetPlatform.iOS: DisplayOption(
GalleryLocalizations.of(context).settingsPlatformIOS,
),
}),
onOptionChanged: (newPlatform) => GalleryOptions.update(
context,
options.copyWith(platform: newPlatform),
),
onTapSetting: () => onTapSetting(_ExpandableSetting.platform),
isExpanded: expandedSettingId == _ExpandableSetting.platform,
),
SettingsListItem<ThemeMode>(
title: GalleryLocalizations.of(context).settingsTheme,
selectedOption: options.themeMode,
options: LinkedHashMap.of({
ThemeMode.system: DisplayOption(
GalleryLocalizations.of(context).settingsSystemDefault,
),
ThemeMode.dark: DisplayOption(
GalleryLocalizations.of(context).settingsDarkTheme,
),
ThemeMode.light: DisplayOption(
GalleryLocalizations.of(context).settingsLightTheme,
),
}),
onOptionChanged: (newThemeMode) => GalleryOptions.update(
context,
options.copyWith(themeMode: newThemeMode),
),
onTapSetting: () => onTapSetting(_ExpandableSetting.theme),
isExpanded: expandedSettingId == _ExpandableSetting.theme,
),
SlowMotionSetting(),
if (!isDesktop) ...[
SizedBox(height: 16),
Divider(thickness: 2, height: 0, color: colorScheme.background),
SizedBox(height: 12),
SettingsAbout(),
SettingsFeedback(),
SizedBox(height: 12),
Divider(thickness: 2, height: 0, color: colorScheme.background),
SettingsAttribution(),
],
],
),
),
),
);
}
}
class SettingsAbout extends StatelessWidget {
@override
Widget build(BuildContext context) {
return _SettingsLink(
title: GalleryLocalizations.of(context).settingsAbout,
icon: Icons.info_outline,
onTap: () {
about.showAboutDialog(context: context);
},
);
}
}
class SettingsFeedback extends StatelessWidget {
@override
Widget build(BuildContext context) {
return _SettingsLink(
title: GalleryLocalizations.of(context).settingsFeedback,
icon: Icons.feedback,
onTap: () async {
final url = 'https://github.com/flutter/flutter/issues/new/choose/';
if (await canLaunch(url)) {
await launch(
url,
forceSafariVC: false,
);
}
},
);
}
}
class SettingsAttribution extends StatelessWidget {
@override
Widget build(BuildContext context) {
final isDesktop = isDisplayDesktop(context);
final verticalPadding = isDesktop ? 0.0 : 28.0;
return MergeSemantics(
child: Padding(
padding: EdgeInsetsDirectional.only(
start: isDesktop ? 48 : 32,
end: isDesktop ? 0 : 32,
top: verticalPadding,
bottom: verticalPadding,
),
child: Text(
GalleryLocalizations.of(context).settingsAttribution,
style: Theme.of(context).textTheme.body2.copyWith(
fontSize: 12,
color: Theme.of(context).colorScheme.onSecondary,
),
textAlign: isDesktop ? TextAlign.end : TextAlign.start,
),
),
);
}
}
class _SettingsLink extends StatelessWidget {
final String title;
final IconData icon;
final GestureTapCallback onTap;
_SettingsLink({this.title, this.icon, this.onTap});
@override
Widget build(BuildContext context) {
final textTheme = Theme.of(context).textTheme;
final colorScheme = Theme.of(context).colorScheme;
final isDesktop = isDisplayDesktop(context);
return InkWell(
onTap: onTap,
child: Padding(
padding: EdgeInsetsDirectional.only(
start: isDesktop ? 48 : 32,
end: isDesktop ? 0 : 32,
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
icon,
color: colorScheme.onSecondary.withOpacity(0.5),
size: 24,
),
Flexible(
child: Padding(
padding: const EdgeInsetsDirectional.only(
start: 16,
top: 12,
bottom: 12,
),
child: Text(
title,
style: textTheme.subtitle.apply(
color: colorScheme.onSecondary,
),
textAlign: isDesktop ? TextAlign.end : TextAlign.start,
),
),
),
],
),
),
);
}
}