mirror of
https://github.com/vodemn/m3_lightmeter.git
synced 2024-11-21 23:10:40 +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
|
||||
- [x] Dark theme
|
||||
- [x] Picking primary color
|
||||
- [ ] Russian language
|
||||
- [x] Russian language
|
||||
|
||||
## Build
|
||||
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
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: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);
|
||||
|
||||
|
|
|
@ -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
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(
|
||||
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,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
|
@ -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/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(
|
||||
|
|
Loading…
Reference in a new issue