Initial connection adjudication screen

codemagic-setup
Hank Grabowski 2022-12-19 13:59:33 -05:00
rodzic fbab2dcae1
commit c3ee438f1c
5 zmienionych plików z 287 dodań i 18 usunięć

Wyświetl plik

@ -54,12 +54,10 @@ class NotificationControl extends StatelessWidget {
onTap: () async {
switch (notification.type) {
case NotificationType.follow:
buildSnackbar(
context, 'Want to follow ${notification.fromName}?');
context.push('/connect/${notification.fromId}');
break;
case NotificationType.follow_request:
buildSnackbar(
context, 'Want to accept follow ${notification.fromName}?');
context.push('/connect/${notification.fromId}');
break;
case NotificationType.unknown:
buildSnackbar(context, 'Unknown message type, nothing to do');

Wyświetl plik

@ -336,15 +336,55 @@ class FriendicaClient {
});
}
FutureResult<Connection, ExecError> acceptFollow(
Connection connection) async {
final id = connection.id;
final url =
Uri.parse('https://$serverName/api/v1/follow_requests/$id/authorize');
final result =
await _postUrl(url, {}).andThenSuccessAsync((jsonString) async {
return _updateConnectionFromFollowRequestResult(connection, jsonString);
});
return result.mapError((error) => error is ExecError
? error
: ExecError(type: ErrorType.localError, message: error.toString()));
}
FutureResult<Connection, ExecError> rejectFollow(
Connection connection) async {
final id = connection.id;
final url =
Uri.parse('https://$serverName/api/v1/follow_requests/$id/reject');
final result =
await _postUrl(url, {}).andThenSuccessAsync((jsonString) async {
return _updateConnectionFromFollowRequestResult(connection, jsonString);
});
return result.mapError((error) => error is ExecError
? error
: ExecError(type: ErrorType.localError, message: error.toString()));
}
FutureResult<Connection, ExecError> ignoreFollow(
Connection connection) async {
final id = connection.id;
final url =
Uri.parse('https://$serverName/api/v1/follow_requests/$id/ignore');
final result =
await _postUrl(url, {}).andThenSuccessAsync((jsonString) async {
return _updateConnectionFromFollowRequestResult(connection, jsonString);
});
return result.mapError((error) => error is ExecError
? error
: ExecError(type: ErrorType.localError, message: error.toString()));
}
FutureResult<Connection, ExecError> followConnection(
Connection connection) async {
final id = connection.id;
final url = Uri.parse('https://$serverName/api/v1/accounts/$id/follow');
final result = await _postUrl(url, {}).andThenSuccessAsync((_) async {
final newStatus = connection.status == ConnectionStatus.theyFollowYou
? ConnectionStatus.mutual
: ConnectionStatus.youFollowThem;
return connection.copy(status: newStatus);
final result =
await _postUrl(url, {}).andThenSuccessAsync((jsonString) async {
return _updateConnectionFromFollowRequestResult(connection, jsonString);
});
return result.mapError((error) => error is ExecError
? error
@ -355,11 +395,9 @@ class FriendicaClient {
Connection connection) async {
final id = connection.id;
final url = Uri.parse('https://$serverName/api/v1/accounts/$id/unfollow');
final result = await _postUrl(url, {}).andThenSuccessAsync((_) async {
final newStatus = connection.status == ConnectionStatus.mutual
? ConnectionStatus.theyFollowYou
: ConnectionStatus.none;
return connection.copy(status: newStatus);
final result =
await _postUrl(url, {}).andThenSuccessAsync((jsonString) async {
return _updateConnectionFromFollowRequestResult(connection, jsonString);
});
return result.mapError((error) => error is ExecError
? error
@ -503,4 +541,22 @@ class FriendicaClient {
return pagingData;
}
Connection _updateConnectionFromFollowRequestResult(
Connection connection, String jsonString) {
final json = jsonDecode(jsonString) as Map<String, dynamic>;
final theyFollowYou = json['followed_by'] ?? 'false';
final youFollowThem = json['following'] ?? 'false';
late final ConnectionStatus newStatus;
if (theyFollowYou && youFollowThem) {
newStatus = ConnectionStatus.mutual;
} else if (theyFollowYou) {
newStatus = ConnectionStatus.theyFollowYou;
} else if (youFollowThem) {
newStatus = ConnectionStatus.youFollowThem;
} else {
newStatus = ConnectionStatus.none;
}
return connection.copy(status: newStatus);
}
}

Wyświetl plik

@ -3,6 +3,7 @@ import 'package:go_router/go_router.dart';
import 'globals.dart';
import 'screens/contacts_screen.dart';
import 'screens/editor.dart';
import 'screens/follow_request_adjudication_screen.dart';
import 'screens/home.dart';
import 'screens/notifications_screen.dart';
import 'screens/post_screen.dart';
@ -14,6 +15,7 @@ import 'screens/user_profile_screen.dart';
import 'services/auth_service.dart';
class ScreenPaths {
static String connectHandle = '/connect';
static String contacts = '/contacts';
static String splash = '/splash';
static String timelines = '/';
@ -68,6 +70,12 @@ final appRouter = GoRouter(
child: ContactsScreen(),
),
),
GoRoute(
path: '/connect/:id',
name: ScreenPaths.connectHandle,
builder: (context, state) =>
FollowRequestAdjudicationScreen(userId: state.params['id']!),
),
GoRoute(
path: ScreenPaths.timelines,
name: ScreenPaths.timelines,

Wyświetl plik

@ -1,16 +1,164 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:provider/provider.dart';
import '../controls/padding.dart';
import '../models/connection.dart';
import '../services/connections_manager.dart';
class FollowRequestAdjudicationScreen extends StatefulWidget {
final String userId;
const FollowRequestAdjudicationScreen({super.key, required this.userId});
@override
State<FollowRequestAdjudicationScreen> createState() =>
_FollowRequestAdjudicationScreenState();
}
class _FollowRequestAdjudicationScreenState
extends State<FollowRequestAdjudicationScreen> {
var processing = false;
class FollowRequestAdjudicationScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
// with ID, get contact
final manager = context.watch<ConnectionsManager>();
final connResult = manager.getById(widget.userId);
late final Widget body;
if (connResult.isFailure) {
body = Text('Error getting contact information: ${connResult.error}');
}
final contact = connResult.value;
switch (contact.status) {
case ConnectionStatus.mutual:
case ConnectionStatus.theyFollowYou:
case ConnectionStatus.youFollowThem:
case ConnectionStatus.none:
body = _buildMainPanel(context, manager, contact);
break;
case ConnectionStatus.you:
case ConnectionStatus.unknown:
body = Text('Invalid state, nothing to do here: ${contact.status}');
break;
}
return Scaffold(
appBar: AppBar(
title: const Text(
'Accept Request?',
)),
body: Padding(
padding: const EdgeInsets.all(8.0),
child: Center(child: body),
),
);
}
Widget _buildMainPanel(
BuildContext context, ConnectionsManager manager, Connection contact) {
// Options are:
// Accept and follow back
// Accept and don't follow back
// Reject
// Back with no action
// Calling method should check if completed (true) or not (false) to decide if updating their view of that item
// TODO: implement build
throw UnimplementedError();
return Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
CachedNetworkImage(imageUrl: contact.avatarUrl.toString()),
Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
contact.name,
style: Theme.of(context).textTheme.titleLarge,
),
const HorizontalPadding(),
],
),
const VerticalPadding(),
ElevatedButton(
onPressed: processing
? null
: () async => await accept(manager, contact, true),
child: const Text('Accept and follow back'),
),
const VerticalPadding(),
ElevatedButton(
onPressed:
processing ? null : () async => accept(manager, contact, false),
child: const Text("Accept but don't follow back"),
),
const VerticalPadding(),
ElevatedButton(
onPressed: processing ? null : () async => reject(manager, contact),
child: const Text('Reject'),
),
const VerticalPadding(),
ElevatedButton(
onPressed: processing ? null : () async => ignore(manager, contact),
child: const Text('Ignore (Rejects but user cannot ask again)'),
),
],
);
}
Future<void> accept(
ConnectionsManager manager,
Connection contact,
bool followBack,
) async {
setState(() {
processing = true;
});
await manager.acceptFollowRequest(contact);
if (followBack) {
await manager.follow(contact);
}
setState(() {
processing = false;
});
if (mounted && context.canPop()) {
context.pop();
}
}
Future<void> reject(ConnectionsManager manager, Connection contact) async {
setState(() {
processing = true;
});
await manager.rejectFollowRequest(contact);
setState(() {
processing = false;
});
if (mounted && context.canPop()) {
context.pop();
}
}
Future<void> ignore(ConnectionsManager manager, Connection contact) async {
setState(() {
processing = true;
});
await manager.ignoreFollowRequest(contact);
setState(() {
processing = false;
});
if (mounted && context.canPop()) {
context.pop();
}
}
}

Wyświetl plik

@ -27,6 +27,8 @@ class ConnectionsManager extends ChangeNotifier {
_connectionsByName.clear();
_connectionsByProfileUrl.clear();
_groupsForConnection.clear();
_myGroups.clear();
_myContacts.clear();
}
bool addConnection(Connection connection) {
@ -71,6 +73,63 @@ class ConnectionsManager extends ChangeNotifier {
return result;
}
Future<void> acceptFollowRequest(Connection connection) async {
_logger.finest(
'Attempting to accept follow request ${connection.name}: ${connection.status}');
await getIt<AuthService>()
.currentClient
.andThenAsync((client) => client.acceptFollow(connection))
.match(
onSuccess: (update) {
_logger
.finest('Successfully followed ${update.name}: ${update.status}');
updateConnection(update);
notifyListeners();
},
onError: (error) {
_logger.severe('Error following ${connection.name}');
},
);
}
Future<void> rejectFollowRequest(Connection connection) async {
_logger.finest(
'Attempting to accept follow request ${connection.name}: ${connection.status}');
await getIt<AuthService>()
.currentClient
.andThenAsync((client) => client.rejectFollow(connection))
.match(
onSuccess: (update) {
_logger
.finest('Successfully followed ${update.name}: ${update.status}');
updateConnection(update);
notifyListeners();
},
onError: (error) {
_logger.severe('Error following ${connection.name}');
},
);
}
Future<void> ignoreFollowRequest(Connection connection) async {
_logger.finest(
'Attempting to accept follow request ${connection.name}: ${connection.status}');
await getIt<AuthService>()
.currentClient
.andThenAsync((client) => client.ignoreFollow(connection))
.match(
onSuccess: (update) {
_logger
.finest('Successfully followed ${update.name}: ${update.status}');
updateConnection(update);
notifyListeners();
},
onError: (error) {
_logger.severe('Error following ${connection.name}');
},
);
}
Future<void> follow(Connection connection) async {
_logger.finest(
'Attempting to follow ${connection.name}: ${connection.status}');