mirror of
https://github.com/vodemn/m3_lightmeter.git
synced 2024-11-22 07:20:39 +00:00
ML-28 Implement language picker (#29)
* added language picker * Create intl_ru.arb * Update README.md
This commit is contained in:
parent
afdbc92ac4
commit
485636c706
10 changed files with 230 additions and 20 deletions
|
@ -45,7 +45,7 @@ The list of features that the old lightmeter app has and that have to be impleme
|
||||||
### Theme
|
### Theme
|
||||||
- [x] Dark theme
|
- [x] Dark theme
|
||||||
- [x] Picking primary color
|
- [x] Picking primary color
|
||||||
- [ ] Russian language
|
- [x] Russian language
|
||||||
|
|
||||||
## Build
|
## Build
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,8 @@ import 'package:flutter_localizations/flutter_localizations.dart';
|
||||||
import 'package:light_sensor/light_sensor.dart';
|
import 'package:light_sensor/light_sensor.dart';
|
||||||
import 'package:lightmeter/data/caffeine_service.dart';
|
import 'package:lightmeter/data/caffeine_service.dart';
|
||||||
import 'package:lightmeter/data/haptics_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:provider/provider.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
|
@ -37,7 +39,8 @@ class Application extends StatelessWidget {
|
||||||
return MultiProvider(
|
return MultiProvider(
|
||||||
providers: [
|
providers: [
|
||||||
Provider.value(value: env.copyWith(hasLightSensor: snapshot.data![1] as bool)),
|
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 CaffeineService()),
|
||||||
Provider(create: (_) => const HapticsService()),
|
Provider(create: (_) => const HapticsService()),
|
||||||
Provider(create: (_) => PermissionsService()),
|
Provider(create: (_) => PermissionsService()),
|
||||||
|
@ -45,23 +48,12 @@ class Application extends StatelessWidget {
|
||||||
],
|
],
|
||||||
child: StopTypeProvider(
|
child: StopTypeProvider(
|
||||||
child: EvSourceTypeProvider(
|
child: EvSourceTypeProvider(
|
||||||
|
child: SupportedLocaleProvider(
|
||||||
child: ThemeProvider(
|
child: ThemeProvider(
|
||||||
builder: (context, _) {
|
builder: (context, _) => _AnnotatedRegionWrapper(
|
||||||
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: MaterialApp(
|
child: MaterialApp(
|
||||||
theme: context.watch<ThemeData>(),
|
theme: context.watch<ThemeData>(),
|
||||||
|
locale: Locale(context.watch<SupportedLocale>().intlName),
|
||||||
localizationsDelegates: const [
|
localizationsDelegates: const [
|
||||||
S.delegate,
|
S.delegate,
|
||||||
GlobalMaterialLocalizations.delegate,
|
GlobalMaterialLocalizations.delegate,
|
||||||
|
@ -79,8 +71,8 @@ class Application extends StatelessWidget {
|
||||||
"settings": (context) => const SettingsFlow(),
|
"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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
28
lib/data/models/supported_locale.dart
Normal file
28
lib/data/models/supported_locale.dart
Normal 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 'Русский';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:lightmeter/data/models/supported_locale.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
import 'models/ev_source_type.dart';
|
import 'models/ev_source_type.dart';
|
||||||
|
@ -16,6 +17,7 @@ class UserPreferencesService {
|
||||||
|
|
||||||
static const _caffeineKey = "caffeine";
|
static const _caffeineKey = "caffeine";
|
||||||
static const _hapticsKey = "haptics";
|
static const _hapticsKey = "haptics";
|
||||||
|
static const _localeKey = "locale";
|
||||||
|
|
||||||
static const _themeTypeKey = "themeType";
|
static const _themeTypeKey = "themeType";
|
||||||
static const _primaryColorKey = "primaryColor";
|
static const _primaryColorKey = "primaryColor";
|
||||||
|
@ -40,6 +42,12 @@ class UserPreferencesService {
|
||||||
bool get haptics => _sharedPreferences.getBool(_hapticsKey) ?? true;
|
bool get haptics => _sharedPreferences.getBool(_hapticsKey) ?? true;
|
||||||
set haptics(bool value) => _sharedPreferences.setBool(_hapticsKey, value);
|
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;
|
double get cameraEvCalibration => _sharedPreferences.getDouble(_cameraEvCalibrationKey) ?? 0.0;
|
||||||
set cameraEvCalibration(double value) => _sharedPreferences.setDouble(_cameraEvCalibrationKey, value);
|
set cameraEvCalibration(double value) => _sharedPreferences.setDouble(_cameraEvCalibrationKey, value);
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
"nd": "ND",
|
"nd": "ND",
|
||||||
"ndFilterFactor": "Neutral density filter factor",
|
"ndFilterFactor": "Neutral density filter factor",
|
||||||
"noExposurePairs": "There are no exposure pairs for the selected settings.",
|
"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.",
|
"noCameraPermission": "Camera permission is not granted.",
|
||||||
"otherCameraError": "An error occurred when connecting to the camera.",
|
"otherCameraError": "An error occurred when connecting to the camera.",
|
||||||
"none": "None",
|
"none": "None",
|
||||||
|
@ -36,6 +36,8 @@
|
||||||
"general": "General",
|
"general": "General",
|
||||||
"keepScreenOn": "Keep screen on",
|
"keepScreenOn": "Keep screen on",
|
||||||
"haptics": "Haptics",
|
"haptics": "Haptics",
|
||||||
|
"language": "Language",
|
||||||
|
"chooseLanguage": "Choose language",
|
||||||
"theme": "Theme",
|
"theme": "Theme",
|
||||||
"chooseTheme": "Choose theme",
|
"chooseTheme": "Choose theme",
|
||||||
"themeLight": "Light",
|
"themeLight": "Light",
|
||||||
|
|
65
lib/l10n/intl_ru.arb
Normal file
65
lib/l10n/intl_ru.arb
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
53
lib/providers/supported_locale_provider.dart
Normal file
53
lib/providers/supported_locale_provider.dart
Normal 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;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -57,11 +57,13 @@ class _PhotographyValuePickerDialogState<T extends PhotographyValue>
|
||||||
Text(
|
Text(
|
||||||
widget.title,
|
widget.title,
|
||||||
style: Theme.of(context).textTheme.headlineSmall!,
|
style: Theme.of(context).textTheme.headlineSmall!,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
const SizedBox(height: Dimens.grid16),
|
const SizedBox(height: Dimens.grid16),
|
||||||
Text(
|
Text(
|
||||||
widget.subtitle,
|
widget.subtitle,
|
||||||
style: Theme.of(context).textTheme.bodyMedium!,
|
style: Theme.of(context).textTheme.bodyMedium!,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,6 +7,7 @@ import 'package:provider/provider.dart';
|
||||||
import 'components/caffeine/provider_list_tile_caffeine.dart';
|
import 'components/caffeine/provider_list_tile_caffeine.dart';
|
||||||
import 'components/calibration/widget_list_tile_calibration.dart';
|
import 'components/calibration/widget_list_tile_calibration.dart';
|
||||||
import 'components/haptics/provider_list_tile_haptics.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/primary_color/widget_list_tile_primary_color.dart';
|
||||||
import 'components/report_issue/widget_list_tile_report_issue.dart';
|
import 'components/report_issue/widget_list_tile_report_issue.dart';
|
||||||
import 'components/shared/settings_section/widget_settings_section.dart';
|
import 'components/shared/settings_section/widget_settings_section.dart';
|
||||||
|
@ -65,6 +66,7 @@ class SettingsScreen extends StatelessWidget {
|
||||||
children: const [
|
children: const [
|
||||||
CaffeineListTileProvider(),
|
CaffeineListTileProvider(),
|
||||||
HapticsListTileProvider(),
|
HapticsListTileProvider(),
|
||||||
|
LanguageListTile(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
SettingsSection(
|
SettingsSection(
|
||||||
|
|
Loading…
Reference in a new issue