import 'package:auto_route/auto_route.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:gap/gap.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:island/models/auth.dart'; import 'package:island/pods/network.dart'; import 'package:island/screens/account/me/settings.dart'; import 'package:island/screens/auth/oidc.native.dart'; import 'package:island/services/time.dart'; import 'package:island/widgets/alert.dart'; import 'package:island/widgets/content/sheet.dart'; import 'package:island/widgets/response.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:sign_in_with_apple/sign_in_with_apple.dart'; import 'package:styled_widget/styled_widget.dart'; // Helper function to get provider icon and localized name IconData getProviderIcon(String provider) { switch (provider.toLowerCase()) { case 'apple': return Icons.apple; case 'microsoft': return Symbols.window; // Microsoft icon alternative case 'google': return Symbols.g_translate; // Google icon alternative case 'github': return Symbols.code; // GitHub icon case 'discord': return Symbols.forum; // Discord icon alternative default: return Symbols.link; } } String getLocalizedProviderName(String provider) { switch (provider.toLowerCase()) { case 'apple': return 'accountConnectionProviderApple'.tr(); case 'microsoft': return 'accountConnectionProviderMicrosoft'.tr(); case 'google': return 'accountConnectionProviderGoogle'.tr(); case 'github': return 'accountConnectionProviderGithub'.tr(); case 'discord': return 'accountConnectionProviderDiscord'.tr(); default: return provider; } } class AccountConnectionSheet extends HookConsumerWidget { final SnAccountConnection connection; const AccountConnectionSheet({super.key, required this.connection}); @override Widget build(BuildContext context, WidgetRef ref) { Future deleteConnection() async { final confirm = await showConfirmAlert( 'accountConnectionDeleteHint'.tr(), 'accountConnectionDelete'.tr(), ); if (!confirm || !context.mounted) return; try { showLoadingModal(context); final client = ref.read(apiClientProvider); await client.delete('/accounts/me/connections/${connection.id}'); if (context.mounted) Navigator.pop(context, true); } catch (err) { showErrorAlert(err); } finally { if (context.mounted) hideLoadingModal(context); } } return SheetScaffold( titleText: 'accountConnections'.tr(), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(getProviderIcon(connection.provider), size: 32), const Gap(8), Text(getLocalizedProviderName(connection.provider)).tr(), const Gap(4), Text( connection.providedIdentifier, style: Theme.of(context).textTheme.bodySmall, ), const Gap(8), Text( connection.lastUsedAt.formatSystem(), style: Theme.of(context).textTheme.bodySmall, ).opacity(0.85), ], ).padding(all: 20), const Divider(height: 1), ListTile( leading: const Icon(Symbols.delete), title: Text('accountConnectionDelete').tr(), onTap: deleteConnection, contentPadding: const EdgeInsets.symmetric(horizontal: 20), ), ], ), ); } } class AccountConnectionNewSheet extends HookConsumerWidget { const AccountConnectionNewSheet({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { final selectedProvider = useState('apple'); // List of available providers final providers = ['apple', 'microsoft', 'google', 'github', 'discord']; Future addConnection() async { final client = ref.watch(apiClientProvider); switch (selectedProvider.value.toLowerCase()) { case 'apple': try { final credential = await SignInWithApple.getAppleIDCredential( scopes: [AppleIDAuthorizationScopes.email], webAuthenticationOptions: WebAuthenticationOptions( clientId: 'dev.solsynth.solarpass', redirectUri: Uri.parse( 'https://nt.solian.app/auth/callback/apple', ), ), ); if (context.mounted) showLoadingModal(context); await client.post( '/auth/connect/apple/mobile', data: { 'identity_token': credential.identityToken!, 'authorization_code': credential.authorizationCode, }, ); if (context.mounted) { showSnackBar(context, 'accountConnectionAddSuccess'.tr()); Navigator.pop(context, true); } } catch (err) { if (err is SignInWithAppleAuthorizationException) return; showErrorAlert(err); } finally { if (context.mounted) hideLoadingModal(context); } case 'microsoft': case 'google': case 'github': case 'discord': final token = await Navigator.of(context).push( MaterialPageRoute( builder: (context) => OidcScreen( provider: selectedProvider.value.toLowerCase(), ), ), ); print(token); break; default: showSnackBar(context, 'accountConnectionAddError'.tr()); return; } } return SheetScaffold( titleText: 'accountConnectionAdd'.tr(), child: Column( spacing: 16, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ DropdownButtonFormField( value: selectedProvider.value, decoration: InputDecoration( prefixIcon: Icon(getProviderIcon(selectedProvider.value)), labelText: 'accountConnectionProvider'.tr(), border: const OutlineInputBorder(), ), items: providers.map((String provider) { return DropdownMenuItem( value: provider, child: Row( children: [Text(getLocalizedProviderName(provider)).tr()], ), ); }).toList(), onChanged: (String? newValue) { if (newValue != null) { selectedProvider.value = newValue; } }, ), Padding( padding: const EdgeInsets.symmetric(horizontal: 16.0), child: Text('accountConnectionDescription'.tr()), ), Row( mainAxisAlignment: MainAxisAlignment.end, children: [ TextButton.icon( onPressed: addConnection, icon: const Icon(Symbols.add), label: Text('next').tr(), ), ], ), ], ).padding(horizontal: 20, vertical: 24), ); } } class AccountConnectionsSheet extends HookConsumerWidget { const AccountConnectionsSheet({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { final connections = ref.watch(accountConnectionsProvider); return SheetScaffold( titleText: 'accountConnections'.tr(), actions: [ IconButton( icon: const Icon(Symbols.add), onPressed: () async { final result = await showModalBottomSheet( context: context, isScrollControlled: true, builder: (context) => const AccountConnectionNewSheet(), ); if (result == true) { ref.invalidate(accountConnectionsProvider); } }, ), ], child: connections.when( data: (data) => RefreshIndicator( onRefresh: () => Future.sync( () => ref.invalidate(accountConnectionsProvider), ), child: data.isEmpty ? Center( child: Text( 'accountConnectionsEmpty'.tr(), textAlign: TextAlign.center, ).padding(horizontal: 32), ) : ListView.builder( padding: EdgeInsets.zero, itemCount: data.length, itemBuilder: (context, index) { final connection = data[index]; return Dismissible( key: Key('connection-${connection.id}'), direction: DismissDirection.endToStart, background: Container( color: Colors.red, alignment: Alignment.centerRight, padding: const EdgeInsets.symmetric( horizontal: 20, ), child: const Icon( Icons.delete, color: Colors.white, ), ), confirmDismiss: (direction) async { final confirm = await showConfirmAlert( 'accountConnectionDeleteHint'.tr(), 'accountConnectionDelete'.tr(), ); if (confirm && context.mounted) { try { final client = ref.read(apiClientProvider); await client.delete( '/accounts/me/connections/${connection.id}', ); ref.invalidate(accountConnectionsProvider); return true; } catch (err) { showErrorAlert(err); return false; } } return false; }, child: ListTile( leading: Icon( getProviderIcon(connection.provider), ), title: Text( getLocalizedProviderName( connection.provider, ), ).tr(), subtitle: Text(connection.providedIdentifier), trailing: Text( DateFormat.yMd().format( connection.lastUsedAt.toLocal(), ), style: Theme.of(context).textTheme.bodySmall, ), onTap: () async { final result = await showModalBottomSheet( context: context, isScrollControlled: true, builder: (context) => AccountConnectionSheet( connection: connection, ), ); if (result == true) { ref.invalidate(accountConnectionsProvider); } }, ), ); }, ), ), error: (err, _) => ResponseErrorWidget( error: err, onRetry: () => ref.invalidate(accountConnectionsProvider), ), loading: () => const ResponseLoadingWidget(), ), ); } }