Skip to content

Commit

Permalink
feat: replaceable event references (#593)
Browse files Browse the repository at this point in the history
## Description
THis PR adds `ImmutableEventReference` and `ReplaceableEventReference` +
make `ionConnectEntity` (and cache itself) to work with both reference
types, so that we could work with replaceable entities like article or
mutable posts.

## Type of Change
- [ ] Bug fix
- [x] New feature
- [ ] Breaking change
- [x] Refactoring
- [ ] Documentation
- [ ] Chore
  • Loading branch information
ice-orion authored Jan 23, 2025
1 parent 2befb57 commit 08d9779
Show file tree
Hide file tree
Showing 77 changed files with 551 additions and 395 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ class RepliesCounterButton extends HookConsumerWidget {
if (!context.mounted) return;

if (canReply) {
await CreatePostRoute(parentEvent: eventReference.toString()).push<void>(context);
await CreatePostRoute(parentEvent: eventReference.encode()).push<void>(context);
await HapticFeedback.lightImpact();
} else {
await showSimpleBottomSheet<void>(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,7 @@ class RepostsCounterButton extends ConsumerWidget {
return GestureDetector(
onTap: () {
HapticFeedback.lightImpact();
RepostOptionsModalRoute(
eventReference: eventReference.toString(),
).push<void>(context);
RepostOptionsModalRoute(eventReference: eventReference.encode()).push<void>(context);
},
child: TextActionButton(
icon: Assets.svg.iconBlockRepost.icon(
Expand Down
2 changes: 1 addition & 1 deletion lib/app/components/counter_items_footer/share_button.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ class ShareButton extends StatelessWidget {
return GestureDetector(
onTap: () {
HapticFeedback.lightImpact();
SharePostModalRoute(postId: eventReference.eventId).push<void>(context);
SharePostModalRoute(eventReference: eventReference.encode()).push<void>(context);
},
child: TextActionButton(
icon: Assets.svg.iconBlockShare.icon(
Expand Down
5 changes: 5 additions & 0 deletions lib/app/exceptions/exceptions.dart
Original file line number Diff line number Diff line change
Expand Up @@ -232,3 +232,8 @@ class DeleteEntityUnsupportedTypeException extends IONException {
DeleteEntityUnsupportedTypeException()
: super(10048, 'Failed to delete entity, unsupported type');
}

class UnknownEventReferenceType extends IONException {
UnknownEventReferenceType({required String type})
: super(10049, 'Unknown event reference type $type');
}
26 changes: 7 additions & 19 deletions lib/app/features/chat/providers/user_chat_relays_provider.c.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:ion/app/exceptions/exceptions.dart';
import 'package:ion/app/features/auth/providers/auth_provider.c.dart';
import 'package:ion/app/features/ion_connect/ion_connect.dart';
import 'package:ion/app/features/ion_connect/model/action_source.dart';
import 'package:ion/app/features/ion_connect/providers/ion_connect_cache.c.dart';
import 'package:ion/app/features/ion_connect/model/event_reference.c.dart';
import 'package:ion/app/features/ion_connect/providers/ion_connect_entity_provider.c.dart';
import 'package:ion/app/features/ion_connect/providers/ion_connect_notifier.c.dart';
import 'package:ion/app/features/user/model/user_chat_relays.c.dart';
import 'package:ion/app/features/user/model/user_relays.c.dart';
Expand All @@ -16,22 +15,11 @@ part 'user_chat_relays_provider.c.g.dart';

@riverpod
Future<UserChatRelaysEntity?> userChatRelays(Ref ref, String pubkey) async {
final cached = ref.watch(
ionConnectCacheProvider.select<UserChatRelaysEntity?>(
cacheSelector(UserChatRelaysEntity.cacheKeyBuilder(pubkey: pubkey)),
),
);
if (cached != null) return cached;

final requestMessage = RequestMessage()
..addFilter(
RequestFilter(kinds: const [UserChatRelaysEntity.kind], authors: [pubkey]),
);

return ref.watch(ionConnectNotifierProvider.notifier).requestEntity<UserChatRelaysEntity>(
requestMessage,
actionSource: ActionSourceUser(pubkey),
);
return await ref.watch(
ionConnectEntityProvider(
eventReference: ReplaceableEventReference(pubkey: pubkey, kind: UserChatRelaysEntity.kind),
).future,
) as UserChatRelaysEntity?;
}

@riverpod
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:ion/app/extensions/num.dart';
import 'package:ion/app/features/feed/data/models/entities/article_data.c.dart';
import 'package:ion/app/features/feed/views/components/article/article.dart';
import 'package:ion/app/features/ion_connect/model/event_reference.c.dart';
import 'package:ion/app/router/app_routes.c.dart';

class ArticleListItem extends ConsumerWidget {
Expand All @@ -15,14 +14,14 @@ class ArticleListItem extends ConsumerWidget {

@override
Widget build(BuildContext context, WidgetRef ref) {
final eventReference = EventReference.fromIonConnectEntity(article);
final eventReference = article.toEventReference();

return Padding(
padding: EdgeInsets.only(top: 12.0.s, bottom: 24.0.s, right: 16.0.s),
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () =>
ArticleDetailsRoute(eventReference: eventReference.toString()).push<void>(context),
ArticleDetailsRoute(eventReference: eventReference.encode()).push<void>(context),
child: Article(
eventReference: eventReference,
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,12 @@ class GenericRepostListItem extends StatelessWidget {
return Text('Repost of kind ${repost.data.kind} is not supported');
}

final eventReference = EventReference(eventId: repost.data.eventId, pubkey: repost.data.pubkey);
//TODO:replaceable - should not be immutable
final eventReference =
ImmutableEventReference(eventId: repost.data.eventId, pubkey: repost.data.pubkey);

return GestureDetector(
onTap: () =>
ArticleDetailsRoute(eventReference: eventReference.toString()).push<void>(context),
onTap: () => ArticleDetailsRoute(eventReference: eventReference.encode()).push<void>(context),
behavior: HitTestBehavior.opaque,
child: ScreenSideOffset.small(
child: Column(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import 'package:flutter/widgets.dart';
import 'package:ion/app/components/screen_offset/screen_side_offset.dart';
import 'package:ion/app/features/feed/data/models/entities/post_data.c.dart';
import 'package:ion/app/features/feed/views/components/post/post.dart';
import 'package:ion/app/features/ion_connect/model/event_reference.c.dart';
import 'package:ion/app/router/app_routes.c.dart';

class PostListItem extends StatelessWidget {
Expand All @@ -16,11 +15,11 @@ class PostListItem extends StatelessWidget {

@override
Widget build(BuildContext context) {
final eventReference = EventReference.fromIonConnectEntity(post);
final eventReference = post.toEventReference();
// TODO: process 20002 in the feed provider to fetch 10002

return GestureDetector(
onTap: () => PostDetailsRoute(eventReference: eventReference.toString()).push<void>(context),
onTap: () => PostDetailsRoute(eventReference: eventReference.encode()).push<void>(context),
behavior: HitTestBehavior.opaque,
child: ScreenSideOffset.small(
child: Post(eventReference: eventReference, showParent: showParent),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,11 @@ class RepostListItem extends StatelessWidget {

@override
Widget build(BuildContext context) {
final eventReference = EventReference(eventId: repost.data.eventId, pubkey: repost.data.pubkey);
final eventReference =
ImmutableEventReference(eventId: repost.data.eventId, pubkey: repost.data.pubkey);

return GestureDetector(
onTap: () => PostDetailsRoute(eventReference: eventReference.toString()).push<void>(context),
onTap: () => PostDetailsRoute(eventReference: eventReference.encode()).push<void>(context),
behavior: HitTestBehavior.opaque,
child: ScreenSideOffset.small(
child: Column(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ class _EntityListItem extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final userMetadata =
ref.watch(userMetadataProvider(entity.masterPubkey, cacheOnly: true)).valueOrNull;
ref.watch(userMetadataProvider(entity.masterPubkey, network: false)).valueOrNull;

final isBlockedOrBlocking =
ref.watch(isEntityBlockedOrBlockingProvider(entity, cacheOnly: true)).valueOrNull ?? true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,13 +82,13 @@ class CreatePostNotifier extends _$CreatePostNotifier {
);
}

if (quotedEvent != null) {
if (quotedEvent != null && quotedEvent is ImmutableEventReference) {
data = data.copyWith(
quotedEvent: QuotedEvent(eventId: quotedEvent.eventId, pubkey: quotedEvent.pubkey),
);
}

if (parentEvent != null) {
if (parentEvent != null && parentEvent is ImmutableEventReference) {
final parentEntity =
await ref.read(ionConnectEntityProvider(eventReference: parentEvent).future);
if (parentEntity == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ class ReplyInputField extends HookConsumerWidget {
GestureDetector(
onTap: () async {
final content = await CreatePostRoute(
parentEvent: eventReference.toString(),
parentEvent: eventReference.encode(),
showCollapseButton: true,
content: textEditorController.document.toPlainText().trim(),
).push<String>(context);
Expand Down
22 changes: 16 additions & 6 deletions lib/app/features/feed/data/models/bookmarks/bookmarks.c.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,17 @@ import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:ion/app/exceptions/exceptions.dart';
import 'package:ion/app/extensions/extensions.dart';
import 'package:ion/app/features/ion_connect/ion_connect.dart';
import 'package:ion/app/features/ion_connect/model/event_reference.c.dart';
import 'package:ion/app/features/ion_connect/model/event_serializable.dart';
import 'package:ion/app/features/ion_connect/model/ion_connect_entity.dart';
import 'package:ion/app/features/ion_connect/model/replaceable_event_reference.c.dart';
import 'package:ion/app/features/ion_connect/providers/ion_connect_cache.c.dart';

part 'bookmarks.c.freezed.dart';

@Freezed(equal: false)
class BookmarksEntity with _$BookmarksEntity, IonConnectEntity implements CacheableEntity {
class BookmarksEntity
with _$BookmarksEntity, IonConnectEntity, CacheableEntity
implements ReplaceableEntity {
const factory BookmarksEntity({
required String id,
required String pubkey,
Expand Down Expand Up @@ -44,15 +46,15 @@ class BookmarksEntity with _$BookmarksEntity, IonConnectEntity implements Cachea
}

@override
String get cacheKey => cacheKeyBuilder(pubkey: masterPubkey);

static String cacheKeyBuilder({required String pubkey}) => '$kind:$pubkey';
ReplaceableEventReference toEventReference() {
return data.toReplaceableEventReference(masterPubkey);
}

static const int kind = 10003;
}

@freezed
class BookmarksData with _$BookmarksData implements EventSerializable {
class BookmarksData with _$BookmarksData implements EventSerializable, ReplaceableEntityData {
const factory BookmarksData({
required List<String> ids,
required List<ReplaceableEventReference> bookmarksSetRefs,
Expand Down Expand Up @@ -89,4 +91,12 @@ class BookmarksData with _$BookmarksData implements EventSerializable {
content: '',
);
}

@override
ReplaceableEventReference toReplaceableEventReference(String pubkey) {
return ReplaceableEventReference(
kind: BookmarksEntity.kind,
pubkey: pubkey,
);
}
}
16 changes: 9 additions & 7 deletions lib/app/features/feed/data/models/bookmarks/bookmarks_set.c.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:ion/app/exceptions/exceptions.dart';
import 'package:ion/app/extensions/extensions.dart';
import 'package:ion/app/features/ion_connect/ion_connect.dart';
import 'package:ion/app/features/ion_connect/model/event_reference.c.dart';
import 'package:ion/app/features/ion_connect/model/event_serializable.dart';
import 'package:ion/app/features/ion_connect/model/ion_connect_entity.dart';
import 'package:ion/app/features/ion_connect/model/replaceable_event_identifier.c.dart';
import 'package:ion/app/features/ion_connect/model/replaceable_event_reference.c.dart';
import 'package:ion/app/features/ion_connect/providers/ion_connect_cache.c.dart';

part 'bookmarks_set.c.freezed.dart';
Expand All @@ -28,7 +28,9 @@ enum BookmarksSetType {
}

@Freezed(equal: false)
class BookmarksSetEntity with _$BookmarksSetEntity, IonConnectEntity implements CacheableEntity {
class BookmarksSetEntity
with _$BookmarksSetEntity, IonConnectEntity, CacheableEntity
implements ReplaceableEntity {
const factory BookmarksSetEntity({
required String id,
required String pubkey,
Expand Down Expand Up @@ -57,16 +59,15 @@ class BookmarksSetEntity with _$BookmarksSetEntity, IonConnectEntity implements
}

@override
String get cacheKey => cacheKeyBuilder(pubkey: masterPubkey, type: data.type);

static String cacheKeyBuilder({required String pubkey, required BookmarksSetType type}) =>
'$kind:$type:$pubkey';
ReplaceableEventReference toEventReference() {
return data.toReplaceableEventReference(masterPubkey);
}

static const int kind = 30003;
}

@freezed
class BookmarksSetData with _$BookmarksSetData implements EventSerializable {
class BookmarksSetData with _$BookmarksSetData implements EventSerializable, ReplaceableEntityData {
const factory BookmarksSetData({
required BookmarksSetType type,
required List<String> postsIds,
Expand Down Expand Up @@ -121,6 +122,7 @@ class BookmarksSetData with _$BookmarksSetData implements EventSerializable {
);
}

@override
ReplaceableEventReference toReplaceableEventReference(String pubkey) {
return ReplaceableEventReference(
pubkey: pubkey,
Expand Down
31 changes: 17 additions & 14 deletions lib/app/features/feed/data/models/entities/article_data.c.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,23 @@ import 'package:ion/app/extensions/extensions.dart';
import 'package:ion/app/features/feed/data/models/who_can_reply_settings_option.dart';
import 'package:ion/app/features/ion_connect/ion_connect.dart';
import 'package:ion/app/features/ion_connect/model/entity_published_at.c.dart';
import 'package:ion/app/features/ion_connect/model/event_reference.c.dart';
import 'package:ion/app/features/ion_connect/model/event_serializable.dart';
import 'package:ion/app/features/ion_connect/model/event_setting.c.dart';
import 'package:ion/app/features/ion_connect/model/ion_connect_entity.dart';
import 'package:ion/app/features/ion_connect/model/media_attachment.dart';
import 'package:ion/app/features/ion_connect/model/related_hashtag.c.dart';
import 'package:ion/app/features/ion_connect/model/replaceable_event_identifier.c.dart';
import 'package:ion/app/features/ion_connect/model/replaceable_event_reference.c.dart';
import 'package:ion/app/features/ion_connect/providers/ion_connect_cache.c.dart';

part 'article_data.c.freezed.dart';

const textEditorSingleImageKey = 'text-editor-single-image';

@Freezed(equal: false)
class ArticleEntity with _$ArticleEntity, IonConnectEntity implements CacheableEntity {
class ArticleEntity
with _$ArticleEntity, IonConnectEntity, CacheableEntity
implements ReplaceableEntity {
const factory ArticleEntity({
required String id,
required String pubkey,
Expand Down Expand Up @@ -52,23 +54,15 @@ class ArticleEntity with _$ArticleEntity, IonConnectEntity implements CacheableE
}

@override
String get cacheKey => cacheKeyBuilder(id: id);

static String cacheKeyBuilder({required String id}) => id;
ReplaceableEventReference toEventReference() {
return data.toReplaceableEventReference(masterPubkey);
}

static const kind = 30023;

ReplaceableEventReference toReplaceableEventReference() {
return ReplaceableEventReference(
kind: kind,
pubkey: masterPubkey,
dTag: id,
);
}
}

@freezed
class ArticleData with _$ArticleData implements EventSerializable {
class ArticleData with _$ArticleData implements EventSerializable, ReplaceableEntityData {
const factory ArticleData({
required String content,
required Map<String, MediaAttachment> media,
Expand Down Expand Up @@ -160,6 +154,15 @@ class ArticleData with _$ArticleData implements EventSerializable {
);
}

@override
ReplaceableEventReference toReplaceableEventReference(String pubkey) {
return ReplaceableEventReference(
kind: ArticleEntity.kind,
pubkey: pubkey,
dTag: replaceableEventId.value,
);
}

static List<RelatedHashtag> extractHashtagsFromMarkdown(String content) {
final operations = jsonDecode(content) as List<dynamic>;
const insertKey = 'insert';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@ part 'event_count_request_data.c.freezed.dart';

@Freezed(equal: false)
class EventCountRequestEntity
with _$EventCountRequestEntity, IonConnectEntity
implements CacheableEntity {
with _$EventCountRequestEntity, IonConnectEntity, ImmutableEntity, CacheableEntity {
const factory EventCountRequestEntity({
required String id,
required String pubkey,
Expand Down Expand Up @@ -45,11 +44,6 @@ class EventCountRequestEntity
);
}

@override
String get cacheKey => cacheKeyBuilder(id: id);

static String cacheKeyBuilder({required String id}) => id;

static const int kind = 5400;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,7 @@ enum EventCountResultType {

@Freezed(equal: false)
class EventCountResultEntity
with _$EventCountResultEntity, IonConnectEntity
implements CacheableEntity {
with _$EventCountResultEntity, IonConnectEntity, ImmutableEntity, CacheableEntity {
const factory EventCountResultEntity({
required String id,
required String pubkey,
Expand Down
Loading

0 comments on commit 08d9779

Please sign in to comment.