diff --git a/docs/code_snippets/03_02_querying_activities.dart b/docs/code_snippets/03_02_querying_activities.dart index 6df61813..64f3ca9f 100644 --- a/docs/code_snippets/03_02_querying_activities.dart +++ b/docs/code_snippets/03_02_querying_activities.dart @@ -5,7 +5,7 @@ late Feed feed; Future activitySearchAndQueries() async { final query = ActivitiesQuery( - filter: Filter.equal(ActivitiesFilterField.type, 'post'), + filter: Filter.equal(ActivitiesFilterField.activityType, 'post'), sort: [ActivitiesSort.desc(ActivitiesSortField.createdAt)], limit: 10, ); diff --git a/melos.yaml b/melos.yaml index b4ad6bef..3e8a8380 100644 --- a/melos.yaml +++ b/melos.yaml @@ -48,7 +48,7 @@ command: shared_preferences: ^2.5.3 state_notifier: ^1.0.0 stream_feeds: ^0.4.0 - stream_core: ^0.3.1 + stream_core: ^0.3.2 video_player: ^2.10.0 uuid: ^4.5.1 diff --git a/packages/stream_feeds/CHANGELOG.md b/packages/stream_feeds/CHANGELOG.md index 91befff8..390dd538 100644 --- a/packages/stream_feeds/CHANGELOG.md +++ b/packages/stream_feeds/CHANGELOG.md @@ -1,6 +1,10 @@ ## Upcoming - [BREAKING] Unified `ThreadedCommentData` into `CommentData` to handle both flat and threaded comments. +- [BREAKING] Renamed `ActivitiesFilterField.type` to `ActivitiesFilterField.activityType`. +- [BREAKING] Changed `ActivityData.location` field type from `ActivityLocation?` to `LocationCoordinate?`. - Add support for `enforceUnique` parameter while adding reactions. +- Add location filtering support for activities with `ActivitiesFilterField.near` and `ActivitiesFilterField.withinBounds` filter fields. +- Add new activity filter fields: `ActivitiesFilterField.feed` and `ActivitiesFilterField.interestTags`. ## 0.4.0 - [BREAKING] Change `queryFollowSuggestions` return type to `List`. diff --git a/packages/stream_feeds/lib/src/feeds_client.dart b/packages/stream_feeds/lib/src/feeds_client.dart index 434ad932..a46cbf1b 100644 --- a/packages/stream_feeds/lib/src/feeds_client.dart +++ b/packages/stream_feeds/lib/src/feeds_client.dart @@ -319,7 +319,7 @@ abstract interface class StreamFeedsClient { /// ```dart /// final activityList = client.activityList(ActivitiesQuery( /// filter: Filter.and([ - /// Filter.equal(ActivitiesFilterField.type, 'post'), + /// Filter.equal(ActivitiesFilterField.activityType, 'post'), /// Filter.greaterThan(ActivitiesFilterField.createdAt, /// DateTime.now().subtract(Duration(days: 7))), /// ]), diff --git a/packages/stream_feeds/lib/src/models.dart b/packages/stream_feeds/lib/src/models.dart index e4e469a7..bf4a4e9c 100644 --- a/packages/stream_feeds/lib/src/models.dart +++ b/packages/stream_feeds/lib/src/models.dart @@ -1,3 +1,6 @@ +export 'package:stream_core/stream_core.dart' + show BoundingBox, CircularRegion, LocationCoordinate; + export 'models/activity_data.dart'; export 'models/aggregated_activity_data.dart'; export 'models/bookmark_data.dart'; diff --git a/packages/stream_feeds/lib/src/models/activity_data.dart b/packages/stream_feeds/lib/src/models/activity_data.dart index baeefcb9..7833cc14 100644 --- a/packages/stream_feeds/lib/src/models/activity_data.dart +++ b/packages/stream_feeds/lib/src/models/activity_data.dart @@ -127,7 +127,7 @@ class ActivityData with _$ActivityData { /// Geographic location data associated with the activity, if any. @override - final ActivityLocation? location; + final LocationCoordinate? location; /// Users mentioned in the activity. @override @@ -277,7 +277,12 @@ extension ActivityResponseMapper on ActivityResponse { interestTags: interestTags, isWatched: isWatched, latestReactions: [...latestReactions.map((r) => r.toModel())], - location: location, + location: location?.let( + (it) => LocationCoordinate( + latitude: it.lat, + longitude: it.lng, + ), + ), mentionedUsers: [...mentionedUsers.map((u) => u.toModel())], moderation: moderation?.toModel(), notificationContext: notificationContext, diff --git a/packages/stream_feeds/lib/src/models/activity_data.freezed.dart b/packages/stream_feeds/lib/src/models/activity_data.freezed.dart index 05c6bdaa..14a0faf3 100644 --- a/packages/stream_feeds/lib/src/models/activity_data.freezed.dart +++ b/packages/stream_feeds/lib/src/models/activity_data.freezed.dart @@ -30,7 +30,7 @@ mixin _$ActivityData { List get interestTags; bool? get isWatched; List get latestReactions; - ActivityLocation? get location; + LocationCoordinate? get location; List get mentionedUsers; Moderation? get moderation; NotificationContext? get notificationContext; @@ -204,7 +204,7 @@ abstract mixin class $ActivityDataCopyWith<$Res> { List interestTags, bool? isWatched, List latestReactions, - ActivityLocation? location, + LocationCoordinate? location, List mentionedUsers, Moderation? moderation, NotificationContext? notificationContext, @@ -344,7 +344,7 @@ class _$ActivityDataCopyWithImpl<$Res> implements $ActivityDataCopyWith<$Res> { location: freezed == location ? _self.location : location // ignore: cast_nullable_to_non_nullable - as ActivityLocation?, + as LocationCoordinate?, mentionedUsers: null == mentionedUsers ? _self.mentionedUsers : mentionedUsers // ignore: cast_nullable_to_non_nullable diff --git a/packages/stream_feeds/lib/src/state/query/activities_query.dart b/packages/stream_feeds/lib/src/state/query/activities_query.dart index 210a71d0..c8796355 100644 --- a/packages/stream_feeds/lib/src/state/query/activities_query.dart +++ b/packages/stream_feeds/lib/src/state/query/activities_query.dart @@ -16,7 +16,7 @@ part 'activities_query.freezed.dart'; /// ## Example /// ```dart /// final query = ActivitiesQuery( -/// filter: Filter.equal(ActivitiesFilterField.type, "post"), +/// filter: Filter.equal(ActivitiesFilterField.activityType, "post"), /// sort: [ActivitiesSort.desc(ActivitiesSortField.createdAt)], /// limit: 20, /// ); @@ -66,6 +66,14 @@ class ActivitiesFilterField extends FilterField { /// Creates a new activities filter field. ActivitiesFilterField(super.remote, super.value); + /// Filter by the type of activity (e.g., "post", "comment", "reaction"). + /// + /// **Supported operators:** `.equal`, `.in` + static final activityType = ActivitiesFilterField( + 'activity_type', + (data) => data.type, + ); + /// Filter by the creation timestamp of the activity. /// /// **Supported operators:** `.equal`, `.greaterThan`, `.lessThan`, `.greaterThanOrEqual`, `.lessThanOrEqual` @@ -76,7 +84,8 @@ class ActivitiesFilterField extends FilterField { /// Filter by the expiration timestamp of the activity. /// - /// **Supported operators:** `.exists` + /// **Supported operators:** `.equal`, `.notEqual`, `.greaterThan`, + /// `.lessThan`, `.greaterThanOrEqual`, `.lessThanOrEqual`, `.exists` static final expiresAt = ActivitiesFilterField( 'expires_at', (data) => data.expiresAt, @@ -90,14 +99,30 @@ class ActivitiesFilterField extends FilterField { (data) => data.id, ); + /// Filter by the feed ID(s) the activity belongs to. + /// + /// **Supported operators:** `.equal`, `.in` + static final feed = ActivitiesFilterField( + 'feed', + (data) => data.feeds, + ); + /// Filter by the filter tags associated with the activity. /// - /// **Supported operators:** `.equal`, `.in`, `.customContains` + /// **Supported operators:** `.equal`, `.in`, `.contains` static final filterTags = ActivitiesFilterField( 'filter_tags', (data) => data.filterTags, ); + /// Filter by the interest tags associated with the activity. + /// + /// **Supported operators:** `.equal`, `.in`, `.contains` + static final interestTags = ActivitiesFilterField( + 'interest_tags', + (data) => data.interestTags, + ); + /// Filter by the popularity score of the activity. /// /// **Supported operators:** `.equal`, `.greaterThan`, `.lessThan`, `.greaterThanOrEqual`, `.lessThanOrEqual` @@ -108,7 +133,7 @@ class ActivitiesFilterField extends FilterField { /// Filter by the search data content of the activity. /// - /// **Supported operators:** `.equal`, `.customQ`, `.customAutocomplete` + /// **Supported operators:** `.contains`, `.in`, `.pathExists` static final searchData = ActivitiesFilterField( 'search_data', (data) => data.searchData, @@ -116,20 +141,12 @@ class ActivitiesFilterField extends FilterField { /// Filter by the text content of the activity. /// - /// **Supported operators:** `.equal`, `.customQ`, `.customAutocomplete` + /// **Supported operators:** `.equal`, `.q` (full-text search), `.autocomplete` static final text = ActivitiesFilterField( 'text', (data) => data.text, ); - /// Filter by the type of activity (e.g., "post", "comment", "reaction"). - /// - /// **Supported operators:** `.equal`, `.in` - static final type = ActivitiesFilterField( - 'type', - (data) => data.type, - ); - /// Filter by the user ID who created the activity. /// /// **Supported operators:** `.equal`, `.in` @@ -137,6 +154,28 @@ class ActivitiesFilterField extends FilterField { 'user_id', (data) => data.user.id, ); + + /// Filter by the proximity to a specific location. + /// + /// Note: This requires an object with latitude ('lat'), longitude ('lng') + /// and distance in km ('distance). + /// + /// **Supported operators:** `.equal` + static final near = ActivitiesFilterField( + 'near', + (data) => data.location, + ); + + /// Filter by activities within specific geographical bounds. + /// + /// Note: This requires an object with 'sw_lat', 'sw_lng' (southwest corner) + /// and 'ne_lat', 'ne_lng' (northeast) corner keys. + /// + /// **Supported operators:** `.equal` + static final withinBounds = ActivitiesFilterField( + 'within_bounds', + (data) => data.location, + ); } // endregion diff --git a/packages/stream_feeds/lib/src/state/query/feed_query.dart b/packages/stream_feeds/lib/src/state/query/feed_query.dart index bb27b281..de7c226f 100644 --- a/packages/stream_feeds/lib/src/state/query/feed_query.dart +++ b/packages/stream_feeds/lib/src/state/query/feed_query.dart @@ -19,7 +19,7 @@ part 'feed_query.freezed.dart'; /// ```dart /// final query = FeedQuery( /// fid: FeedId(group: 'user', id: 'john'), -/// activityFilter: Filter.equal(ActivitiesFilterField.type, 'post'), +/// activityFilter: Filter.equal(ActivitiesFilterField.activityType, 'post'), /// activityLimit: 25, /// watch: true, /// ); diff --git a/packages/stream_feeds/pubspec.yaml b/packages/stream_feeds/pubspec.yaml index 997e7f90..bdc775b4 100644 --- a/packages/stream_feeds/pubspec.yaml +++ b/packages/stream_feeds/pubspec.yaml @@ -30,7 +30,7 @@ dependencies: retrofit: ">=4.6.0 <=4.9.0" rxdart: ^0.28.0 state_notifier: ^1.0.0 - stream_core: ^0.3.1 + stream_core: ^0.3.2 uuid: ^4.5.1 dev_dependencies: diff --git a/packages/stream_feeds/test/state/activity_list_test.dart b/packages/stream_feeds/test/state/activity_list_test.dart index 23509790..e96fbbea 100644 --- a/packages/stream_feeds/test/state/activity_list_test.dart +++ b/packages/stream_feeds/test/state/activity_list_test.dart @@ -24,7 +24,7 @@ void main() { 'ActivityUpdatedEvent - should remove activity when updated to non-matching type', build: (client) => client.activityList( ActivitiesQuery( - filter: Filter.equal(ActivitiesFilterField.type, 'post'), + filter: Filter.equal(ActivitiesFilterField.activityType, 'post'), ), ), setUp: (tester) => tester.get( @@ -54,7 +54,7 @@ void main() { 'ActivityReactionAddedEvent - should remove activity when reaction causes filter mismatch', build: (client) => client.activityList( ActivitiesQuery( - filter: Filter.equal(ActivitiesFilterField.type, 'post'), + filter: Filter.equal(ActivitiesFilterField.activityType, 'post'), ), ), setUp: (tester) => tester.get( @@ -91,7 +91,7 @@ void main() { 'ActivityReactionDeletedEvent - should remove activity when reaction deletion causes filter mismatch', build: (client) => client.activityList( ActivitiesQuery( - filter: Filter.equal(ActivitiesFilterField.type, 'post'), + filter: Filter.equal(ActivitiesFilterField.activityType, 'post'), ), ), setUp: (tester) => tester.get( @@ -128,7 +128,7 @@ void main() { 'BookmarkAddedEvent - should remove activity when bookmark causes filter mismatch', build: (client) => client.activityList( ActivitiesQuery( - filter: Filter.equal(ActivitiesFilterField.type, 'post'), + filter: Filter.equal(ActivitiesFilterField.activityType, 'post'), ), ), setUp: (tester) => tester.get( @@ -157,7 +157,7 @@ void main() { 'BookmarkDeletedEvent - should remove activity when bookmark deletion causes filter mismatch', build: (client) => client.activityList( ActivitiesQuery( - filter: Filter.equal(ActivitiesFilterField.type, 'post'), + filter: Filter.equal(ActivitiesFilterField.activityType, 'post'), ), ), setUp: (tester) => tester.get( @@ -186,7 +186,7 @@ void main() { 'CommentAddedEvent - should remove activity when comment causes filter mismatch', build: (client) => client.activityList( ActivitiesQuery( - filter: Filter.equal(ActivitiesFilterField.type, 'post'), + filter: Filter.equal(ActivitiesFilterField.activityType, 'post'), ), ), setUp: (tester) => tester.get( @@ -220,7 +220,7 @@ void main() { build: (client) => client.activityList( ActivitiesQuery( filter: Filter.and([ - Filter.equal(ActivitiesFilterField.type, 'post'), + Filter.equal(ActivitiesFilterField.activityType, 'post'), Filter.equal(ActivitiesFilterField.filterTags, ['featured']), ]), ), @@ -254,7 +254,7 @@ void main() { build: (client) => client.activityList( ActivitiesQuery( filter: Filter.or([ - Filter.equal(ActivitiesFilterField.type, 'post'), + Filter.equal(ActivitiesFilterField.activityType, 'post'), Filter.equal(ActivitiesFilterField.filterTags, ['featured']), ]), ), diff --git a/packages/stream_feeds/test/state/feed_test.dart b/packages/stream_feeds/test/state/feed_test.dart index 98491a04..37c285da 100644 --- a/packages/stream_feeds/test/state/feed_test.dart +++ b/packages/stream_feeds/test/state/feed_test.dart @@ -416,7 +416,10 @@ void main() { build: (client) => client.feedFromQuery( FeedQuery( fid: feedId, - activityFilter: Filter.equal(ActivitiesFilterField.type, 'post'), + activityFilter: Filter.equal( + ActivitiesFilterField.activityType, + 'post', + ), ), ), setUp: (tester) => tester.getOrCreate( @@ -448,7 +451,10 @@ void main() { build: (client) => client.feedFromQuery( FeedQuery( fid: feedId, - activityFilter: Filter.equal(ActivitiesFilterField.type, 'post'), + activityFilter: Filter.equal( + ActivitiesFilterField.activityType, + 'post', + ), ), ), setUp: (tester) => tester.getOrCreate( @@ -480,7 +486,10 @@ void main() { build: (client) => client.feedFromQuery( FeedQuery( fid: feedId, - activityFilter: Filter.equal(ActivitiesFilterField.type, 'post'), + activityFilter: Filter.equal( + ActivitiesFilterField.activityType, + 'post', + ), ), ), setUp: (tester) => tester.getOrCreate( @@ -558,7 +567,10 @@ void main() { build: (client) => client.feedFromQuery( FeedQuery( fid: feedId, - activityFilter: Filter.equal(ActivitiesFilterField.type, 'post'), + activityFilter: Filter.equal( + ActivitiesFilterField.activityType, + 'post', + ), ), ), setUp: (tester) => tester.getOrCreate( @@ -597,7 +609,10 @@ void main() { build: (client) => client.feedFromQuery( FeedQuery( fid: feedId, - activityFilter: Filter.equal(ActivitiesFilterField.type, 'post'), + activityFilter: Filter.equal( + ActivitiesFilterField.activityType, + 'post', + ), ), ), setUp: (tester) => tester.getOrCreate( @@ -634,7 +649,10 @@ void main() { build: (client) => client.feedFromQuery( FeedQuery( fid: feedId, - activityFilter: Filter.equal(ActivitiesFilterField.type, 'post'), + activityFilter: Filter.equal( + ActivitiesFilterField.activityType, + 'post', + ), ), ), setUp: (tester) => tester.getOrCreate( @@ -672,7 +690,7 @@ void main() { FeedQuery( fid: feedId, activityFilter: Filter.and([ - Filter.equal(ActivitiesFilterField.type, 'post'), + Filter.equal(ActivitiesFilterField.activityType, 'post'), Filter.in_(ActivitiesFilterField.filterTags, ['featured']), ]), ), @@ -711,7 +729,7 @@ void main() { FeedQuery( fid: feedId, activityFilter: Filter.or([ - Filter.equal(ActivitiesFilterField.type, 'post'), + Filter.equal(ActivitiesFilterField.activityType, 'post'), Filter.in_(ActivitiesFilterField.filterTags, ['featured']), ]), ),