import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import '../controls/html_text_viewer_control.dart'; import '../controls/login_aware_cached_network_image.dart'; import '../controls/padding.dart'; import '../globals.dart'; import '../models/auth/profile.dart'; import '../models/connection.dart'; import '../models/timeline_grouping_list_data.dart'; import '../riverpod_controllers/account_services.dart'; import '../riverpod_controllers/blocks_services.dart'; import '../riverpod_controllers/circles_repo_services.dart'; import '../riverpod_controllers/connection_manager_services.dart'; import '../routes.dart'; import '../utils/snackbar_builder.dart'; import '../utils/url_opening_utils.dart'; class UserProfileScreen extends ConsumerStatefulWidget { final String userId; const UserProfileScreen({super.key, required this.userId}); @override ConsumerState<UserProfileScreen> createState() => _UserProfileScreenState(); } class _UserProfileScreenState extends ConsumerState<UserProfileScreen> { var isUpdating = false; @override void initState() { super.initState(); final profile = ref.read(activeProfileProvider); ref.read(connectionByIdProvider(profile, widget.userId, forceUpdate: true)); } @override Widget build(BuildContext context) { final profile = ref.watch(activeProfileProvider); ref.watch(blocksManagerProvider(profile)); final body = ref.watch(connectionByIdProvider(profile, widget.userId)).fold( onSuccess: (connectionProfile) { final notMyProfile = profile.userId != connectionProfile.id; return RefreshIndicator( onRefresh: () async { await ref .read(blocksManagerProvider(profile).notifier) .updateBlock(connectionProfile); setState(() {}); }, child: SingleChildScrollView( physics: const AlwaysScrollableScrollPhysics(), child: Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.center, children: [ LoginAwareCachedNetworkImage( imageUrl: connectionProfile.avatarUrl.toString()), const Row( crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center, children: [], ), Text( notMyProfile ? '${connectionProfile.name} (${connectionProfile.handle})' : '${connectionProfile.name} (Your Account)', softWrap: true, textAlign: TextAlign.center, style: Theme.of(context).textTheme.titleLarge, ), const VerticalPadding(), if (connectionProfile.status != ConnectionStatus.unknown) Text('( ${connectionProfile.status.label()} )'), const VerticalPadding(), Wrap( spacing: 10.0, runSpacing: 10.0, children: [ ElevatedButton( onPressed: () => context.pushNamed( ScreenPaths.userPosts, pathParameters: {'id': connectionProfile.id}, ), child: const Text('Posts'), ), ElevatedButton( onPressed: () => context.pushNamed( ScreenPaths.userPostsAndComments, pathParameters: {'id': connectionProfile.id}, ), child: const Text('Posts & Comments'), ), ElevatedButton( onPressed: () => context.pushNamed( ScreenPaths.userMedia, pathParameters: {'id': connectionProfile.id}, ), child: const Text('Media'), ), ElevatedButton( onPressed: () async => await openProfileExternal(context, connectionProfile), child: const Text('Open In Browser'), ), ], ), const VerticalPadding(), Wrap( spacing: 10.0, runSpacing: 10.0, children: [ if (notMyProfile) ...[ buildConnectionStatusToggle( context, profile, connectionProfile, ), buildBlockToggle(context, profile, connectionProfile), ], ], ), const VerticalPadding(), HtmlTextViewerControl( content: connectionProfile.note, onTapUrl: (url) async { return await openUrlStringInSystembrowser( context, url, 'link'); }, ), const VerticalPadding(), Text( '#Followers: ${connectionProfile.followerCount} followers, #Following, ${connectionProfile.followingCount}, #Statuses: ${connectionProfile.statusesCount}'), const VerticalPadding(), Text('Last Status: ${connectionProfile.lastStatus ?? "Unknown"}'), const VerticalPadding(), if (connectionProfile.status == ConnectionStatus.mutual || connectionProfile.status == ConnectionStatus.youFollowThem) buildCircles(context, profile, connectionProfile), ], ), ), ); }, onError: (error) { return Text('Error getting profile: $error'); }); return Scaffold( appBar: AppBar( title: const Text('Profile'), leading: context.canPop() ? null : IconButton( onPressed: () => context.go(ScreenPaths.timelines), icon: const Icon(Icons.arrow_back), ), ), body: Padding( padding: const EdgeInsets.all(8.0), child: Center( child: body, ), ), ); } Widget buildCircles( BuildContext context, Profile profile, Connection connectionProfile, ) { final myCircles = ref.watch(timelineGroupingListProvider(profile, GroupingType.circle)); final usersCircles = ref .watch(circlesProvider(profile).notifier) .getCirclesForUser(connectionProfile.id) .fold( onSuccess: (circles) => circles.toSet(), onError: (error) { buildSnackbar(context, 'Error getting circle data: $error'); return <TimelineGroupingListData>{}; }); if (myCircles.isNotEmpty) { myCircles.sort((g1, g2) => g1.name.compareTo(g2.name)); } final circlesWidgets = myCircles.map((g) { return CheckboxListTile( title: Text(g.name), value: usersCircles.contains(g), onChanged: isUpdating ? null : (bool? value) async { if (isUpdating) { return; } final isAdding = value == true; final confirm = await showYesNoDialog( context, isAdding ? 'Add user to ${g.name}' : 'Remove user from ${g.name}'); if (confirm != true) { return; } setState(() { isUpdating = true; }); if (isAdding) { await ref .watch(circlesProvider(profile).notifier) .addConnectionToCircle(g, connectionProfile); } else { await ref .watch(circlesProvider(profile).notifier) .removeConnectionFromCircle(g, connectionProfile); } if (context.mounted) { buildSnackbar(context, "User's Circles Updated"); } setState(() { isUpdating = false; }); }, ); }).toList(); return Column( children: [ Text( 'Circles: ', style: Theme.of(context).textTheme.titleMedium, ), const VerticalPadding(), ...circlesWidgets, ], ); } Widget buildBlockToggle( BuildContext context, Profile profile, Connection connection, ) { late Widget blockToggleButton; switch (connection.status) { case ConnectionStatus.blocked: blockToggleButton = ElevatedButton( onPressed: isUpdating ? null : () async { final confirm = await showYesNoDialog( context, 'Unblock ${connection.name}'); if (confirm != true) { return; } setState(() { isUpdating = true; }); await ref .read(blocksManagerProvider(profile).notifier) .unblockConnection(connection); setState(() { isUpdating = false; }); }, child: const Text('Unblock'), ); break; case ConnectionStatus.mutual: case ConnectionStatus.theyFollowYou: case ConnectionStatus.youFollowThem: case ConnectionStatus.none: case ConnectionStatus.unknown: blockToggleButton = ElevatedButton( onPressed: isUpdating ? null : () async { final confirm = await showYesNoDialog( context, 'Block ${connection.name}'); if (confirm != true) { return; } setState(() { isUpdating = true; }); await ref .read(blocksManagerProvider(profile).notifier) .blockConnection(connection); setState(() { isUpdating = false; }); }, child: const Text('Block'), ); break; case ConnectionStatus.you: blockToggleButton = const SizedBox(); break; } return blockToggleButton; } Widget buildConnectionStatusToggle( BuildContext context, Profile profile, Connection connectionProfile, ) { late Widget followToggleButton; switch (connectionProfile.status) { case ConnectionStatus.mutual: case ConnectionStatus.youFollowThem: followToggleButton = ElevatedButton( onPressed: isUpdating ? null : () async { final confirm = await showYesNoDialog( context, 'Unfollow ${connectionProfile.name}'); if (confirm != true) { return; } setState(() { isUpdating = true; }); await ref .read( connectionModifierProvider(profile, connectionProfile) .notifier) .unfollow(); setState(() { isUpdating = false; }); }, child: const Text('Unfollow'), ); break; case ConnectionStatus.theyFollowYou: case ConnectionStatus.none: followToggleButton = ElevatedButton( onPressed: isUpdating ? null : () async { final confirm = await showYesNoDialog( context, 'Follow ${connectionProfile.name}'); if (confirm != true) { return; } setState(() { isUpdating = true; }); await ref .read( connectionModifierProvider(profile, connectionProfile) .notifier) .follow(); setState(() { isUpdating = false; }); }, child: const Text('Follow'), ); break; case ConnectionStatus.blocked: case ConnectionStatus.you: case ConnectionStatus.unknown: followToggleButton = const SizedBox(); break; } return followToggleButton; } }