Refactory Interactions Details to Riverpod providers

merge-requests/67/merge
Hank Grabowski 2024-11-26 17:10:51 -05:00
rodzic c4e739a962
commit 733751038b
8 zmienionych plików z 171 dodań i 152 usunięć

Wyświetl plik

@ -0,0 +1,43 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
typedef ValueWidgetBuilder<T> = Widget Function(
BuildContext context, WidgetRef ref, T value);
class AsyncValueWidget<T> extends ConsumerWidget {
final AsyncValue<T> asyncValue;
final ValueWidgetBuilder<T> valueBuilder;
final bool standaloneHolders;
const AsyncValueWidget(
this.asyncValue, {
required this.valueBuilder,
this.standaloneHolders = false,
super.key,
});
@override
Widget build(BuildContext context, WidgetRef ref) {
return switch (asyncValue) {
AsyncError(:final error) => !standaloneHolders
? Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [Text('Error getting data: $error')],
),
)
: Text('Error getting data: $error'),
AsyncData<T>(:final value) => valueBuilder(context, ref, value),
_ => !standaloneHolders
? const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CircularProgressIndicator(),
],
),
)
: const CircularProgressIndicator(),
};
}
}

Wyświetl plik

@ -0,0 +1,25 @@
import 'package:flutter/material.dart';
class ErrorMessageWidget extends StatelessWidget {
final String message;
final bool standalone;
const ErrorMessageWidget(
{super.key, required this.message, this.standalone = true});
@override
Widget build(BuildContext context) {
final errorWidget = Text(message);
if (standalone) {
return errorWidget;
}
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [errorWidget],
),
);
}
}

Wyświetl plik

@ -22,7 +22,6 @@ import 'services/feature_version_checker.dart';
import 'services/fediverse_server_validator.dart';
import 'services/follow_requests_manager.dart';
import 'services/gallery_service.dart';
import 'services/interactions_manager.dart';
import 'services/network_status_service.dart';
import 'services/notifications_manager.dart';
import 'services/persistent_info_service.dart';
@ -110,9 +109,6 @@ Future<void> dependencyInjectionInitialization() async {
getIt.registerSingleton<ActiveProfileSelector<FollowRequestsManager>>(
ActiveProfileSelector((p) => FollowRequestsManager(p))
..subscribeToProfileSwaps());
getIt.registerSingleton<ActiveProfileSelector<InteractionsManager>>(
ActiveProfileSelector((p) => InteractionsManager(p))
..subscribeToProfileSwaps());
}
Future<void> updateProfileDependencyInjectors(Profile profile) async {

Wyświetl plik

@ -20,7 +20,6 @@ import 'services/connections_manager.dart';
import 'services/entry_manager_service.dart';
import 'services/follow_requests_manager.dart';
import 'services/gallery_service.dart';
import 'services/interactions_manager.dart';
import 'services/notifications_manager.dart';
import 'services/timeline_manager.dart';
import 'update_timer_initialization.dart';
@ -137,9 +136,6 @@ class _AppState extends fr.ConsumerState<App> {
create: (_) =>
getIt<ActiveProfileSelector<FollowRequestsManager>>(),
),
ChangeNotifierProvider<ActiveProfileSelector<InteractionsManager>>(
create: (_) => getIt<ActiveProfileSelector<InteractionsManager>>(),
),
],
child: MaterialApp.router(
// TODO Add back Device Preview once supported in Flutter 3.22+

Wyświetl plik

@ -0,0 +1,39 @@
import 'package:logging/logging.dart';
import 'package:relatica/riverpod_controllers/rp_provider_extension.dart';
import 'package:result_monad/result_monad.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import '../friendica_client/friendica_client.dart';
import '../models/auth/profile.dart';
import '../models/connection.dart';
import '../models/exec_error.dart';
part 'interactions_details_services.g.dart';
const _cacheDuration = Duration(minutes: 5);
final _likesLogger = Logger('LikesForStatusProvider');
final _resharesLogger = Logger('ResharesForStatusProvider');
@riverpod
Future<Result<List<Connection>, ExecError>> likesForStatus(
LikesForStatusRef ref, Profile profile, String statusId) async {
_likesLogger.info('Creating provider for $statusId for Profile $profile');
ref.cacheFor(_cacheDuration);
final likesResult = await InteractionsClient(profile).getLikes(statusId);
_likesLogger.info('Values received for $statusId for Profile $profile');
return likesResult;
}
@riverpod
Future<Result<List<Connection>, ExecError>> resharesForStatus(
ResharesForStatusRef ref, Profile profile, String statusId) async {
ref.cacheFor(_cacheDuration);
_resharesLogger.info('Creating provider for $statusId for Profile $profile');
final resharesResult =
await InteractionsClient(profile).getReshares(statusId);
_resharesLogger.info('Values received for $statusId for Profile $profile');
return resharesResult;
}

Wyświetl plik

@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:provider/provider.dart';
import 'package:relatica/models/connection.dart';
import 'package:relatica/controls/async_value_widget.dart';
import '../controls/image_control.dart';
import '../riverpod_controllers/blocks_services.dart';
@ -16,60 +16,45 @@ class BlocksScreen extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
final profile = context.watch<AccountsService>().currentProfile;
final blocksValue = ref.watch(blocksManagerProvider(profile));
final body = switch (blocksValue) {
AsyncError(:final error) => Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [Text('Error getting blocks: $error')],
),
),
AsyncData<List<Connection>>(:final value) => ListView.builder(
itemBuilder: (context, index) {
final contact = value[index];
return ListTile(
onTap: () async {
context.pushNamed(ScreenPaths.userProfile,
pathParameters: {'id': contact.id});
},
leading: ImageControl(
imageUrl: contact.avatarUrl.toString(),
iconOverride: const Icon(Icons.person),
width: 32.0,
),
title: Text(
'${contact.name} (${contact.handle})',
softWrap: true,
),
subtitle: Text(
'Last Status: ${contact.lastStatus?.toIso8601String() ?? "Unknown"}',
softWrap: true,
),
trailing: ElevatedButton(
onPressed: () async => await ref
.read(blocksManagerProvider(profile).notifier)
.unblockConnection(contact),
child: const Text('Unblock'),
),
);
},
itemCount: value.length,
),
_ => const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CircularProgressIndicator(),
],
),
),
};
return Scaffold(
appBar: AppBar(
title: const Text('Blocks'),
),
body: SafeArea(
child: body,
child: AsyncValueWidget(
blocksValue,
valueBuilder: (context, ref, value) => ListView.builder(
itemBuilder: (context, index) {
final contact = value[index];
return ListTile(
onTap: () async {
context.pushNamed(ScreenPaths.userProfile,
pathParameters: {'id': contact.id});
},
leading: ImageControl(
imageUrl: contact.avatarUrl.toString(),
iconOverride: const Icon(Icons.person),
width: 32.0,
),
title: Text(
'${contact.name} (${contact.handle})',
softWrap: true,
),
subtitle: Text(
'Last Status: ${contact.lastStatus?.toIso8601String() ?? "Unknown"}',
softWrap: true,
),
trailing: ElevatedButton(
onPressed: () async => await ref
.read(blocksManagerProvider(profile).notifier)
.unblockConnection(contact),
child: const Text('Unblock'),
),
);
},
itemCount: value.length,
),
),
),
);
}

Wyświetl plik

@ -1,23 +1,25 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:provider/provider.dart';
import 'package:result_monad/result_monad.dart';
import 'package:relatica/controls/async_value_widget.dart';
import 'package:relatica/riverpod_controllers/interactions_details_services.dart';
import '../controls/error_message_widget.dart';
import '../controls/image_control.dart';
import '../controls/responsive_max_width.dart';
import '../controls/standard_appbar.dart';
import '../controls/status_and_refresh_button.dart';
import '../globals.dart';
import '../models/connection.dart';
import '../models/exec_error.dart';
import '../models/auth/profile.dart';
import '../models/interaction_type_enum.dart';
import '../routes.dart';
import '../services/auth_service.dart';
import '../services/connections_manager.dart';
import '../services/interactions_manager.dart';
import '../services/network_status_service.dart';
import '../utils/active_profile_selector.dart';
class InteractionsViewerScreen extends StatelessWidget {
class InteractionsViewerScreen extends ConsumerWidget {
final String statusId;
final InteractionType type;
@ -27,44 +29,43 @@ class InteractionsViewerScreen extends StatelessWidget {
required this.type,
});
List<Connection> getInteractors(InteractionsManager manager) {
void refreshInteractors(WidgetRef ref, Profile profile) async {
switch (type) {
case InteractionType.like:
return manager.getLikes(statusId);
return ref.invalidate(likesForStatusProvider(profile, statusId));
case InteractionType.reshare:
return manager.getReshares(statusId);
}
}
FutureResult<List<Connection>, ExecError> refreshInteractors(
InteractionsManager manager) async {
switch (type) {
case InteractionType.like:
return await manager.updateLikesForStatus(statusId);
case InteractionType.reshare:
return await manager.updateResharesForStatus(statusId);
return ref.invalidate(resharesForStatusProvider(profile, statusId));
}
}
@override
Widget build(BuildContext context) {
Widget build(BuildContext context, WidgetRef ref) {
final profile = context.watch<AccountsService>().currentProfile;
final nss = getIt<NetworkStatusService>();
final manager = context
.watch<ActiveProfileSelector<InteractionsManager>>()
.activeEntry
.value;
final connections = getInteractors(manager);
final connectionsAsyncValue = switch (type) {
InteractionType.like =>
ref.watch(likesForStatusProvider(profile, statusId)),
InteractionType.reshare =>
ref.watch(resharesForStatusProvider(profile, statusId)),
};
return Scaffold(
appBar: StandardAppBar.build(context, buildTitle(), actions: [
StatusAndRefreshButton(
valueListenable: nss.interactionsLoadingStatus,
refreshFunction: () async => await refreshInteractors(manager),
refreshFunction: () async => refreshInteractors(ref, profile),
busyColor: Theme.of(context).colorScheme.surface,
)
]),
body: Center(
child: ResponsiveMaxWidth(
child: ListView.separated(
child: AsyncValueWidget(connectionsAsyncValue,
valueBuilder: (context, ref, connectionsResult) {
if (connectionsResult.isFailure) {
return ErrorMessageWidget(message: connectionsResult.error.message);
}
final connections = connectionsResult.value;
return ListView.separated(
itemCount: connections.length,
itemBuilder: (context, index) {
final connection = connections[index];
@ -94,8 +95,8 @@ class InteractionsViewerScreen extends StatelessWidget {
);
},
separatorBuilder: (_, __) => const Divider(),
),
),
);
})),
),
);
}

Wyświetl plik

@ -1,66 +0,0 @@
import 'package:flutter/foundation.dart';
import 'package:result_monad/result_monad.dart';
import '../friendica_client/friendica_client.dart';
import '../globals.dart';
import '../models/auth/profile.dart';
import '../models/connection.dart';
import '../models/exec_error.dart';
import 'auth_service.dart';
class InteractionsManager extends ChangeNotifier {
final _likesByStatusId = <String, List<Connection>>{};
final _resharesByStatusId = <String, List<Connection>>{};
final Profile profile;
InteractionsManager(this.profile);
void clear() {
_likesByStatusId.clear();
_resharesByStatusId.clear();
notifyListeners();
}
List<Connection> getLikes(String statusId) {
if (!_likesByStatusId.containsKey(statusId)) {
updateLikesForStatus(statusId);
return [];
}
return _likesByStatusId[statusId]!;
}
List<Connection> getReshares(String statusId) {
if (!_resharesByStatusId.containsKey(statusId)) {
updateResharesForStatus(statusId);
return [];
}
return _resharesByStatusId[statusId]!;
}
FutureResult<List<Connection>, ExecError> updateLikesForStatus(
String statusId) async {
final likesResult =
await InteractionsClient(getIt<AccountsService>().currentProfile)
.getLikes(statusId);
if (likesResult.isSuccess) {
_likesByStatusId[statusId] = likesResult.value;
notifyListeners();
}
return likesResult;
}
FutureResult<List<Connection>, ExecError> updateResharesForStatus(
String statusId) async {
final resharesResult =
await InteractionsClient(getIt<AccountsService>().currentProfile)
.getReshares(statusId);
if (resharesResult.isSuccess) {
_resharesByStatusId[statusId] = resharesResult.value;
notifyListeners();
}
return resharesResult;
}
}