kopia lustrzana https://gitlab.com/mysocialportal/relatica
Add suggested connections panel to the Explorer Screen
rodzic
ea36d9c75f
commit
9913161a3a
|
@ -0,0 +1,50 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import '../models/connection.dart';
|
||||
import '../models/exec_error.dart';
|
||||
import '../riverpod_controllers/account_services.dart';
|
||||
import '../riverpod_controllers/connection_manager_services.dart';
|
||||
import '../routes.dart';
|
||||
import 'image_control.dart';
|
||||
|
||||
class ConnectionListWidget extends ConsumerWidget {
|
||||
final String contactId;
|
||||
|
||||
const ConnectionListWidget({super.key, required this.contactId});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final profile = ref.watch(activeProfileProvider);
|
||||
final contactResult = ref.watch(connectionByIdProvider(profile, contactId));
|
||||
|
||||
return contactResult.fold(onSuccess: (contact) {
|
||||
return ListTile(
|
||||
onTap: () {
|
||||
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: Text(contact.status.label()),
|
||||
);
|
||||
}, onError: (error) {
|
||||
final msg = error.type == ErrorType.notFound
|
||||
? 'Connection not found'
|
||||
: error.message;
|
||||
return ListTile(title: Text(msg));
|
||||
});
|
||||
}
|
||||
}
|
|
@ -146,7 +146,7 @@ extension FriendStatusWriter on ConnectionStatus {
|
|||
case ConnectionStatus.you:
|
||||
return "You";
|
||||
case ConnectionStatus.unknown:
|
||||
return 'Unknown';
|
||||
return '';
|
||||
case ConnectionStatus.blocked:
|
||||
return 'Blocked';
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ import '../models/auth/profile.dart';
|
|||
import '../models/connection.dart';
|
||||
import '../models/exec_error.dart';
|
||||
import '../models/networking/paging_data.dart';
|
||||
import '../riverpod_controllers/networking/friendica_trending_client_services.dart';
|
||||
import 'circles_repo_services.dart';
|
||||
import 'networking/friendica_relationship_client_services.dart';
|
||||
import 'persistent_info_services.dart';
|
||||
|
@ -433,3 +434,20 @@ class AllContactsUpdater extends _$AllContactsUpdater {
|
|||
state = false;
|
||||
}
|
||||
}
|
||||
|
||||
@riverpod
|
||||
Future<Result<List<Connection>, ExecError>> suggestedConnections(
|
||||
Ref ref, Profile profile) async {
|
||||
final result =
|
||||
await ref.watch(suggestedConnectionsClientProvider(profile).future);
|
||||
|
||||
await result.withResultAsync((suggestions) async {
|
||||
for (final s in suggestions) {
|
||||
await ref
|
||||
.read(connectionModifierProvider(profile, s).notifier)
|
||||
.upsertConnection(s);
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -1015,6 +1015,144 @@ class _UpdateStatusProviderElement extends AutoDisposeProviderElement<String>
|
|||
Profile get profile => (origin as UpdateStatusProvider).profile;
|
||||
}
|
||||
|
||||
String _$suggestedConnectionsHash() =>
|
||||
r'5b9997d6815db3666b5e99f7d7d39b3f7f35fd8c';
|
||||
|
||||
/// See also [suggestedConnections].
|
||||
@ProviderFor(suggestedConnections)
|
||||
const suggestedConnectionsProvider = SuggestedConnectionsFamily();
|
||||
|
||||
/// See also [suggestedConnections].
|
||||
class SuggestedConnectionsFamily
|
||||
extends Family<AsyncValue<Result<List<Connection>, ExecError>>> {
|
||||
/// See also [suggestedConnections].
|
||||
const SuggestedConnectionsFamily();
|
||||
|
||||
/// See also [suggestedConnections].
|
||||
SuggestedConnectionsProvider call(
|
||||
Profile profile,
|
||||
) {
|
||||
return SuggestedConnectionsProvider(
|
||||
profile,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
SuggestedConnectionsProvider getProviderOverride(
|
||||
covariant SuggestedConnectionsProvider provider,
|
||||
) {
|
||||
return call(
|
||||
provider.profile,
|
||||
);
|
||||
}
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _dependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
|
||||
_allTransitiveDependencies;
|
||||
|
||||
@override
|
||||
String? get name => r'suggestedConnectionsProvider';
|
||||
}
|
||||
|
||||
/// See also [suggestedConnections].
|
||||
class SuggestedConnectionsProvider
|
||||
extends AutoDisposeFutureProvider<Result<List<Connection>, ExecError>> {
|
||||
/// See also [suggestedConnections].
|
||||
SuggestedConnectionsProvider(
|
||||
Profile profile,
|
||||
) : this._internal(
|
||||
(ref) => suggestedConnections(
|
||||
ref as SuggestedConnectionsRef,
|
||||
profile,
|
||||
),
|
||||
from: suggestedConnectionsProvider,
|
||||
name: r'suggestedConnectionsProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$suggestedConnectionsHash,
|
||||
dependencies: SuggestedConnectionsFamily._dependencies,
|
||||
allTransitiveDependencies:
|
||||
SuggestedConnectionsFamily._allTransitiveDependencies,
|
||||
profile: profile,
|
||||
);
|
||||
|
||||
SuggestedConnectionsProvider._internal(
|
||||
super._createNotifier, {
|
||||
required super.name,
|
||||
required super.dependencies,
|
||||
required super.allTransitiveDependencies,
|
||||
required super.debugGetCreateSourceHash,
|
||||
required super.from,
|
||||
required this.profile,
|
||||
}) : super.internal();
|
||||
|
||||
final Profile profile;
|
||||
|
||||
@override
|
||||
Override overrideWith(
|
||||
FutureOr<Result<List<Connection>, ExecError>> Function(
|
||||
SuggestedConnectionsRef provider)
|
||||
create,
|
||||
) {
|
||||
return ProviderOverride(
|
||||
origin: this,
|
||||
override: SuggestedConnectionsProvider._internal(
|
||||
(ref) => create(ref as SuggestedConnectionsRef),
|
||||
from: from,
|
||||
name: null,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
debugGetCreateSourceHash: null,
|
||||
profile: profile,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
AutoDisposeFutureProviderElement<Result<List<Connection>, ExecError>>
|
||||
createElement() {
|
||||
return _SuggestedConnectionsProviderElement(this);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is SuggestedConnectionsProvider && other.profile == profile;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||
hash = _SystemHash.combine(hash, profile.hashCode);
|
||||
|
||||
return _SystemHash.finish(hash);
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
mixin SuggestedConnectionsRef
|
||||
on AutoDisposeFutureProviderRef<Result<List<Connection>, ExecError>> {
|
||||
/// The parameter `profile` of this provider.
|
||||
Profile get profile;
|
||||
}
|
||||
|
||||
class _SuggestedConnectionsProviderElement
|
||||
extends AutoDisposeFutureProviderElement<
|
||||
Result<List<Connection>, ExecError>> with SuggestedConnectionsRef {
|
||||
_SuggestedConnectionsProviderElement(super.provider);
|
||||
|
||||
@override
|
||||
Profile get profile => (origin as SuggestedConnectionsProvider).profile;
|
||||
}
|
||||
|
||||
String _$connectionsRepoSyncHash() =>
|
||||
r'982c1723afad0c4b4aaccc2949664117856f4944';
|
||||
|
||||
|
|
|
@ -1,15 +1,17 @@
|
|||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:relatica/models/link_preview_data.dart';
|
||||
import 'package:relatica/serializers/mastodon/link_preview_mastodon_extensions.dart';
|
||||
import 'package:result_monad/result_monad.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
|
||||
import '../../models/auth/profile.dart';
|
||||
import '../../models/connection.dart';
|
||||
import '../../models/exec_error.dart';
|
||||
import '../../models/hashtag.dart';
|
||||
import '../../models/link_preview_data.dart';
|
||||
import '../../models/networking/paging_data.dart';
|
||||
import '../../models/timeline_entry.dart';
|
||||
import '../../serializers/mastodon/connection_mastodon_extensions.dart';
|
||||
import '../../serializers/mastodon/hashtag_mastodon_extensions.dart';
|
||||
import '../../serializers/mastodon/link_preview_mastodon_extensions.dart';
|
||||
import '../../serializers/mastodon/timeline_entry_mastodon_extensions.dart';
|
||||
import 'friendica_client_services.dart';
|
||||
|
||||
|
@ -67,3 +69,16 @@ Future<Result<List<LinkPreviewData>, ExecError>> trendingLinks(
|
|||
.toList());
|
||||
return result.execErrorCast();
|
||||
}
|
||||
|
||||
@riverpod
|
||||
Future<Result<List<Connection>, ExecError>> suggestedConnectionsClient(
|
||||
Ref ref, Profile profile) async {
|
||||
final url = 'https://${profile.serverName}/api/v2/suggestions';
|
||||
final request = Uri.parse(url);
|
||||
final result = await ref
|
||||
.read(getApiListRequestProvider(profile, request).future)
|
||||
.transform((response) => response.data
|
||||
.map((json) => connectionFromJson(ref, profile, json['account']))
|
||||
.toList());
|
||||
return result.execErrorCast();
|
||||
}
|
||||
|
|
|
@ -501,5 +501,145 @@ class _TrendingLinksProviderElement extends AutoDisposeFutureProviderElement<
|
|||
@override
|
||||
PagingData get page => (origin as TrendingLinksProvider).page;
|
||||
}
|
||||
|
||||
String _$suggestedConnectionsClientHash() =>
|
||||
r'7f593123b01add5c75894697c03c57dc8024b2d2';
|
||||
|
||||
/// See also [suggestedConnectionsClient].
|
||||
@ProviderFor(suggestedConnectionsClient)
|
||||
const suggestedConnectionsClientProvider = SuggestedConnectionsClientFamily();
|
||||
|
||||
/// See also [suggestedConnectionsClient].
|
||||
class SuggestedConnectionsClientFamily
|
||||
extends Family<AsyncValue<Result<List<Connection>, ExecError>>> {
|
||||
/// See also [suggestedConnectionsClient].
|
||||
const SuggestedConnectionsClientFamily();
|
||||
|
||||
/// See also [suggestedConnectionsClient].
|
||||
SuggestedConnectionsClientProvider call(
|
||||
Profile profile,
|
||||
) {
|
||||
return SuggestedConnectionsClientProvider(
|
||||
profile,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
SuggestedConnectionsClientProvider getProviderOverride(
|
||||
covariant SuggestedConnectionsClientProvider provider,
|
||||
) {
|
||||
return call(
|
||||
provider.profile,
|
||||
);
|
||||
}
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _dependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
|
||||
_allTransitiveDependencies;
|
||||
|
||||
@override
|
||||
String? get name => r'suggestedConnectionsClientProvider';
|
||||
}
|
||||
|
||||
/// See also [suggestedConnectionsClient].
|
||||
class SuggestedConnectionsClientProvider
|
||||
extends AutoDisposeFutureProvider<Result<List<Connection>, ExecError>> {
|
||||
/// See also [suggestedConnectionsClient].
|
||||
SuggestedConnectionsClientProvider(
|
||||
Profile profile,
|
||||
) : this._internal(
|
||||
(ref) => suggestedConnectionsClient(
|
||||
ref as SuggestedConnectionsClientRef,
|
||||
profile,
|
||||
),
|
||||
from: suggestedConnectionsClientProvider,
|
||||
name: r'suggestedConnectionsClientProvider',
|
||||
debugGetCreateSourceHash:
|
||||
const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$suggestedConnectionsClientHash,
|
||||
dependencies: SuggestedConnectionsClientFamily._dependencies,
|
||||
allTransitiveDependencies:
|
||||
SuggestedConnectionsClientFamily._allTransitiveDependencies,
|
||||
profile: profile,
|
||||
);
|
||||
|
||||
SuggestedConnectionsClientProvider._internal(
|
||||
super._createNotifier, {
|
||||
required super.name,
|
||||
required super.dependencies,
|
||||
required super.allTransitiveDependencies,
|
||||
required super.debugGetCreateSourceHash,
|
||||
required super.from,
|
||||
required this.profile,
|
||||
}) : super.internal();
|
||||
|
||||
final Profile profile;
|
||||
|
||||
@override
|
||||
Override overrideWith(
|
||||
FutureOr<Result<List<Connection>, ExecError>> Function(
|
||||
SuggestedConnectionsClientRef provider)
|
||||
create,
|
||||
) {
|
||||
return ProviderOverride(
|
||||
origin: this,
|
||||
override: SuggestedConnectionsClientProvider._internal(
|
||||
(ref) => create(ref as SuggestedConnectionsClientRef),
|
||||
from: from,
|
||||
name: null,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
debugGetCreateSourceHash: null,
|
||||
profile: profile,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
AutoDisposeFutureProviderElement<Result<List<Connection>, ExecError>>
|
||||
createElement() {
|
||||
return _SuggestedConnectionsClientProviderElement(this);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is SuggestedConnectionsClientProvider &&
|
||||
other.profile == profile;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||
hash = _SystemHash.combine(hash, profile.hashCode);
|
||||
|
||||
return _SystemHash.finish(hash);
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
mixin SuggestedConnectionsClientRef
|
||||
on AutoDisposeFutureProviderRef<Result<List<Connection>, ExecError>> {
|
||||
/// The parameter `profile` of this provider.
|
||||
Profile get profile;
|
||||
}
|
||||
|
||||
class _SuggestedConnectionsClientProviderElement
|
||||
extends AutoDisposeFutureProviderElement<
|
||||
Result<List<Connection>, ExecError>>
|
||||
with SuggestedConnectionsClientRef {
|
||||
_SuggestedConnectionsClientProviderElement(super.provider);
|
||||
|
||||
@override
|
||||
Profile get profile => (origin as SuggestedConnectionsClientProvider).profile;
|
||||
}
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
import '../controls/app_bottom_nav_bar.dart';
|
||||
import '../controls/connection_list_widget.dart';
|
||||
import '../controls/current_profile_button.dart';
|
||||
import '../controls/image_control.dart';
|
||||
import '../controls/responsive_max_width.dart';
|
||||
import '../controls/standard_app_drawer.dart';
|
||||
import '../controls/status_and_refresh_button.dart';
|
||||
|
@ -14,7 +13,6 @@ import '../models/connection.dart';
|
|||
import '../riverpod_controllers/account_services.dart';
|
||||
import '../riverpod_controllers/connection_manager_services.dart';
|
||||
import '../riverpod_controllers/networking/network_status_services.dart';
|
||||
import '../routes.dart';
|
||||
import '../utils/snackbar_builder.dart';
|
||||
|
||||
class ContactsScreen extends ConsumerStatefulWidget {
|
||||
|
@ -60,27 +58,7 @@ class _ContactsScreenState extends ConsumerState<ContactsScreen> {
|
|||
child: ListView.separated(
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
itemBuilder: (context, index) {
|
||||
final contact = contacts[index];
|
||||
return ListTile(
|
||||
onTap: () {
|
||||
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: Text(contact.status.label()),
|
||||
);
|
||||
return ConnectionListWidget(contactId: contacts[index].id);
|
||||
},
|
||||
separatorBuilder: (context, index) => const Divider(),
|
||||
itemCount: contacts.length),
|
||||
|
|
|
@ -5,6 +5,7 @@ import 'package:logging/logging.dart';
|
|||
|
||||
import '../controls/app_bottom_nav_bar.dart';
|
||||
import '../controls/async_value_widget.dart';
|
||||
import '../controls/connection_list_widget.dart';
|
||||
import '../controls/current_profile_button.dart';
|
||||
import '../controls/error_message_widget.dart';
|
||||
import '../controls/padding.dart';
|
||||
|
@ -17,6 +18,7 @@ import '../models/auth/profile.dart';
|
|||
import '../models/networking/paging_data.dart';
|
||||
import '../models/timeline_entry.dart';
|
||||
import '../riverpod_controllers/account_services.dart';
|
||||
import '../riverpod_controllers/connection_manager_services.dart';
|
||||
import '../riverpod_controllers/entry_tree_item_services.dart';
|
||||
import '../riverpod_controllers/hashtag_service.dart';
|
||||
import '../riverpod_controllers/networking/friendica_trending_client_services.dart';
|
||||
|
@ -29,7 +31,7 @@ class ExploreScreen extends ConsumerWidget {
|
|||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
return DefaultTabController(
|
||||
length: 3,
|
||||
length: 4,
|
||||
child: Scaffold(
|
||||
drawer: const StandardAppDrawer(skipPopDismiss: false),
|
||||
appBar: AppBar(
|
||||
|
@ -39,6 +41,7 @@ class ExploreScreen extends ConsumerWidget {
|
|||
Tab(icon: Icon(Icons.search)),
|
||||
Tab(icon: Icon(Icons.bolt)),
|
||||
Tab(icon: Icon(Icons.tag)),
|
||||
Tab(icon: Icon(Icons.recommend))
|
||||
],
|
||||
),
|
||||
),
|
||||
|
@ -47,6 +50,7 @@ class ExploreScreen extends ConsumerWidget {
|
|||
const SearchPanel(),
|
||||
_TrendsPanel(),
|
||||
const _FollowedTagsPanel(),
|
||||
const _SuggestedConnectionsWidget(),
|
||||
]),
|
||||
),
|
||||
bottomNavigationBar: const AppBottomNavBar(
|
||||
|
@ -308,3 +312,32 @@ class _FollowedTagsPanel extends ConsumerWidget {
|
|||
]));
|
||||
}
|
||||
}
|
||||
|
||||
class _SuggestedConnectionsWidget extends ConsumerWidget {
|
||||
static final _logger = Logger('SuggestedFollowersWidget');
|
||||
|
||||
const _SuggestedConnectionsWidget();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
_logger.fine('Build');
|
||||
final profile = ref.watch(activeProfileProvider);
|
||||
return AsyncValueWidget(
|
||||
ref.watch(suggestedConnectionsProvider(profile)),
|
||||
valueBuilder: (vbContext, vbRef, result) => result.fold(
|
||||
onSuccess: (connections) {
|
||||
return ListView.builder(
|
||||
itemCount: connections.length,
|
||||
itemBuilder: (_, index) {
|
||||
return ConnectionListWidget(contactId: connections[index].id);
|
||||
});
|
||||
},
|
||||
onError: (error) => Center(
|
||||
child: Column(
|
||||
children: [ErrorMessageWidget(message: error.message)],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
Ładowanie…
Reference in New Issue