Update codebase
This commit is contained in:
@@ -1,15 +1,30 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
import '../../../core/build_config.dart';
|
||||
import '../../../core/design/tokens.dart';
|
||||
import '../../../core/design/tripz_colors.dart';
|
||||
import '../../../core/design/typography.dart';
|
||||
import '../../../core/di.dart';
|
||||
import '../../../core/l10n/l10n.dart';
|
||||
import '../../../core/router.dart';
|
||||
import '../../../core/session/session_cubit.dart';
|
||||
import '../../../core/store_links.dart';
|
||||
import '../../../core/ui/tripz_scaffold.dart';
|
||||
import '../../profile/data/profile_repository.dart';
|
||||
import '../cubit/settings_cubit.dart';
|
||||
import '../cubit/settings_state.dart';
|
||||
|
||||
/// المظهر واللغة. تبديل اللغة **يقلب الاتجاه كاملاً** بلا إعادة تشغيل —
|
||||
/// `MaterialApp` يشتقّ RTL/LTR من الـlocale (docs/26 §4).
|
||||
/// المظهر واللغة ومعلومات التطبيق — بأقسام كما في سيرو
|
||||
/// (`setting_page.dart`: General · Preferences · Support & Info) بدل قائمة
|
||||
/// مسطّحة بلا عناوين.
|
||||
///
|
||||
/// تبديل اللغة **يقلب الاتجاه كاملاً** بلا إعادة تشغيل — `MaterialApp`
|
||||
/// يشتقّ RTL/LTR من الـlocale (docs/26 §4).
|
||||
class SettingsPage extends StatelessWidget {
|
||||
const SettingsPage({super.key});
|
||||
|
||||
@@ -23,8 +38,8 @@ class SettingsPage extends StatelessWidget {
|
||||
title: l10n.settingsTitle,
|
||||
child: ListView(
|
||||
children: [
|
||||
Text(l10n.settingsTheme, style: context.texts.titleMedium),
|
||||
const SizedBox(height: Space.xs),
|
||||
_SectionHeader(title: l10n.settingsSectionGeneral),
|
||||
Text(l10n.settingsTheme, style: context.texts.titleSmall),
|
||||
RadioGroup<ThemeMode>(
|
||||
groupValue: state.themeMode,
|
||||
onChanged: (v) => cubit.setThemeMode(v ?? ThemeMode.system),
|
||||
@@ -48,12 +63,15 @@ class SettingsPage extends StatelessWidget {
|
||||
],
|
||||
),
|
||||
),
|
||||
const Divider(height: Space.xl),
|
||||
Text(l10n.settingsLanguage, style: context.texts.titleMedium),
|
||||
const SizedBox(height: Space.xs),
|
||||
const SizedBox(height: Space.md),
|
||||
Text(l10n.settingsLanguage, style: context.texts.titleSmall),
|
||||
RadioGroup<String>(
|
||||
groupValue: state.locale.languageCode,
|
||||
onChanged: (v) => cubit.setLocale(Locale(v ?? 'ar')),
|
||||
// اللغة تُحفظ محلياً **وتُرسل للخادم**: الإشعارات تُصاغ على
|
||||
// الخادم بلغة المستخدم المخزّنة عنده (`PATCH /users/me`
|
||||
// يقبل `language` — docs/38 §3)، فلغةٌ محليّة وحدها تعني
|
||||
// واجهةً عربية وإشعاراتٍ إنجليزية.
|
||||
onChanged: (v) => _setLanguage(context, v ?? 'ar'),
|
||||
child: Column(
|
||||
children: [
|
||||
RadioListTile(
|
||||
@@ -69,10 +87,127 @@ class SettingsPage extends StatelessWidget {
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
_SectionHeader(title: l10n.settingsSectionSupport),
|
||||
_Tile(
|
||||
icon: Icons.help_outline_rounded,
|
||||
title: l10n.menuSupport,
|
||||
subtitle: l10n.settingsSupportLead,
|
||||
onTap: () => context.push(Routes.support),
|
||||
),
|
||||
_Tile(
|
||||
icon: Icons.share_outlined,
|
||||
title: l10n.menuShare,
|
||||
subtitle: l10n.settingsShareLead,
|
||||
onTap: () => SharePlus.instance.share(
|
||||
ShareParams(text: l10n.shareMessagePlain(StoreLinks.thisApp)),
|
||||
),
|
||||
),
|
||||
_Tile(
|
||||
icon: Icons.shield_outlined,
|
||||
title: l10n.menuPrivacy,
|
||||
onTap: () async {
|
||||
final uri = Uri.tryParse(BuildConfig.privacyUrl);
|
||||
if (uri == null) return;
|
||||
await launchUrl(uri, mode: LaunchMode.externalApplication);
|
||||
},
|
||||
),
|
||||
const _VersionTile(),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// فشل الخادم **لا يمنع** تبديل اللغة محلياً: الواجهة تستجيب فوراً،
|
||||
/// والمزامنة تُعاد عند التبديل التالي. عكسه يعني زرّاً لا يستجيب بلا شبكة.
|
||||
static Future<void> _setLanguage(BuildContext context, String code) async {
|
||||
await context.read<SettingsCubit>().setLocale(Locale(code));
|
||||
try {
|
||||
final user = await sl<ProfileRepository>().update(language: code);
|
||||
sl<SessionCubit>().onProfileCompleted(user);
|
||||
} catch (_) {
|
||||
// المزامنة أفضل-جهد — لا رسالة خطأ على تبديل نجح محلياً.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _SectionHeader extends StatelessWidget {
|
||||
const _SectionHeader({required this.title});
|
||||
|
||||
final String title;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: Space.lg, bottom: Space.xs),
|
||||
child: Text(
|
||||
title,
|
||||
style: context.texts.labelMedium?.copyWith(
|
||||
color: context.colors.primary,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _Tile extends StatelessWidget {
|
||||
const _Tile({
|
||||
required this.icon,
|
||||
required this.title,
|
||||
required this.onTap,
|
||||
this.subtitle,
|
||||
});
|
||||
|
||||
final IconData icon;
|
||||
final String title;
|
||||
final String? subtitle;
|
||||
final VoidCallback onTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListTile(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
leading: Icon(icon),
|
||||
title: Text(title),
|
||||
subtitle: subtitle == null ? null : Text(subtitle!),
|
||||
trailing: Icon(
|
||||
Icons.chevron_left_rounded,
|
||||
color: context.colors.onSurfaceVariant,
|
||||
),
|
||||
onTap: onTap,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// رقم الإصدار — أول ما يُسأل عنه في الدعم، وكان غائباً رغم وجود مفتاح
|
||||
/// ترجمته أصلاً.
|
||||
class _VersionTile extends StatelessWidget {
|
||||
const _VersionTile();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = context.l10n;
|
||||
return FutureBuilder<PackageInfo>(
|
||||
future: PackageInfo.fromPlatform(),
|
||||
builder: (context, snapshot) {
|
||||
final info = snapshot.data;
|
||||
return ListTile(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
leading: const Icon(Icons.info_outline_rounded),
|
||||
title: Text(l10n.aboutVersion),
|
||||
trailing: Text(
|
||||
// قبل وصول المعلومة يُترك المكان فارغاً لا «—»: القيمة تصل
|
||||
// في إطار واحد، والشرطة تومض بلا داعٍ.
|
||||
info == null ? '' : '${info.version} (${info.buildNumber})',
|
||||
textDirection: TextDirection.ltr,
|
||||
style: numericStyle(context.texts.bodySmall).copyWith(
|
||||
color: context.colors.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user