202 lines
7.2 KiB
Dart
202 lines
7.2 KiB
Dart
import 'dart:async';
|
||
|
||
import 'package:flutter/material.dart';
|
||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||
|
||
import 'package:maibu_satabot_v2/core/localization/app_localizations.dart';
|
||
import 'package:maibu_satabot_v2/core/localization/locale_cubit.dart';
|
||
|
||
/// 可选语言项:tag 对应 assets/languages/{tag}.json,nativeName 用该语言本地写法展示。
|
||
class LanguageItem {
|
||
final Locale locale;
|
||
final String nativeName;
|
||
const LanguageItem(this.locale, this.nativeName);
|
||
|
||
String get tag => locale.countryCode == null || locale.countryCode!.isEmpty
|
||
? locale.languageCode
|
||
: '${locale.languageCode}-${locale.countryCode}';
|
||
}
|
||
|
||
/// 全局唯一的“可选语言列表”来源。
|
||
/// 后续接入“接口下载语言包”时,只需在这里扩展(例如把已下载的语言追加进来),
|
||
/// 登录强制弹窗与设置页入口都会自动同步,无需改多处。
|
||
class AppLanguages {
|
||
AppLanguages._();
|
||
static final AppLanguages instance = AppLanguages._();
|
||
|
||
final List<LanguageItem> items = const [
|
||
LanguageItem(AppLocalizations.chinese, '简体中文'),
|
||
LanguageItem(AppLocalizations.english, 'English'),
|
||
];
|
||
|
||
String nameOf(Locale locale) {
|
||
final tag = locale.countryCode == null || locale.countryCode!.isEmpty
|
||
? locale.languageCode
|
||
: '${locale.languageCode}-${locale.countryCode}';
|
||
for (final item in items) {
|
||
if (item.tag == tag) return item.nativeName;
|
||
}
|
||
return tag;
|
||
}
|
||
}
|
||
|
||
/// 语言选择弹窗。
|
||
/// [mandatory] = true:登录时强制弹出,拦截系统返回键;可点某个语言即时切换,或点“继续”确认当前默认语言后关闭。
|
||
/// [mandatory] = false:设置页中普通选择,可取消。
|
||
class LanguageSelectionDialog extends StatelessWidget {
|
||
final bool mandatory;
|
||
const LanguageSelectionDialog({super.key, this.mandatory = true});
|
||
|
||
void _onSelect(BuildContext context, Locale locale) {
|
||
if (ModalRoute.of(context)?.isCurrent != true) return;
|
||
unawaited(context.read<LocaleCubit>().setLocale(locale));
|
||
Navigator.of(context).pop();
|
||
}
|
||
|
||
static const _navy = Color(0xFF1A2B4A);
|
||
static const _blue = Color(0xFF1C96F6);
|
||
static const _gray = Color(0xFF63758A);
|
||
static const _border = Color(0xFFE5EDF7);
|
||
static const _softBlue = Color(0xFFEAF3FE);
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
final current = context.select<LocaleCubit, Locale>((c) => c.state);
|
||
final isChinese = current.languageCode == 'zh';
|
||
final buttonShape = RoundedRectangleBorder(
|
||
borderRadius: BorderRadius.circular(12),
|
||
);
|
||
|
||
return PopScope(
|
||
canPop: !mandatory,
|
||
child: Dialog(
|
||
backgroundColor: Colors.white,
|
||
surfaceTintColor: Colors.transparent,
|
||
insetPadding: const EdgeInsets.symmetric(horizontal: 24, vertical: 20),
|
||
constraints: const BoxConstraints(maxWidth: 360),
|
||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(24)),
|
||
child: SingleChildScrollView(
|
||
padding: const EdgeInsets.all(20),
|
||
child: Column(
|
||
mainAxisSize: MainAxisSize.min,
|
||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||
children: [
|
||
Row(
|
||
children: [
|
||
Container(
|
||
width: 44,
|
||
height: 44,
|
||
decoration: BoxDecoration(
|
||
color: _softBlue,
|
||
borderRadius: BorderRadius.circular(12),
|
||
),
|
||
child: const Icon(Icons.language_rounded, color: _blue),
|
||
),
|
||
const SizedBox(width: 12),
|
||
Expanded(
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(
|
||
isChinese ? '选择语言' : 'Select language',
|
||
style: const TextStyle(
|
||
color: _navy,
|
||
fontSize: 18,
|
||
fontWeight: FontWeight.w800,
|
||
),
|
||
),
|
||
const SizedBox(height: 2),
|
||
Text(
|
||
isChinese ? 'Select language' : '选择语言',
|
||
style: const TextStyle(color: _gray, fontSize: 12),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
],
|
||
),
|
||
const SizedBox(height: 18),
|
||
for (final item in AppLanguages.instance.items) ...[
|
||
_languageOption(context, item, selected: item.locale == current),
|
||
const SizedBox(height: 8),
|
||
],
|
||
const SizedBox(height: 8),
|
||
if (mandatory)
|
||
FilledButton(
|
||
style: FilledButton.styleFrom(
|
||
backgroundColor: const Color(0xFF0B6DCC),
|
||
foregroundColor: Colors.white,
|
||
minimumSize: const Size.fromHeight(44),
|
||
shape: buttonShape,
|
||
),
|
||
onPressed: () => _onSelect(context, current),
|
||
child: Text(isChinese ? '继续 / Continue' : 'Continue / 继续'),
|
||
)
|
||
else
|
||
OutlinedButton(
|
||
style: OutlinedButton.styleFrom(
|
||
foregroundColor: _navy,
|
||
side: const BorderSide(color: _border),
|
||
minimumSize: const Size.fromHeight(44),
|
||
shape: buttonShape,
|
||
),
|
||
onPressed: () => Navigator.of(context).pop(),
|
||
child: Text(
|
||
AppLocalizations.of(context).translate('common.cancel'),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
Widget _languageOption(
|
||
BuildContext context,
|
||
LanguageItem item, {
|
||
required bool selected,
|
||
}) {
|
||
return Semantics(
|
||
selected: selected,
|
||
button: true,
|
||
child: Material(
|
||
color: selected ? _softBlue : const Color(0xFFF6FAFE),
|
||
shape: RoundedRectangleBorder(
|
||
borderRadius: BorderRadius.circular(12),
|
||
side: BorderSide(color: selected ? _blue : _border),
|
||
),
|
||
clipBehavior: Clip.antiAlias,
|
||
child: InkWell(
|
||
onTap: () => _onSelect(context, item.locale),
|
||
child: Padding(
|
||
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 14),
|
||
child: Row(
|
||
children: [
|
||
Expanded(
|
||
child: Text(
|
||
item.nativeName,
|
||
style: const TextStyle(
|
||
color: _navy,
|
||
fontSize: 15,
|
||
fontWeight: FontWeight.w600,
|
||
),
|
||
),
|
||
),
|
||
const SizedBox(width: 10),
|
||
Icon(
|
||
selected
|
||
? Icons.check_circle_rounded
|
||
: Icons.radio_button_unchecked_rounded,
|
||
size: 20,
|
||
color: selected ? _blue : const Color(0xFFABBBCD),
|
||
),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
),
|
||
);
|
||
}
|
||
}
|