Merge branch 'rename-groups-to-circles' into 'main'

Renaming groups to circles everywhere in code and labels

See merge request mysocialportal/relatica!46
This commit is contained in:
HankG 2023-11-15 21:33:50 +00:00
commit b39b5673dc
25 changed files with 472 additions and 472 deletions

View file

@ -86,8 +86,8 @@ class StandardAppDrawer extends StatelessWidget {
), ),
buildMenuButton( buildMenuButton(
context, context,
'Groups Management', 'Circles Management',
() => context.pushNamed(ScreenPaths.groupManagement), () => context.pushNamed(ScreenPaths.circleManagement),
), ),
buildMenuButton( buildMenuButton(
context, context,

View file

@ -0,0 +1,27 @@
import 'package:result_monad/result_monad.dart';
import '../../models/circle_data.dart';
import '../../models/connection.dart';
import '../../models/exec_error.dart';
abstract class ICirclesRepo {
void clear();
void addAllCircles(List<CircleData> circles);
void addConnectionToCircle(CircleData circle, Connection connection);
void clearMyCircles();
void upsertCircle(CircleData circle);
void deleteCircle(CircleData circle);
List<CircleData> getMyCircles();
Result<List<Connection>, ExecError> getCircleMembers(CircleData circle);
Result<List<CircleData>, ExecError> getCirclesForUser(String id);
bool updateConnectionCircleData(String id, List<CircleData> currentCircless);
}

View file

@ -1,27 +0,0 @@
import 'package:result_monad/result_monad.dart';
import '../../models/connection.dart';
import '../../models/exec_error.dart';
import '../../models/group_data.dart';
abstract class IGroupsRepo {
void clear();
void addAllGroups(List<GroupData> groups);
void addConnectionToGroup(GroupData group, Connection connection);
void clearMyGroups();
void upsertGroup(GroupData group);
void deleteGroup(GroupData group);
List<GroupData> getMyGroups();
Result<List<Connection>, ExecError> getGroupMembers(GroupData group);
Result<List<GroupData>, ExecError> getGroupsForUser(String id);
bool updateConnectionGroupData(String id, List<GroupData> currentGroups);
}

View file

@ -0,0 +1,86 @@
import 'package:result_monad/result_monad.dart';
import '../../models/circle_data.dart';
import '../../models/connection.dart';
import '../../models/exec_error.dart';
import '../interfaces/circles_repo_intf.dart';
class MemoryCirclesRepo implements ICirclesRepo {
final _circlesForConnection = <String, List<CircleData>>{};
final _connectionsForCircle = <String, Set<Connection>>{};
final _myCircles = <CircleData>{};
@override
void clear() {
_circlesForConnection.clear();
_connectionsForCircle.clear();
_myCircles.clear();
}
@override
Result<List<CircleData>, ExecError> getCirclesForUser(String id) {
if (!_circlesForConnection.containsKey(id)) {
return Result.error(ExecError(
type: ErrorType.notFound,
message: '$id not a known user ID',
));
}
return Result.ok(_circlesForConnection[id]!);
}
@override
List<CircleData> getMyCircles() {
return _myCircles.toList();
}
@override
Result<List<Connection>, ExecError> getCircleMembers(CircleData circle) {
if (_connectionsForCircle.containsKey(circle.id)) {
return Result.ok(_connectionsForCircle[circle.id]!.toList());
}
return buildErrorResult(
type: ErrorType.notFound,
message: 'Circle ${circle.id} not found',
);
}
@override
void clearMyCircles() {
_myCircles.clear();
}
@override
void addConnectionToCircle(CircleData circle, Connection connection) {
_connectionsForCircle.putIfAbsent(circle.id, () => {}).add(connection);
_circlesForConnection[connection.id]?.add(circle);
}
@override
void addAllCircles(List<CircleData> circle) {
_myCircles.addAll(circle);
}
@override
bool updateConnectionCircleData(String id, List<CircleData> currentCircles) {
_circlesForConnection[id] = currentCircles;
return true;
}
@override
void upsertCircle(CircleData circle) {
_connectionsForCircle.putIfAbsent(circle.id, () => {});
_myCircles.remove(circle);
_myCircles.add(circle);
}
@override
void deleteCircle(CircleData circle) {
for (final conCircles in _circlesForConnection.values) {
conCircles.remove(circle);
}
_connectionsForCircle.remove(circle.id);
_myCircles.remove(circle);
}
}

View file

@ -1,86 +0,0 @@
import 'package:result_monad/result_monad.dart';
import '../../models/connection.dart';
import '../../models/exec_error.dart';
import '../../models/group_data.dart';
import '../interfaces/groups_repo.intf.dart';
class MemoryGroupsRepo implements IGroupsRepo {
final _groupsForConnection = <String, List<GroupData>>{};
final _connectionsForGroup = <String, Set<Connection>>{};
final _myGroups = <GroupData>{};
@override
void clear() {
_groupsForConnection.clear();
_connectionsForGroup.clear();
_myGroups.clear();
}
@override
Result<List<GroupData>, ExecError> getGroupsForUser(String id) {
if (!_groupsForConnection.containsKey(id)) {
return Result.error(ExecError(
type: ErrorType.notFound,
message: '$id not a known user ID',
));
}
return Result.ok(_groupsForConnection[id]!);
}
@override
List<GroupData> getMyGroups() {
return _myGroups.toList();
}
@override
Result<List<Connection>, ExecError> getGroupMembers(GroupData group) {
if (_connectionsForGroup.containsKey(group.id)) {
return Result.ok(_connectionsForGroup[group.id]!.toList());
}
return buildErrorResult(
type: ErrorType.notFound,
message: 'Group ${group.id} not found',
);
}
@override
void clearMyGroups() {
_myGroups.clear();
}
@override
void addConnectionToGroup(GroupData group, Connection connection) {
_connectionsForGroup.putIfAbsent(group.id, () => {}).add(connection);
_groupsForConnection[connection.id]?.add(group);
}
@override
void addAllGroups(List<GroupData> groups) {
_myGroups.addAll(groups);
}
@override
bool updateConnectionGroupData(String id, List<GroupData> currentGroups) {
_groupsForConnection[id] = currentGroups;
return true;
}
@override
void upsertGroup(GroupData group) {
_connectionsForGroup.putIfAbsent(group.id, () => {});
_myGroups.remove(group);
_myGroups.add(group);
}
@override
void deleteGroup(GroupData group) {
for (final conGroups in _groupsForConnection.values) {
conGroups.remove(group);
}
_connectionsForGroup.remove(group.id);
_myGroups.remove(group);
}
}

View file

@ -4,10 +4,10 @@ import 'package:logging/logging.dart';
import 'package:path/path.dart' as p; import 'package:path/path.dart' as p;
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'data/interfaces/circles_repo_intf.dart';
import 'data/interfaces/connections_repo_intf.dart'; import 'data/interfaces/connections_repo_intf.dart';
import 'data/interfaces/groups_repo.intf.dart';
import 'data/interfaces/hashtag_repo_intf.dart'; import 'data/interfaces/hashtag_repo_intf.dart';
import 'data/memory/memory_groups_repo.dart'; import 'data/memory/memory_circles_repo.dart';
import 'data/objectbox/objectbox_cache.dart'; import 'data/objectbox/objectbox_cache.dart';
import 'data/objectbox/objectbox_connections_repo.dart'; import 'data/objectbox/objectbox_connections_repo.dart';
import 'data/objectbox/objectbox_hashtag_repo.dart'; import 'data/objectbox/objectbox_hashtag_repo.dart';
@ -96,8 +96,8 @@ Future<void> dependencyInjectionInitialization() async {
ActiveProfileSelector((p) => ReshareViaService()) ActiveProfileSelector((p) => ReshareViaService())
..subscribeToProfileSwaps()); ..subscribeToProfileSwaps());
getIt.registerSingleton<ActiveProfileSelector<IGroupsRepo>>( getIt.registerSingleton<ActiveProfileSelector<ICirclesRepo>>(
ActiveProfileSelector((p) => MemoryGroupsRepo()) ActiveProfileSelector((p) => MemoryCirclesRepo())
..subscribeToProfileSwaps()); ..subscribeToProfileSwaps());
getIt.registerSingleton<ActiveProfileSelector<ConnectionsManager>>( getIt.registerSingleton<ActiveProfileSelector<ConnectionsManager>>(
@ -105,7 +105,7 @@ Future<void> dependencyInjectionInitialization() async {
(p) => ConnectionsManager( (p) => ConnectionsManager(
p, p,
getIt<ActiveProfileSelector<IConnectionsRepo>>().getForProfile(p).value, getIt<ActiveProfileSelector<IConnectionsRepo>>().getForProfile(p).value,
getIt<ActiveProfileSelector<IGroupsRepo>>().getForProfile(p).value, getIt<ActiveProfileSelector<ICirclesRepo>>().getForProfile(p).value,
), ),
)); ));
@ -118,7 +118,7 @@ Future<void> dependencyInjectionInitialization() async {
getIt.registerSingleton<ActiveProfileSelector<TimelineManager>>( getIt.registerSingleton<ActiveProfileSelector<TimelineManager>>(
ActiveProfileSelector((p) => TimelineManager( ActiveProfileSelector((p) => TimelineManager(
p, p,
getIt<ActiveProfileSelector<IGroupsRepo>>().getForProfile(p).value, getIt<ActiveProfileSelector<ICirclesRepo>>().getForProfile(p).value,
getIt<ActiveProfileSelector<EntryManagerService>>() getIt<ActiveProfileSelector<EntryManagerService>>()
.getForProfile(p) .getForProfile(p)
.value, .value,

View file

@ -10,12 +10,12 @@ import '../friendica_client/paged_response.dart';
import '../globals.dart'; import '../globals.dart';
import '../models/TimelineIdentifiers.dart'; import '../models/TimelineIdentifiers.dart';
import '../models/auth/profile.dart'; import '../models/auth/profile.dart';
import '../models/circle_data.dart';
import '../models/connection.dart'; import '../models/connection.dart';
import '../models/direct_message.dart'; import '../models/direct_message.dart';
import '../models/exec_error.dart'; import '../models/exec_error.dart';
import '../models/follow_request.dart'; import '../models/follow_request.dart';
import '../models/gallery_data.dart'; import '../models/gallery_data.dart';
import '../models/group_data.dart';
import '../models/image_entry.dart'; import '../models/image_entry.dart';
import '../models/instance_info.dart'; import '../models/instance_info.dart';
import '../models/media_attachment_uploads/image_types_enum.dart'; import '../models/media_attachment_uploads/image_types_enum.dart';
@ -28,9 +28,9 @@ import '../serializers/friendica/direct_message_friendica_extensions.dart';
import '../serializers/friendica/gallery_data_friendica_extensions.dart'; import '../serializers/friendica/gallery_data_friendica_extensions.dart';
import '../serializers/friendica/image_entry_friendica_extensions.dart'; import '../serializers/friendica/image_entry_friendica_extensions.dart';
import '../serializers/friendica/visibility_friendica_extensions.dart'; import '../serializers/friendica/visibility_friendica_extensions.dart';
import '../serializers/mastodon/circle_data_mastodon_extensions.dart';
import '../serializers/mastodon/connection_mastodon_extensions.dart'; import '../serializers/mastodon/connection_mastodon_extensions.dart';
import '../serializers/mastodon/follow_request_mastodon_extensions.dart'; import '../serializers/mastodon/follow_request_mastodon_extensions.dart';
import '../serializers/mastodon/group_data_mastodon_extensions.dart';
import '../serializers/mastodon/instance_info_mastodon_extensions.dart'; import '../serializers/mastodon/instance_info_mastodon_extensions.dart';
import '../serializers/mastodon/notification_mastodon_extension.dart'; import '../serializers/mastodon/notification_mastodon_extension.dart';
import '../serializers/mastodon/search_result_mastodon_extensions.dart'; import '../serializers/mastodon/search_result_mastodon_extensions.dart';
@ -168,32 +168,33 @@ class GalleryClient extends FriendicaClient {
} }
} }
class GroupsClient extends FriendicaClient { class CirclesClient extends FriendicaClient {
static final _logger = Logger('$GroupsClient'); static final _logger = Logger('$CirclesClient');
GroupsClient(super.credentials) : super(); CirclesClient(super.credentials) : super();
FutureResult<List<GroupData>, ExecError> getGroups() async { FutureResult<List<CircleData>, ExecError> getCircles() async {
_logger.finest(() => 'Getting group (Mastodon List) data'); _logger.finest(() => 'Getting circle (Mastodon List) data');
final url = 'https://$serverName/api/v1/lists'; final url = 'https://$serverName/api/v1/lists';
final request = Uri.parse(url); final request = Uri.parse(url);
return (await _getApiListRequest(request).andThenSuccessAsync( return (await _getApiListRequest(request).andThenSuccessAsync(
(listsJson) async => listsJson.data (listsJson) async => listsJson.data
.map((json) => GroupDataMastodonExtensions.fromJson(json)) .map((json) => CircleDataMastodonExtensions.fromJson(json))
.toList())) .toList()))
.mapError((error) => error is ExecError .mapError((error) => error is ExecError
? error ? error
: ExecError(type: ErrorType.localError, message: error.toString())); : ExecError(type: ErrorType.localError, message: error.toString()));
} }
FutureResult<PagedResponse<List<Connection>>, ExecError> getGroupMembers( FutureResult<PagedResponse<List<Connection>>, ExecError> getCircleMembers(
GroupData groupData, CircleData circleData,
PagingData page, PagingData page,
) async { ) async {
_networkStatusService.startConnectionUpdateStatus(); _networkStatusService.startConnectionUpdateStatus();
_logger.finest(() => _logger.finest(() =>
'Getting members for group (Mastodon List) of name ${groupData.name} with paging: $page'); 'Getting members for circle (Mastodon List) of name ${circleData.name} with paging: $page');
final baseUrl = 'https://$serverName/api/v1/lists/${groupData.id}/accounts'; final baseUrl =
'https://$serverName/api/v1/lists/${circleData.id}/accounts';
final url = Uri.parse('$baseUrl?${page.toQueryParameters()}'); final url = Uri.parse('$baseUrl?${page.toQueryParameters()}');
final result = await _getApiPagedRequest(url); final result = await _getApiPagedRequest(url);
_networkStatusService.finishConnectionUpdateStatus(); _networkStatusService.finishConnectionUpdateStatus();
@ -205,8 +206,8 @@ class GroupsClient extends FriendicaClient {
.execErrorCast(); .execErrorCast();
} }
FutureResult<GroupData, ExecError> createGroup(String title) async { FutureResult<CircleData, ExecError> createCircle(String title) async {
_logger.finest(() => 'Creating group (Mastodon List) of name $title'); _logger.finest(() => 'Creating circle (Mastodon List) of name $title');
final url = 'https://$serverName/api/v1/lists'; final url = 'https://$serverName/api/v1/lists';
final body = { final body = {
'title': title, 'title': title,
@ -215,14 +216,14 @@ class GroupsClient extends FriendicaClient {
Uri.parse(url), Uri.parse(url),
body, body,
headers: _headers, headers: _headers,
).andThenSuccessAsync( ).andThenSuccessAsync((data) async =>
(data) async => GroupDataMastodonExtensions.fromJson(jsonDecode(data))); CircleDataMastodonExtensions.fromJson(jsonDecode(data)));
return result.execErrorCast(); return result.execErrorCast();
} }
FutureResult<GroupData, ExecError> renameGroup( FutureResult<CircleData, ExecError> renameCircle(
String id, String title) async { String id, String title) async {
_logger.finest(() => 'Reanming group (Mastodon List) to name $title'); _logger.finest(() => 'Reanming circle (Mastodon List) to name $title');
final url = 'https://$serverName/api/v1/lists/$id'; final url = 'https://$serverName/api/v1/lists/$id';
final body = { final body = {
'title': title, 'title': title,
@ -233,38 +234,38 @@ class GroupsClient extends FriendicaClient {
headers: _headers, headers: _headers,
).andThenSuccessAsync((data) async { ).andThenSuccessAsync((data) async {
final json = jsonDecode(data); final json = jsonDecode(data);
return GroupDataMastodonExtensions.fromJson(json); return CircleDataMastodonExtensions.fromJson(json);
}); });
return result.execErrorCast(); return result.execErrorCast();
} }
FutureResult<bool, ExecError> deleteGroup(GroupData groupData) async { FutureResult<bool, ExecError> deleteCircle(CircleData circleData) async {
_logger.finest( _logger.finest(
() => 'Reanming group (Mastodon List) to name ${groupData.name}'); () => 'Reanming circle (Mastodon List) to name ${circleData.name}');
final url = 'https://$serverName/api/v1/lists/${groupData.id}'; final url = 'https://$serverName/api/v1/lists/${circleData.id}';
final result = await deleteUrl(Uri.parse(url), {}, headers: _headers); final result = await deleteUrl(Uri.parse(url), {}, headers: _headers);
return result.mapValue((_) => true).execErrorCast(); return result.mapValue((_) => true).execErrorCast();
} }
FutureResult<List<GroupData>, ExecError> getMemberGroupsForConnection( FutureResult<List<CircleData>, ExecError> getMemberCirclesForConnection(
String connectionId) async { String connectionId) async {
_logger.finest(() => _logger.finest(() =>
'Getting groups (Mastodon Lists) containing connection: $connectionId'); 'Getting circles (Mastodon Lists) containing connection: $connectionId');
final url = 'https://$serverName/api/v1/accounts/$connectionId/lists'; final url = 'https://$serverName/api/v1/accounts/$connectionId/lists';
final request = Uri.parse(url); final request = Uri.parse(url);
return (await _getApiListRequest(request).andThenSuccessAsync( return (await _getApiListRequest(request).andThenSuccessAsync(
(listsJson) async => listsJson.data (listsJson) async => listsJson.data
.map((json) => GroupDataMastodonExtensions.fromJson(json)) .map((json) => CircleDataMastodonExtensions.fromJson(json))
.toList())) .toList()))
.mapError((error) => error as ExecError); .mapError((error) => error as ExecError);
} }
FutureResult<bool, ExecError> addConnectionToGroup( FutureResult<bool, ExecError> addConnectionToCircle(
GroupData group, CircleData circle,
Connection connection, Connection connection,
) async { ) async {
_logger.finest(() => 'Adding connection to group'); _logger.finest(() => 'Adding connection to circle');
final url = 'https://$serverName/api/v1/lists/${group.id}/accounts'; final url = 'https://$serverName/api/v1/lists/${circle.id}/accounts';
final request = Uri.parse(url); final request = Uri.parse(url);
final requestData = { final requestData = {
'account_ids': [connection.id] 'account_ids': [connection.id]
@ -273,12 +274,12 @@ class GroupsClient extends FriendicaClient {
.mapValue((_) => true); .mapValue((_) => true);
} }
FutureResult<bool, ExecError> removeConnectionFromGroup( FutureResult<bool, ExecError> removeConnectionFromCircle(
GroupData group, CircleData circle,
Connection connection, Connection connection,
) async { ) async {
_logger.finest(() => 'Adding connection to group'); _logger.finest(() => 'Adding connection to circle');
final url = 'https://$serverName/api/v1/lists/${group.id}/accounts'; final url = 'https://$serverName/api/v1/lists/${circle.id}/accounts';
final request = Uri.parse(url); final request = Uri.parse(url);
final requestData = { final requestData = {
'account_ids': [connection.id] 'account_ids': [connection.id]
@ -549,7 +550,7 @@ class RelationshipsClient extends FriendicaClient {
FutureResult<Connection, ExecError> getConnectionWithStatus( FutureResult<Connection, ExecError> getConnectionWithStatus(
Connection connection) async { Connection connection) async {
_logger.finest(() => 'Getting group (Mastodon List) data'); _logger.finest(() => 'Getting circle (Mastodon List) data');
_networkStatusService.startConnectionUpdateStatus(); _networkStatusService.startConnectionUpdateStatus();
final myId = profile.userId; final myId = profile.userId;
final id = int.parse(connection.id); final id = int.parse(connection.id);
@ -1044,7 +1045,7 @@ class TimelineClient extends FriendicaClient {
return 'timelines/public'; return 'timelines/public';
case TimelineType.local: case TimelineType.local:
return 'timelines/public'; return 'timelines/public';
case TimelineType.group: case TimelineType.circle:
return 'timelines/list/${type.auxData}'; return 'timelines/list/${type.auxData}';
case TimelineType.profile: case TimelineType.profile:
return '/accounts/${type.auxData}/statuses'; return '/accounts/${type.auxData}/statuses';
@ -1059,7 +1060,7 @@ class TimelineClient extends FriendicaClient {
case TimelineType.home: case TimelineType.home:
case TimelineType.global: case TimelineType.global:
case TimelineType.profile: case TimelineType.profile:
case TimelineType.group: case TimelineType.circle:
case TimelineType.self: case TimelineType.self:
return ''; return '';
case TimelineType.local: case TimelineType.local:

View file

@ -2,7 +2,7 @@ enum TimelineType {
home, home,
global, global,
local, local,
group, circle,
profile, profile,
self; self;
@ -14,8 +14,8 @@ enum TimelineType {
return 'Global Fediverse'; return 'Global Fediverse';
case TimelineType.local: case TimelineType.local:
return 'Local Fediverse'; return 'Local Fediverse';
case TimelineType.group: case TimelineType.circle:
return 'Groups (Lists)'; return 'Circles';
case TimelineType.profile: case TimelineType.profile:
return 'Profile'; return 'Profile';
case TimelineType.self: case TimelineType.self:

View file

@ -0,0 +1,22 @@
class CircleData {
static final followersPseudoCircle = CircleData('~', 'Followers');
final String id;
final String name;
CircleData(this.id, this.name);
@override
String toString() {
return 'CircleData{id: $id, name: $name}';
}
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is CircleData && runtimeType == other.runtimeType && id == other.id;
@override
int get hashCode => id.hashCode;
}

View file

@ -1,22 +0,0 @@
class GroupData {
static final followersPseudoGroup = GroupData('~', 'Followers');
final String id;
final String name;
GroupData(this.id, this.name);
@override
String toString() {
return 'GroupData{id: $id, name: $name}';
}
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is GroupData && runtimeType == other.runtimeType && id == other.id;
@override
int get hashCode => id.hashCode;
}

View file

@ -20,31 +20,29 @@ class Visibility {
final List<String> excludedUserIds; final List<String> excludedUserIds;
final List<String> allowedGroupIds; final List<String> allowedCircleIds;
final List<String> excludedGroupIds; final List<String> excludedCircleIds;
bool get hasDetails => bool get hasDetails =>
allowedUserIds.isNotEmpty || allowedUserIds.isNotEmpty ||
excludedUserIds.isNotEmpty || excludedUserIds.isNotEmpty ||
allowedGroupIds.isNotEmpty || allowedCircleIds.isNotEmpty ||
excludedGroupIds.isNotEmpty; excludedCircleIds.isNotEmpty;
const Visibility({ const Visibility({
required this.type, required this.type,
this.allowedUserIds = const [], this.allowedUserIds = const [],
this.excludedUserIds = const [], this.excludedUserIds = const [],
this.allowedGroupIds = const [], this.allowedCircleIds = const [],
this.excludedGroupIds = const [], this.excludedCircleIds = const [],
}); });
factory Visibility.public() => factory Visibility.public() => const Visibility(
const Visibility(
type: VisibilityType.public, type: VisibilityType.public,
); );
factory Visibility.private() => factory Visibility.private() => const Visibility(
const Visibility(
type: VisibilityType.private, type: VisibilityType.private,
); );
@ -56,14 +54,14 @@ class Visibility {
type == other.type && type == other.type &&
allowedUserIds == other.allowedUserIds && allowedUserIds == other.allowedUserIds &&
excludedUserIds == other.excludedUserIds && excludedUserIds == other.excludedUserIds &&
allowedGroupIds == other.allowedGroupIds && allowedCircleIds == other.allowedCircleIds &&
excludedGroupIds == other.excludedGroupIds; excludedCircleIds == other.excludedCircleIds;
@override @override
int get hashCode => int get hashCode =>
type.hashCode ^ type.hashCode ^
allowedUserIds.hashCode ^ allowedUserIds.hashCode ^
excludedUserIds.hashCode ^ excludedUserIds.hashCode ^
allowedGroupIds.hashCode ^ allowedCircleIds.hashCode ^
excludedGroupIds.hashCode; excludedCircleIds.hashCode;
} }

View file

@ -3,6 +3,10 @@ import 'package:go_router/go_router.dart';
import 'globals.dart'; import 'globals.dart';
import 'models/interaction_type_enum.dart'; import 'models/interaction_type_enum.dart';
import 'screens/blocks_screen.dart'; import 'screens/blocks_screen.dart';
import 'screens/circle_add_users_screen.dart';
import 'screens/circle_create_screen.dart';
import 'screens/circle_editor_screen.dart';
import 'screens/circle_management_screen.dart';
import 'screens/contacts_screen.dart'; import 'screens/contacts_screen.dart';
import 'screens/editor.dart'; import 'screens/editor.dart';
import 'screens/filter_editor_screen.dart'; import 'screens/filter_editor_screen.dart';
@ -10,10 +14,6 @@ import 'screens/filters_screen.dart';
import 'screens/follow_request_adjudication_screen.dart'; import 'screens/follow_request_adjudication_screen.dart';
import 'screens/gallery_browsers_screen.dart'; import 'screens/gallery_browsers_screen.dart';
import 'screens/gallery_screen.dart'; import 'screens/gallery_screen.dart';
import 'screens/group_add_users_screen.dart';
import 'screens/group_create_screen.dart';
import 'screens/group_editor_screen.dart';
import 'screens/group_management_screen.dart';
import 'screens/home.dart'; import 'screens/home.dart';
import 'screens/image_editor_screen.dart'; import 'screens/image_editor_screen.dart';
import 'screens/interactions_viewer_screen.dart'; import 'screens/interactions_viewer_screen.dart';
@ -44,7 +44,7 @@ class ScreenPaths {
static String notifications = '/notifications'; static String notifications = '/notifications';
static String signin = '/signin'; static String signin = '/signin';
static String manageProfiles = '/switchProfiles'; static String manageProfiles = '/switchProfiles';
static String groupManagement = '/group_management'; static String circleManagement = '/circle_management';
static String signup = '/signup'; static String signup = '/signup';
static String userProfile = '/user_profile'; static String userProfile = '/user_profile';
static String userPosts = '/user_posts'; static String userPosts = '/user_posts';
@ -127,8 +127,8 @@ final appRouter = GoRouter(
GoRoute( GoRoute(
path: '/connect/:id', path: '/connect/:id',
name: ScreenPaths.connectHandle, name: ScreenPaths.connectHandle,
builder: (context, state) => builder: (context, state) => FollowRequestAdjudicationScreen(
FollowRequestAdjudicationScreen(userId: state.pathParameters['id']!), userId: state.pathParameters['id']!),
), ),
GoRoute( GoRoute(
path: ScreenPaths.timelines, path: ScreenPaths.timelines,
@ -156,28 +156,28 @@ final appRouter = GoRouter(
GoRoute( GoRoute(
name: ScreenPaths.thread, name: ScreenPaths.thread,
path: ScreenPaths.thread, path: ScreenPaths.thread,
builder: (context, state) => builder: (context, state) => MessageThreadScreen(
MessageThreadScreen(parentThreadId: state.uri.queryParameters['uri']!), parentThreadId: state.uri.queryParameters['uri']!),
), ),
GoRoute( GoRoute(
name: ScreenPaths.groupManagement, name: ScreenPaths.circleManagement,
path: ScreenPaths.groupManagement, path: ScreenPaths.circleManagement,
builder: (context, state) => const GroupManagementScreen(), builder: (context, state) => const CircleManagementScreen(),
routes: [ routes: [
GoRoute( GoRoute(
path: 'show/:id', path: 'show/:id',
builder: (context, state) => GroupEditorScreen( builder: (context, state) => CircleEditorScreen(
groupId: state.pathParameters['id']!, circleId: state.pathParameters['id']!,
), ),
), ),
GoRoute( GoRoute(
path: 'new', path: 'new',
builder: (context, state) => const GroupCreateScreen(), builder: (context, state) => const CircleCreateScreen(),
), ),
GoRoute( GoRoute(
path: 'add_users/:id', path: 'add_users/:id',
builder: (context, state) => builder: (context, state) =>
GroupAddUsersScreen(groupId: state.pathParameters['id']!), CircleAddUsersScreen(circleId: state.pathParameters['id']!),
), ),
], ],
), ),

View file

@ -8,47 +8,47 @@ import '../controls/linear_status_indicator.dart';
import '../controls/responsive_max_width.dart'; import '../controls/responsive_max_width.dart';
import '../controls/status_and_refresh_button.dart'; import '../controls/status_and_refresh_button.dart';
import '../globals.dart'; import '../globals.dart';
import '../models/circle_data.dart';
import '../models/connection.dart'; import '../models/connection.dart';
import '../models/exec_error.dart'; import '../models/exec_error.dart';
import '../models/group_data.dart';
import '../routes.dart'; import '../routes.dart';
import '../services/connections_manager.dart'; import '../services/connections_manager.dart';
import '../services/network_status_service.dart'; import '../services/network_status_service.dart';
import '../utils/active_profile_selector.dart'; import '../utils/active_profile_selector.dart';
import '../utils/snackbar_builder.dart'; import '../utils/snackbar_builder.dart';
class GroupAddUsersScreen extends StatefulWidget { class CircleAddUsersScreen extends StatefulWidget {
final String groupId; final String circleId;
const GroupAddUsersScreen({super.key, required this.groupId}); const CircleAddUsersScreen({super.key, required this.circleId});
@override @override
State<GroupAddUsersScreen> createState() => _GroupAddUsersScreenState(); State<CircleAddUsersScreen> createState() => _CircleAddUsersScreenState();
} }
class _GroupAddUsersScreenState extends State<GroupAddUsersScreen> { class _CircleAddUsersScreenState extends State<CircleAddUsersScreen> {
static final _logger = Logger('$GroupAddUsersScreen'); static final _logger = Logger('$CircleAddUsersScreen');
var filterText = ''; var filterText = '';
late GroupData groupData; late CircleData circleData;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
final manager = final manager =
getIt<ActiveProfileSelector<ConnectionsManager>>().activeEntry.value; getIt<ActiveProfileSelector<ConnectionsManager>>().activeEntry.value;
groupData = circleData =
manager.getMyGroups().where((g) => g.id == widget.groupId).first; manager.getMyCircles().where((g) => g.id == widget.circleId).first;
} }
Future<void> addUserToGroup( Future<void> addUserToCircle(
ConnectionsManager manager, ConnectionsManager manager,
Connection connection, Connection connection,
) async { ) async {
final messageBase = '${connection.name} from ${groupData.name}'; final messageBase = '${connection.name} from ${circleData.name}';
final confirm = await showYesNoDialog(context, 'Add $messageBase?'); final confirm = await showYesNoDialog(context, 'Add $messageBase?');
if (context.mounted && confirm == true) { if (context.mounted && confirm == true) {
final message = await manager final message = await manager
.addUserToGroup(groupData, connection) .addUserToCircle(circleData, connection)
.withResult((p0) => setState(() {})) .withResult((p0) => setState(() {}))
.fold( .fold(
onSuccess: (_) => 'Added $messageBase', onSuccess: (_) => 'Added $messageBase',
@ -66,15 +66,15 @@ class _GroupAddUsersScreenState extends State<GroupAddUsersScreen> {
.watch<ActiveProfileSelector<ConnectionsManager>>() .watch<ActiveProfileSelector<ConnectionsManager>>()
.activeEntry .activeEntry
.value; .value;
final groupMembers = manager final circleMembers = manager
.getGroupMembers(groupData) .getCircleMembers(circleData)
.withError((e) => logError(e, _logger)) .withError((e) => logError(e, _logger))
.getValueOrElse(() => []) .getValueOrElse(() => [])
.toSet(); .toSet();
final allContacts = manager.getMyContacts(); final allContacts = manager.getMyContacts();
final filterTextLC = filterText.toLowerCase(); final filterTextLC = filterText.toLowerCase();
final contacts = allContacts final contacts = allContacts
.where((c) => !groupMembers.contains(c)) .where((c) => !circleMembers.contains(c))
.where((c) => .where((c) =>
filterText.isEmpty || filterText.isEmpty ||
c.name.toLowerCase().contains(filterTextLC) || c.name.toLowerCase().contains(filterTextLC) ||
@ -83,7 +83,7 @@ class _GroupAddUsersScreenState extends State<GroupAddUsersScreen> {
contacts.sort((c1, c2) => c1.name.compareTo(c2.name)); contacts.sort((c1, c2) => c1.name.compareTo(c2.name));
_logger.finer( _logger.finer(
() => () =>
'# in group: ${groupMembers.length} # Contacts: ${allContacts.length}, #filtered: ${contacts.length}', '# in circle: ${circleMembers.length} # Contacts: ${allContacts.length}, #filtered: ${contacts.length}',
); );
late Widget body; late Widget body;
if (contacts.isEmpty) { if (contacts.isEmpty) {
@ -111,7 +111,8 @@ class _GroupAddUsersScreenState extends State<GroupAddUsersScreen> {
softWrap: true, softWrap: true,
), ),
trailing: IconButton( trailing: IconButton(
onPressed: () async => await addUserToGroup(manager, contact), onPressed: () async =>
await addUserToCircle(manager, contact),
icon: const Icon(Icons.add)), icon: const Icon(Icons.add)),
); );
}, },
@ -129,14 +130,14 @@ class _GroupAddUsersScreenState extends State<GroupAddUsersScreen> {
if (nss.connectionUpdateStatus.value) { if (nss.connectionUpdateStatus.value) {
return; return;
} }
manager.refreshGroupMemberships(groupData); manager.refreshCircleMemberships(circleData);
return; return;
}, },
child: ResponsiveMaxWidth( child: ResponsiveMaxWidth(
child: Column( child: Column(
children: [ children: [
Text( Text(
'Group: ${groupData.name}', 'Circle: ${circleData.name}',
style: Theme.of(context).textTheme.bodyLarge, style: Theme.of(context).textTheme.bodyLarge,
softWrap: true, softWrap: true,
), ),
@ -169,7 +170,7 @@ class _GroupAddUsersScreenState extends State<GroupAddUsersScreen> {
child: StatusAndRefreshButton( child: StatusAndRefreshButton(
valueListenable: nss.connectionUpdateStatus, valueListenable: nss.connectionUpdateStatus,
refreshFunction: () async => refreshFunction: () async =>
manager.refreshGroupMemberships(groupData), manager.refreshCircleMemberships(circleData),
), ),
) )
], ],

View file

@ -8,28 +8,28 @@ import '../services/connections_manager.dart';
import '../utils/active_profile_selector.dart'; import '../utils/active_profile_selector.dart';
import '../utils/snackbar_builder.dart'; import '../utils/snackbar_builder.dart';
class GroupCreateScreen extends StatefulWidget { class CircleCreateScreen extends StatefulWidget {
const GroupCreateScreen({super.key}); const CircleCreateScreen({super.key});
@override @override
State<GroupCreateScreen> createState() => _GroupCreateScreenState(); State<CircleCreateScreen> createState() => _CircleCreateScreenState();
} }
class _GroupCreateScreenState extends State<GroupCreateScreen> { class _CircleCreateScreenState extends State<CircleCreateScreen> {
final groupTextController = TextEditingController(); final circleTextController = TextEditingController();
Future<void> createGroup(ConnectionsManager manager) async { Future<void> createCircle(ConnectionsManager manager) async {
if (groupTextController.text.isEmpty) { if (circleTextController.text.isEmpty) {
buildSnackbar(context, "Group name can't be empty"); buildSnackbar(context, "Circle name can't be empty");
return; return;
} }
final result = await manager.createGroup(groupTextController.text); final result = await manager.createCircle(circleTextController.text);
if (context.mounted) { if (context.mounted) {
result.match( result.match(
onSuccess: (_) => context.canPop() ? context.pop() : null, onSuccess: (_) => context.canPop() ? context.pop() : null,
onError: (error) { onError: (error) {
buildSnackbar(context, 'Error trying to create new group: $error'); buildSnackbar(context, 'Error trying to create new circle: $error');
}); });
} }
} }
@ -44,7 +44,7 @@ class _GroupCreateScreenState extends State<GroupCreateScreen> {
return Scaffold( return Scaffold(
appBar: StandardAppBar.build( appBar: StandardAppBar.build(
context, context,
'New group', 'New circle',
withHome: false, withHome: false,
), ),
body: Padding( body: Padding(
@ -52,10 +52,10 @@ class _GroupCreateScreenState extends State<GroupCreateScreen> {
child: Column( child: Column(
children: [ children: [
TextFormField( TextFormField(
controller: groupTextController, controller: circleTextController,
textCapitalization: TextCapitalization.sentences, textCapitalization: TextCapitalization.sentences,
decoration: InputDecoration( decoration: InputDecoration(
labelText: 'Group Name', labelText: 'Circle Name',
border: OutlineInputBorder( border: OutlineInputBorder(
borderSide: const BorderSide(), borderSide: const BorderSide(),
borderRadius: BorderRadius.circular(5.0), borderRadius: BorderRadius.circular(5.0),
@ -64,7 +64,7 @@ class _GroupCreateScreenState extends State<GroupCreateScreen> {
), ),
const VerticalPadding(), const VerticalPadding(),
ElevatedButton( ElevatedButton(
onPressed: () => createGroup(manager), onPressed: () => createCircle(manager),
child: const Text('Create'), child: const Text('Create'),
), ),
], ],

View file

@ -9,75 +9,75 @@ import '../controls/responsive_max_width.dart';
import '../controls/standard_appbar.dart'; import '../controls/standard_appbar.dart';
import '../controls/status_and_refresh_button.dart'; import '../controls/status_and_refresh_button.dart';
import '../globals.dart'; import '../globals.dart';
import '../models/circle_data.dart';
import '../models/connection.dart'; import '../models/connection.dart';
import '../models/group_data.dart';
import '../routes.dart'; import '../routes.dart';
import '../services/connections_manager.dart'; import '../services/connections_manager.dart';
import '../services/network_status_service.dart'; import '../services/network_status_service.dart';
import '../utils/active_profile_selector.dart'; import '../utils/active_profile_selector.dart';
import '../utils/snackbar_builder.dart'; import '../utils/snackbar_builder.dart';
class GroupEditorScreen extends StatefulWidget { class CircleEditorScreen extends StatefulWidget {
final String groupId; final String circleId;
const GroupEditorScreen({super.key, required this.groupId}); const CircleEditorScreen({super.key, required this.circleId});
@override @override
State<GroupEditorScreen> createState() => _GroupEditorScreenState(); State<CircleEditorScreen> createState() => _CircleEditorScreenState();
} }
class _GroupEditorScreenState extends State<GroupEditorScreen> { class _CircleEditorScreenState extends State<CircleEditorScreen> {
final groupTextController = TextEditingController(); final circleTextController = TextEditingController();
var processingUpdate = false; var processingUpdate = false;
var allowNameEditing = false; var allowNameEditing = false;
var filterText = ''; var filterText = '';
late GroupData groupData; late CircleData circleData;
Future<void> updateGroupName( Future<void> updateCircleName(
BuildContext context, ConnectionsManager manager) async { BuildContext context, ConnectionsManager manager) async {
processingUpdate = true; processingUpdate = true;
final updated = groupTextController.text; final updated = circleTextController.text;
if (groupTextController.text != groupData.name) { if (circleTextController.text != circleData.name) {
final confirm = await showYesNoDialog( final confirm = await showYesNoDialog(context,
context, 'Change the group name from ${groupData.name} to $updated?'); 'Change the circle name from ${circleData.name} to $updated?');
if (context.mounted && confirm == true) { if (context.mounted && confirm == true) {
await manager.renameGroup(widget.groupId, updated).match( await manager.renameCircle(widget.circleId, updated).match(
onSuccess: (updatedGroupData) { onSuccess: (updatedCircleData) {
groupData = updatedGroupData; circleData = updatedCircleData;
setState(() { setState(() {
allowNameEditing = false; allowNameEditing = false;
}); });
}, onError: (error) { }, onError: (error) {
buildSnackbar(context, 'Error renaming group: $error'); buildSnackbar(context, 'Error renaming circle: $error');
}); });
} else { } else {
groupTextController.text = groupData.name; circleTextController.text = circleData.name;
} }
} }
processingUpdate = false; processingUpdate = false;
} }
Future<void> deleteGroup(ConnectionsManager manager) async { Future<void> deleteCircle(ConnectionsManager manager) async {
final confirm = await showYesNoDialog(context, final confirm = await showYesNoDialog(context,
"Permanently delete group ${groupData.name}? This can't be undone."); "Permanently delete circle ${circleData.name}? This can't be undone.");
if (context.mounted && confirm == true) { if (context.mounted && confirm == true) {
await manager.deleteGroup(groupData).match( await manager.deleteCircle(circleData).match(
onSuccess: (_) => context.canPop() ? context.pop() : null, onSuccess: (_) => context.canPop() ? context.pop() : null,
onError: (error) => onError: (error) =>
buildSnackbar(context, 'Error trying to delete group: $error'), buildSnackbar(context, 'Error trying to delete circle: $error'),
); );
} }
} }
Future<void> removeUserFromGroup( Future<void> removeUserFromCircle(
ConnectionsManager manager, ConnectionsManager manager,
Connection connection, Connection connection,
) async { ) async {
final messageBase = '${connection.name} from ${groupData.name}'; final messageBase = '${connection.name} from ${circleData.name}';
final confirm = await showYesNoDialog(context, 'Remove $messageBase?'); final confirm = await showYesNoDialog(context, 'Remove $messageBase?');
if (context.mounted && confirm == true) { if (context.mounted && confirm == true) {
final message = final message =
await manager.removeUserFromGroup(groupData, connection).fold( await manager.removeUserFromCircle(circleData, connection).fold(
onSuccess: (_) => 'Removed $messageBase', onSuccess: (_) => 'Removed $messageBase',
onError: (error) => 'Error removing $messageBase: $error', onError: (error) => 'Error removing $messageBase: $error',
); );
@ -90,14 +90,14 @@ class _GroupEditorScreenState extends State<GroupEditorScreen> {
super.initState(); super.initState();
final manager = final manager =
getIt<ActiveProfileSelector<ConnectionsManager>>().activeEntry.value; getIt<ActiveProfileSelector<ConnectionsManager>>().activeEntry.value;
groupData = manager circleData = manager
.getMyGroups() .getMyCircles()
.where( .where(
(g) => g.id == widget.groupId, (g) => g.id == widget.circleId,
) )
.first; .first;
manager.refreshGroupMemberships(groupData); manager.refreshCircleMemberships(circleData);
groupTextController.text = groupData.name; circleTextController.text = circleData.name;
} }
@override @override
@ -110,7 +110,7 @@ class _GroupEditorScreenState extends State<GroupEditorScreen> {
final filterTextLC = filterText.toLowerCase(); final filterTextLC = filterText.toLowerCase();
final members = manager final members = manager
.getGroupMembers(groupData) .getCircleMembers(circleData)
.transform((ms) => ms .transform((ms) => ms
.where((m) => .where((m) =>
filterText.isEmpty || filterText.isEmpty ||
@ -122,11 +122,11 @@ class _GroupEditorScreenState extends State<GroupEditorScreen> {
return Scaffold( return Scaffold(
appBar: StandardAppBar.build( appBar: StandardAppBar.build(
context, context,
'Group Editor', 'Circle Editor',
withHome: false, withHome: false,
actions: [ actions: [
IconButton( IconButton(
onPressed: () => deleteGroup(manager), onPressed: () => deleteCircle(manager),
icon: const Icon(Icons.delete), icon: const Icon(Icons.delete),
), ),
], ],
@ -135,7 +135,7 @@ class _GroupEditorScreenState extends State<GroupEditorScreen> {
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
child: RefreshIndicator( child: RefreshIndicator(
onRefresh: () async { onRefresh: () async {
manager.refreshGroups(); manager.refreshCircles();
}, },
child: ResponsiveMaxWidth( child: ResponsiveMaxWidth(
child: Column( child: Column(
@ -153,18 +153,18 @@ class _GroupEditorScreenState extends State<GroupEditorScreen> {
if (processingUpdate) { if (processingUpdate) {
return; return;
} }
updateGroupName(context, manager); updateCircleName(context, manager);
}, },
onTapOutside: (_) async { onTapOutside: (_) async {
if (processingUpdate) { if (processingUpdate) {
return; return;
} }
updateGroupName(context, manager); updateCircleName(context, manager);
}, },
controller: groupTextController, controller: circleTextController,
textCapitalization: TextCapitalization.sentences, textCapitalization: TextCapitalization.sentences,
decoration: InputDecoration( decoration: InputDecoration(
labelText: 'Group Name', labelText: 'Circle Name',
border: OutlineInputBorder( border: OutlineInputBorder(
borderSide: const BorderSide(), borderSide: const BorderSide(),
borderRadius: BorderRadius.circular(5.0), borderRadius: BorderRadius.circular(5.0),
@ -176,7 +176,7 @@ class _GroupEditorScreenState extends State<GroupEditorScreen> {
IconButton( IconButton(
onPressed: () { onPressed: () {
if (allowNameEditing) { if (allowNameEditing) {
groupTextController.text = groupData.name; circleTextController.text = circleData.name;
} }
setState(() { setState(() {
allowNameEditing = !allowNameEditing; allowNameEditing = !allowNameEditing;
@ -191,12 +191,12 @@ class _GroupEditorScreenState extends State<GroupEditorScreen> {
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text('Group Members:', Text('Circle Members:',
style: Theme.of(context).textTheme.headlineSmall), style: Theme.of(context).textTheme.headlineSmall),
IconButton( IconButton(
onPressed: () { onPressed: () {
context.push( context.push(
'${ScreenPaths.groupManagement}/add_users/${widget.groupId}'); '${ScreenPaths.circleManagement}/add_users/${widget.circleId}');
}, },
icon: const Icon(Icons.add)), icon: const Icon(Icons.add)),
], ],
@ -230,7 +230,7 @@ class _GroupEditorScreenState extends State<GroupEditorScreen> {
child: StatusAndRefreshButton( child: StatusAndRefreshButton(
valueListenable: nss.connectionUpdateStatus, valueListenable: nss.connectionUpdateStatus,
refreshFunction: () async => refreshFunction: () async =>
manager.refreshGroupMemberships(groupData), manager.refreshCircleMemberships(circleData),
), ),
) )
], ],
@ -255,7 +255,7 @@ class _GroupEditorScreenState extends State<GroupEditorScreen> {
), ),
trailing: IconButton( trailing: IconButton(
onPressed: () async => onPressed: () async =>
removeUserFromGroup(manager, m), removeUserFromCircle(manager, m),
icon: const Icon(Icons.remove), icon: const Icon(Icons.remove),
), ),
); );

View file

@ -8,8 +8,8 @@ import '../routes.dart';
import '../services/connections_manager.dart'; import '../services/connections_manager.dart';
import '../utils/active_profile_selector.dart'; import '../utils/active_profile_selector.dart';
class GroupManagementScreen extends StatelessWidget { class CircleManagementScreen extends StatelessWidget {
const GroupManagementScreen({super.key}); const CircleManagementScreen({super.key});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -17,17 +17,17 @@ class GroupManagementScreen extends StatelessWidget {
.watch<ActiveProfileSelector<ConnectionsManager>>() .watch<ActiveProfileSelector<ConnectionsManager>>()
.activeEntry .activeEntry
.value; .value;
final groups = manager.getMyGroups(); final circles = manager.getMyCircles();
groups.sort((g1, g2) => g1.name.compareTo(g2.name)); circles.sort((g1, g2) => g1.name.compareTo(g2.name));
return Scaffold( return Scaffold(
appBar: StandardAppBar.build( appBar: StandardAppBar.build(
context, context,
'Groups Management', 'Circles Management',
withHome: false, withHome: false,
actions: [ actions: [
IconButton( IconButton(
onPressed: () => context.push( onPressed: () => context.push(
'${ScreenPaths.groupManagement}/new', '${ScreenPaths.circleManagement}/new',
), ),
icon: const Icon(Icons.add), icon: const Icon(Icons.add),
), ),
@ -36,21 +36,21 @@ class GroupManagementScreen extends StatelessWidget {
body: Center( body: Center(
child: RefreshIndicator( child: RefreshIndicator(
onRefresh: () async { onRefresh: () async {
manager.refreshGroups(); manager.refreshCircles();
}, },
child: ResponsiveMaxWidth( child: ResponsiveMaxWidth(
child: ListView.separated( child: ListView.separated(
physics: const AlwaysScrollableScrollPhysics(), physics: const AlwaysScrollableScrollPhysics(),
itemBuilder: (context, index) { itemBuilder: (context, index) {
final group = groups[index]; final circle = circles[index];
return ListTile( return ListTile(
title: Text(group.name), title: Text(circle.name),
onTap: () => context.push( onTap: () => context.push(
'${ScreenPaths.groupManagement}/show/${group.id}'), '${ScreenPaths.circleManagement}/show/${circle.id}'),
); );
}, },
separatorBuilder: (_, __) => const Divider(), separatorBuilder: (_, __) => const Divider(),
itemCount: groups.length, itemCount: circles.length,
), ),
), ),
), ),

View file

@ -16,8 +16,8 @@ import '../controls/padding.dart';
import '../controls/standard_appbar.dart'; import '../controls/standard_appbar.dart';
import '../controls/timeline/status_header_control.dart'; import '../controls/timeline/status_header_control.dart';
import '../globals.dart'; import '../globals.dart';
import '../models/circle_data.dart';
import '../models/exec_error.dart'; import '../models/exec_error.dart';
import '../models/group_data.dart';
import '../models/image_entry.dart'; import '../models/image_entry.dart';
import '../models/link_preview_data.dart'; import '../models/link_preview_data.dart';
import '../models/media_attachment_uploads/new_entry_media_items.dart'; import '../models/media_attachment_uploads/new_entry_media_items.dart';
@ -56,7 +56,7 @@ class _EditorScreenState extends State<EditorScreen> {
final existingMediaItems = <ImageEntry>[]; final existingMediaItems = <ImageEntry>[];
final focusNode = FocusNode(); final focusNode = FocusNode();
Visibility visibility = Visibility.public(); Visibility visibility = Visibility.public();
GroupData? currentGroup; CircleData? currentCircle;
var isSubmitting = false; var isSubmitting = false;
@ -568,25 +568,25 @@ class _EditorScreenState extends State<EditorScreen> {
], ],
); );
} }
final groups = context final circles = context
.watch<ActiveProfileSelector<TimelineManager>>() .watch<ActiveProfileSelector<TimelineManager>>()
.activeEntry .activeEntry
.andThen((tm) => tm.getGroups()) .andThen((tm) => tm.getCircles())
.getValueOrElse(() => []); .getValueOrElse(() => []);
groups.sort((g1, g2) => g1.name.compareTo(g2.name)); circles.sort((g1, g2) => g1.name.compareTo(g2.name));
final groupMenuItems = <DropdownMenuEntry<GroupData>>[]; final circleMenuItems = <DropdownMenuEntry<CircleData>>[];
groupMenuItems.add(DropdownMenuEntry( circleMenuItems.add(DropdownMenuEntry(
value: GroupData.followersPseudoGroup, value: CircleData.followersPseudoCircle,
label: GroupData.followersPseudoGroup.name)); label: CircleData.followersPseudoCircle.name));
groupMenuItems.add(DropdownMenuEntry( circleMenuItems.add(DropdownMenuEntry(
value: GroupData('', ''), label: '-', enabled: false)); value: CircleData('', ''), label: '-', enabled: false));
groupMenuItems.addAll(groups.map((g) => DropdownMenuEntry( circleMenuItems.addAll(circles.map((g) => DropdownMenuEntry(
value: g, value: g,
label: g.name, label: g.name,
))); )));
if (!groups.contains(currentGroup)) { if (!circles.contains(currentCircle)) {
currentGroup = null; currentCircle = null;
} }
return Row( return Row(
children: [ children: [
@ -602,14 +602,14 @@ class _EditorScreenState extends State<EditorScreen> {
return; return;
} }
if (value == VisibilityType.private && currentGroup == null) { if (value == VisibilityType.private && currentCircle == null) {
visibility = Visibility.private(); visibility = Visibility.private();
return; return;
} }
visibility = Visibility( visibility = Visibility(
type: VisibilityType.private, type: VisibilityType.private,
allowedGroupIds: [currentGroup!.id], allowedCircleIds: [currentCircle!.id],
); );
}); });
}, },
@ -622,20 +622,20 @@ class _EditorScreenState extends State<EditorScreen> {
), ),
const HorizontalPadding(), const HorizontalPadding(),
if (visibility.type == VisibilityType.private) if (visibility.type == VisibilityType.private)
DropdownMenu<GroupData>( DropdownMenu<CircleData>(
enabled: !widget.forEditing, enabled: !widget.forEditing,
initialSelection: currentGroup, initialSelection: currentCircle,
onSelected: (value) { onSelected: (value) {
setState(() { setState(() {
currentGroup = value; currentCircle = value;
visibility = Visibility( visibility = Visibility(
type: VisibilityType.private, type: VisibilityType.private,
allowedGroupIds: allowedCircleIds:
currentGroup == null ? [] : [currentGroup!.id], currentCircle == null ? [] : [currentCircle!.id],
); );
}); });
}, },
dropdownMenuEntries: groupMenuItems, dropdownMenuEntries: circleMenuItems,
), ),
], ],
); );

View file

@ -12,7 +12,7 @@ import '../controls/standard_app_drawer.dart';
import '../controls/timeline/timeline_panel.dart'; import '../controls/timeline/timeline_panel.dart';
import '../globals.dart'; import '../globals.dart';
import '../models/TimelineIdentifiers.dart'; import '../models/TimelineIdentifiers.dart';
import '../models/group_data.dart'; import '../models/circle_data.dart';
import '../services/auth_service.dart'; import '../services/auth_service.dart';
import '../services/network_status_service.dart'; import '../services/network_status_service.dart';
import '../services/timeline_manager.dart'; import '../services/timeline_manager.dart';
@ -30,18 +30,18 @@ class _HomeScreenState extends State<HomeScreen> {
final postText = TextEditingController(); final postText = TextEditingController();
var currentType = TimelineType.home; var currentType = TimelineType.home;
GroupData? currentGroup; CircleData? currentCircle;
final types = [ final types = [
TimelineType.self, TimelineType.self,
TimelineType.home, TimelineType.home,
TimelineType.global, TimelineType.circle,
TimelineType.local, TimelineType.local,
TimelineType.group, TimelineType.circle,
]; ];
void updateTimeline(TimelineManager manager) { void updateTimeline(TimelineManager manager) {
if (currentType == TimelineType.group && currentGroup == null) { if (currentType == TimelineType.circle && currentCircle == null) {
_logger.finest('Group timeline but no group selected so not updating'); _logger.finest('Circle timeline but no circle selected so not updating');
return; return;
} }
_logger.finest('Updating timeline: $currentTimeline'); _logger.finest('Updating timeline: $currentTimeline');
@ -53,7 +53,7 @@ class _HomeScreenState extends State<HomeScreen> {
TimelineIdentifiers get currentTimeline => TimelineIdentifiers( TimelineIdentifiers get currentTimeline => TimelineIdentifiers(
timeline: currentType, timeline: currentType,
auxData: currentGroup?.id ?? '', auxData: currentCircle?.id ?? '',
); );
@override @override
@ -73,10 +73,10 @@ class _HomeScreenState extends State<HomeScreen> {
.watch<ActiveProfileSelector<TimelineManager>>() .watch<ActiveProfileSelector<TimelineManager>>()
.activeEntry .activeEntry
.value; .value;
final groups = manager.getGroups().getValueOrElse(() => []).toList(); final circles = manager.getCircles().getValueOrElse(() => []).toList();
groups.sort((g1, g2) => g1.name.compareTo(g2.name)); circles.sort((g1, g2) => g1.name.compareTo(g2.name));
if (!groups.contains(currentGroup)) { if (!circles.contains(currentCircle)) {
currentGroup = null; currentCircle = null;
} }
final timeline = TimelinePanel(timeline: currentTimeline); final timeline = TimelinePanel(timeline: currentTimeline);
@ -97,7 +97,7 @@ class _HomeScreenState extends State<HomeScreen> {
title: Row( title: Row(
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
children: [ children: [
if (currentType == TimelineType.group) if (currentType == TimelineType.circle)
PopupMenuButton<TimelineType>( PopupMenuButton<TimelineType>(
initialValue: currentType, initialValue: currentType,
// Callback that sets the selected popup menu item. // Callback that sets the selected popup menu item.
@ -113,7 +113,7 @@ class _HomeScreenState extends State<HomeScreen> {
child: Text(e.toLabel()), child: Text(e.toLabel()),
)) ))
.toList()), .toList()),
if (currentType != TimelineType.group) if (currentType != TimelineType.circle)
DropdownButton<TimelineType>( DropdownButton<TimelineType>(
value: currentType, value: currentType,
items: types items: types
@ -131,18 +131,18 @@ class _HomeScreenState extends State<HomeScreen> {
const HorizontalPadding( const HorizontalPadding(
width: 5.0, width: 5.0,
), ),
if (currentType == TimelineType.group) if (currentType == TimelineType.circle)
DropdownButton<GroupData>( DropdownButton<CircleData>(
value: currentGroup, value: currentCircle,
items: groups items: circles
.map((g) => DropdownMenuItem<GroupData>( .map((g) => DropdownMenuItem<CircleData>(
value: g, value: g,
child: Text(g.name), child: Text(g.name),
)) ))
.toList(), .toList(),
onChanged: (value) { onChanged: (value) {
setState(() { setState(() {
currentGroup = value; currentCircle = value;
}); });
updateTimeline(manager); updateTimeline(manager);
}), }),

View file

@ -6,8 +6,8 @@ import '../controls/html_text_viewer_control.dart';
import '../controls/login_aware_cached_network_image.dart'; import '../controls/login_aware_cached_network_image.dart';
import '../controls/padding.dart'; import '../controls/padding.dart';
import '../globals.dart'; import '../globals.dart';
import '../models/circle_data.dart';
import '../models/connection.dart'; import '../models/connection.dart';
import '../models/group_data.dart';
import '../routes.dart'; import '../routes.dart';
import '../services/auth_service.dart'; import '../services/auth_service.dart';
import '../services/blocks_manager.dart'; import '../services/blocks_manager.dart';
@ -117,7 +117,7 @@ class _UserProfileScreenState extends State<UserProfileScreen> {
const VerticalPadding(), const VerticalPadding(),
if (profile.status == ConnectionStatus.mutual || if (profile.status == ConnectionStatus.mutual ||
profile.status == ConnectionStatus.youFollowThem) profile.status == ConnectionStatus.youFollowThem)
buildGroups(context, profile, connectionManager), buildCircles(context, profile, connectionManager),
], ],
), ),
), ),
@ -138,23 +138,23 @@ class _UserProfileScreenState extends State<UserProfileScreen> {
); );
} }
Widget buildGroups( Widget buildCircles(
BuildContext context, BuildContext context,
Connection profile, Connection profile,
ConnectionsManager manager, ConnectionsManager manager,
) { ) {
final myGroups = manager.getMyGroups(); final myCircles = manager.getMyCircles();
final usersGroups = manager.getGroupsForUser(profile.id).fold( final usersCircles = manager.getCirclesForUser(profile.id).fold(
onSuccess: (groups) => groups.toSet(), onSuccess: (circles) => circles.toSet(),
onError: (error) { onError: (error) {
buildSnackbar(context, 'Error getting group data: $error'); buildSnackbar(context, 'Error getting circle data: $error');
return <GroupData>{}; return <CircleData>{};
}); });
myGroups.sort((g1, g2) => g1.name.compareTo(g2.name)); myCircles.sort((g1, g2) => g1.name.compareTo(g2.name));
final groupsWidgets = myGroups.map((g) { final circlesWidgets = myCircles.map((g) {
return CheckboxListTile( return CheckboxListTile(
title: Text(g.name), title: Text(g.name),
value: usersGroups.contains(g), value: usersCircles.contains(g),
onChanged: (bool? value) async { onChanged: (bool? value) async {
if (isUpdating) { if (isUpdating) {
return; return;
@ -173,9 +173,9 @@ class _UserProfileScreenState extends State<UserProfileScreen> {
isUpdating = true; isUpdating = true;
}); });
if (isAdding) { if (isAdding) {
await manager.addUserToGroup(g, profile); await manager.addUserToCircle(g, profile);
} else { } else {
await manager.removeUserFromGroup(g, profile); await manager.removeUserFromCircle(g, profile);
} }
setState(() { setState(() {
isUpdating = false; isUpdating = false;
@ -186,11 +186,11 @@ class _UserProfileScreenState extends State<UserProfileScreen> {
return Column( return Column(
children: [ children: [
Text( Text(
'Groups: ', 'Circles: ',
style: Theme.of(context).textTheme.titleMedium, style: Theme.of(context).textTheme.titleMedium,
), ),
const VerticalPadding(), const VerticalPadding(),
...groupsWidgets, ...circlesWidgets,
], ],
); );
} }

View file

@ -3,16 +3,16 @@ import '../../models/visibility.dart';
extension VisibilityFriendicaExtensions on Visibility { extension VisibilityFriendicaExtensions on Visibility {
static Visibility fromJson(Map<String, dynamic> json) { static Visibility fromJson(Map<String, dynamic> json) {
final allowedUserIds = _parseAcl(json['allow_cid']); final allowedUserIds = _parseAcl(json['allow_cid']);
final excludedGroupIds = _parseAcl(json['deny_cid']); final excludedCircleIds = _parseAcl(json['deny_cid']);
final allowedGroupIds = _parseAcl(json['allow_gid']); final allowedCircleIds = _parseAcl(json['allow_gid']);
final excludedUserIds = _parseAcl(json['deny_cid']); final excludedUserIds = _parseAcl(json['deny_cid']);
final topLevelPrivate = json['friendica_private']; final topLevelPrivate = json['friendica_private'];
late final VisibilityType type; late final VisibilityType type;
if (topLevelPrivate == null) { if (topLevelPrivate == null) {
type = allowedUserIds.isEmpty && type = allowedUserIds.isEmpty &&
excludedUserIds.isEmpty && excludedUserIds.isEmpty &&
allowedGroupIds.isEmpty && allowedCircleIds.isEmpty &&
excludedGroupIds.isEmpty excludedCircleIds.isEmpty
? VisibilityType.public ? VisibilityType.public
: VisibilityType.private; : VisibilityType.private;
} else { } else {
@ -23,8 +23,8 @@ extension VisibilityFriendicaExtensions on Visibility {
type: type, type: type,
allowedUserIds: allowedUserIds, allowedUserIds: allowedUserIds,
excludedUserIds: excludedUserIds, excludedUserIds: excludedUserIds,
allowedGroupIds: allowedGroupIds, allowedCircleIds: allowedCircleIds,
excludedGroupIds: excludedGroupIds, excludedCircleIds: excludedCircleIds,
); );
} }
@ -32,8 +32,8 @@ extension VisibilityFriendicaExtensions on Visibility {
return { return {
'allow_cid': _idsListToAclString(allowedUserIds), 'allow_cid': _idsListToAclString(allowedUserIds),
'deny_cid': _idsListToAclString(excludedUserIds), 'deny_cid': _idsListToAclString(excludedUserIds),
'allow_gid': _idsListToAclString(allowedGroupIds), 'allow_gid': _idsListToAclString(allowedCircleIds),
'deny_gid': _idsListToAclString(excludedGroupIds), 'deny_gid': _idsListToAclString(excludedCircleIds),
}; };
} }

View file

@ -0,0 +1,8 @@
import '../../models/circle_data.dart';
extension CircleDataMastodonExtensions on CircleData {
static CircleData fromJson(Map<String, dynamic> json) => CircleData(
json['id'],
json['title'],
);
}

View file

@ -1,8 +0,0 @@
import '../../models/group_data.dart';
extension GroupDataMastodonExtensions on GroupData {
static GroupData fromJson(Map<String, dynamic> json) => GroupData(
json['id'],
json['title'],
);
}

View file

@ -1,4 +1,4 @@
import 'package:relatica/models/group_data.dart'; import 'package:relatica/models/circle_data.dart';
import '../../models/visibility.dart'; import '../../models/visibility.dart';
@ -9,12 +9,12 @@ extension VisibilityMastodonExtensions on Visibility {
} }
if (hasDetails) { if (hasDetails) {
final groupId = allowedGroupIds.first; final circleId = allowedCircleIds.first;
if (groupId == GroupData.followersPseudoGroup.id) { if (circleId == CircleData.followersPseudoCircle.id) {
return 'private'; return 'private';
} }
return groupId; return circleId;
} }
return 'private'; return 'private';

View file

@ -5,24 +5,24 @@ import 'package:flutter/material.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:result_monad/result_monad.dart'; import 'package:result_monad/result_monad.dart';
import '../data/interfaces/circles_repo_intf.dart';
import '../data/interfaces/connections_repo_intf.dart'; import '../data/interfaces/connections_repo_intf.dart';
import '../data/interfaces/groups_repo.intf.dart';
import '../friendica_client/friendica_client.dart'; import '../friendica_client/friendica_client.dart';
import '../friendica_client/paging_data.dart'; import '../friendica_client/paging_data.dart';
import '../globals.dart'; import '../globals.dart';
import '../models/auth/profile.dart'; import '../models/auth/profile.dart';
import '../models/circle_data.dart';
import '../models/connection.dart'; import '../models/connection.dart';
import '../models/exec_error.dart'; import '../models/exec_error.dart';
import '../models/group_data.dart';
import '../utils/active_profile_selector.dart'; import '../utils/active_profile_selector.dart';
import 'persistent_info_service.dart'; import 'persistent_info_service.dart';
class ConnectionsManager extends ChangeNotifier { class ConnectionsManager extends ChangeNotifier {
static final _logger = Logger('$ConnectionsManager'); static final _logger = Logger('$ConnectionsManager');
late final IConnectionsRepo conRepo; late final IConnectionsRepo conRepo;
late final IGroupsRepo groupsRepo; late final ICirclesRepo circlesRepo;
late final Profile profile; late final Profile profile;
var groupsNotInitialized = true; var circlesNotInitialized = true;
var _lastUpdateStatus = ''; var _lastUpdateStatus = '';
String get lastUpdateStatus => _lastUpdateStatus.isNotEmpty String get lastUpdateStatus => _lastUpdateStatus.isNotEmpty
@ -34,12 +34,12 @@ class ConnectionsManager extends ChangeNotifier {
.withResult((text) => _lastUpdateStatus = text) .withResult((text) => _lastUpdateStatus = text)
.getValueOrElse(() => 'Unknown'); .getValueOrElse(() => 'Unknown');
ConnectionsManager(this.profile, this.conRepo, this.groupsRepo); ConnectionsManager(this.profile, this.conRepo, this.circlesRepo);
void clear() { void clear() {
conRepo.clear(); conRepo.clear();
groupsRepo.clear(); circlesRepo.clear();
groupsNotInitialized = true; circlesNotInitialized = true;
_lastUpdateStatus = ''; _lastUpdateStatus = '';
notifyListeners(); notifyListeners();
} }
@ -234,18 +234,18 @@ class ConnectionsManager extends ChangeNotifier {
notifyListeners(); notifyListeners();
} }
List<GroupData> getMyGroups() { List<CircleData> getMyCircles() {
if (groupsNotInitialized) { if (circlesNotInitialized) {
groupsNotInitialized = false; circlesNotInitialized = false;
_updateMyGroups(true); _updateMyCircles(true);
} }
return groupsRepo.getMyGroups(); return circlesRepo.getMyCircles();
} }
Result<List<Connection>, ExecError> getGroupMembers(GroupData group) { Result<List<Connection>, ExecError> getCircleMembers(CircleData circle) {
return groupsRepo return circlesRepo
.getGroupMembers(group) .getCircleMembers(circle)
.transform( .transform(
(members) => members (members) => members
..sort((c1, c2) => ..sort((c1, c2) =>
@ -254,71 +254,71 @@ class ConnectionsManager extends ChangeNotifier {
.execErrorCast(); .execErrorCast();
} }
FutureResult<GroupData, ExecError> createGroup(String newName) async { FutureResult<CircleData, ExecError> createCircle(String newName) async {
final result = await GroupsClient(profile) final result = await CirclesClient(profile)
.createGroup(newName) .createCircle(newName)
.withResultAsync((newGroup) async { .withResultAsync((newCircle) async {
groupsRepo.upsertGroup(newGroup); circlesRepo.upsertCircle(newCircle);
notifyListeners(); notifyListeners();
}); });
return result.execErrorCast(); return result.execErrorCast();
} }
FutureResult<GroupData, ExecError> renameGroup( FutureResult<CircleData, ExecError> renameCircle(
String id, String newName) async { String id, String newName) async {
final result = await GroupsClient(profile) final result = await CirclesClient(profile)
.renameGroup(id, newName) .renameCircle(id, newName)
.withResultAsync((renamedGroup) async { .withResultAsync((renamedCircle) async {
groupsRepo.upsertGroup(renamedGroup); circlesRepo.upsertCircle(renamedCircle);
notifyListeners(); notifyListeners();
}); });
return result.execErrorCast(); return result.execErrorCast();
} }
FutureResult<bool, ExecError> deleteGroup(GroupData groupData) async { FutureResult<bool, ExecError> deleteCircle(CircleData circleData) async {
final result = await GroupsClient(profile) final result = await CirclesClient(profile)
.deleteGroup(groupData) .deleteCircle(circleData)
.withResultAsync((_) async { .withResultAsync((_) async {
groupsRepo.deleteGroup(groupData); circlesRepo.deleteCircle(circleData);
notifyListeners(); notifyListeners();
}); });
return result.execErrorCast(); return result.execErrorCast();
} }
void refreshGroups() { void refreshCircles() {
_updateMyGroups(true); _updateMyCircles(true);
} }
Future<void> refreshGroupMemberships(GroupData group) async { Future<void> refreshCircleMemberships(CircleData circle) async {
var page = PagingData(limit: 50); var page = PagingData(limit: 50);
final client = GroupsClient(profile); final client = CirclesClient(profile);
final allResults = <Connection>{}; final allResults = <Connection>{};
var moreResults = true; var moreResults = true;
while (moreResults) { while (moreResults) {
await client.getGroupMembers(group, page).match(onSuccess: (results) { await client.getCircleMembers(circle, page).match(onSuccess: (results) {
moreResults = results.data.isNotEmpty && results.next != null; moreResults = results.data.isNotEmpty && results.next != null;
page = results.next ?? page; page = results.next ?? page;
allResults.addAll(results.data); allResults.addAll(results.data);
}, onError: (error) { }, onError: (error) {
_logger.severe('Error getting group listing data: $error'); _logger.severe('Error getting circle listing data: $error');
moreResults = false; moreResults = false;
}); });
} }
groupsRepo.deleteGroup(group); circlesRepo.deleteCircle(circle);
groupsRepo.upsertGroup(group); circlesRepo.upsertCircle(circle);
for (final c in allResults) { for (final c in allResults) {
upsertConnection(c); upsertConnection(c);
groupsRepo.addConnectionToGroup(group, c); circlesRepo.addConnectionToCircle(circle, c);
} }
notifyListeners(); notifyListeners();
} }
Result<List<GroupData>, ExecError> getGroupsForUser(String id) { Result<List<CircleData>, ExecError> getCirclesForUser(String id) {
final result = groupsRepo.getGroupsForUser(id); final result = circlesRepo.getCirclesForUser(id);
if (result.isSuccess) { if (result.isSuccess) {
print("Groups for user $id: $result"); print("Circles for user $id: $result");
return result; return result;
} }
@ -326,35 +326,35 @@ class ConnectionsManager extends ChangeNotifier {
return result; return result;
} }
_refreshGroupListData(id, true); _refreshCircleListData(id, true);
return Result.ok(UnmodifiableListView([])); return Result.ok(UnmodifiableListView([]));
} }
FutureResult<bool, ExecError> addUserToGroup( FutureResult<bool, ExecError> addUserToCircle(
GroupData group, Connection connection) async { CircleData circle, Connection connection) async {
_logger.finest('Adding ${connection.name} to group: ${group.name}'); _logger.finest('Adding ${connection.name} to circle: ${circle.name}');
return await GroupsClient(profile) return await CirclesClient(profile)
.addConnectionToGroup(group, connection) .addConnectionToCircle(circle, connection)
.withResultAsync((_) async => await refreshGroupMemberships(group)) .withResultAsync((_) async => await refreshCircleMemberships(circle))
.withResult((_) => notifyListeners()) .withResult((_) => notifyListeners())
.mapError((error) { .mapError((error) {
_logger _logger.severe(
.severe('Error adding ${connection.name} from group: ${group.name}'); 'Error adding ${connection.name} from circle: ${circle.name}');
return error; return error;
}); });
} }
FutureResult<bool, ExecError> removeUserFromGroup( FutureResult<bool, ExecError> removeUserFromCircle(
GroupData group, Connection connection) async { CircleData circle, Connection connection) async {
_logger.finest('Removing ${connection.name} from group: ${group.name}'); _logger.finest('Removing ${connection.name} from circle: ${circle.name}');
return GroupsClient(profile) return CirclesClient(profile)
.removeConnectionFromGroup(group, connection) .removeConnectionFromCircle(circle, connection)
.withResultAsync((_) async => await refreshGroupMemberships(group)) .withResultAsync((_) async => await refreshCircleMemberships(circle))
.withResult((_) => notifyListeners()) .withResult((_) => notifyListeners())
.mapError( .mapError(
(error) { (error) {
_logger.severe( _logger.severe(
'Error removing ${connection.name} from group: ${group.name}'); 'Error removing ${connection.name} from circle: ${circle.name}');
return error; return error;
}, },
); );
@ -391,19 +391,19 @@ class ConnectionsManager extends ChangeNotifier {
Connection connection, { Connection connection, {
bool withNotifications = true, bool withNotifications = true,
}) async { }) async {
await _updateMyGroups(false); await _updateMyCircles(false);
await _refreshGroupListData(connection.id, false); await _refreshCircleListData(connection.id, false);
await _refreshConnection(connection, false); await _refreshConnection(connection, false);
if (withNotifications) { if (withNotifications) {
notifyListeners(); notifyListeners();
} }
} }
Future<void> _refreshGroupListData(String id, bool withNotification) async { Future<void> _refreshCircleListData(String id, bool withNotification) async {
_logger.finest('Refreshing member list data for Connection $id'); _logger.finest('Refreshing member list data for Connection $id');
await GroupsClient(profile).getMemberGroupsForConnection(id).match( await CirclesClient(profile).getMemberCirclesForConnection(id).match(
onSuccess: (groups) { onSuccess: (circles) {
groupsRepo.updateConnectionGroupData(id, groups); circlesRepo.updateConnectionCircleData(id, circles);
if (withNotification) { if (withNotification) {
notifyListeners(); notifyListeners();
} }
@ -432,19 +432,19 @@ class ConnectionsManager extends ChangeNotifier {
); );
} }
Future<void> _updateMyGroups(bool withNotification) async { Future<void> _updateMyCircles(bool withNotification) async {
_logger.finest('Refreshing my groups list'); _logger.finest('Refreshing my circles list');
await GroupsClient(profile).getGroups().match( await CirclesClient(profile).getCircles().match(
onSuccess: (groups) { onSuccess: (circles) {
_logger.finest('Got updated groups:${groups.map((e) => e.name)}'); _logger.finest('Got updated circles:${circles.map((e) => e.name)}');
groupsRepo.clearMyGroups(); circlesRepo.clearMyCircles();
groupsRepo.addAllGroups(groups); circlesRepo.addAllCircles(circles);
if (withNotification) { if (withNotification) {
notifyListeners(); notifyListeners();
} }
}, },
onError: (error) { onError: (error) {
_logger.severe('Error getting my groups: $error'); _logger.severe('Error getting my circles: $error');
}, },
); );
} }

View file

@ -2,13 +2,13 @@ import 'package:flutter/material.dart' hide Visibility;
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import 'package:result_monad/result_monad.dart'; import 'package:result_monad/result_monad.dart';
import '../data/interfaces/groups_repo.intf.dart'; import '../data/interfaces/circles_repo_intf.dart';
import '../friendica_client/friendica_client.dart'; import '../friendica_client/friendica_client.dart';
import '../models/TimelineIdentifiers.dart'; import '../models/TimelineIdentifiers.dart';
import '../models/auth/profile.dart'; import '../models/auth/profile.dart';
import '../models/circle_data.dart';
import '../models/entry_tree_item.dart'; import '../models/entry_tree_item.dart';
import '../models/exec_error.dart'; import '../models/exec_error.dart';
import '../models/group_data.dart';
import '../models/image_entry.dart'; import '../models/image_entry.dart';
import '../models/media_attachment_uploads/new_entry_media_items.dart'; import '../models/media_attachment_uploads/new_entry_media_items.dart';
import '../models/timeline.dart'; import '../models/timeline.dart';
@ -25,38 +25,38 @@ enum TimelineRefreshType {
class TimelineManager extends ChangeNotifier { class TimelineManager extends ChangeNotifier {
static final _logger = Logger('$TimelineManager'); static final _logger = Logger('$TimelineManager');
final IGroupsRepo groupsRepo; final ICirclesRepo circlesRepo;
final EntryManagerService entryManagerService; final EntryManagerService entryManagerService;
var groupsNotInitialized = true; var circlesNotInitialized = true;
final Profile profile; final Profile profile;
final cachedTimelines = <TimelineIdentifiers, Timeline>{}; final cachedTimelines = <TimelineIdentifiers, Timeline>{};
TimelineManager(this.profile, this.groupsRepo, this.entryManagerService); TimelineManager(this.profile, this.circlesRepo, this.entryManagerService);
void clear() { void clear() {
groupsNotInitialized = true; circlesNotInitialized = true;
cachedTimelines.clear(); cachedTimelines.clear();
entryManagerService.clear(); entryManagerService.clear();
groupsRepo.clear(); circlesRepo.clear();
notifyListeners(); notifyListeners();
} }
Result<List<GroupData>, ExecError> getGroups() { Result<List<CircleData>, ExecError> getCircles() {
if (groupsNotInitialized) { if (circlesNotInitialized) {
_refreshGroupData(); _refreshCircleData();
groupsNotInitialized = false; circlesNotInitialized = false;
return Result.ok([]); return Result.ok([]);
} }
return Result.ok(groupsRepo.getMyGroups()); return Result.ok(circlesRepo.getMyCircles());
} }
Future<void> _refreshGroupData() async { Future<void> _refreshCircleData() async {
_logger.finest('Refreshing member group data '); _logger.finest('Refreshing member circle data ');
await GroupsClient(profile).getGroups().match( await CirclesClient(profile).getCircles().match(
onSuccess: (groups) { onSuccess: (circles) {
groupsRepo.addAllGroups(groups); circlesRepo.addAllCircles(circles);
notifyListeners(); notifyListeners();
}, },
onError: (error) { onError: (error) {