mirror of
https://gitlab.com/mysocialportal/relatica
synced 2024-10-18 13:33:32 +00:00
Merge branch 'advanced-suggestions-ordering' into 'main'
Autocomplete and Profile Load Updates Closes #62 See merge request mysocialportal/relatica!58
This commit is contained in:
commit
45c2b3b490
10 changed files with 106 additions and 16 deletions
|
@ -24,7 +24,10 @@
|
|||
* Multiple profiles from the same server now works again. Affected users have to use the new "Clear All" button to
|
||||
clear out existing credentials and re-add them all to fix
|
||||
though. ([Feature #72](https://gitlab.com/mysocialportal/relatica/-/issues/72))
|
||||
*
|
||||
* Fix empty profiles and/or sometimes lack of bidirectional contact data by always pulling profile data on refresh
|
||||
requests and adding explicit redraw of panel after
|
||||
user requests refresh ([Issue #36](https://gitlab.com/mysocialportal/relatica/-/issues/36)
|
||||
and [Issue #62](https://gitlab.com/mysocialportal/relatica/-/issues/62))
|
||||
* New Features
|
||||
* Shows the network of the post/comment ([Feature #82](https://gitlab.com/mysocialportal/relatica/-/issues/82))
|
||||
* User configurable ability to limit reacting to, commenting on, or resharing posts by network
|
||||
|
@ -36,6 +39,8 @@
|
|||
level. Defaults to on. ([Feature #42](https://gitlab.com/mysocialportal/relatica/-/issues/42))
|
||||
* Throws a confirm dialog box up if adding a comment to a post/comment over 30 days
|
||||
old. ([Feature #58](https://gitlab.com/mysocialportal/relatica/-/issues/58))
|
||||
* Autocomplete now lists hashtags and accounts that are used in a post or post above the rest of the
|
||||
results. ([Feature #28](https://gitlab.com/mysocialportal/relatica/-/issues/28))
|
||||
|
||||
## Version 0.10.1 (beta)
|
||||
|
||||
|
|
|
@ -1,21 +1,34 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../../globals.dart';
|
||||
import '../../services/entry_manager_service.dart';
|
||||
import '../../services/hashtag_service.dart';
|
||||
import '../../utils/active_profile_selector.dart';
|
||||
|
||||
class HashtagAutocompleteOptions extends StatelessWidget {
|
||||
const HashtagAutocompleteOptions({
|
||||
super.key,
|
||||
required this.id,
|
||||
required this.query,
|
||||
required this.onHashtagTap,
|
||||
});
|
||||
|
||||
final String id;
|
||||
final String query;
|
||||
final ValueSetter<String> onHashtagTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final hashtags = getIt<HashtagService>().getMatchingHashTags(query);
|
||||
final manager = context
|
||||
.read<ActiveProfileSelector<EntryManagerService>>()
|
||||
.activeEntry
|
||||
.value;
|
||||
final postTreeHashtags =
|
||||
manager.getPostTreeHashtags(id).getValueOrElse(() => [])..sort();
|
||||
final hashtagsFromService =
|
||||
getIt<HashtagService>().getMatchingHashTags(query);
|
||||
final hashtags = [...postTreeHashtags, ...hashtagsFromService];
|
||||
|
||||
if (hashtags.isEmpty) return const SizedBox.shrink();
|
||||
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../../globals.dart';
|
||||
import '../../models/connection.dart';
|
||||
import '../../services/connections_manager.dart';
|
||||
import '../../services/entry_manager_service.dart';
|
||||
import '../../utils/active_profile_selector.dart';
|
||||
import '../image_control.dart';
|
||||
|
||||
|
@ -12,24 +13,39 @@ class MentionAutocompleteOptions extends StatelessWidget {
|
|||
|
||||
const MentionAutocompleteOptions({
|
||||
super.key,
|
||||
required this.id,
|
||||
required this.query,
|
||||
required this.onMentionUserTap,
|
||||
});
|
||||
|
||||
final String id;
|
||||
final String query;
|
||||
final ValueSetter<Connection> onMentionUserTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final users = getIt<ActiveProfileSelector<ConnectionsManager>>()
|
||||
final entryManager = context
|
||||
.read<ActiveProfileSelector<EntryManagerService>>()
|
||||
.activeEntry
|
||||
.andThenSuccess((manager) => manager.getKnownUsersByName(query))
|
||||
.fold(
|
||||
onSuccess: (users) => users,
|
||||
onError: (error) {
|
||||
_logger.severe('Error getting users list: $error');
|
||||
return [];
|
||||
});
|
||||
.value;
|
||||
|
||||
final connectionManager = context
|
||||
.read<ActiveProfileSelector<ConnectionsManager>>()
|
||||
.activeEntry
|
||||
.value;
|
||||
|
||||
final postTreeUsers = entryManager
|
||||
.getPostTreeConnectionIds(id)
|
||||
.getValueOrElse(() => [])
|
||||
.map((id) => connectionManager.getById(id))
|
||||
.where((result) => result.isSuccess)
|
||||
.map((result) => result.value)
|
||||
.toList()
|
||||
..sort((u1, u2) => u1.name.compareTo(u2.name));
|
||||
|
||||
final knownUsers = connectionManager.getKnownUsersByName(query);
|
||||
|
||||
final users = [...postTreeUsers, ...knownUsers];
|
||||
|
||||
if (users.isEmpty) return const SizedBox.shrink();
|
||||
|
||||
|
|
|
@ -554,6 +554,14 @@ class RelationshipsClient extends FriendicaClient {
|
|||
_networkStatusService.startConnectionUpdateStatus();
|
||||
final myId = profile.userId;
|
||||
final id = int.parse(connection.id);
|
||||
final connectionUpdateUrl =
|
||||
Uri.parse('https://$serverName/api/v1/accounts/$id');
|
||||
final updatedConnection = await _getApiRequest(connectionUpdateUrl).fold(
|
||||
onSuccess: (json) => ConnectionMastodonExtensions.fromJson(json),
|
||||
onError: (error) {
|
||||
_logger.severe('Error getting connection for $id');
|
||||
return connection;
|
||||
});
|
||||
final paging = '?min_id=${id - 1}&max_id=${id + 1}';
|
||||
final baseUrl = 'https://$serverName/api/v1/accounts/$myId';
|
||||
final following =
|
||||
|
@ -581,7 +589,7 @@ class RelationshipsClient extends FriendicaClient {
|
|||
}
|
||||
|
||||
_networkStatusService.finishConnectionUpdateStatus();
|
||||
return Result.ok(connection.copy(status: status));
|
||||
return Result.ok(updatedConnection.copy(status: status));
|
||||
}
|
||||
|
||||
FutureResult<PagedResponse<List<Connection>>, ExecError>
|
||||
|
|
|
@ -333,6 +333,7 @@ class _EditorScreenState extends State<EditorScreen> {
|
|||
trigger: '@',
|
||||
optionsViewBuilder: (context, autocompleteQuery, controller) {
|
||||
return MentionAutocompleteOptions(
|
||||
id: parentEntry?.id ?? '',
|
||||
query: autocompleteQuery.query,
|
||||
onMentionUserTap: (user) {
|
||||
final autocomplete = MultiTriggerAutocomplete.of(context);
|
||||
|
@ -345,6 +346,7 @@ class _EditorScreenState extends State<EditorScreen> {
|
|||
trigger: '#',
|
||||
optionsViewBuilder: (context, autocompleteQuery, controller) {
|
||||
return HashtagAutocompleteOptions(
|
||||
id: parentEntry?.id ?? '',
|
||||
query: autocompleteQuery.query,
|
||||
onHashtagTap: (hashtag) {
|
||||
final autocomplete = MultiTriggerAutocomplete.of(context);
|
||||
|
|
|
@ -407,6 +407,7 @@ class _FilterEditorScreenState extends State<FilterEditorScreen> {
|
|||
optionsViewBuilder:
|
||||
(ovbContext, autocompleteQuery, controller) {
|
||||
return MentionAutocompleteOptions(
|
||||
id: '',
|
||||
query: autocompleteQuery.query,
|
||||
onMentionUserTap: (user) {
|
||||
final autocomplete =
|
||||
|
@ -480,6 +481,7 @@ class _FilterEditorScreenState extends State<FilterEditorScreen> {
|
|||
optionsViewBuilder:
|
||||
(ovbContext, autocompleteQuery, controller) {
|
||||
return HashtagAutocompleteOptions(
|
||||
id: '',
|
||||
query: autocompleteQuery.query,
|
||||
onHashtagTap: (hashtag) {
|
||||
final autocomplete =
|
||||
|
|
|
@ -49,6 +49,7 @@ class MessagesNewThread extends StatelessWidget {
|
|||
trigger: '@',
|
||||
optionsViewBuilder: (context, autocompleteQuery, controller) {
|
||||
return MentionAutocompleteOptions(
|
||||
id: '',
|
||||
query: autocompleteQuery.query,
|
||||
onMentionUserTap: (user) {
|
||||
final autocomplete =
|
||||
|
|
|
@ -44,8 +44,9 @@ class _UserProfileScreenState extends State<UserProfileScreen> {
|
|||
.value;
|
||||
final blocksManager =
|
||||
context.watch<ActiveProfileSelector<BlocksManager>>().activeEntry.value;
|
||||
final body =
|
||||
connectionManager.getById(widget.userId).fold(onSuccess: (profile) {
|
||||
final body = connectionManager
|
||||
.getById(widget.userId, forceUpdate: true)
|
||||
.fold(onSuccess: (profile) {
|
||||
final notMyProfile =
|
||||
getIt<AccountsService>().currentProfile.userId != profile.id;
|
||||
|
||||
|
@ -54,6 +55,7 @@ class _UserProfileScreenState extends State<UserProfileScreen> {
|
|||
await connectionManager.fullRefresh(profile,
|
||||
withNotifications: false);
|
||||
await blocksManager.updateBlock(profile);
|
||||
setState(() {});
|
||||
},
|
||||
child: SingleChildScrollView(
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
|
|
|
@ -2,7 +2,7 @@ import '../../models/connection.dart';
|
|||
|
||||
extension ConnectionFriendicaExtensions on Connection {
|
||||
static Connection fromJson(Map<String, dynamic> json) {
|
||||
final status = json['following'] == 'true'
|
||||
final status = json['following']
|
||||
? ConnectionStatus.youFollowThem
|
||||
: ConnectionStatus.none;
|
||||
final name = json['name'] ?? '';
|
||||
|
|
|
@ -23,6 +23,8 @@ class EntryManagerService extends ChangeNotifier {
|
|||
final _entries = <String, TimelineEntry>{};
|
||||
final _parentPostIds = <String, String>{};
|
||||
final _postNodes = <String, _Node>{};
|
||||
final _postThreadHashtags = <String, Set<String>>{};
|
||||
final _postTreeConnections = <String, Set<String>>{};
|
||||
final Profile profile;
|
||||
|
||||
EntryManagerService(this.profile);
|
||||
|
@ -61,6 +63,32 @@ class EntryManagerService extends ChangeNotifier {
|
|||
return Result.ok(_nodeToTreeItem(postNode, profile.userId));
|
||||
}
|
||||
|
||||
Result<List<String>, ExecError> getPostTreeHashtags(String id) {
|
||||
final postId = _getPostRootNode(id)?.id ?? '';
|
||||
if (postId.isEmpty) {
|
||||
return buildErrorResult(
|
||||
type: ErrorType.notFound,
|
||||
message: 'Root Post ID not found for $id',
|
||||
);
|
||||
}
|
||||
final hashtags = _postThreadHashtags[postId]?.toList() ?? [];
|
||||
|
||||
return Result.ok(hashtags);
|
||||
}
|
||||
|
||||
Result<List<String>, ExecError> getPostTreeConnectionIds(String id) {
|
||||
final postId = _getPostRootNode(id)?.id ?? '';
|
||||
if (postId.isEmpty) {
|
||||
return buildErrorResult(
|
||||
type: ErrorType.notFound,
|
||||
message: 'Root Post ID not found for $id',
|
||||
);
|
||||
}
|
||||
final hashtags = _postTreeConnections[postId]?.toList() ?? [];
|
||||
|
||||
return Result.ok(hashtags);
|
||||
}
|
||||
|
||||
Result<TimelineEntry, ExecError> getEntryById(String id) {
|
||||
if (_entries.containsKey(id)) {
|
||||
return Result.ok(_entries[id]!);
|
||||
|
@ -353,6 +381,11 @@ class EntryManagerService extends ChangeNotifier {
|
|||
if (item.parentId.isEmpty) {
|
||||
final postNode =
|
||||
_postNodes.putIfAbsent(item.id, () => _Node(item.id));
|
||||
final pth = _postThreadHashtags.putIfAbsent(item.id, () => {});
|
||||
final ptc = _postTreeConnections.putIfAbsent(item.id, () => {});
|
||||
pth.addAll(item.tags);
|
||||
ptc.add(item.authorId);
|
||||
ptc.add(item.parentAuthorId);
|
||||
postNodesToReturn.add(postNode);
|
||||
allSeenItems.remove(item);
|
||||
} else {
|
||||
|
@ -364,6 +397,14 @@ class EntryManagerService extends ChangeNotifier {
|
|||
'Error finding parent ${item.parentId} for entry ${item.id}');
|
||||
continue;
|
||||
}
|
||||
final pth =
|
||||
_postThreadHashtags.putIfAbsent(parentParentPostId!, () => {});
|
||||
final ptc =
|
||||
_postTreeConnections.putIfAbsent(parentParentPostId, () => {});
|
||||
pth.addAll(item.tags);
|
||||
ptc.add(item.authorId);
|
||||
ptc.add(item.parentAuthorId);
|
||||
|
||||
final parentPostNode = _postNodes[parentParentPostId]!;
|
||||
postNodesToReturn.add(parentPostNode);
|
||||
_parentPostIds[item.id] = parentPostNode.id;
|
||||
|
|
Loading…
Reference in a new issue