feat: language
This commit is contained in:
@@ -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<LoginPage> 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<LoginPage> 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<LoginPage> 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<LoginPage> 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<LoginPage> 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,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
|
||||
@@ -64,7 +64,7 @@ class _HomePageState extends State<HomePage> 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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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<LanguagePage> createState() => _LanguagePageState();
|
||||
}
|
||||
|
||||
class _LanguagePageState extends State<LanguagePage> {
|
||||
String selectedLanguage = 'en'; // Default language
|
||||
|
||||
final List<Map<String, dynamic>> 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<LanguageBloc, LanguageState>(
|
||||
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<LanguageBloc>().add(
|
||||
LanguageEvent.changeLanguage(language),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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<String, dynamic> 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,
|
||||
),
|
||||
|
||||
@@ -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<MainBottomNavbar> {
|
||||
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,
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
@@ -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<SplashPage> 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,
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user