ML-28 Implement language picker (#29)

* added language picker

* Create intl_ru.arb

* Update README.md
This commit is contained in:
Vadim 2023-02-11 22:19:18 +03:00 committed by GitHub
parent afdbc92ac4
commit 485636c706
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 230 additions and 20 deletions

View file

@ -45,7 +45,7 @@ The list of features that the old lightmeter app has and that have to be impleme
### Theme
- [x] Dark theme
- [x] Picking primary color
- [ ] Russian language
- [x] Russian language
## Build

View file

@ -6,6 +6,8 @@ import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:light_sensor/light_sensor.dart';
import 'package:lightmeter/data/caffeine_service.dart';
import 'package:lightmeter/data/haptics_service.dart';
import 'package:lightmeter/data/models/supported_locale.dart';
import 'package:lightmeter/providers/supported_locale_provider.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
@ -37,7 +39,8 @@ class Application extends StatelessWidget {
return MultiProvider(
providers: [
Provider.value(value: env.copyWith(hasLightSensor: snapshot.data![1] as bool)),
Provider(create: (_) => UserPreferencesService(snapshot.data![0] as SharedPreferences)),
Provider(
create: (_) => UserPreferencesService(snapshot.data![0] as SharedPreferences)),
Provider(create: (_) => const CaffeineService()),
Provider(create: (_) => const HapticsService()),
Provider(create: (_) => PermissionsService()),
@ -45,23 +48,12 @@ class Application extends StatelessWidget {
],
child: StopTypeProvider(
child: EvSourceTypeProvider(
child: ThemeProvider(
builder: (context, _) {
final systemIconsBrightness = ThemeData.estimateBrightnessForColor(
context.watch<ThemeData>().colorScheme.onSurface,
);
return AnnotatedRegion(
value: SystemUiOverlayStyle(
statusBarColor: Colors.transparent,
statusBarBrightness: systemIconsBrightness == Brightness.light
? Brightness.dark
: Brightness.light,
statusBarIconBrightness: systemIconsBrightness,
systemNavigationBarColor: Colors.transparent,
systemNavigationBarIconBrightness: systemIconsBrightness,
),
child: SupportedLocaleProvider(
child: ThemeProvider(
builder: (context, _) => _AnnotatedRegionWrapper(
child: MaterialApp(
theme: context.watch<ThemeData>(),
locale: Locale(context.watch<SupportedLocale>().intlName),
localizationsDelegates: const [
S.delegate,
GlobalMaterialLocalizations.delegate,
@ -79,8 +71,8 @@ class Application extends StatelessWidget {
"settings": (context) => const SettingsFlow(),
},
),
);
},
),
),
),
),
),
@ -91,3 +83,27 @@ class Application extends StatelessWidget {
);
}
}
class _AnnotatedRegionWrapper extends StatelessWidget {
final Widget child;
const _AnnotatedRegionWrapper({required this.child});
@override
Widget build(BuildContext context) {
final systemIconsBrightness = ThemeData.estimateBrightnessForColor(
context.watch<ThemeData>().colorScheme.onSurface,
);
return AnnotatedRegion(
value: SystemUiOverlayStyle(
statusBarColor: Colors.transparent,
statusBarBrightness:
systemIconsBrightness == Brightness.light ? Brightness.dark : Brightness.light,
statusBarIconBrightness: systemIconsBrightness,
systemNavigationBarColor: Colors.transparent,
systemNavigationBarIconBrightness: systemIconsBrightness,
),
child: child,
);
}
}

View file

@ -0,0 +1,28 @@
import 'package:intl/intl.dart';
enum SupportedLocale { en, ru }
SupportedLocale get currentLanguage {
switch (Intl.getCurrentLocale()) {
case "en":
return SupportedLocale.en;
case "ru":
return SupportedLocale.ru;
default:
return SupportedLocale.en;
}
}
extension SupportedLocaleExtension on SupportedLocale {
String get intlName => toString().replaceAll("SupportedLocale.", "");
String get localizedName {
switch (this) {
case SupportedLocale.en:
return 'English';
case SupportedLocale.ru:
return 'Русский';
}
}
}

View file

@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:lightmeter/data/models/supported_locale.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'models/ev_source_type.dart';
@ -16,6 +17,7 @@ class UserPreferencesService {
static const _caffeineKey = "caffeine";
static const _hapticsKey = "haptics";
static const _localeKey = "locale";
static const _themeTypeKey = "themeType";
static const _primaryColorKey = "primaryColor";
@ -40,6 +42,12 @@ class UserPreferencesService {
bool get haptics => _sharedPreferences.getBool(_hapticsKey) ?? true;
set haptics(bool value) => _sharedPreferences.setBool(_hapticsKey, value);
SupportedLocale get locale => SupportedLocale.values.firstWhere(
(e) => e.toString() == _sharedPreferences.getString(_localeKey),
orElse: () => SupportedLocale.en,
);
set locale(SupportedLocale value) => _sharedPreferences.setString(_localeKey, value.toString());
double get cameraEvCalibration => _sharedPreferences.getDouble(_cameraEvCalibrationKey) ?? 0.0;
set cameraEvCalibration(double value) => _sharedPreferences.setDouble(_cameraEvCalibrationKey, value);

View file

@ -15,7 +15,7 @@
"nd": "ND",
"ndFilterFactor": "Neutral density filter factor",
"noExposurePairs": "There are no exposure pairs for the selected settings.",
"noCamerasDetected": "Seems like your device doesn't have any cameras connected",
"noCamerasDetected": "Seems like your device doesn't have any cameras connected.",
"noCameraPermission": "Camera permission is not granted.",
"otherCameraError": "An error occurred when connecting to the camera.",
"none": "None",
@ -36,6 +36,8 @@
"general": "General",
"keepScreenOn": "Keep screen on",
"haptics": "Haptics",
"language": "Language",
"chooseLanguage": "Choose language",
"theme": "Theme",
"chooseTheme": "Choose theme",
"themeLight": "Light",

65
lib/l10n/intl_ru.arb Normal file
View file

@ -0,0 +1,65 @@
{
"@@locale": "ru",
"fastestExposurePair": "Короткая выдержка",
"slowestExposurePair": "Длинная выдержка",
"ev": "{value} EV",
"@ev": {
"placeholders": {
"value": {
"type": "String"
}
}
},
"iso": "ISO",
"filmSpeed": "Чувствительность плёнки",
"nd": "ND",
"ndFilterFactor": "Степень затемнения нейтрального фильтра",
"noExposurePairs": "Для выбранных настроек нет пар экспозиции.",
"noCamerasDetected": "Похоже, ваше устройство не имеет камеры.",
"noCameraPermission": "Нет разрешения на доступ к камере.",
"otherCameraError": "Произошла ошибка при подключении к камере.",
"none": "Нет",
"cancel": "Отменить",
"select": "Выбрать",
"save": "Сохранить",
"settings": "Настройки",
"metering": "Измерения",
"fractionalStops": "Дробные значения",
"showFractionalStops": "Показывать дробные значения",
"halfStops": "1/2",
"thirdStops": "1/3",
"calibration": "Калибровка",
"calibrationMessage": "Точность измерений данного приложения полностью зависит от точности камеры и датчика освещенности вашего устройства. Поэтому рекомендуется самостоятельно подобрать калибровочные значения, которые дадут желаемый результат измерений.",
"calibrationMessageCameraOnly": "Точность измерений данного приложения полностью зависит от точности камеры вашего устройства. Поэтому рекомендуется самостоятельно подобрать калибровочное значение, которое даст желаемый результат измерений.",
"camera": "Камера",
"lightSensor": "Датчик освещённости",
"general": "Общие",
"keepScreenOn": "Запрет блокировки",
"haptics": "Вибрация",
"language": "Язык",
"chooseLanguage": "Выберите язык",
"theme": "Тема",
"chooseTheme": "Выберите тему",
"themeLight": "Светлая",
"themeDark": "Тёмная",
"themeSystemDefault": "Системная",
"dynamicColor": "Динамический цвет",
"primaryColor": "Основной цвет",
"choosePrimaryColor": "Выберите основной цвет",
"about": "О приложении",
"sourceCode": "Исходный код",
"reportIssue": "Сообщить о проблеме",
"writeEmail": "Написать на почту",
"version": "Версия",
"versionNumber": "{version} ({buildNumber})",
"@versionNumber": {
"placeholders": {
"version": {
"type": "String"
},
"buildNumber": {
"type": "String"
}
}
}
}

View file

@ -0,0 +1,53 @@
import 'package:flutter/material.dart';
import 'package:lightmeter/data/models/supported_locale.dart';
import 'package:lightmeter/data/shared_prefs_service.dart';
import 'package:lightmeter/generated/l10n.dart';
import 'package:provider/provider.dart';
class SupportedLocaleProvider extends StatefulWidget {
final Widget child;
const SupportedLocaleProvider({required this.child, super.key});
static SupportedLocaleProviderState of(BuildContext context) {
return context.findAncestorStateOfType<SupportedLocaleProviderState>()!;
}
@override
State<SupportedLocaleProvider> createState() => SupportedLocaleProviderState();
}
class SupportedLocaleProviderState extends State<SupportedLocaleProvider> {
late final ValueNotifier<SupportedLocale> valueListenable;
@override
void initState() {
super.initState();
valueListenable = ValueNotifier(context.read<UserPreferencesService>().locale);
}
@override
void dispose() {
valueListenable.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return ValueListenableBuilder(
valueListenable: valueListenable,
builder: (_, value, child) => Provider.value(
value: value,
child: child,
),
child: widget.child,
);
}
void setLocale(SupportedLocale locale) {
S.load(Locale(locale.intlName)).then((value) {
valueListenable.value = locale;
context.read<UserPreferencesService>().locale = locale;
});
}
}

View file

@ -57,11 +57,13 @@ class _PhotographyValuePickerDialogState<T extends PhotographyValue>
Text(
widget.title,
style: Theme.of(context).textTheme.headlineSmall!,
textAlign: TextAlign.center,
),
const SizedBox(height: Dimens.grid16),
Text(
widget.subtitle,
style: Theme.of(context).textTheme.bodyMedium!,
textAlign: TextAlign.center,
),
],
),

View file

@ -0,0 +1,34 @@
import 'package:flutter/material.dart';
import 'package:lightmeter/data/models/supported_locale.dart';
import 'package:lightmeter/generated/l10n.dart';
import 'package:lightmeter/providers/supported_locale_provider.dart';
import 'package:lightmeter/screens/settings/components/shared/dialog_picker.dart/widget_dialog_picker.dart';
import 'package:provider/provider.dart';
class LanguageListTile extends StatelessWidget {
const LanguageListTile({super.key});
@override
Widget build(BuildContext context) {
return ListTile(
leading: const Icon(Icons.language),
title: Text(S.of(context).language),
trailing: Text(context.watch<SupportedLocale>().localizedName),
onTap: () {
showDialog<SupportedLocale>(
context: context,
builder: (_) => DialogPicker<SupportedLocale>(
title: S.of(context).chooseLanguage,
selectedValue: context.read<SupportedLocale>(),
values: SupportedLocale.values,
titleAdapter: (context, value) => value.localizedName,
),
).then((value) {
if (value != null) {
SupportedLocaleProvider.of(context).setLocale(value);
}
});
},
);
}
}

View file

@ -7,6 +7,7 @@ import 'package:provider/provider.dart';
import 'components/caffeine/provider_list_tile_caffeine.dart';
import 'components/calibration/widget_list_tile_calibration.dart';
import 'components/haptics/provider_list_tile_haptics.dart';
import 'components/language/widget_list_tile_language.dart';
import 'components/primary_color/widget_list_tile_primary_color.dart';
import 'components/report_issue/widget_list_tile_report_issue.dart';
import 'components/shared/settings_section/widget_settings_section.dart';
@ -65,6 +66,7 @@ class SettingsScreen extends StatelessWidget {
children: const [
CaffeineListTileProvider(),
HapticsListTileProvider(),
LanguageListTile(),
],
),
SettingsSection(