Files
CityCards_Customer_Flutter/lib/common_packages/language_selection_bottomsheet.dart
2026-04-15 19:06:30 +05:30

202 lines
6.6 KiB
Dart

import 'package:citycards_customer/common_bloc/language_selection_bloc.dart';
import 'package:citycards_customer/common_packages/custom_filled_button.dart';
import 'package:citycards_customer/common_packages/custom_text.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import '../l10n/app_localizations.dart';
import '../localPreference/local_preference.dart';
class LanguageSelectionBottomsheet extends StatefulWidget {
const LanguageSelectionBottomsheet({super.key});
@override
State<LanguageSelectionBottomsheet> createState() =>
_LanguageSelectionBottomsheetState();
}
class _LanguageSelectionBottomsheetState
extends State<LanguageSelectionBottomsheet> {
/// Each entry: display label → BCP-47 code for google_mlkit_translation
final List<Map<String, String>> languages = [
{'label': 'English / English', 'code': 'en'},
{'label': 'Dutch / Nederlands', 'code': 'nl'},
{'label': 'Spanish / Español', 'code': 'es'},
{'label': 'French / Français', 'code': 'fr'},
{'label': 'Japanese / 日本語', 'code': 'ja'},
];
List<Map<String, String>> _filtered = [];
String? _pendingLabel; // highlighted in list but not yet saved
final TextEditingController _searchController = TextEditingController();
@override
void initState() {
super.initState();
_filtered = List.from(languages);
_searchController.addListener(_onSearch);
}
void _onSearch() {
final query = _searchController.text.toLowerCase();
setState(() {
_filtered = languages
.where((l) => l['label']!.toLowerCase().contains(query))
.toList();
});
}
Future<void> _onSave() async {
if (_pendingLabel == null) {
Navigator.of(context).pop();
return;
}
final selected = languages.firstWhere((l) => l['label'] == _pendingLabel);
final code = selected['code']!;
// Persist to SQLite
await LocalPreference.setLanguage(code);
// Update BLoC
if (mounted) {
context.read<LanguageBloc>().add(UpdateLanguage(label: _pendingLabel!, code: code));
Navigator.of(context).pop();
}
}
@override
void dispose() {
_searchController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.only(
left: 20.w,
right: 20.w,
top: 16.h,
bottom: MediaQuery.of(context).viewInsets.bottom + 16.h,
),
child: BlocBuilder<LanguageBloc, LanguageState>(
builder: (context, state) {
// Seed pending selection from current BLoC state on first build
_pendingLabel ??= state.label;
return Column(
mainAxisSize: MainAxisSize.min,
children: [
/// Drag handle
Container(
width: 40.w,
height: 4.h,
decoration: BoxDecoration(
color: const Color(0xFF2D3134),
borderRadius: BorderRadius.circular(4.r),
),
),
SizedBox(height: 20.h),
/// Title
Align(
alignment: Alignment.topLeft,
child: Text(
AppLocalizations.of(context)!.changeLanguageTitle,
style: TextStyle(
fontSize: 18.sp,
fontWeight: FontWeight.w500,
),
),
),
SizedBox(height: 22.h),
/// Search field
TextField(
controller: _searchController,
decoration: InputDecoration(
hintText: AppLocalizations.of(context)!.searchLanguages,
hintStyle: TextStyle(
fontSize: 14.sp,
color: const Color(0xBBC83B61).withOpacity(0.4),
),
suffixIcon:
Image.asset("assets/icons/search.png", scale: 4),
contentPadding:
EdgeInsets.symmetric(horizontal: 24.w),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(10.r),
borderSide: BorderSide(
color: const Color(0xBBC83B61).withOpacity(0.4),
width: .4.w,
),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(10.r),
borderSide: BorderSide(
color: const Color(0xFFF95F62),
width: 1.w,
),
),
),
),
SizedBox(height: 12.h),
/// Language list (fixed height, scrollable)
ConstrainedBox(
constraints: BoxConstraints(maxHeight: 280.h),
child: ListView.builder(
shrinkWrap: true,
itemCount: _filtered.length,
itemBuilder: (context, index) {
final item = _filtered[index];
final label = item['label']!;
final isSelected = _pendingLabel == label;
return ListTile(
dense: true,
onTap: () => setState(() => _pendingLabel = label),
leading: GestureDetector(
onTap: () => setState(() => _pendingLabel = label),
child: isSelected
? Image.asset(
"assets/icons/radio_button_checked.png",
scale: 4,
)
: Image.asset(
"assets/icons/radio_button_unchecked.png",
scale: 4,
),
),
title: CustomText(
text: label,
size: 16.sp,
color: isSelected
? const Color(0xFFF95F62)
: const Color(0xFF000000).withOpacity(.6),
),
);
},
),
),
SizedBox(height: 16.h),
/// Save button
CustomFilledButton(
width: double.infinity,
height: 48.h,
onTap: _onSave,
label: AppLocalizations.of(context)!.save,
),
SizedBox(height: 8.h),
],
);
},
),
);
}
}