kopia lustrzana https://gitlab.com/mysocialportal/relatica
Initial implementation of hashtag tracking and auto-complete
rodzic
45380df0d0
commit
7a41065a1c
|
@ -0,0 +1,60 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../globals.dart';
|
||||
import '../../services/hashtag_service.dart';
|
||||
|
||||
class HashtagAutocompleteOptions extends StatelessWidget {
|
||||
const HashtagAutocompleteOptions({
|
||||
Key? key,
|
||||
required this.query,
|
||||
required this.onHashtagTap,
|
||||
}) : super(key: key);
|
||||
|
||||
final String query;
|
||||
final ValueSetter<String> onHashtagTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final hashtags = getIt<HashtagService>().getMatchingHashTags(query);
|
||||
|
||||
if (hashtags.isEmpty) return const SizedBox.shrink();
|
||||
|
||||
return Card(
|
||||
margin: const EdgeInsets.all(8),
|
||||
elevation: 2,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
clipBehavior: Clip.hardEdge,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
color: const Color(0xFFF7F7F8),
|
||||
child: ListTile(
|
||||
dense: true,
|
||||
horizontalTitleGap: 0,
|
||||
title: Text("Hashtags matching '$query'"),
|
||||
),
|
||||
),
|
||||
LimitedBox(
|
||||
maxHeight: MediaQuery.of(context).size.height * 0.2,
|
||||
child: ListView.separated(
|
||||
padding: EdgeInsets.zero,
|
||||
shrinkWrap: true,
|
||||
itemCount: hashtags.length,
|
||||
separatorBuilder: (_, __) => const Divider(),
|
||||
itemBuilder: (context, i) {
|
||||
final hashtag = hashtags[i];
|
||||
return GestureDetector(
|
||||
onTap: () => onHashtagTap(hashtag),
|
||||
child: Text(hashtag),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -12,6 +12,7 @@ import 'services/auth_service.dart';
|
|||
import 'services/connections_manager.dart';
|
||||
import 'services/entry_manager_service.dart';
|
||||
import 'services/gallery_service.dart';
|
||||
import 'services/hashtag_service.dart';
|
||||
import 'services/media_upload_attachment_helper.dart';
|
||||
import 'services/notifications_manager.dart';
|
||||
import 'services/secrets_service.dart';
|
||||
|
@ -36,6 +37,7 @@ void main() async {
|
|||
final timelineManager = TimelineManager();
|
||||
final galleryService = GalleryService();
|
||||
getIt.registerLazySingleton<ConnectionsManager>(() => ConnectionsManager());
|
||||
getIt.registerLazySingleton<HashtagService>(() => HashtagService());
|
||||
getIt.registerSingleton(galleryService);
|
||||
getIt.registerSingleton<EntryManagerService>(entryManagerService);
|
||||
getIt.registerSingleton<SecretsService>(secretsService);
|
||||
|
@ -94,6 +96,10 @@ class App extends StatelessWidget {
|
|||
create: (_) => getIt<GalleryService>(),
|
||||
lazy: true,
|
||||
),
|
||||
ChangeNotifierProvider<HashtagService>(
|
||||
create: (_) => getIt<HashtagService>(),
|
||||
lazy: true,
|
||||
),
|
||||
ChangeNotifierProvider<TimelineManager>(
|
||||
create: (_) => getIt<TimelineManager>(),
|
||||
),
|
||||
|
|
|
@ -6,6 +6,7 @@ import 'package:multi_trigger_autocomplete/multi_trigger_autocomplete.dart';
|
|||
import 'package:provider/provider.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
import '../controls/autocomplete/hashtag_autocomplete_options.dart';
|
||||
import '../controls/autocomplete/mention_autocomplete_options.dart';
|
||||
import '../controls/entry_media_attachments/gallery_selector_control.dart';
|
||||
import '../controls/entry_media_attachments/media_uploads_control.dart';
|
||||
|
@ -187,6 +188,18 @@ class _EditorScreenState extends State<EditorScreen> {
|
|||
);
|
||||
},
|
||||
),
|
||||
AutocompleteTrigger(
|
||||
trigger: '#',
|
||||
optionsViewBuilder: (context, autocompleteQuery, controller) {
|
||||
return HashtagAutocompleteOptions(
|
||||
query: autocompleteQuery.query,
|
||||
onHashtagTap: (hashtag) {
|
||||
final autocomplete = MultiTriggerAutocomplete.of(context);
|
||||
return autocomplete.acceptAutocompleteOption(hashtag);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
fieldViewBuilder: (context, controller, focusNode) => TextFormField(
|
||||
focusNode: focusNode,
|
||||
|
|
|
@ -7,6 +7,7 @@ import '../../models/location_data.dart';
|
|||
import '../../models/media_attachment.dart';
|
||||
import '../../models/timeline_entry.dart';
|
||||
import '../../services/connections_manager.dart';
|
||||
import '../../services/hashtag_service.dart';
|
||||
import '../../utils/dateutils.dart';
|
||||
import 'connection_mastodon_extensions.dart';
|
||||
|
||||
|
@ -71,6 +72,15 @@ extension TimelineEntryMastodonExtensions on TimelineEntry {
|
|||
reshareAuthor = '';
|
||||
}
|
||||
|
||||
final List<dynamic>? tags = json['tags'];
|
||||
if (tags?.isNotEmpty ?? false) {
|
||||
final tagManager = getIt<HashtagService>();
|
||||
for (final tag in tags!) {
|
||||
final tagName = tag['name'];
|
||||
tagManager.addHashtage(tagName);
|
||||
}
|
||||
}
|
||||
|
||||
return TimelineEntry(
|
||||
creationTimestamp: timestamp,
|
||||
modificationTimestamp: modificationTimestamp,
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
|
||||
class HashtagService extends ChangeNotifier {
|
||||
final _hashTags = <String>{};
|
||||
|
||||
void clear() {
|
||||
_hashTags.clear();
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void addHashtage(String hashtag) {
|
||||
_hashTags.add(hashtag);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
List<String> getMatchingHashTags(String searchString) {
|
||||
return _hashTags
|
||||
.where((tag) => tag.toLowerCase().contains(searchString.toLowerCase()))
|
||||
.toList();
|
||||
}
|
||||
}
|
Ładowanie…
Reference in New Issue