Files
tripz-llc/apps/rider_new/lib/features/settings/view/settings_page.dart
T
2026-08-05 14:12:41 +03:00

214 lines
7.8 KiB
Dart

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';
/// المظهر واللغة ومعلومات التطبيق — بأقسام كما في سيرو
/// (`setting_page.dart`: General · Preferences · Support & Info) بدل قائمة
/// مسطّحة بلا عناوين.
///
/// تبديل اللغة **يقلب الاتجاه كاملاً** بلا إعادة تشغيل — `MaterialApp`
/// يشتقّ RTL/LTR من الـlocale (docs/26 §4).
class SettingsPage extends StatelessWidget {
const SettingsPage({super.key});
@override
Widget build(BuildContext context) {
final l10n = context.l10n;
return BlocBuilder<SettingsCubit, SettingsState>(
builder: (context, state) {
final cubit = context.read<SettingsCubit>();
return TripzScaffold(
title: l10n.settingsTitle,
child: ListView(
children: [
_SectionHeader(title: l10n.settingsSectionGeneral),
Text(l10n.settingsTheme, style: context.texts.titleSmall),
RadioGroup<ThemeMode>(
groupValue: state.themeMode,
onChanged: (v) => cubit.setThemeMode(v ?? ThemeMode.system),
child: Column(
children: [
RadioListTile(
value: ThemeMode.system,
contentPadding: EdgeInsets.zero,
title: Text(l10n.settingsThemeSystem),
),
RadioListTile(
value: ThemeMode.light,
contentPadding: EdgeInsets.zero,
title: Text(l10n.settingsThemeLight),
),
RadioListTile(
value: ThemeMode.dark,
contentPadding: EdgeInsets.zero,
title: Text(l10n.settingsThemeDark),
),
],
),
),
const SizedBox(height: Space.md),
Text(l10n.settingsLanguage, style: context.texts.titleSmall),
RadioGroup<String>(
groupValue: state.locale.languageCode,
// اللغة تُحفظ محلياً **وتُرسل للخادم**: الإشعارات تُصاغ على
// الخادم بلغة المستخدم المخزّنة عنده (`PATCH /users/me`
// يقبل `language` — docs/38 §3)، فلغةٌ محليّة وحدها تعني
// واجهةً عربية وإشعاراتٍ إنجليزية.
onChanged: (v) => _setLanguage(context, v ?? 'ar'),
child: Column(
children: [
RadioListTile(
value: 'ar',
contentPadding: EdgeInsets.zero,
title: Text(l10n.settingsLanguageArabic),
),
RadioListTile(
value: 'en',
contentPadding: EdgeInsets.zero,
title: Text(l10n.settingsLanguageEnglish),
),
],
),
),
_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,
),
),
);
},
);
}
}