| // 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, |
| ), |
| ), |
| ), |
| ], |
| ), |
| ), |
| ); |
| } |
| } |