Add initial contacts screen

codemagic-setup
Hank Grabowski 2022-12-14 22:27:30 -05:00
rodzic b862360f99
commit 93d08dcf82
5 zmienionych plików z 201 dodań i 11 usunięć

Wyświetl plik

@ -44,7 +44,7 @@ class AppBottomNavBar extends StatelessWidget {
// TODO: Handle this case.
break;
case NavBarButtons.contacts:
// TODO: Handle this case.
context.pushNamed(ScreenPaths.contacts);
break;
case NavBarButtons.profile:
context.pushNamed(ScreenPaths.profile);

Wyświetl plik

@ -13,6 +13,7 @@ import 'models/group_data.dart';
import 'models/timeline_entry.dart';
import 'models/user_notification.dart';
import 'serializers/friendica/connection_friendica_extensions.dart';
import 'serializers/mastodon/connection_mastodon_extensions.dart';
import 'serializers/mastodon/group_data_mastodon_extensions.dart';
import 'serializers/mastodon/notification_mastodon_extension.dart';
import 'serializers/mastodon/timeline_entry_mastodon_extensions.dart';
@ -109,6 +110,36 @@ class FriendicaClient {
return (await _deleteUrl(request, requestData)).mapValue((_) => true);
}
FutureResult<List<Connection>, ExecError> getMyFollowing(
{int sinceId = -1, int maxId = -1, int limit = 50}) async {
_logger.finest(() =>
'Getting following data since $sinceId, maxId $maxId, limit $limit');
final myId = getIt<AuthService>().currentId;
final paging =
_buildPagingData(sinceId: sinceId, maxId: maxId, limit: limit);
final baseUrl = 'https://$serverName/api/v1/accounts/$myId';
return (await _getApiListRequest(Uri.parse('$baseUrl/following&$paging'))
.andThenSuccessAsync((listJson) async => listJson
.map((json) => ConnectionMastodonExtensions.fromJson(json))
.toList()))
.execErrorCast();
}
FutureResult<List<Connection>, ExecError> getMyFollowers(
{int sinceId = -1, int maxId = -1, int limit = 50}) async {
_logger.finest(() =>
'Getting followers data since $sinceId, maxId $maxId, limit $limit');
final myId = getIt<AuthService>().currentId;
final paging =
_buildPagingData(sinceId: sinceId, maxId: maxId, limit: limit);
final baseUrl = 'https://$serverName/api/v1/accounts/$myId';
return (await _getApiListRequest(Uri.parse('$baseUrl/followers&$paging'))
.andThenSuccessAsync((listJson) async => listJson
.map((json) => ConnectionMastodonExtensions.fromJson(json))
.toList()))
.execErrorCast();
}
FutureResult<Connection, ExecError> getConnectionWithStatus(
Connection connection) async {
_logger.finest(() => 'Getting group (Mastodon List) data');
@ -179,14 +210,8 @@ class FriendicaClient {
final String timelinePath = _typeToTimelinePath(type);
final String timelineQPs = _typeToTimelineQueryParameters(type);
final baseUrl = 'https://$serverName/api/v1/$timelinePath';
var pagingData = 'limit=$limit';
if (maxId > 0) {
pagingData = '$pagingData&max_id=$maxId';
}
if (sinceId > 0) {
pagingData = '&since_id=$sinceId';
}
final pagingData =
_buildPagingData(sinceId: sinceId, maxId: maxId, limit: limit);
final url = '$baseUrl?exclude_replies=true&$pagingData&$timelineQPs';
final request = Uri.parse(url);
@ -464,4 +489,18 @@ class FriendicaClient {
throw UnimplementedError('These types are not supported yet');
}
}
String _buildPagingData(
{required int sinceId, required int maxId, required int limit}) {
var pagingData = 'limit=$limit';
if (maxId > 0) {
pagingData = '$pagingData&max_id=$maxId';
}
if (sinceId > 0) {
pagingData = '&since_id=$sinceId';
}
return pagingData;
}
}

Wyświetl plik

@ -1,6 +1,7 @@
import 'package:go_router/go_router.dart';
import 'globals.dart';
import 'screens/contacts_screen.dart';
import 'screens/editor.dart';
import 'screens/home.dart';
import 'screens/notifications_screen.dart';
@ -13,6 +14,7 @@ import 'screens/user_profile_screen.dart';
import 'services/auth_service.dart';
class ScreenPaths {
static String contacts = '/contacts';
static String splash = '/splash';
static String timelines = '/';
static String profile = '/profile';
@ -58,6 +60,14 @@ final appRouter = GoRouter(
name: ScreenPaths.signin,
builder: (context, state) => SignInScreen(),
),
GoRoute(
path: ScreenPaths.contacts,
name: ScreenPaths.contacts,
pageBuilder: (context, state) => NoTransitionPage(
name: ScreenPaths.contacts,
child: ContactsScreen(),
),
),
GoRoute(
path: ScreenPaths.timelines,
name: ScreenPaths.timelines,

Wyświetl plik

@ -0,0 +1,53 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:provider/provider.dart';
import '../controls/app_bottom_nav_bar.dart';
import '../models/connection.dart';
import '../routes.dart';
import '../services/connections_manager.dart';
class ContactsScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
final manager = context.watch<ConnectionsManager>();
final contacts = manager.getMyContacts();
contacts.sort((c1, c2) => c1.name.compareTo(c2.name));
late Widget body;
if (contacts.isEmpty) {
body = const SingleChildScrollView(
physics: AlwaysScrollableScrollPhysics(),
child: Text('No Contacts'),
);
} else {
body = ListView.separated(
physics: const AlwaysScrollableScrollPhysics(),
itemBuilder: (context, index) {
final contact = contacts[index];
return ListTile(
onTap: () {
context.pushNamed(ScreenPaths.userProfile,
params: {'id': contact.id});
},
title: Text(contact.name),
trailing: Text(contact.status.label()),
);
},
separatorBuilder: (context, index) => const Divider(),
itemCount: contacts.length);
}
return Scaffold(
body: RefreshIndicator(
onRefresh: () async {
await manager.updateAllContacts();
},
child: Center(
child: body,
),
),
bottomNavigationBar: AppBottomNavBar(
currentButton: NavBarButtons.contacts,
),
);
}
}

Wyświetl plik

@ -1,3 +1,5 @@
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:logging/logging.dart';
import 'package:result_monad/result_monad.dart';
@ -15,6 +17,8 @@ class ConnectionsManager extends ChangeNotifier {
final _connectionsByProfileUrl = <Uri, Connection>{};
final _groupsForConnection = <String, List<GroupData>>{};
final _myGroups = <GroupData>{};
final _myContacts = <Connection>[];
var _myContactsInitialized = false;
int get length => _connectionsById.length;
@ -36,6 +40,23 @@ class ConnectionsManager extends ChangeNotifier {
_connectionsById[connection.id] = connection;
_connectionsByName[connection.name] = connection;
_connectionsByProfileUrl[connection.profileUrl] = connection;
int index = _myContacts.indexWhere((c) => c.id == connection.id);
if (index >= 0) {
_myContacts.removeAt(index);
}
switch (connection.status) {
case ConnectionStatus.youFollowThem:
case ConnectionStatus.theyFollowYou:
case ConnectionStatus.mutual:
if (index > 0) {
_myContacts.insert(index, connection);
} else {
_myContacts.add(connection);
}
break;
default:
break;
}
return true;
}
@ -88,6 +109,73 @@ class ConnectionsManager extends ChangeNotifier {
);
}
List<Connection> getMyContacts() {
if (!_myContactsInitialized) {
updateAllContacts();
_myContactsInitialized = true;
}
return _myContacts.toList(growable: false);
}
Future<void> updateAllContacts() async {
_logger.fine('Updating all contacts');
final clientResult = getIt<AuthService>().currentClient;
if (clientResult.isFailure) {
_logger.severe(
'Unable to update contacts due to client error: ${clientResult.error}');
return;
}
final client = clientResult.value;
final results = <String, Connection>{};
var moreResults = true;
var maxId = -1;
const limit = 100;
while (moreResults) {
await client.getMyFollowers(sinceId: maxId, limit: limit).match(
onSuccess: (followers) {
if (followers.length < limit) {
moreResults = false;
for (final f in followers) {
results[f.id] = f.copy(status: ConnectionStatus.theyFollowYou);
int id = int.parse(f.id);
maxId = max(maxId, id);
}
}
}, onError: (error) {
_logger.severe('Error getting followers data: $error');
});
}
moreResults = true;
maxId = -1;
while (moreResults) {
await client.getMyFollowing(sinceId: maxId, limit: limit).match(
onSuccess: (following) {
if (following.length < limit) {
moreResults = false;
for (final f in following) {
if (results.containsKey(f.id)) {
results[f.id] = f.copy(status: ConnectionStatus.mutual);
} else {
results[f.id] = f.copy(status: ConnectionStatus.youFollowThem);
}
int id = int.parse(f.id);
maxId = max(maxId, id);
}
}
}, onError: (error) {
_logger.severe('Error getting followers data: $error');
});
}
_myContacts.clear();
_myContacts.addAll(results.values);
addAllConnections(results.values);
_myContacts.sort((c1, c2) => c1.name.compareTo(c2.name));
notifyListeners();
}
List<GroupData> getMyGroups() {
if (_myGroups.isNotEmpty) {
return _myGroups.toList(growable: false);
@ -140,9 +228,9 @@ class ConnectionsManager extends ChangeNotifier {
Result<Connection, String> getById(String id) {
final result = _connectionsById[id];
if (result == null) {
Result.error('$id not found');
return Result.error('$id not found');
}
if (result!.status == ConnectionStatus.unknown) {
if (result.status == ConnectionStatus.unknown) {
_refreshConnection(result, true);
}
return Result.ok(result);