relatica/lib/screens/follow_request_adjudication...

296 wiersze
8.9 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:result_monad/result_monad.dart';
import '../controls/html_text_viewer_control.dart';
import '../controls/login_aware_cached_network_image.dart';
import '../controls/padding.dart';
import '../models/auth/profile.dart';
import '../models/connection.dart';
import '../models/exec_error.dart';
import '../riverpod_controllers/account_services.dart';
import '../riverpod_controllers/connection_manager_services.dart';
import '../riverpod_controllers/feature_checker_services.dart';
import '../riverpod_controllers/follow_requests_services.dart';
import '../riverpod_controllers/networking/network_status_services.dart';
import '../riverpod_controllers/notification_services.dart';
import '../routes.dart';
import '../utils/url_opening_utils.dart';
class FollowRequestAdjudicationScreen extends ConsumerStatefulWidget {
final String userId;
const FollowRequestAdjudicationScreen({super.key, required this.userId});
@override
ConsumerState<FollowRequestAdjudicationScreen> createState() =>
_FollowRequestAdjudicationScreenState();
}
class _FollowRequestAdjudicationScreenState
extends ConsumerState<FollowRequestAdjudicationScreen> {
var processing = false;
@override
Widget build(BuildContext context) {
final profile = ref.watch(activeProfileProvider);
final loading = ref.watch(followRequestsLoadingProvider(profile)) ||
ref.watch(connectionsLoadingProvider(profile));
late final Result<Connection, ExecError> result;
if (ref.read(featureCheckProvider(
profile, RelaticaFeatures.usingActualFollowRequests))) {
result = ref
.watch(followRequestByIdProvider(profile, widget.userId))
.mapValue((request) => request.connection);
} else {
result = ref.watch(connectionByIdProvider(
profile,
widget.userId,
forceUpdate: true,
));
}
late final Widget body;
if (result.isFailure) {
if (result.error.type == ErrorType.notFound && loading) {
ref.read(followRequestsProvider(profile).notifier).update();
body = const Text('Loading...');
} else {
body = Text('Error getting request info: ${result.error}');
}
} else {
final contact = result.value;
final contactStatus = ref
.watch(
connectionByIdProvider(
profile,
contact.id,
forceUpdate: true,
),
)
.transform((c) => c.status)
.getValueOrElse(() => ConnectionStatus.unknown);
switch (contactStatus) {
case ConnectionStatus.theyFollowYou:
case ConnectionStatus.youFollowThem:
case ConnectionStatus.unknown:
case ConnectionStatus.none:
body = _buildMainPanel(context, contact, profile);
break;
case ConnectionStatus.mutual:
body = const Text('Already allowed them to connect');
break;
case ConnectionStatus.you:
body = Text(loading
? 'Loading...'
: 'Invalid state, nothing to do here: ${contact.status}');
break;
case ConnectionStatus.blocked:
// we should never get here because a blocked user shouldn't be allowed to create a connection request.
body = const Text(
'User is blocked. Unblock to accept connection request.',
);
break;
}
}
return Scaffold(
appBar: AppBar(
title: const Text(
'Accept Request?',
),
leading: processing
? const IconButton(onPressed: null, icon: Icon(Icons.arrow_back))
: null,
),
body: Padding(
padding: const EdgeInsets.all(8.0),
child: Center(child: body),
),
);
}
Widget _buildMainPanel(
BuildContext context,
Connection contact,
Profile profile,
) {
return SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
LoginAwareCachedNetworkImage(imageUrl: contact.avatarUrl.toString()),
Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'${contact.name}(${contact.handle})',
style: Theme.of(context).textTheme.titleLarge,
),
const HorizontalPadding(),
],
),
const VerticalPadding(),
Wrap(
runSpacing: 5.0,
spacing: 5.0,
alignment: WrapAlignment.center,
children: [
ElevatedButton(
onPressed: processing
? null
: () async => await accept(profile, contact, true),
child: const Text('Accept and follow back'),
),
ElevatedButton(
onPressed: processing
? null
: () async => accept(profile, contact, false),
child: const Text("Accept but don't follow back"),
),
ElevatedButton(
onPressed:
processing ? null : () async => reject(profile, contact),
child: const Text('Reject'),
),
ElevatedButton(
onPressed:
processing ? null : () async => ignore(profile, contact),
child: const Text('Ignore (Rejects but user cannot ask again)'),
),
],
),
const VerticalPadding(),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton(
onPressed: processing
? null
: () => context.pushNamed(
ScreenPaths.userPosts,
pathParameters: {'id': contact.id},
),
child: const Text('Posts')),
ElevatedButton(
onPressed: processing
? null
: () async => await openProfileExternal(context, contact),
child: const Text('Open In Browser'),
),
],
),
const VerticalPadding(),
HtmlTextViewerControl(
content: contact.note,
onTapUrl: (url) async {
return await openUrlStringInSystembrowser(context, url, 'link');
},
),
const VerticalPadding(),
Text(
'#Followers: ${contact.followerCount} followers, #Following, ${contact.followingCount}, #Statuses: ${contact.statusesCount}'),
const VerticalPadding(),
Text('Last Status: ${contact.lastStatus ?? "Unknown"}'),
],
),
);
}
Future<void> accept(
Profile profile,
Connection contact,
bool followBack,
) async {
setState(() {
processing = true;
});
await ref
.read(connectionModifierProvider(profile, contact).notifier)
.acceptFollowRequest();
if (followBack) {
ref
.read(notificationsManagerProvider(profile).notifier)
.refreshNotifications();
await ref
.read(connectionModifierProvider(profile, contact).notifier)
.follow();
}
_performUpdates(profile);
setState(() {
processing = false;
});
if (mounted) {
ref
.read(notificationsManagerProvider(profile).notifier)
.refreshNotifications();
context
.goNamed(ScreenPaths.userProfile, pathParameters: {'id': contact.id});
}
}
Future<void> reject(
Profile profile,
Connection contact,
) async {
setState(() {
processing = true;
});
await ref
.read(connectionModifierProvider(profile, contact).notifier)
.rejectFollowRequest();
_performUpdates(profile);
setState(() {
processing = false;
});
if (mounted && context.canPop()) {
ref
.read(notificationsManagerProvider(profile).notifier)
.refreshConnectionRequestNotifications();
context.pop();
}
}
Future<void> ignore(
Profile profile,
Connection contact,
) async {
setState(() {
processing = true;
});
await ref
.read(connectionModifierProvider(profile, contact).notifier)
.ignoreFollowRequest();
_performUpdates(profile);
setState(() {
processing = false;
});
if (mounted && context.canPop()) {
ref
.read(notificationsManagerProvider(profile).notifier)
.refreshConnectionRequestNotifications();
context.pop();
}
}
void _performUpdates(Profile profile) {
ref.read(followRequestsProvider(profile).notifier).update();
ref
.read(notificationsManagerProvider(profile).notifier)
.refreshNotifications();
}
}