import 'package:combine/combine.dart'; import 'package:fluentui_system_icons/fluentui_system_icons.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:gap/gap.dart'; import 'package:go_router/go_router.dart'; import 'package:hiddify/core/localization/translations.dart'; import 'package:hiddify/core/notification/in_app_notification_controller.dart'; import 'package:hiddify/core/preferences/preferences_provider.dart'; import 'package:hiddify/core/router/router.dart'; import 'package:hiddify/features/common/qr_code_scanner_screen.dart'; import 'package:hiddify/features/config_option/data/config_option_repository.dart'; import 'package:hiddify/features/config_option/notifier/warp_option_notifier.dart'; import 'package:hiddify/features/config_option/overview/warp_options_widgets.dart'; import 'package:hiddify/features/profile/notifier/profile_notifier.dart'; import 'package:hiddify/utils/utils.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; class AddProfileModal extends HookConsumerWidget { const AddProfileModal({ super.key, this.url, this.scrollController, }); static const warpConsentGiven = "warp_consent_given"; final String? url; final ScrollController? scrollController; @override Widget build(BuildContext context, WidgetRef ref) { final t = ref.watch(translationsProvider); final addProfileState = ref.watch(addProfileProvider); ref.listen( addProfileProvider, (previous, next) { if (next case AsyncData(value: final _?)) { WidgetsBinding.instance.addPostFrameCallback( (_) { if (context.mounted && context.canPop()) context.pop(); }, ); } }, ); useMemoized(() async { await Future.delayed(const Duration(milliseconds: 200)); if (url != null && context.mounted) { if (addProfileState.isLoading) return; ref.read(addProfileProvider.notifier).add(url!); } }); final theme = Theme.of(context); const buttonsPadding = 24.0; const buttonsGap = 16.0; return SingleChildScrollView( controller: scrollController, child: AnimatedSize( duration: const Duration(milliseconds: 250), child: Builder( builder: (context) { // Fixed button width instead of using LayoutBuilder final buttonWidth = (MediaQuery.of(context).size.width / 2) - (buttonsPadding + (buttonsGap / 2)); return AnimatedCrossFade( firstChild: SizedBox( height: buttonWidth.clamp(0, 168), child: Padding( padding: const EdgeInsets.symmetric(horizontal: 64), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( t.profile.add.addingProfileMsg, style: theme.textTheme.bodySmall, ), const Gap(8), const LinearProgressIndicator( backgroundColor: Colors.transparent, ), const Gap(8), TextButton( onPressed: () { ref.invalidate(addProfileProvider); }, child: Text( MaterialLocalizations.of(context).cancelButtonLabel, ), ), ], ), ), ), secondChild: Column( children: [ // Заголовок Padding( padding: const EdgeInsets.fromLTRB(24, 16, 24, 8), child: Text( t.profile.add.buttonText, style: theme.textTheme.titleLarge?.copyWith( fontWeight: FontWeight.bold, ), ), ), // Основные кнопки в виде больших карточек Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: Column( children: [ _ModernButton( key: const ValueKey("add_from_clipboard_button"), label: t.profile.add.fromClipboard, subtitle: "Paste from clipboard", icon: FluentIcons.clipboard_paste_24_filled, gradient: LinearGradient( colors: [ theme.colorScheme.primaryContainer, theme.colorScheme.secondaryContainer, ], ), onTap: () async { final captureResult = await Clipboard.getData(Clipboard.kTextPlain).then((value) => value?.text ?? ''); if (addProfileState.isLoading) return; ref.read(addProfileProvider.notifier).add(captureResult); }, ), const Gap(12), if (!PlatformUtils.isDesktop) _ModernButton( key: const ValueKey("add_by_qr_code_button"), label: t.profile.add.scanQr, subtitle: "Camera scanner", icon: FluentIcons.qr_code_24_filled, gradient: LinearGradient( colors: [ theme.colorScheme.tertiaryContainer, theme.colorScheme.primaryContainer.withOpacity(0.7), ], ), onTap: () async { final cr = await QRCodeScannerScreen().open(context); if (cr == null) return; if (addProfileState.isLoading) return; ref.read(addProfileProvider.notifier).add(cr); }, ) else _ModernButton( key: const ValueKey("add_manually_button"), label: t.profile.add.manually, subtitle: "Create new config", icon: FluentIcons.edit_24_filled, gradient: LinearGradient( colors: [ theme.colorScheme.tertiaryContainer, theme.colorScheme.primaryContainer.withOpacity(0.7), ], ), onTap: () async { context.pop(); await const NewProfileRoute().push(context); }, ), ], ), ), const Gap(16), // Дополнительные опции Padding( padding: const EdgeInsets.symmetric(horizontal: 16), child: Column( children: [ _CompactButton( key: const ValueKey("add_warp_button"), label: t.profile.add.addWarp, icon: FluentIcons.cloud_add_24_regular, color: theme.colorScheme.primary, onTap: () async { await addProfileModal(context, ref); }, ), if (!PlatformUtils.isDesktop) const Gap(12), if (!PlatformUtils.isDesktop) _CompactButton( key: const ValueKey("add_manually_button"), label: t.profile.add.manually, icon: FluentIcons.edit_24_regular, color: theme.colorScheme.secondary, onTap: () async { context.pop(); await const NewProfileRoute().push(context); }, ), ], ), ), const Gap(24), ], ), crossFadeState: addProfileState.isLoading ? CrossFadeState.showFirst : CrossFadeState.showSecond, duration: const Duration(milliseconds: 250), ); }, ), ), ); } Future addProfileModal(BuildContext context, WidgetRef ref) async { final _prefs = ref.read(sharedPreferencesProvider).requireValue; final _warp = ref.read(warpOptionNotifierProvider.notifier); final _profile = ref.read(addProfileProvider.notifier); final consent = (_prefs.getBool(warpConsentGiven) ?? false); final region = ref.read(ConfigOptions.region.notifier).raw(); context.pop(); final t = ref.read(translationsProvider); final notification = ref.read(inAppNotificationControllerProvider); if (!consent) { final agreed = await showDialog( context: context, builder: (context) => const WarpLicenseAgreementModal(), ); if (agreed != true) return; } await _prefs.setBool(warpConsentGiven, true); var toast = notification.showInfoToast(t.profile.add.addingWarpMsg, duration: const Duration(milliseconds: 100)); toast?.pause(); await _warp.generateWarpConfig(); toast?.start(); // final accountId = _prefs.getString("warp2-account-id"); // final accessToken = _prefs.getString("warp2-access-token"); // final hasWarp2Config = accountId != null && accessToken != null; // if (!hasWarp2Config || true) { toast = notification.showInfoToast(t.profile.add.addingWarpMsg, duration: const Duration(milliseconds: 100)); toast?.pause(); await _warp.generateWarp2Config(); toast?.start(); // } if (region == "cn") { await _profile.add("#profile-title: Hiddify WARP\nwarp://p1@auto#National&&detour=warp://p2@auto#WoW"); // } else { await _profile.add("https://raw.githubusercontent.com/hiddify/hiddify-next/main/test.configs/warp"); // } } } // Современная большая кнопка с градиентом class _ModernButton extends StatelessWidget { const _ModernButton({ super.key, required this.label, required this.subtitle, required this.icon, required this.gradient, required this.onTap, }); final String label; final String subtitle; final IconData icon; final Gradient gradient; final VoidCallback onTap; @override Widget build(BuildContext context) { final theme = Theme.of(context); return Semantics( button: true, child: Material( elevation: 4, shadowColor: theme.colorScheme.primary.withOpacity(0.2), borderRadius: BorderRadius.circular(20), clipBehavior: Clip.antiAlias, child: InkWell( onTap: onTap, child: Container( height: 80, decoration: BoxDecoration( gradient: gradient, borderRadius: BorderRadius.circular(20), ), child: Padding( padding: const EdgeInsets.symmetric(horizontal: 20), child: Row( children: [ Container( width: 56, height: 56, decoration: BoxDecoration( color: theme.colorScheme.surface.withOpacity(0.9), borderRadius: BorderRadius.circular(16), ), child: Icon( icon, size: 28, color: theme.colorScheme.primary, ), ), const Gap(16), Expanded( child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( label, style: theme.textTheme.titleMedium?.copyWith( fontWeight: FontWeight.bold, color: theme.colorScheme.onPrimaryContainer, ), ), const Gap(2), Text( subtitle, style: theme.textTheme.bodySmall?.copyWith( color: theme.colorScheme.onPrimaryContainer.withOpacity(0.7), ), ), ], ), ), Icon( FluentIcons.chevron_right_24_regular, color: theme.colorScheme.onPrimaryContainer.withOpacity(0.5), ), ], ), ), ), ), ), ); } } // Компактная кнопка для дополнительных опций class _CompactButton extends StatelessWidget { const _CompactButton({ super.key, required this.label, required this.icon, required this.color, required this.onTap, }); final String label; final IconData icon; final Color color; final VoidCallback onTap; @override Widget build(BuildContext context) { final theme = Theme.of(context); return Semantics( button: true, child: Material( elevation: 2, color: theme.colorScheme.surface, surfaceTintColor: theme.colorScheme.surfaceTint, shadowColor: Colors.transparent, borderRadius: BorderRadius.circular(12), clipBehavior: Clip.antiAlias, child: InkWell( onTap: onTap, child: Container( height: 56, padding: const EdgeInsets.symmetric(horizontal: 16), child: Row( children: [ Container( width: 40, height: 40, decoration: BoxDecoration( color: color.withOpacity(0.1), borderRadius: BorderRadius.circular(10), ), child: Icon( icon, size: 20, color: color, ), ), const Gap(12), Expanded( child: Text( label, style: theme.textTheme.titleSmall?.copyWith( fontWeight: FontWeight.w600, color: color, ), ), ), Icon( FluentIcons.add_24_regular, size: 20, color: color.withOpacity(0.5), ), ], ), ), ), ), ); } } class _Button extends StatelessWidget { const _Button({ super.key, required this.label, required this.icon, required this.size, required this.onTap, }); final String label; final IconData icon; final double size; final VoidCallback onTap; @override Widget build(BuildContext context) { final theme = Theme.of(context); final color = theme.colorScheme.primary; return Semantics( button: true, child: SizedBox( width: size, height: size, child: Material( elevation: 8, color: theme.colorScheme.surface, surfaceTintColor: theme.colorScheme.surfaceTint, shadowColor: Colors.transparent, borderRadius: BorderRadius.circular(8), clipBehavior: Clip.antiAlias, child: InkWell( onTap: onTap, child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon( icon, size: size / 3, color: color, ), const Gap(16), Flexible( child: Text( label, style: theme.textTheme.labelLarge?.copyWith(color: color), textAlign: TextAlign.center, ), ), ], ), ), ), ), ); } }