import 'dart:ui' show PlatformDispatcher; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:gap/gap.dart'; import 'package:umbrix/core/analytics/analytics_controller.dart'; import 'package:umbrix/core/localization/locale_preferences.dart'; import 'package:umbrix/core/localization/translations.dart'; import 'package:umbrix/core/model/region.dart'; import 'package:umbrix/core/preferences/general_preferences.dart'; import 'package:umbrix/features/common/general_pref_tiles.dart'; import 'package:umbrix/features/config_option/data/config_option_repository.dart'; import 'package:umbrix/features/settings/about/terms_and_conditions_screen.dart'; import 'package:umbrix/gen/assets.gen.dart'; import 'package:umbrix/utils/utils.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:sliver_tools/sliver_tools.dart'; import 'package:timezone_to_country/timezone_to_country.dart'; class IntroPage extends HookConsumerWidget with PresLogger { const IntroPage({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { final t = ref.watch(translationsProvider); final theme = Theme.of(context); final isStarting = useState(false); // Автовыбор региона теперь через useEffect (хуки) useEffect(() { autoSelectRegion(ref).then((value) => loggy.debug("Auto Region selection finished!")); return null; }, const [],); return Scaffold( body: Container( decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [ theme.colorScheme.surface, theme.colorScheme.surfaceContainerHighest, ], ), ), child: SafeArea( child: CustomScrollView( shrinkWrap: true, slivers: [ // Логотип и заголовок SliverToBoxAdapter( child: Padding( padding: const EdgeInsets.symmetric(vertical: 32, horizontal: 24), child: Column( children: [ // Логотип с анимацией Hero( tag: 'app_logo', child: Container( width: 120, height: 120, padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: theme.colorScheme.primaryContainer.withOpacity(0.3), borderRadius: BorderRadius.circular(30), boxShadow: [ BoxShadow( color: theme.colorScheme.primary.withOpacity(0.2), blurRadius: 20, offset: const Offset(0, 10), ), ], ), child: Assets.images.umbrixLogo.image( fit: BoxFit.contain, ), ), ), const Gap(24), // Заголовок Text( t.intro.welcomeTitle, style: theme.textTheme.headlineMedium?.copyWith( fontWeight: FontWeight.bold, color: theme.colorScheme.onSurface, ), textAlign: TextAlign.center, ), const Gap(8), Text( t.intro.subtitle, style: theme.textTheme.bodyLarge?.copyWith( color: theme.colorScheme.onSurfaceVariant, ), textAlign: TextAlign.center, ), ], ), ), ), // Настройки в виде карточек SliverCrossAxisConstrained( maxCrossAxisExtent: 400, child: MultiSliver( children: [ // Язык const SliverToBoxAdapter( child: Padding( padding: EdgeInsets.symmetric(horizontal: 24, vertical: 6), child: _SettingCard( child: LocalePrefTile(), ), ), ), // Регион const SliverToBoxAdapter( child: Padding( padding: EdgeInsets.symmetric(horizontal: 24, vertical: 6), child: _SettingCard( child: RegionPrefTile(), ), ), ), // Аналитика const SliverToBoxAdapter( child: Padding( padding: EdgeInsets.symmetric(horizontal: 24, vertical: 6), child: _SettingCard( child: EnableAnalyticsPrefTile(), ), ), ), const SliverGap(16), // Условия использования SliverToBoxAdapter( child: Padding( padding: const EdgeInsets.symmetric(horizontal: 32), child: Text.rich( t.intro.termsAndPolicyCaution( tap: (text) => TextSpan( text: text, style: TextStyle( color: theme.colorScheme.primary, fontWeight: FontWeight.w600, decoration: TextDecoration.underline, ), recognizer: TapGestureRecognizer() ..onTap = () { Navigator.of(context).push( MaterialPageRoute( builder: (context) => const TermsAndConditionsScreen(), ), ); }, ), ), style: theme.textTheme.bodySmall?.copyWith( color: theme.colorScheme.onSurfaceVariant, ), textAlign: TextAlign.center, ), ), ), // Кнопка начать SliverToBoxAdapter( child: Padding( padding: const EdgeInsets.fromLTRB(24, 24, 24, 32), child: Container( decoration: BoxDecoration( gradient: LinearGradient( colors: [ theme.colorScheme.primary, theme.colorScheme.primary.withOpacity(0.8), ], ), borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( color: theme.colorScheme.primary.withOpacity(0.4), blurRadius: 12, offset: const Offset(0, 6), ), ], ), child: Material( color: Colors.transparent, child: InkWell( onTap: isStarting.value ? null : () async { isStarting.value = true; if (!ref.read(analyticsControllerProvider).requireValue) { loggy.info("disabling analytics per user request"); try { await ref.read(analyticsControllerProvider.notifier).disableAnalytics(); } catch (error, stackTrace) { loggy.error( "could not disable analytics", error, stackTrace, ); } } await ref.read(Preferences.introCompleted.notifier).update(true); }, borderRadius: BorderRadius.circular(16), child: Container( height: 56, alignment: Alignment.center, child: isStarting.value ? SizedBox( width: 24, height: 24, child: CircularProgressIndicator( strokeWidth: 3, valueColor: AlwaysStoppedAnimation( theme.colorScheme.onPrimary, ), ), ) : Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( t.intro.start, style: theme.textTheme.titleMedium?.copyWith( color: theme.colorScheme.onPrimary, fontWeight: FontWeight.bold, ), ), const Gap(8), Icon( Icons.arrow_forward_rounded, color: theme.colorScheme.onPrimary, ), ], ), ), ), ), ), ), ), ], ), ), ], ), ), ), ); } Future autoSelectRegion(WidgetRef ref) async { try { final countryCode = await TimeZoneToCountry.getLocalCountryCode(); final regionLocale = _getRegionLocale(countryCode); loggy.debug( 'Timezone Region: ${regionLocale.region} Locale: ${regionLocale.locale}', ); await ref.read(ConfigOptions.region.notifier).update(regionLocale.region); await ref.watch(ConfigOptions.directDnsAddress.notifier).reset(); await ref.read(localePreferencesProvider.notifier).changeLocale(regionLocale.locale); return; } catch (e) { loggy.warning( 'Could not get the local country code based on timezone', e, ); } // UMBRIX: Используем timezone вместо IP для приватности try { final timezone = DateTime.now().timeZoneName; final systemLocale = PlatformDispatcher.instance.locale; loggy.debug('Using timezone: $timezone, system locale: ${systemLocale.languageCode}'); // Определяем регион по системной локали (без интернет-запросов!) final countryCode = systemLocale.countryCode ?? ''; if (countryCode.isNotEmpty) { final regionLocale = _getRegionLocale(countryCode); await ref.read(ConfigOptions.region.notifier).update(regionLocale.region); await ref.read(localePreferencesProvider.notifier).changeLocale(regionLocale.locale); loggy.debug('Region set from system locale: ${regionLocale.region}'); } else { loggy.debug('Could not determine region from system locale, using default'); } } catch (e) { loggy.warning('Could not auto-select region: $e'); } } RegionLocale _getRegionLocale(String country) { switch (country.toUpperCase()) { case "IR": return RegionLocale(Region.ir, AppLocale.fa); case "CN": return RegionLocale(Region.cn, AppLocale.zhCn); case "RU": return RegionLocale(Region.ru, AppLocale.ru); case "AF": return RegionLocale(Region.af, AppLocale.fa); case "BR": return RegionLocale(Region.other, AppLocale.ptBr); case "TR": return RegionLocale(Region.other, AppLocale.tr); default: return RegionLocale(Region.other, AppLocale.en); } } } class RegionLocale { final Region region; final AppLocale locale; RegionLocale(this.region, this.locale); } // Карточка для настроек class _SettingCard extends StatelessWidget { const _SettingCard({required this.child}); final Widget child; @override Widget build(BuildContext context) { final theme = Theme.of(context); return Container( decoration: BoxDecoration( color: theme.colorScheme.surfaceContainerHighest.withOpacity(0.5), borderRadius: BorderRadius.circular(16), border: Border.all( color: theme.colorScheme.outline.withOpacity(0.1), ), ), child: ClipRRect( borderRadius: BorderRadius.circular(16), child: child, ), ); } }