Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
03414b5
feat(llc): implement attachment uploads
xsahil03x Sep 2, 2025
c2cc816
feat: include custom data in attachment uploads
xsahil03x Sep 2, 2025
06bb0f2
chore: fix typo in `addCommentsBatch` method doc
xsahil03x Sep 2, 2025
ca57f6c
refactor: move CDN files to cdn directory
xsahil03x Sep 2, 2025
085953e
temp
xsahil03x Sep 2, 2025
8d6894d
Merge remote-tracking branch 'origin/main' into feat/attachment-uploader
xsahil03x Sep 2, 2025
1a15dfc
Merge remote-tracking branch 'origin/main' into feat/attachment-uploader
xsahil03x Sep 2, 2025
8c222e4
chore: update stream_core dependency and fix comment request
xsahil03x Sep 2, 2025
92b2aa4
Discard changes to sample_app/linux/flutter/generated_plugin_registra…
xsahil03x Sep 2, 2025
3dffebc
Discard changes to sample_app/linux/flutter/generated_plugins.cmake
xsahil03x Sep 2, 2025
b62ebb5
Discard changes to sample_app/macos/Runner/DebugProfile.entitlements
xsahil03x Sep 2, 2025
5d538e6
Discard changes to sample_app/macos/Runner/Info.plist
xsahil03x Sep 2, 2025
cc8eb64
Discard changes to sample_app/macos/Runner/Release.entitlements
xsahil03x Sep 2, 2025
7e5e44d
Discard changes to sample_app/windows/flutter/generated_plugins.cmake
xsahil03x Sep 2, 2025
cf78da3
feat(llc): add uploader utility for processing attachments
xsahil03x Sep 3, 2025
696e148
feat(sample): add support for creating activities, with attachments
xsahil03x Sep 3, 2025
e30613b
feat: add support for creating activities, with attachments
xsahil03x Sep 3, 2025
c2c2320
Merge remote-tracking branch 'origin/main' into feat/attachment-uploader
xsahil03x Sep 3, 2025
44093f5
refactor: Introduce session scope for DI and remove SessionScope widget
xsahil03x Sep 4, 2025
ca4d5b4
Refactor: Rename AuthModule to SessionModule and update client getter
xsahil03x Sep 4, 2025
6d3009d
Merge remote-tracking branch 'origin/main' into feat/attachment-uploader
xsahil03x Sep 4, 2025
4cdeeb4
docs: update file upload and activity snippets
xsahil03x Sep 4, 2025
bda59a6
docs(uploads): update deleteFile and deleteImage methods
xsahil03x Sep 4, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions docs_code_snippets/01_01_quickstart.dart
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,9 @@ Future<void> socialMediaFeed() async {

// Add a comment to activity
await timeline.addComment(
request: AddCommentRequest(
request: ActivityAddCommentRequest(
comment: 'Great post!',
objectId: 'activity_123',
objectType: 'activity',
activityId: 'activity_123',
),
);

Expand All @@ -54,6 +53,7 @@ Future<void> socialMediaFeed() async {
activityId: 'activity_123',
fid: FeedId(group: 'timeline', id: 'john'),
);

await activity.addCommentReaction(
commentId: 'commentId',
request: AddCommentReactionRequest(type: 'like'),
Expand Down
18 changes: 14 additions & 4 deletions docs_code_snippets/03_01_activities.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,11 @@ Future<void> imageAndVideo() async {
final imageActivity = await feed.addActivity(
request: const FeedAddActivityRequest(
attachments: [
Attachment(imageUrl: 'https://example.com/image.jpg', type: 'image'),
Attachment(
imageUrl: 'https://example.com/image.jpg',
type: 'image',
custom: {'width': 600, 'height': 400},
),
],
text: 'look at NYC',
type: 'post',
Expand All @@ -41,10 +45,12 @@ Future<void> stories() async {
const Attachment(
imageUrl: 'https://example.com/image1.jpg',
type: 'image',
custom: {'width': 600, 'height': 400},
),
const Attachment(
assetUrl: 'https://example.com/video1.mp4',
type: 'video',
custom: {'width': 1920, 'height': 1080, 'duration': 12},
),
],
expiresAt: tomorrow.toIso8601String(),
Expand All @@ -69,6 +75,7 @@ Future<void> addManyActivities() async {
type: 'post',
),
];

final upsertedActivities = await client.upsertActivities(
activities: activities,
);
Expand All @@ -83,11 +90,14 @@ Future<void> updatingAndDeletingActivities() async {
custom: {'custom': 'custom'},
),
);

// Delete an activity
await feed.deleteActivity(
id: '123',
// Soft delete sets deleted at but retains the data, hard delete fully removes it
hardDelete: false,
);

const hardDelete =
false; // Soft delete sets deleted at but retains the data, hard delete fully removes it
await feed.deleteActivity(id: '123', hardDelete: hardDelete);
// Batch delete activities
await client.deleteActivities(
request: const DeleteActivitiesRequest(
Expand Down
196 changes: 194 additions & 2 deletions docs_code_snippets/03_03_file_uploads.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,195 @@
// ignore_for_file: file_names
// ignore_for_file: unused_local_variable, file_names, avoid_redundant_argument_values

// TODO
import 'package:stream_feeds/stream_feeds.dart';

late StreamFeedsClient client;
late Feed feed;
late Activity activity;
late StreamAttachmentUploader attachmentUploader;
late List<Attachment> uploadedAttachments;

Future<void> howToUploadAFileOrImageStep1() async {
// Create an instance of AttachmentFile with the file path
//
// Note: On web, use `AttachmentFile.fromData`. Or if you are working with
// plugins which provide XFile then use `AttachmentFile.fromXFile`.
final file = AttachmentFile('path/to/file');

// Create a StreamAttachment with the file and type (image, video, file)
final streamAttachment = StreamAttachment(
file: file,
type: AttachmentType.image,
custom: {'width': 600, 'height': 400},
);

// Upload the attachment
final result = await attachmentUploader.upload(
streamAttachment,
// Optionally track upload progress
onProgress: (progress) {
// Handle progress updates
},
);

// Map the result to an Attachment model to send with an activity
final uploadedAttachment = result.getOrThrow();
final attachmentReadyToBeSent = Attachment(
imageUrl: uploadedAttachment.remoteUrl,
assetUrl: uploadedAttachment.remoteUrl,
thumbUrl: uploadedAttachment.thumbnailUrl,
custom: {...?uploadedAttachment.custom},
);
}

Future<void> howToUploadAFileOrImageStep2() async {
// Add an activity with the uploaded attachment
final activity = await feed.addActivity(
request: FeedAddActivityRequest(
attachments: uploadedAttachments,
text: 'look at NYC',
type: 'post',
),
);
}

Future<void> howToUploadAFileOrImageStep3() async {
// Create a list of attachments to upload
final attachmentUploads = <StreamAttachment>[];

// Add an activity with the attachment needing upload
final activity = await feed.addActivity(
request: FeedAddActivityRequest(
attachmentUploads: attachmentUploads,
text: 'look at NYC',
type: 'post',
),
);
}

Future<void> howToUploadAFileOrImageStep4() async {
// Create a list of attachments to upload
final attachmentUploads = <StreamAttachment>[];

// Add a comment with the attachment needing upload
final comment = await activity.addComment(
request: ActivityAddCommentRequest(
attachmentUploads: attachmentUploads,
activityId: activity.activityId,
comment: 'look at NYC',
),
);
}

Future<void> howToDeleteAFileOrImage() async {
// Delete an image from the CDN
await client.deleteImage(url: 'https://mycdn.com/image.png');

// Delete a file from the CDN
await client.deleteFile(url: 'https://mycdn.com/file.pdf');
}

// Your custom implementation of CdnClient
class CustomCDN implements CdnClient {
@override
Future<Result<UploadedFile>> uploadFile(
AttachmentFile file, {
ProgressCallback? onProgress,
CancelToken? cancelToken,
}) {
// Use your own CDN upload logic here.
// For example, you might upload the file to AWS S3, Google Cloud Storage, etc.
// After uploading, return a Result<UploadedFile> with the file URLs.
//
// Note: Make sure to handle progress updates and cancellation if needed.
return uploadToYourOwnCdn(
file,
onProgress: onProgress,
cancelToken: cancelToken,
);
}

@override
Future<Result<UploadedFile>> uploadImage(
AttachmentFile image, {
ProgressCallback? onProgress,
CancelToken? cancelToken,
}) {
// Use your own CDN upload logic here.
// For example, you might upload the image to AWS S3, Google Cloud Storage, etc.
// After uploading, return a Result<UploadedFile> with the image URLs.
//
// Note: Make sure to handle progress updates and cancellation if needed.
return uploadToYourOwnCdn(
image,
onProgress: onProgress,
cancelToken: cancelToken,
);
}

@override
Future<Result<void>> deleteFile(
String url, {
CancelToken? cancelToken,
}) {
// Use your own CDN deletion logic here.
// For example, you might delete the file from AWS S3, Google Cloud Storage, etc.
// After deleting, return a Result<void> indicating success or failure.
//
// Note: Make sure to handle cancellation if needed.
return deleteFromYourOwnCdn(
url,
cancelToken: cancelToken,
);
}

@override
Future<Result<void>> deleteImage(
String url, {
CancelToken? cancelToken,
}) {
// Use your own CDN deletion logic here.
// For example, you might delete the image from AWS S3, Google Cloud Storage, etc.
// After deleting, return a Result<void> indicating success or failure.
//
// Note: Make sure to handle cancellation if needed.
return deleteFromYourOwnCdn(
url,
cancelToken: cancelToken,
);
}
}

Future<void> usingYourOwnCdn() async {
// Create a config with your custom CDN client
final config = FeedsConfig(cdnClient: CustomCDN());

// Initialize the StreamFeedsClient with the custom config
final client = StreamFeedsClient(
apiKey: 'your_api_key',
user: const User(id: 'user_id'),
config: config,
);
}

// region Helper methods to simulate your own CDN logic

Future<Result<UploadedFile>> uploadToYourOwnCdn(
AttachmentFile file, {
ProgressCallback? onProgress,
CancelToken? cancelToken,
}) {
// Implement your file upload logic here
// Return a Result<UploadedFile> indicating success or failure
throw UnimplementedError();
}

Future<Result<void>> deleteFromYourOwnCdn(
String url, {
CancelToken? cancelToken,
}) {
// Implement your file deletion logic here
// Return a Result<void> indicating success or failure
throw UnimplementedError();
}

// endregion
37 changes: 27 additions & 10 deletions docs_code_snippets/04_01_feeds.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ Future<void> creatingAFeed() async {
visibility: FeedVisibility.public,
),
);

final feed2 = client.feedFromQuery(query);
await feed2.getOrCreate();
}
Expand All @@ -29,31 +30,37 @@ Future<void> readingAFeed() async {
final feedData = feed.state.feed;
final activities = feed.state.activities;
final members = feed.state.members;
// Always dispose the feed when you are done with it

// Note: Always dispose the feed when you are done with it
feed.dispose();
}

Future<void> readingAFeedMoreOptions() async {
final query = FeedQuery(
fid: const FeedId(group: 'user', id: 'john'),
activityFilter: Filter.in_(ActivitiesFilterField.filterTags, const [
'green',
]), // filter activities with filter tag green
// filter activities with filter tag green
activityFilter: Filter.in_(
ActivitiesFilterField.filterTags,
const ['green'],
),
activityLimit: 10,
externalRanking: {'user_score': 0.8}, // additional data used for ranking
// additional data used for ranking
externalRanking: {'user_score': 0.8},
followerLimit: 10,
followingLimit: 10,
memberLimit: 10,
view:
'myview', // overwrite the default ranking or aggregation logic for this feed. good for split testing
watch: true, // receive web-socket events with real-time updates
// overwrite the default ranking or aggregation logic for this feed. good for split testing
view: 'myview',
// receive web-socket events with real-time updates
watch: true,
);

final feed = client.feedFromQuery(query);
await feed.getOrCreate();
final activities = feed.state.activities;
final feedData = feed.state.feed;
// Always dispose the feed when you are done with it

// Note: Always dispose the feed when you are done with it
feed.dispose();
}

Expand All @@ -64,6 +71,7 @@ Future<void> feedPagination() async {
activityLimit: 10,
),
);

// Page 1
await feed.getOrCreate();
final activities = feed.state.activities; // First 10 activities
Expand Down Expand Up @@ -97,16 +105,19 @@ Future<void> filteringExamples() async {
type: 'activity',
),
]);

// Now read the feed, this will fetch activity 1 and 2
final query = FeedQuery(
fid: feedId,
activityFilter: Filter.in_(ActivitiesFilterField.filterTags, const [
'blue',
]),
);

final feed = client.feedFromQuery(query);
await feed.getOrCreate();
final activities = feed.state.activities; // contains first and second
// contains first and second
final activities = feed.state.activities;
}

Future<void> moreComplexFilterExamples() async {
Expand All @@ -124,6 +135,7 @@ Future<void> moreComplexFilterExamples() async {
]),
]),
);

await feed.getOrCreate();
final activities = feed.state.activities;
}
Expand Down Expand Up @@ -191,6 +203,7 @@ Future<void> queryMyFeeds() async {
limit: 10,
watch: true,
);

final feedList = client.feedList(query);

// Page 1
Expand All @@ -217,14 +230,17 @@ Future<void> queryFeedsByNameOrVisibility() async {
Filter.query(FeedsFilterField.name, 'Sports'),
]),
);

final sportsFeedList = client.feedList(sportsQuery);
final sportsFeeds = await sportsFeedList.get();

final techQuery = FeedsQuery(
filter: Filter.and([
Filter.equal(FeedsFilterField.visibility, 'public'),
Filter.autoComplete(FeedsFilterField.description, 'tech'),
]),
);

final techFeedList = client.feedList(techQuery);
final techFeeds = await techFeedList.get();
}
Expand All @@ -236,6 +252,7 @@ Future<void> queryFeedsByCreatorName() async {
Filter.query(FeedsFilterField.createdByName, 'Thompson'),
]),
);

final feedList = client.feedList(query);
final feeds = await feedList.get();
}
Loading
Loading