diff --git a/lib/application/language/language_bloc.dart b/lib/application/language/language_bloc.dart new file mode 100644 index 0000000..db5b347 --- /dev/null +++ b/lib/application/language/language_bloc.dart @@ -0,0 +1,50 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:injectable/injectable.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +import '../../common/constant/local_storage_key.dart'; +import '../../domain/language/language.dart'; +import '../../infrastructure/language/language.dart'; + +part 'language_event.dart'; +part 'language_state.dart'; +part 'language_bloc.freezed.dart'; + +@injectable +class LanguageBloc extends Bloc { + final SharedPreferences _prefs; + LanguageBloc(this._prefs) : super(LanguageState.initial()) { + on(_onLanguageEvent); + } + + Future _onLanguageEvent( + LanguageEvent event, + Emitter emit, + ) async { + switch (event) { + case _ChangeLanguage(:final language): + await _prefs.setString( + LocalStorageKey.lang, + language.locale.languageCode, + ); + emit(state.copyWith(language: language)); + break; + + case _LoadLanguage(): + final selectedLanguage = _prefs.getString(LocalStorageKey.lang); + + final lang = languages.firstWhere( + (item) => item.locale.languageCode == selectedLanguage, + orElse: () => Language.indonesian(), + ); + + emit( + state.copyWith( + language: selectedLanguage != null ? lang : Language.indonesian(), + ), + ); + break; + } + } +} diff --git a/lib/application/language/language_bloc.freezed.dart b/lib/application/language/language_bloc.freezed.dart new file mode 100644 index 0000000..3366671 --- /dev/null +++ b/lib/application/language/language_bloc.freezed.dart @@ -0,0 +1,488 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'language_bloc.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models', +); + +/// @nodoc +mixin _$LanguageEvent { + @optionalTypeArgs + TResult when({ + required TResult Function(Language language) changeLanguage, + required TResult Function() loadLanguage, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(Language language)? changeLanguage, + TResult? Function()? loadLanguage, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(Language language)? changeLanguage, + TResult Function()? loadLanguage, + required TResult orElse(), + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(_ChangeLanguage value) changeLanguage, + required TResult Function(_LoadLanguage value) loadLanguage, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_ChangeLanguage value)? changeLanguage, + TResult? Function(_LoadLanguage value)? loadLanguage, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_ChangeLanguage value)? changeLanguage, + TResult Function(_LoadLanguage value)? loadLanguage, + required TResult orElse(), + }) => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $LanguageEventCopyWith<$Res> { + factory $LanguageEventCopyWith( + LanguageEvent value, + $Res Function(LanguageEvent) then, + ) = _$LanguageEventCopyWithImpl<$Res, LanguageEvent>; +} + +/// @nodoc +class _$LanguageEventCopyWithImpl<$Res, $Val extends LanguageEvent> + implements $LanguageEventCopyWith<$Res> { + _$LanguageEventCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of LanguageEvent + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc +abstract class _$$ChangeLanguageImplCopyWith<$Res> { + factory _$$ChangeLanguageImplCopyWith( + _$ChangeLanguageImpl value, + $Res Function(_$ChangeLanguageImpl) then, + ) = __$$ChangeLanguageImplCopyWithImpl<$Res>; + @useResult + $Res call({Language language}); + + $LanguageCopyWith<$Res> get language; +} + +/// @nodoc +class __$$ChangeLanguageImplCopyWithImpl<$Res> + extends _$LanguageEventCopyWithImpl<$Res, _$ChangeLanguageImpl> + implements _$$ChangeLanguageImplCopyWith<$Res> { + __$$ChangeLanguageImplCopyWithImpl( + _$ChangeLanguageImpl _value, + $Res Function(_$ChangeLanguageImpl) _then, + ) : super(_value, _then); + + /// Create a copy of LanguageEvent + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({Object? language = null}) { + return _then( + _$ChangeLanguageImpl( + null == language + ? _value.language + : language // ignore: cast_nullable_to_non_nullable + as Language, + ), + ); + } + + /// Create a copy of LanguageEvent + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $LanguageCopyWith<$Res> get language { + return $LanguageCopyWith<$Res>(_value.language, (value) { + return _then(_value.copyWith(language: value)); + }); + } +} + +/// @nodoc + +class _$ChangeLanguageImpl implements _ChangeLanguage { + const _$ChangeLanguageImpl(this.language); + + @override + final Language language; + + @override + String toString() { + return 'LanguageEvent.changeLanguage(language: $language)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ChangeLanguageImpl && + (identical(other.language, language) || + other.language == language)); + } + + @override + int get hashCode => Object.hash(runtimeType, language); + + /// Create a copy of LanguageEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$ChangeLanguageImplCopyWith<_$ChangeLanguageImpl> get copyWith => + __$$ChangeLanguageImplCopyWithImpl<_$ChangeLanguageImpl>( + this, + _$identity, + ); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(Language language) changeLanguage, + required TResult Function() loadLanguage, + }) { + return changeLanguage(language); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(Language language)? changeLanguage, + TResult? Function()? loadLanguage, + }) { + return changeLanguage?.call(language); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(Language language)? changeLanguage, + TResult Function()? loadLanguage, + required TResult orElse(), + }) { + if (changeLanguage != null) { + return changeLanguage(language); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_ChangeLanguage value) changeLanguage, + required TResult Function(_LoadLanguage value) loadLanguage, + }) { + return changeLanguage(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_ChangeLanguage value)? changeLanguage, + TResult? Function(_LoadLanguage value)? loadLanguage, + }) { + return changeLanguage?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_ChangeLanguage value)? changeLanguage, + TResult Function(_LoadLanguage value)? loadLanguage, + required TResult orElse(), + }) { + if (changeLanguage != null) { + return changeLanguage(this); + } + return orElse(); + } +} + +abstract class _ChangeLanguage implements LanguageEvent { + const factory _ChangeLanguage(final Language language) = _$ChangeLanguageImpl; + + Language get language; + + /// Create a copy of LanguageEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ChangeLanguageImplCopyWith<_$ChangeLanguageImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$LoadLanguageImplCopyWith<$Res> { + factory _$$LoadLanguageImplCopyWith( + _$LoadLanguageImpl value, + $Res Function(_$LoadLanguageImpl) then, + ) = __$$LoadLanguageImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$LoadLanguageImplCopyWithImpl<$Res> + extends _$LanguageEventCopyWithImpl<$Res, _$LoadLanguageImpl> + implements _$$LoadLanguageImplCopyWith<$Res> { + __$$LoadLanguageImplCopyWithImpl( + _$LoadLanguageImpl _value, + $Res Function(_$LoadLanguageImpl) _then, + ) : super(_value, _then); + + /// Create a copy of LanguageEvent + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$LoadLanguageImpl implements _LoadLanguage { + const _$LoadLanguageImpl(); + + @override + String toString() { + return 'LanguageEvent.loadLanguage()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$LoadLanguageImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(Language language) changeLanguage, + required TResult Function() loadLanguage, + }) { + return loadLanguage(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(Language language)? changeLanguage, + TResult? Function()? loadLanguage, + }) { + return loadLanguage?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(Language language)? changeLanguage, + TResult Function()? loadLanguage, + required TResult orElse(), + }) { + if (loadLanguage != null) { + return loadLanguage(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_ChangeLanguage value) changeLanguage, + required TResult Function(_LoadLanguage value) loadLanguage, + }) { + return loadLanguage(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_ChangeLanguage value)? changeLanguage, + TResult? Function(_LoadLanguage value)? loadLanguage, + }) { + return loadLanguage?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_ChangeLanguage value)? changeLanguage, + TResult Function(_LoadLanguage value)? loadLanguage, + required TResult orElse(), + }) { + if (loadLanguage != null) { + return loadLanguage(this); + } + return orElse(); + } +} + +abstract class _LoadLanguage implements LanguageEvent { + const factory _LoadLanguage() = _$LoadLanguageImpl; +} + +/// @nodoc +mixin _$LanguageState { + Language get language => throw _privateConstructorUsedError; + + /// Create a copy of LanguageState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $LanguageStateCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $LanguageStateCopyWith<$Res> { + factory $LanguageStateCopyWith( + LanguageState value, + $Res Function(LanguageState) then, + ) = _$LanguageStateCopyWithImpl<$Res, LanguageState>; + @useResult + $Res call({Language language}); + + $LanguageCopyWith<$Res> get language; +} + +/// @nodoc +class _$LanguageStateCopyWithImpl<$Res, $Val extends LanguageState> + implements $LanguageStateCopyWith<$Res> { + _$LanguageStateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of LanguageState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({Object? language = null}) { + return _then( + _value.copyWith( + language: null == language + ? _value.language + : language // ignore: cast_nullable_to_non_nullable + as Language, + ) + as $Val, + ); + } + + /// Create a copy of LanguageState + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $LanguageCopyWith<$Res> get language { + return $LanguageCopyWith<$Res>(_value.language, (value) { + return _then(_value.copyWith(language: value) as $Val); + }); + } +} + +/// @nodoc +abstract class _$$LanguageStateImplCopyWith<$Res> + implements $LanguageStateCopyWith<$Res> { + factory _$$LanguageStateImplCopyWith( + _$LanguageStateImpl value, + $Res Function(_$LanguageStateImpl) then, + ) = __$$LanguageStateImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({Language language}); + + @override + $LanguageCopyWith<$Res> get language; +} + +/// @nodoc +class __$$LanguageStateImplCopyWithImpl<$Res> + extends _$LanguageStateCopyWithImpl<$Res, _$LanguageStateImpl> + implements _$$LanguageStateImplCopyWith<$Res> { + __$$LanguageStateImplCopyWithImpl( + _$LanguageStateImpl _value, + $Res Function(_$LanguageStateImpl) _then, + ) : super(_value, _then); + + /// Create a copy of LanguageState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({Object? language = null}) { + return _then( + _$LanguageStateImpl( + language: null == language + ? _value.language + : language // ignore: cast_nullable_to_non_nullable + as Language, + ), + ); + } +} + +/// @nodoc + +class _$LanguageStateImpl implements _LanguageState { + const _$LanguageStateImpl({required this.language}); + + @override + final Language language; + + @override + String toString() { + return 'LanguageState(language: $language)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$LanguageStateImpl && + (identical(other.language, language) || + other.language == language)); + } + + @override + int get hashCode => Object.hash(runtimeType, language); + + /// Create a copy of LanguageState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$LanguageStateImplCopyWith<_$LanguageStateImpl> get copyWith => + __$$LanguageStateImplCopyWithImpl<_$LanguageStateImpl>(this, _$identity); +} + +abstract class _LanguageState implements LanguageState { + const factory _LanguageState({required final Language language}) = + _$LanguageStateImpl; + + @override + Language get language; + + /// Create a copy of LanguageState + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$LanguageStateImplCopyWith<_$LanguageStateImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/application/language/language_event.dart b/lib/application/language/language_event.dart new file mode 100644 index 0000000..08374c7 --- /dev/null +++ b/lib/application/language/language_event.dart @@ -0,0 +1,8 @@ +part of 'language_bloc.dart'; + +@freezed +class LanguageEvent with _$LanguageEvent { + const factory LanguageEvent.changeLanguage(Language language) = + _ChangeLanguage; + const factory LanguageEvent.loadLanguage() = _LoadLanguage; +} diff --git a/lib/application/language/language_state.dart b/lib/application/language/language_state.dart new file mode 100644 index 0000000..df17325 --- /dev/null +++ b/lib/application/language/language_state.dart @@ -0,0 +1,9 @@ +part of 'language_bloc.dart'; + +@freezed +abstract class LanguageState with _$LanguageState { + const factory LanguageState({required Language language}) = _LanguageState; + + factory LanguageState.initial() => + LanguageState(language: Language.indonesian()); +} diff --git a/lib/common/constant/app_constant.dart b/lib/common/constant/app_constant.dart index b0dbc06..e421be3 100644 --- a/lib/common/constant/app_constant.dart +++ b/lib/common/constant/app_constant.dart @@ -1,3 +1,3 @@ class AppConstant { - static const String appName = ""; + static const String appName = "Apskel Owner"; } diff --git a/lib/common/constant/local_storage_key.dart b/lib/common/constant/local_storage_key.dart new file mode 100644 index 0000000..612a7f3 --- /dev/null +++ b/lib/common/constant/local_storage_key.dart @@ -0,0 +1,3 @@ +class LocalStorageKey { + static const String lang = 'lang'; +} diff --git a/lib/common/extension/build_context_extension.dart b/lib/common/extension/build_context_extension.dart new file mode 100644 index 0000000..6b8ed04 --- /dev/null +++ b/lib/common/extension/build_context_extension.dart @@ -0,0 +1,5 @@ +part of 'extension.dart'; + +extension BuildContextX on BuildContext { + AppLocalizations get lang => AppLocalizations.of(this)!; +} diff --git a/lib/common/extension/extension.dart b/lib/common/extension/extension.dart index 12aec9b..64bd608 100644 --- a/lib/common/extension/extension.dart +++ b/lib/common/extension/extension.dart @@ -1,5 +1,9 @@ +import 'package:flutter/widgets.dart'; import 'package:intl/intl.dart'; +import '../../l10n/app_localizations.dart'; + part 'int_extension.dart'; part 'date_extension.dart'; part 'string_extension.dart'; +part 'build_context_extension.dart'; diff --git a/lib/domain/language/language.dart b/lib/domain/language/language.dart new file mode 100644 index 0000000..d87a4d7 --- /dev/null +++ b/lib/domain/language/language.dart @@ -0,0 +1,37 @@ +import 'package:flutter/widgets.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'language.freezed.dart'; + +@freezed +abstract class Language with _$Language { + factory Language({ + required Locale locale, + required String name, + required String nativeName, + required String path, + }) = _Language; + + const Language._(); + + factory Language.empty() => Language( + locale: const Locale('id', 'ID'), + name: '', + path: '', + nativeName: '', + ); + + factory Language.indonesian() => Language( + locale: const Locale('id', 'ID'), + name: 'Indonesian', + nativeName: 'Bahasa Indonesia', + path: '🇮🇩', + ); + + factory Language.english() => Language( + locale: const Locale('en', 'US'), + name: 'English', + path: '🇺🇸', + nativeName: 'English', + ); +} diff --git a/lib/domain/language/language.freezed.dart b/lib/domain/language/language.freezed.dart new file mode 100644 index 0000000..c8f89a4 --- /dev/null +++ b/lib/domain/language/language.freezed.dart @@ -0,0 +1,210 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'language.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models', +); + +/// @nodoc +mixin _$Language { + Locale get locale => throw _privateConstructorUsedError; + String get name => throw _privateConstructorUsedError; + String get nativeName => throw _privateConstructorUsedError; + String get path => throw _privateConstructorUsedError; + + /// Create a copy of Language + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $LanguageCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $LanguageCopyWith<$Res> { + factory $LanguageCopyWith(Language value, $Res Function(Language) then) = + _$LanguageCopyWithImpl<$Res, Language>; + @useResult + $Res call({Locale locale, String name, String nativeName, String path}); +} + +/// @nodoc +class _$LanguageCopyWithImpl<$Res, $Val extends Language> + implements $LanguageCopyWith<$Res> { + _$LanguageCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of Language + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? locale = null, + Object? name = null, + Object? nativeName = null, + Object? path = null, + }) { + return _then( + _value.copyWith( + locale: null == locale + ? _value.locale + : locale // ignore: cast_nullable_to_non_nullable + as Locale, + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + nativeName: null == nativeName + ? _value.nativeName + : nativeName // ignore: cast_nullable_to_non_nullable + as String, + path: null == path + ? _value.path + : path // ignore: cast_nullable_to_non_nullable + as String, + ) + as $Val, + ); + } +} + +/// @nodoc +abstract class _$$LanguageImplCopyWith<$Res> + implements $LanguageCopyWith<$Res> { + factory _$$LanguageImplCopyWith( + _$LanguageImpl value, + $Res Function(_$LanguageImpl) then, + ) = __$$LanguageImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({Locale locale, String name, String nativeName, String path}); +} + +/// @nodoc +class __$$LanguageImplCopyWithImpl<$Res> + extends _$LanguageCopyWithImpl<$Res, _$LanguageImpl> + implements _$$LanguageImplCopyWith<$Res> { + __$$LanguageImplCopyWithImpl( + _$LanguageImpl _value, + $Res Function(_$LanguageImpl) _then, + ) : super(_value, _then); + + /// Create a copy of Language + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? locale = null, + Object? name = null, + Object? nativeName = null, + Object? path = null, + }) { + return _then( + _$LanguageImpl( + locale: null == locale + ? _value.locale + : locale // ignore: cast_nullable_to_non_nullable + as Locale, + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + nativeName: null == nativeName + ? _value.nativeName + : nativeName // ignore: cast_nullable_to_non_nullable + as String, + path: null == path + ? _value.path + : path // ignore: cast_nullable_to_non_nullable + as String, + ), + ); + } +} + +/// @nodoc + +class _$LanguageImpl extends _Language { + _$LanguageImpl({ + required this.locale, + required this.name, + required this.nativeName, + required this.path, + }) : super._(); + + @override + final Locale locale; + @override + final String name; + @override + final String nativeName; + @override + final String path; + + @override + String toString() { + return 'Language(locale: $locale, name: $name, nativeName: $nativeName, path: $path)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$LanguageImpl && + (identical(other.locale, locale) || other.locale == locale) && + (identical(other.name, name) || other.name == name) && + (identical(other.nativeName, nativeName) || + other.nativeName == nativeName) && + (identical(other.path, path) || other.path == path)); + } + + @override + int get hashCode => Object.hash(runtimeType, locale, name, nativeName, path); + + /// Create a copy of Language + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$LanguageImplCopyWith<_$LanguageImpl> get copyWith => + __$$LanguageImplCopyWithImpl<_$LanguageImpl>(this, _$identity); +} + +abstract class _Language extends Language { + factory _Language({ + required final Locale locale, + required final String name, + required final String nativeName, + required final String path, + }) = _$LanguageImpl; + _Language._() : super._(); + + @override + Locale get locale; + @override + String get name; + @override + String get nativeName; + @override + String get path; + + /// Create a copy of Language + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$LanguageImplCopyWith<_$LanguageImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/infrastructure/language/language.dart b/lib/infrastructure/language/language.dart new file mode 100644 index 0000000..ef06c40 --- /dev/null +++ b/lib/infrastructure/language/language.dart @@ -0,0 +1,18 @@ +import 'dart:ui'; + +import '../../domain/language/language.dart'; + +List languages = [ + Language( + locale: const Locale('id', 'ID'), + name: 'Indonesian', + path: '🇮🇩', + nativeName: 'Bahasa Indonesia', + ), + Language( + locale: const Locale('en', 'US'), + name: 'English', + path: '🇺🇸', + nativeName: 'English', + ), +]; diff --git a/lib/injection.config.dart b/lib/injection.config.dart index 96db829..f414336 100644 --- a/lib/injection.config.dart +++ b/lib/injection.config.dart @@ -9,6 +9,8 @@ // coverage:ignore-file // ignore_for_file: no_leading_underscores_for_library_prefixes +import 'package:apskel_owner_flutter/application/language/language_bloc.dart' + as _i455; import 'package:apskel_owner_flutter/common/api/api_client.dart' as _i115; import 'package:apskel_owner_flutter/common/di/di_auto_route.dart' as _i311; import 'package:apskel_owner_flutter/common/di/di_connectivity.dart' as _i586; @@ -50,6 +52,9 @@ extension GetItInjectableX on _i174.GetIt { gh.lazySingleton<_i543.NetworkClient>( () => _i543.NetworkClient(gh<_i895.Connectivity>()), ); + gh.factory<_i455.LanguageBloc>( + () => _i455.LanguageBloc(gh<_i460.SharedPreferences>()), + ); gh.factory<_i6.Env>(() => _i6.DevEnv(), registerFor: {_dev}); gh.lazySingleton<_i115.ApiClient>( () => _i115.ApiClient(gh<_i361.Dio>(), gh<_i6.Env>()), diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb new file mode 100644 index 0000000..5353c21 --- /dev/null +++ b/lib/l10n/app_en.arb @@ -0,0 +1,53 @@ +{ + "@@locale": "en", + "indonesian": "Indonesian", + "@indonesian": {}, + "english": "English", + "@english": {}, + "language": "Language", + "@language": {}, + "version": "Version", + "@version": {}, + "select_language": "Select Language", + "@select_language": {}, + "login_header": "Welcome back", + "@login_header": {}, + "login_desc": "Sign in to your account", + "@login_desc": {}, + "email": "Email", + "@email": {}, + "email_placeholder": "Enter your email", + "@email_placeholder": {}, + "password": "Password", + "@password": {}, + "password_placeholder": "Enter your password", + "@password_placeholder": {}, + "forgot_password": "Forgot Password", + "@forgot_password": {}, + "sign_in": "Sign In", + "@sign_in": {}, + "good_morning": "Good Morning", + "@good_morning": {}, + "good_afternoon": "Good Afternoon", + "@good_afternoon": {}, + "good_evening": "Good Evening", + "@good_evening": {}, + "good_night": "Good Night", + "@good_night": {}, + "home_header_desc": "Let's improve your business performance today", + "@home_header_desc": {}, + "home": "Home", + "@home": {}, + "transaction": "Transaction", + "@transaction": {}, + "transactions": "Transactions", + "@transactions": {}, + "report": "Report", + "@report": {}, + "reports": "Reports", + "@reports": {}, + "profile": "Profile", + "@profile": {}, + "sales_today": "Sales today", + "@sales_today": {} +} diff --git a/lib/l10n/app_id.arb b/lib/l10n/app_id.arb new file mode 100644 index 0000000..ed3af6d --- /dev/null +++ b/lib/l10n/app_id.arb @@ -0,0 +1,53 @@ +{ + "@@locale": "id", + "indonesian": "Indonesia", + "@indonesian": {}, + "english": "Inggris", + "@english": {}, + "language": "Bahasa", + "@language": {}, + "version": "Versi", + "@version": {}, + "select_language": "Pilih Bahasa", + "@select_language": {}, + "login_header": "Selamat Datang kembali", + "@login_header": {}, + "login_desc": "Masuk ke akun Anda", + "@login_desc": {}, + "email": "Email", + "@email": {}, + "email_placeholder": "Masukkan email Anda", + "@email_placeholder": {}, + "password": "Kata Sandi", + "@password": {}, + "password_placeholder": "Masukkan kata sandi Anda", + "@password_placeholder": {}, + "forgot_password": "Lupa Kata Sandi", + "@forgot_password": {}, + "sign_in": "Masuk", + "@sign_in": {}, + "good_morning": "Selamat Pagi", + "@good_morning": {}, + "good_afternoon": "Selamat Siang", + "@good_afternoon": {}, + "good_evening": "Selamat Sore", + "@good_evening": {}, + "good_night": "Selamat Malam", + "@good_night": {}, + "home_header_desc": "Mari tingkatkan performa bisnis Anda hari ini", + "@home_header_desc": {}, + "home": "Beranda", + "@home": {}, + "transaction": "Transaksi", + "@transaction": {}, + "transactions": "Transaksi", + "@transactions": {}, + "report": "Laporan", + "@report": {}, + "reports": "Laporan", + "@reports": {}, + "profile": "Profil", + "@profile": {}, + "sales_today": "Penjualan hari ini", + "@sales_today": {} +} diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart new file mode 100644 index 0000000..c03e6a4 --- /dev/null +++ b/lib/l10n/app_localizations.dart @@ -0,0 +1,279 @@ +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; +import 'package:intl/intl.dart' as intl; + +import 'app_localizations_en.dart'; +import 'app_localizations_id.dart'; + +// ignore_for_file: type=lint + +/// Callers can lookup localized strings with an instance of AppLocalizations +/// returned by `AppLocalizations.of(context)`. +/// +/// Applications need to include `AppLocalizations.delegate()` in their app's +/// `localizationDelegates` list, and the locales they support in the app's +/// `supportedLocales` list. For example: +/// +/// ```dart +/// import 'l10n/app_localizations.dart'; +/// +/// return MaterialApp( +/// localizationsDelegates: AppLocalizations.localizationsDelegates, +/// supportedLocales: AppLocalizations.supportedLocales, +/// home: MyApplicationHome(), +/// ); +/// ``` +/// +/// ## Update pubspec.yaml +/// +/// Please make sure to update your pubspec.yaml to include the following +/// packages: +/// +/// ```yaml +/// dependencies: +/// # Internationalization support. +/// flutter_localizations: +/// sdk: flutter +/// intl: any # Use the pinned version from flutter_localizations +/// +/// # Rest of dependencies +/// ``` +/// +/// ## iOS Applications +/// +/// iOS applications define key application metadata, including supported +/// locales, in an Info.plist file that is built into the application bundle. +/// To configure the locales supported by your app, you’ll need to edit this +/// file. +/// +/// First, open your project’s ios/Runner.xcworkspace Xcode workspace file. +/// Then, in the Project Navigator, open the Info.plist file under the Runner +/// project’s Runner folder. +/// +/// Next, select the Information Property List item, select Add Item from the +/// Editor menu, then select Localizations from the pop-up menu. +/// +/// Select and expand the newly-created Localizations item then, for each +/// locale your application supports, add a new item and select the locale +/// you wish to add from the pop-up menu in the Value field. This list should +/// be consistent with the languages listed in the AppLocalizations.supportedLocales +/// property. +abstract class AppLocalizations { + AppLocalizations(String locale) : localeName = intl.Intl.canonicalizedLocale(locale.toString()); + + final String localeName; + + static AppLocalizations? of(BuildContext context) { + return Localizations.of(context, AppLocalizations); + } + + static const LocalizationsDelegate delegate = _AppLocalizationsDelegate(); + + /// A list of this localizations delegate along with the default localizations + /// delegates. + /// + /// Returns a list of localizations delegates containing this delegate along with + /// GlobalMaterialLocalizations.delegate, GlobalCupertinoLocalizations.delegate, + /// and GlobalWidgetsLocalizations.delegate. + /// + /// Additional delegates can be added by appending to this list in + /// MaterialApp. This list does not have to be used at all if a custom list + /// of delegates is preferred or required. + static const List> localizationsDelegates = >[ + delegate, + GlobalMaterialLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + ]; + + /// A list of this localizations delegate's supported locales. + static const List supportedLocales = [ + Locale('en'), + Locale('id') + ]; + + /// No description provided for @indonesian. + /// + /// In en, this message translates to: + /// **'Indonesian'** + String get indonesian; + + /// No description provided for @english. + /// + /// In en, this message translates to: + /// **'English'** + String get english; + + /// No description provided for @language. + /// + /// In en, this message translates to: + /// **'Language'** + String get language; + + /// No description provided for @version. + /// + /// In en, this message translates to: + /// **'Version'** + String get version; + + /// No description provided for @select_language. + /// + /// In en, this message translates to: + /// **'Select Language'** + String get select_language; + + /// No description provided for @login_header. + /// + /// In en, this message translates to: + /// **'Welcome back'** + String get login_header; + + /// No description provided for @login_desc. + /// + /// In en, this message translates to: + /// **'Sign in to your account'** + String get login_desc; + + /// No description provided for @email. + /// + /// In en, this message translates to: + /// **'Email'** + String get email; + + /// No description provided for @email_placeholder. + /// + /// In en, this message translates to: + /// **'Enter your email'** + String get email_placeholder; + + /// No description provided for @password. + /// + /// In en, this message translates to: + /// **'Password'** + String get password; + + /// No description provided for @password_placeholder. + /// + /// In en, this message translates to: + /// **'Enter your password'** + String get password_placeholder; + + /// No description provided for @forgot_password. + /// + /// In en, this message translates to: + /// **'Forgot Password'** + String get forgot_password; + + /// No description provided for @sign_in. + /// + /// In en, this message translates to: + /// **'Sign In'** + String get sign_in; + + /// No description provided for @good_morning. + /// + /// In en, this message translates to: + /// **'Good Morning'** + String get good_morning; + + /// No description provided for @good_afternoon. + /// + /// In en, this message translates to: + /// **'Good Afternoon'** + String get good_afternoon; + + /// No description provided for @good_evening. + /// + /// In en, this message translates to: + /// **'Good Evening'** + String get good_evening; + + /// No description provided for @good_night. + /// + /// In en, this message translates to: + /// **'Good Night'** + String get good_night; + + /// No description provided for @home_header_desc. + /// + /// In en, this message translates to: + /// **'Let\'s improve your business performance today'** + String get home_header_desc; + + /// No description provided for @home. + /// + /// In en, this message translates to: + /// **'Home'** + String get home; + + /// No description provided for @transaction. + /// + /// In en, this message translates to: + /// **'Transaction'** + String get transaction; + + /// No description provided for @transactions. + /// + /// In en, this message translates to: + /// **'Transactions'** + String get transactions; + + /// No description provided for @report. + /// + /// In en, this message translates to: + /// **'Report'** + String get report; + + /// No description provided for @reports. + /// + /// In en, this message translates to: + /// **'Reports'** + String get reports; + + /// No description provided for @profile. + /// + /// In en, this message translates to: + /// **'Profile'** + String get profile; + + /// No description provided for @sales_today. + /// + /// In en, this message translates to: + /// **'Sales today'** + String get sales_today; +} + +class _AppLocalizationsDelegate extends LocalizationsDelegate { + const _AppLocalizationsDelegate(); + + @override + Future load(Locale locale) { + return SynchronousFuture(lookupAppLocalizations(locale)); + } + + @override + bool isSupported(Locale locale) => ['en', 'id'].contains(locale.languageCode); + + @override + bool shouldReload(_AppLocalizationsDelegate old) => false; +} + +AppLocalizations lookupAppLocalizations(Locale locale) { + + + // Lookup logic when only language code is specified. + switch (locale.languageCode) { + case 'en': return AppLocalizationsEn(); + case 'id': return AppLocalizationsId(); + } + + throw FlutterError( + 'AppLocalizations.delegate failed to load unsupported locale "$locale". This is likely ' + 'an issue with the localizations generation tool. Please file an issue ' + 'on GitHub with a reproducible sample app and the gen-l10n configuration ' + 'that was used.' + ); +} diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart new file mode 100644 index 0000000..07d2105 --- /dev/null +++ b/lib/l10n/app_localizations_en.dart @@ -0,0 +1,85 @@ +// ignore: unused_import +import 'package:intl/intl.dart' as intl; +import 'app_localizations.dart'; + +// ignore_for_file: type=lint + +/// The translations for English (`en`). +class AppLocalizationsEn extends AppLocalizations { + AppLocalizationsEn([String locale = 'en']) : super(locale); + + @override + String get indonesian => 'Indonesian'; + + @override + String get english => 'English'; + + @override + String get language => 'Language'; + + @override + String get version => 'Version'; + + @override + String get select_language => 'Select Language'; + + @override + String get login_header => 'Welcome back'; + + @override + String get login_desc => 'Sign in to your account'; + + @override + String get email => 'Email'; + + @override + String get email_placeholder => 'Enter your email'; + + @override + String get password => 'Password'; + + @override + String get password_placeholder => 'Enter your password'; + + @override + String get forgot_password => 'Forgot Password'; + + @override + String get sign_in => 'Sign In'; + + @override + String get good_morning => 'Good Morning'; + + @override + String get good_afternoon => 'Good Afternoon'; + + @override + String get good_evening => 'Good Evening'; + + @override + String get good_night => 'Good Night'; + + @override + String get home_header_desc => 'Let\'s improve your business performance today'; + + @override + String get home => 'Home'; + + @override + String get transaction => 'Transaction'; + + @override + String get transactions => 'Transactions'; + + @override + String get report => 'Report'; + + @override + String get reports => 'Reports'; + + @override + String get profile => 'Profile'; + + @override + String get sales_today => 'Sales today'; +} diff --git a/lib/l10n/app_localizations_id.dart b/lib/l10n/app_localizations_id.dart new file mode 100644 index 0000000..fd09556 --- /dev/null +++ b/lib/l10n/app_localizations_id.dart @@ -0,0 +1,85 @@ +// ignore: unused_import +import 'package:intl/intl.dart' as intl; +import 'app_localizations.dart'; + +// ignore_for_file: type=lint + +/// The translations for Indonesian (`id`). +class AppLocalizationsId extends AppLocalizations { + AppLocalizationsId([String locale = 'id']) : super(locale); + + @override + String get indonesian => 'Indonesia'; + + @override + String get english => 'Inggris'; + + @override + String get language => 'Bahasa'; + + @override + String get version => 'Versi'; + + @override + String get select_language => 'Pilih Bahasa'; + + @override + String get login_header => 'Selamat Datang kembali'; + + @override + String get login_desc => 'Masuk ke akun Anda'; + + @override + String get email => 'Email'; + + @override + String get email_placeholder => 'Masukkan email Anda'; + + @override + String get password => 'Kata Sandi'; + + @override + String get password_placeholder => 'Masukkan kata sandi Anda'; + + @override + String get forgot_password => 'Lupa Kata Sandi'; + + @override + String get sign_in => 'Masuk'; + + @override + String get good_morning => 'Selamat Pagi'; + + @override + String get good_afternoon => 'Selamat Siang'; + + @override + String get good_evening => 'Selamat Sore'; + + @override + String get good_night => 'Selamat Malam'; + + @override + String get home_header_desc => 'Mari tingkatkan performa bisnis Anda hari ini'; + + @override + String get home => 'Beranda'; + + @override + String get transaction => 'Transaksi'; + + @override + String get transactions => 'Transaksi'; + + @override + String get report => 'Laporan'; + + @override + String get reports => 'Laporan'; + + @override + String get profile => 'Profil'; + + @override + String get sales_today => 'Penjualan hari ini'; +} diff --git a/lib/presentation/app_widget.dart b/lib/presentation/app_widget.dart index 38ae400..1dda198 100644 --- a/lib/presentation/app_widget.dart +++ b/lib/presentation/app_widget.dart @@ -1,8 +1,11 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import '../application/language/language_bloc.dart'; import '../common/theme/theme.dart'; import '../common/constant/app_constant.dart'; import '../injection.dart'; +import '../l10n/app_localizations.dart'; import 'router/app_router.dart'; import 'router/app_router_observer.dart'; @@ -18,12 +21,22 @@ class _AppWidgetState extends State { @override Widget build(BuildContext context) { - return MaterialApp.router( - debugShowCheckedModeBanner: false, - title: AppConstant.appName, - theme: ThemeApp.theme, - routerConfig: _appRouter.config( - navigatorObservers: () => [AppRouteObserver()], + return BlocProvider( + create: (context) => getIt(), + child: BlocBuilder( + builder: (context, state) { + return MaterialApp.router( + debugShowCheckedModeBanner: false, + title: AppConstant.appName, + locale: state.language.locale, + supportedLocales: AppLocalizations.supportedLocales, + localizationsDelegates: AppLocalizations.localizationsDelegates, + theme: ThemeApp.theme, + routerConfig: _appRouter.config( + navigatorObservers: () => [AppRouteObserver()], + ), + ); + }, ), ); } diff --git a/lib/presentation/pages/auth/login/login_page.dart b/lib/presentation/pages/auth/login/login_page.dart index 4d487fa..ae829fa 100644 --- a/lib/presentation/pages/auth/login/login_page.dart +++ b/lib/presentation/pages/auth/login/login_page.dart @@ -1,6 +1,7 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; +import '../../../../common/extension/extension.dart'; import '../../../../common/theme/theme.dart'; import '../../../components/button/button.dart'; import '../../../components/spacer/spacer.dart'; @@ -104,11 +105,9 @@ class _LoginPageState extends State with TickerProviderStateMixin { child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - _buildLogo(), + _buildLogo(context), SpaceHeight(48), - _buildLoginCard(), - SpaceHeight(24), - _buildFooter(), + _buildLoginCard(context), ], ), ), @@ -120,26 +119,27 @@ class _LoginPageState extends State with TickerProviderStateMixin { ); } - Widget _buildLogo() { + Widget _buildLogo(BuildContext context) { return Column( children: [ Text( - 'Welcome Back', + context.lang.login_header, style: AppStyle.h1.copyWith( fontWeight: FontWeight.bold, color: AppColor.white, ), + textAlign: TextAlign.center, ), const SpaceHeight(8), Text( - 'Sign in to your account', + context.lang.login_desc, style: AppStyle.lg.copyWith(color: AppColor.textLight), ), ], ); } - Widget _buildLoginCard() { + Widget _buildLoginCard(BuildContext context) { return Container( width: double.infinity, padding: const EdgeInsets.symmetric(vertical: 32, horizontal: 24), @@ -163,7 +163,7 @@ class _LoginPageState extends State with TickerProviderStateMixin { const SpaceHeight(24), LoginPasswordField(controller: _passwordController), const SpaceHeight(16), - _buildForgetPassword(), + _buildForgetPassword(context), const SpaceHeight(32), _buildLoginButton(), ], @@ -172,13 +172,13 @@ class _LoginPageState extends State with TickerProviderStateMixin { ); } - Widget _buildForgetPassword() { + Widget _buildForgetPassword(BuildContext context) { return Align( alignment: Alignment.centerRight, child: GestureDetector( onTap: () {}, child: Text( - 'Forgot Password?', + '${context.lang.forgot_password}?', style: AppStyle.md.copyWith( color: AppColor.primary, fontWeight: FontWeight.w600, @@ -190,31 +190,9 @@ class _LoginPageState extends State with TickerProviderStateMixin { Widget _buildLoginButton() { return AppElevatedButton( - text: 'Sign In', + text: context.lang.sign_in, isLoading: _isLoading, onPressed: _isLoading ? null : _handleLogin, ); } - - Widget _buildFooter() { - return Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - "Don't have an account? ", - style: AppStyle.md.copyWith(color: AppColor.textLight), - ), - GestureDetector( - onTap: () {}, - child: Text( - 'Sign Up', - style: AppStyle.md.copyWith( - color: AppColor.white, - fontWeight: FontWeight.bold, - ), - ), - ), - ], - ); - } } diff --git a/lib/presentation/pages/auth/login/widgets/email_field.dart b/lib/presentation/pages/auth/login/widgets/email_field.dart index 5d96426..f4bca26 100644 --- a/lib/presentation/pages/auth/login/widgets/email_field.dart +++ b/lib/presentation/pages/auth/login/widgets/email_field.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:line_icons/line_icons.dart'; +import '../../../../../common/extension/extension.dart'; import '../../../../../common/validator/validator.dart'; import '../../../../components/field/field.dart'; @@ -11,8 +12,8 @@ class LoginEmailField extends StatelessWidget { @override Widget build(BuildContext context) { return AppTextFormField( - title: 'Email', - hintText: 'Enter your email', + title: context.lang.email, + hintText: context.lang.email_placeholder, prefixIcon: LineIcons.envelope, validator: AppValidator.validateEmail, controller: controller, diff --git a/lib/presentation/pages/auth/login/widgets/password_field.dart b/lib/presentation/pages/auth/login/widgets/password_field.dart index df11f0a..5ab2436 100644 --- a/lib/presentation/pages/auth/login/widgets/password_field.dart +++ b/lib/presentation/pages/auth/login/widgets/password_field.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:line_icons/line_icons.dart'; +import '../../../../../common/extension/extension.dart'; import '../../../../../common/validator/validator.dart'; import '../../../../components/field/field.dart'; @@ -11,9 +12,9 @@ class LoginPasswordField extends StatelessWidget { @override Widget build(BuildContext context) { return AppPasswordTextFormField( - title: 'Password', + title: context.lang.password, prefixIcon: LineIcons.lock, - hintText: 'Enter your password', + hintText: context.lang.password_placeholder, validator: AppValidator.validatePassword, controller: controller, ); diff --git a/lib/presentation/pages/home/home_page.dart b/lib/presentation/pages/home/home_page.dart index 7c08b08..05df01a 100644 --- a/lib/presentation/pages/home/home_page.dart +++ b/lib/presentation/pages/home/home_page.dart @@ -64,7 +64,7 @@ class _HomePageState extends State with TickerProviderStateMixin { slivers: [ // SliverAppBar with HomeHeader as background SliverAppBar( - expandedHeight: 250, // Adjust based on HomeHeader height + expandedHeight: 260, // Adjust based on HomeHeader height floating: true, pinned: true, snap: true, diff --git a/lib/presentation/pages/home/widgets/header.dart b/lib/presentation/pages/home/widgets/header.dart index 2a2eeae..a98129e 100644 --- a/lib/presentation/pages/home/widgets/header.dart +++ b/lib/presentation/pages/home/widgets/header.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; +import '../../../../common/extension/extension.dart'; import '../../../../common/theme/theme.dart'; import '../../../components/spacer/spacer.dart'; @@ -56,13 +57,27 @@ class HomeHeader extends StatelessWidget { ), ), ), - SafeArea(child: _buildContent()), + SafeArea(child: _buildContent(context)), ], ), ); } - Padding _buildContent() { + Padding _buildContent(BuildContext context) { + String greeting(BuildContext context) { + final hour = DateTime.now().hour; + + if (hour >= 4 && hour < 10) { + return context.lang.good_morning; + } else if (hour >= 10 && hour < 15) { + return context.lang.good_afternoon; + } else if (hour >= 15 && hour < 18) { + return context.lang.good_evening; + } else { + return context.lang.good_night; + } + } + return Padding( padding: EdgeInsets.all(AppValue.padding), child: Column( @@ -87,7 +102,7 @@ class HomeHeader extends StatelessWidget { ), const SpaceHeight(2), Text( - 'Dashboard', + 'Manager', style: AppStyle.sm.copyWith( color: AppColor.textLight, fontSize: 11, @@ -120,7 +135,7 @@ class HomeHeader extends StatelessWidget { // Greeting Section Text( - 'Selamat Pagi,', + '${greeting(context)},', style: AppStyle.lg.copyWith( color: AppColor.white, fontWeight: FontWeight.w500, @@ -137,7 +152,7 @@ class HomeHeader extends StatelessWidget { ), const SpaceHeight(8), Text( - 'Mari tingkatkan performa bisnis Anda hari ini', + context.lang.home_header_desc, style: AppStyle.md.copyWith( color: AppColor.white.withOpacity(0.85), fontWeight: FontWeight.w400, @@ -170,7 +185,7 @@ class HomeHeader extends StatelessWidget { ), const SizedBox(width: 6), Text( - 'Penjualan hari ini +25%', + '${context.lang.sales_today} +25%', style: AppStyle.sm.copyWith( color: AppColor.white, fontWeight: FontWeight.w600, diff --git a/lib/presentation/pages/language/language_page.dart b/lib/presentation/pages/language/language_page.dart index fe72a3e..34993b0 100644 --- a/lib/presentation/pages/language/language_page.dart +++ b/lib/presentation/pages/language/language_page.dart @@ -1,115 +1,52 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import '../../../application/language/language_bloc.dart'; +import '../../../common/extension/extension.dart'; import '../../../common/theme/theme.dart'; -import '../../components/button/button.dart'; +import '../../../infrastructure/language/language.dart'; import 'widgets/language_tile.dart'; @RoutePage() -class LanguagePage extends StatefulWidget { +class LanguagePage extends StatelessWidget { const LanguagePage({super.key}); - @override - State createState() => _LanguagePageState(); -} - -class _LanguagePageState extends State { - String selectedLanguage = 'en'; // Default language - - final List> languages = [ - {'code': 'en', 'name': 'English', 'nativeName': 'English', 'flag': '🇺🇸'}, - { - 'code': 'id', - 'name': 'Indonesian', - 'nativeName': 'Bahasa Indonesia', - 'flag': '🇮🇩', - }, - {'code': 'es', 'name': 'Spanish', 'nativeName': 'Español', 'flag': '🇪🇸'}, - {'code': 'fr', 'name': 'French', 'nativeName': 'Français', 'flag': '🇫🇷'}, - {'code': 'de', 'name': 'German', 'nativeName': 'Deutsch', 'flag': '🇩🇪'}, - {'code': 'ja', 'name': 'Japanese', 'nativeName': '日本語', 'flag': '🇯🇵'}, - {'code': 'ko', 'name': 'Korean', 'nativeName': '한국어', 'flag': '🇰🇷'}, - {'code': 'zh', 'name': 'Chinese', 'nativeName': '中文', 'flag': '🇨🇳'}, - {'code': 'ar', 'name': 'Arabic', 'nativeName': 'العربية', 'flag': '🇸🇦'}, - { - 'code': 'pt', - 'name': 'Portuguese', - 'nativeName': 'Português', - 'flag': '🇵🇹', - }, - ]; - - void _selectLanguage(String languageCode) { - setState(() { - selectedLanguage = languageCode; - }); - } - - void _confirmSelection() { - // Here you would typically save the selected language to SharedPreferences - // or pass it back to the parent widget - Navigator.pop(context, selectedLanguage); - - // Show confirmation snackbar - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - 'Language changed to ${_getLanguageName(selectedLanguage)}', - ), - backgroundColor: Colors.green, - duration: const Duration(seconds: 2), - ), - ); - } - - String _getLanguageName(String code) { - return languages.firstWhere((lang) => lang['code'] == code)['name']; - } - @override Widget build(BuildContext context) { return Scaffold( backgroundColor: AppColor.background, appBar: AppBar( - title: const Text('Select Language'), + title: Text(context.lang.select_language), elevation: 0, leading: IconButton( icon: const Icon(Icons.arrow_back, color: Colors.white), onPressed: () => context.router.back(), ), ), - body: Column( - children: [ - // Language list - Expanded( - child: ListView.builder( - padding: const EdgeInsets.all(16), - itemCount: languages.length, - itemBuilder: (context, index) { - final language = languages[index]; - final isSelected = selectedLanguage == language['code']; - - return LanguageTile( - isSelected: isSelected, - language: language, - onTap: () => _selectLanguage(language['code']), - ); - }, - ), - ), - - // Confirm button - Container( + body: BlocBuilder( + builder: (context, state) { + return ListView.builder( padding: const EdgeInsets.all(16), - width: double.infinity, - decoration: BoxDecoration(color: AppColor.white), - child: AppElevatedButton( - text: 'Confirm', - isLoading: false, - onPressed: _confirmSelection, - ), - ), - ], + itemCount: languages.length, + itemBuilder: (context, index) { + final language = languages[index]; + final isSelected = + state.language.locale.languageCode == + language.locale.languageCode + ? true + : false; + + return LanguageTile( + isSelected: isSelected, + language: language, + onTap: () => context.read().add( + LanguageEvent.changeLanguage(language), + ), + ); + }, + ); + }, ), ); } diff --git a/lib/presentation/pages/language/widgets/language_tile.dart b/lib/presentation/pages/language/widgets/language_tile.dart index f52dee0..82721d8 100644 --- a/lib/presentation/pages/language/widgets/language_tile.dart +++ b/lib/presentation/pages/language/widgets/language_tile.dart @@ -1,10 +1,11 @@ import 'package:flutter/material.dart'; import '../../../../common/theme/theme.dart'; +import '../../../../domain/language/language.dart'; class LanguageTile extends StatelessWidget { final bool isSelected; - final Map language; + final Language language; final Function() onTap; const LanguageTile({ super.key, @@ -42,11 +43,11 @@ class LanguageTile extends StatelessWidget { borderRadius: BorderRadius.circular(24), ), child: Center( - child: Text(language['flag'], style: const TextStyle(fontSize: 24)), + child: Text(language.path, style: const TextStyle(fontSize: 24)), ), ), title: Text( - language['name'], + language.name, style: AppStyle.lg.copyWith( fontWeight: FontWeight.w600, @@ -54,7 +55,7 @@ class LanguageTile extends StatelessWidget { ), ), subtitle: Text( - language['nativeName'], + language.nativeName, style: AppStyle.md.copyWith( color: isSelected ? AppColor.primary : AppColor.textPrimary, ), diff --git a/lib/presentation/pages/main/widgets/bottom_navbar.dart b/lib/presentation/pages/main/widgets/bottom_navbar.dart index 0749a45..03c216c 100644 --- a/lib/presentation/pages/main/widgets/bottom_navbar.dart +++ b/lib/presentation/pages/main/widgets/bottom_navbar.dart @@ -3,6 +3,8 @@ import 'package:flutter/material.dart'; import 'package:line_icons/line_icon.dart'; import 'package:line_icons/line_icons.dart'; +import '../../../../common/extension/extension.dart'; + class MainBottomNavbar extends StatefulWidget { final TabsRouter tabsRouter; @@ -26,23 +28,23 @@ class _MainBottomNavbarState extends State { items: [ BottomNavigationBarItem( icon: LineIcon(LineIcons.home), - label: 'Home', - tooltip: 'Home', + label: context.lang.home, + tooltip: context.lang.home, ), BottomNavigationBarItem( icon: LineIcon(LineIcons.moneyBill), - label: 'Transaction', - tooltip: 'Transaction', + label: context.lang.transaction, + tooltip: context.lang.transaction, ), BottomNavigationBarItem( icon: LineIcon(LineIcons.barChart), - label: 'Report', - tooltip: 'Report', + label: context.lang.report, + tooltip: context.lang.report, ), BottomNavigationBarItem( icon: LineIcon(LineIcons.user), - label: 'Profile', - tooltip: 'Profile', + label: context.lang.profile, + tooltip: context.lang.profile, ), ], ); diff --git a/lib/presentation/pages/splash/splash_page.dart b/lib/presentation/pages/splash/splash_page.dart index 9715304..bc5d33c 100644 --- a/lib/presentation/pages/splash/splash_page.dart +++ b/lib/presentation/pages/splash/splash_page.dart @@ -1,6 +1,7 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; +import '../../../common/extension/extension.dart'; import '../../../common/theme/theme.dart'; import '../../components/assets/assets.gen.dart'; import '../../router/app_router.gr.dart'; @@ -156,7 +157,7 @@ class _SplashPageState extends State with TickerProviderStateMixin { child: Opacity( opacity: versionOpacity, child: Text( - 'Version 1.0.0', + '${context.lang.version} 1.0.0', style: AppStyle.md.copyWith(color: AppColor.textLight), textAlign: TextAlign.center, ), diff --git a/pubspec.lock b/pubspec.lock index 2d97622..0e3e17d 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -73,6 +73,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.0" + bloc: + dependency: transitive + description: + name: bloc + sha256: "52c10575f4445c61dd9e0cafcc6356fdd827c4c64dd7945ef3c4105f6b6ac189" + url: "https://pub.dev" + source: hosted + version: "9.0.0" boolean_selector: dependency: transitive description: @@ -358,6 +366,14 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_bloc: + dependency: "direct main" + description: + name: flutter_bloc + sha256: cf51747952201a455a1c840f8171d273be009b932c75093020f9af64f2123e38 + url: "https://pub.dev" + source: hosted + version: "9.1.1" flutter_gen_core: dependency: transitive description: @@ -390,6 +406,11 @@ packages: url: "https://pub.dev" source: hosted version: "5.0.0" + flutter_localizations: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" flutter_spinkit: dependency: "direct main" description: @@ -648,6 +669,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.0" + nested: + dependency: transitive + description: + name: nested + sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" + url: "https://pub.dev" + source: hosted + version: "1.0.0" nm: dependency: transitive description: @@ -768,6 +797,14 @@ packages: url: "https://pub.dev" source: hosted version: "6.0.3" + provider: + dependency: transitive + description: + name: provider + sha256: "4abbd070a04e9ddc287673bf5a030c7ca8b685ff70218720abab8b092f53dd84" + url: "https://pub.dev" + source: hosted + version: "6.1.5" pub_semver: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index a257839..2b5427d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -12,6 +12,9 @@ dependencies: flutter: sdk: flutter + flutter_localizations: + sdk: flutter + cupertino_icons: ^1.0.8 auto_route: ^9.3.0 get_it: ^8.0.3 @@ -32,6 +35,7 @@ dependencies: flutter_spinkit: ^5.2.2 fl_chart: ^1.0.0 another_flushbar: ^1.12.30 + flutter_bloc: ^9.1.1 dev_dependencies: flutter_test: @@ -47,6 +51,7 @@ dev_dependencies: json_serializable: ^6.9.5 flutter: + generate: true uses-material-design: true assets: - assets/images/