Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: imple persistent localization storage #46

Open
wants to merge 5 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,27 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:fluent_ui/fluent_ui.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:gui/src/core/constants/configurations.dart';
import 'package:gui/src/core/di/service_locator.dart';
import 'package:gui/src/core/router/app_router.dart';
import 'package:gui/src/core/utils/gen/localization/codegen_loader.g.dart';
import 'package:gui/src/features/main/theme/bloc/theme_bloc.dart';
import 'package:gui/src/features/main/theme/bloc/theme_state.dart';
import 'src/core/services/shared_preferences_service.dart';
import 'src/features/main/language/presentation/bloc/language_bloc.dart';

void main() async {
WidgetsFlutterBinding.ensureInitialized();
await EasyLocalization.ensureInitialized();
await setupSharedPreferences();

runApp(
MultiBlocProvider(
providers: [
BlocProvider<LanguageBloc>(
create: (_) => LanguageBloc(),
create: (_) => LanguageBloc(locator<SharedPreferencesService>()),
),
BlocProvider<ThemeBloc>(
create: (_) => ThemeBloc(),
create: (_) => ThemeBloc(locator<SharedPreferencesService>()),
),
],
child: PactusGuiApp(),
Expand All @@ -38,6 +44,11 @@ class PactusGuiApp extends StatelessWidget {
assetLoader: const CodegenLoader(),
child: BlocBuilder<LanguageBloc, LanguageState>(
builder: (context, languageState) {
if (context.locale != languageState.selectedLanguage.value) {
WidgetsBinding.instance.addPostFrameCallback((_) {
context.setLocale(languageState.selectedLanguage.value);
});
}
return BlocBuilder<ThemeBloc, ThemeState>(
builder: (context, themeState) {
return FluentApp.router(
Expand Down
16 changes: 0 additions & 16 deletions lib/presentation/bloc/language_bloc/language_bloc.dart

This file was deleted.

4 changes: 2 additions & 2 deletions lib/src/core/common/widgets/theme_switcher.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:gui/src/core/common/colors/app_colors.dart';
import 'package:gui/src/core/enums/theme_modes.dart';
import 'package:gui/src/core/utils/gen/assets/assets.gen.dart';
import 'package:gui/src/features/main/theme/bloc/theme_bloc.dart';
import 'package:gui/src/features/main/theme/bloc/theme_state.dart';

/// ### [ThemeSwitcher] Documentation
/// A widget that toggles between light and dark themes using a animated switch.
Expand Down Expand Up @@ -37,7 +37,7 @@ class ThemeSwitcher extends StatelessWidget {
onTap: () {
context.read<ThemeBloc>().add(
ThemeChanged(
theme: isLightTheme ? ThemeModes.dark : ThemeModes.light,
isLightTheme ? ThemeState.darkTheme : ThemeState.lightTheme,
),
);
},
Expand Down
4 changes: 4 additions & 0 deletions lib/src/core/constants/app_constants.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
class AppConstants {
static const String languagePrefsKey = 'languagePrefs';
static const String themePrefsKey = 'themePrefs';
}
12 changes: 12 additions & 0 deletions lib/src/core/di/service_locator.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import 'package:get_it/get_it.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../services/shared_preferences_service.dart';

final locator = GetIt.instance;

Future<void> setupSharedPreferences() async {
final preferences = await SharedPreferences.getInstance();
locator.registerSingleton<SharedPreferencesService>(
SharedPreferencesService(preferences),
);
}
47 changes: 47 additions & 0 deletions lib/src/core/services/shared_preferences_service.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import 'package:flutter/material.dart';
import 'package:gui/src/core/constants/app_constants.dart';
import 'package:gui/src/features/main/language/data/language_model.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../enums/theme_modes.dart';

class SharedPreferencesService {
SharedPreferencesService(this._preferences);
final SharedPreferences _preferences;

Future<String> getSelectedTheme() async {
final savedTheme = _preferences.getString(AppConstants.themePrefsKey);

if (savedTheme == null) {
final brightness =
WidgetsBinding.instance.platformDispatcher.platformBrightness;

return brightness == Brightness.light
? ThemeModes.light.name
: ThemeModes.dark.name;
}

return savedTheme;
}

Future<void> saveSelectedTheme(String themeCode) async {
if (themeCode.isEmpty) {
await _preferences.remove(AppConstants.themePrefsKey);
} else {
await _preferences.setString(AppConstants.themePrefsKey, themeCode);
}
}

// Language methods
String getSelectedLanguage() {
return _preferences.getString(AppConstants.languagePrefsKey) ??
Language.english.code;
}

Future<bool> setSelectedLanguage(String languageCode) {
return _preferences.setString(AppConstants.languagePrefsKey, languageCode);
}

Future<bool> saveSelectedLanguage(String languageCode) {
return setSelectedLanguage(languageCode);
}
}
5 changes: 5 additions & 0 deletions lib/src/features/main/language/data/language_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,26 @@ enum Language {
english(
Locale('en', 'US'),
'English',
'en',
),
spanish(
Locale('es', 'ES'),
'Español',
'es',
),
french(
Locale('fr', 'FR'),
'Français',
'fr',
);

const Language(
this.value,
this.text,
this.code,
);

final Locale value;
final String text;
final String code;
}
Original file line number Diff line number Diff line change
@@ -1,17 +1,47 @@
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:gui/src/features/main/language/data/language_model.dart';
import '../../../../../core/services/shared_preferences_service.dart';

part 'language_event.dart';
part 'language_state.dart';

class LanguageBloc extends Bloc<LanguageEvent, LanguageState> {
LanguageBloc() : super(const LanguageState()) {
on<ChangeLanguage>(onChangeLanguage);
LanguageBloc(this._sharedPreferencesService) : super(const LanguageState()) {
on<InitializeLanguageEvent>(_onInitializeLanguage);
on<ChangeLanguage>(_onChangeLanguage);
add(InitializeLanguageEvent()); // Trigger initialization on creation
}

final SharedPreferencesService _sharedPreferencesService;

Future<void> _onInitializeLanguage(
InitializeLanguageEvent event,
Emitter<LanguageState> emit,
) async {
final languageCode = _sharedPreferencesService.getSelectedLanguage();

final language = languageCode.isNotEmpty
? Language.values.firstWhere(
(lang) => lang.code == languageCode,
orElse: () => Language.english,
)
: Language.english;

await _sharedPreferencesService.saveSelectedLanguage(
language.code,
);
emit(state.copyWith(selectedLanguage: language));
}
Future<void> onChangeLanguage(

Future<void> _onChangeLanguage(
ChangeLanguage event,
Emitter<LanguageState> emit,
) async {
await _sharedPreferencesService.saveSelectedLanguage(
event.selectedLanguage.code,
);

emit(state.copyWith(selectedLanguage: event.selectedLanguage));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,5 @@ class ChangeLanguage extends LanguageEvent {
@override
List<Object> get props => [selectedLanguage];
}

class InitializeLanguageEvent extends LanguageEvent {}
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,22 @@ part of 'language_bloc.dart';

class LanguageState extends Equatable {
const LanguageState({
this.selectedLanguage = Language.english, // Default to English
this.selectedLanguage = Language.english,
});

final Language selectedLanguage;

@override
List<Object> get props => [selectedLanguage];

LanguageState copyWith({Language? selectedLanguage}) {
LanguageState copyWith({
Language? selectedLanguage,
bool? isInitialized,
}) {
return LanguageState(
selectedLanguage: selectedLanguage ?? this.selectedLanguage,
);
}

@override
List<Object?> get props => [selectedLanguage];
}

class LanguageInitial extends LanguageState {
Expand Down
52 changes: 32 additions & 20 deletions lib/src/features/main/theme/bloc/theme_bloc.dart
Original file line number Diff line number Diff line change
@@ -1,31 +1,43 @@
// lib/src/features/main/theme/bloc/theme_bloc.dart
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:fluent_ui/fluent_ui.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:gui/src/core/enums/theme_modes.dart';
import 'package:gui/src/features/main/theme/theme_data/app_theme_data.dart';
import 'package:gui/src/features/main/theme/bloc/theme_state.dart';
import '../../../../core/services/shared_preferences_service.dart';

part 'theme_event.dart';
part 'theme_state.dart';

class ThemeBloc extends Bloc<ThemeEvent, ThemeState> {
ThemeBloc()
: super(
ThemeState.initial(AppThemeData.themeDataModes[ThemeModes.light]!),
) {
on<ThemeChanged>(_onThemeChanged);
ThemeBloc(this._sharedPreferencesService)
: super(ThemeState(themeData: ThemeState.lightTheme)) {
on<InitializeThemeEvent>(_onInitializeTheme);
on<ThemeChanged>(_onChangeTheme);
add(InitializeThemeEvent());
}

void _onThemeChanged(ThemeChanged event, Emitter<ThemeState> emit) {
final isLightTheme = event.theme == ThemeModes.light;
final SharedPreferencesService _sharedPreferencesService;

final textTheme = isLightTheme
? AppThemeData.lightTypography
: AppThemeData.darkTypography;
Future<void> _onInitializeTheme(
InitializeThemeEvent event,
Emitter<ThemeState> emit,
) async {
final themeCode = await _sharedPreferencesService.getSelectedTheme();
final themeData = themeCode == ThemeMode.dark.name
? ThemeState.darkTheme
: ThemeState.lightTheme;

emit(
ThemeState.initial(
AppThemeData.themeDataModes[event.theme]!
.copyWith(typography: textTheme),
),
);
await _sharedPreferencesService.saveSelectedTheme(themeCode);
emit(state.copyWith(themeData: themeData));
}

Future<void> _onChangeTheme(
ThemeChanged event,
Emitter<ThemeState> emit,
) async {
final themeCode = event.theme.brightness == Brightness.dark
? ThemeMode.dark.name
: ThemeMode.light.name;
await _sharedPreferencesService.saveSelectedTheme(themeCode);
emit(state.copyWith(themeData: event.theme));
}
}
19 changes: 16 additions & 3 deletions lib/src/features/main/theme/bloc/theme_event.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,21 @@
// lib/src/features/main/theme/bloc/theme_event.dart
part of 'theme_bloc.dart';

abstract class ThemeEvent {}
abstract class ThemeEvent extends Equatable {
const ThemeEvent();

@override
List<Object> get props => [];
}

class InitializeThemeEvent extends ThemeEvent {}

class ThemeChanged extends ThemeEvent {
ThemeChanged({required this.theme});
final ThemeModes theme;
const ThemeChanged(this.theme);
final FluentThemeData theme;

@override
List<Object> get props => [theme];
}

class SystemThemeChanged extends ThemeEvent {}
26 changes: 20 additions & 6 deletions lib/src/features/main/theme/bloc/theme_state.dart
Original file line number Diff line number Diff line change
@@ -1,10 +1,24 @@
part of 'theme_bloc.dart';
// lib/src/features/main/theme/bloc/theme_state.dart
import 'package:equatable/equatable.dart';
import 'package:fluent_ui/fluent_ui.dart';

class ThemeState {
ThemeState._({required this.themeData});
class ThemeState extends Equatable {
const ThemeState({
required this.themeData,
});
final FluentThemeData themeData;

static FluentThemeData lightTheme = FluentThemeData(
brightness: Brightness.light,
);
static FluentThemeData darkTheme = FluentThemeData.dark();

factory ThemeState.initial(FluentThemeData themeData) {
return ThemeState._(themeData: themeData);
ThemeState copyWith({FluentThemeData? themeData}) {
return ThemeState(
themeData: themeData ?? this.themeData,
);
}
final FluentThemeData themeData;

@override
List<Object> get props => [themeData];
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:gui/src/core/enums/theme_modes.dart';
import 'package:gui/src/features/main/theme/bloc/theme_bloc.dart';
import 'package:gui/src/features/main/theme/bloc/theme_state.dart';
import 'package:gui/src/features/main/theme/theme_data/pallets/on_surface_pallet.dart';

class ThemeSelector extends StatelessWidget {
Expand All @@ -18,7 +18,7 @@ class ThemeSelector extends StatelessWidget {
onPressed: () {
context.read<ThemeBloc>().add(
ThemeChanged(
theme: isLightTheme ? ThemeModes.dark : ThemeModes.light,
isLightTheme ? ThemeState.darkTheme : ThemeState.lightTheme,
),
);
},
Expand Down
Loading
Loading