Skip to content

Commit e2b1ad5

Browse files
authored
feat(llc): implement attachment uploader (#11)
1 parent 6f4bcb2 commit e2b1ad5

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+2440
-455
lines changed

docs_code_snippets/01_01_quickstart.dart

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,9 @@ Future<void> socialMediaFeed() async {
4242

4343
// Add a comment to activity
4444
await timeline.addComment(
45-
request: AddCommentRequest(
45+
request: ActivityAddCommentRequest(
4646
comment: 'Great post!',
47-
objectId: 'activity_123',
48-
objectType: 'activity',
47+
activityId: 'activity_123',
4948
),
5049
);
5150

@@ -54,6 +53,7 @@ Future<void> socialMediaFeed() async {
5453
activityId: 'activity_123',
5554
fid: FeedId(group: 'timeline', id: 'john'),
5655
);
56+
5757
await activity.addCommentReaction(
5858
commentId: 'commentId',
5959
request: AddCommentReactionRequest(type: 'like'),

docs_code_snippets/03_01_activities.dart

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,11 @@ Future<void> imageAndVideo() async {
2525
final imageActivity = await feed.addActivity(
2626
request: const FeedAddActivityRequest(
2727
attachments: [
28-
Attachment(imageUrl: 'https://example.com/image.jpg', type: 'image'),
28+
Attachment(
29+
imageUrl: 'https://example.com/image.jpg',
30+
type: 'image',
31+
custom: {'width': 600, 'height': 400},
32+
),
2933
],
3034
text: 'look at NYC',
3135
type: 'post',
@@ -41,10 +45,12 @@ Future<void> stories() async {
4145
const Attachment(
4246
imageUrl: 'https://example.com/image1.jpg',
4347
type: 'image',
48+
custom: {'width': 600, 'height': 400},
4449
),
4550
const Attachment(
4651
assetUrl: 'https://example.com/video1.mp4',
4752
type: 'video',
53+
custom: {'width': 1920, 'height': 1080, 'duration': 12},
4854
),
4955
],
5056
expiresAt: tomorrow.toIso8601String(),
@@ -69,6 +75,7 @@ Future<void> addManyActivities() async {
6975
type: 'post',
7076
),
7177
];
78+
7279
final upsertedActivities = await client.upsertActivities(
7380
activities: activities,
7481
);
@@ -83,11 +90,14 @@ Future<void> updatingAndDeletingActivities() async {
8390
custom: {'custom': 'custom'},
8491
),
8592
);
93+
8694
// Delete an activity
95+
await feed.deleteActivity(
96+
id: '123',
97+
// Soft delete sets deleted at but retains the data, hard delete fully removes it
98+
hardDelete: false,
99+
);
87100

88-
const hardDelete =
89-
false; // Soft delete sets deleted at but retains the data, hard delete fully removes it
90-
await feed.deleteActivity(id: '123', hardDelete: hardDelete);
91101
// Batch delete activities
92102
await client.deleteActivities(
93103
request: const DeleteActivitiesRequest(
Lines changed: 194 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,195 @@
1-
// ignore_for_file: file_names
1+
// ignore_for_file: unused_local_variable, file_names, avoid_redundant_argument_values
22

3-
// TODO
3+
import 'package:stream_feeds/stream_feeds.dart';
4+
5+
late StreamFeedsClient client;
6+
late Feed feed;
7+
late Activity activity;
8+
late StreamAttachmentUploader attachmentUploader;
9+
late List<Attachment> uploadedAttachments;
10+
11+
Future<void> howToUploadAFileOrImageStep1() async {
12+
// Create an instance of AttachmentFile with the file path
13+
//
14+
// Note: On web, use `AttachmentFile.fromData`. Or if you are working with
15+
// plugins which provide XFile then use `AttachmentFile.fromXFile`.
16+
final file = AttachmentFile('path/to/file');
17+
18+
// Create a StreamAttachment with the file and type (image, video, file)
19+
final streamAttachment = StreamAttachment(
20+
file: file,
21+
type: AttachmentType.image,
22+
custom: {'width': 600, 'height': 400},
23+
);
24+
25+
// Upload the attachment
26+
final result = await attachmentUploader.upload(
27+
streamAttachment,
28+
// Optionally track upload progress
29+
onProgress: (progress) {
30+
// Handle progress updates
31+
},
32+
);
33+
34+
// Map the result to an Attachment model to send with an activity
35+
final uploadedAttachment = result.getOrThrow();
36+
final attachmentReadyToBeSent = Attachment(
37+
imageUrl: uploadedAttachment.remoteUrl,
38+
assetUrl: uploadedAttachment.remoteUrl,
39+
thumbUrl: uploadedAttachment.thumbnailUrl,
40+
custom: {...?uploadedAttachment.custom},
41+
);
42+
}
43+
44+
Future<void> howToUploadAFileOrImageStep2() async {
45+
// Add an activity with the uploaded attachment
46+
final activity = await feed.addActivity(
47+
request: FeedAddActivityRequest(
48+
attachments: uploadedAttachments,
49+
text: 'look at NYC',
50+
type: 'post',
51+
),
52+
);
53+
}
54+
55+
Future<void> howToUploadAFileOrImageStep3() async {
56+
// Create a list of attachments to upload
57+
final attachmentUploads = <StreamAttachment>[];
58+
59+
// Add an activity with the attachment needing upload
60+
final activity = await feed.addActivity(
61+
request: FeedAddActivityRequest(
62+
attachmentUploads: attachmentUploads,
63+
text: 'look at NYC',
64+
type: 'post',
65+
),
66+
);
67+
}
68+
69+
Future<void> howToUploadAFileOrImageStep4() async {
70+
// Create a list of attachments to upload
71+
final attachmentUploads = <StreamAttachment>[];
72+
73+
// Add a comment with the attachment needing upload
74+
final comment = await activity.addComment(
75+
request: ActivityAddCommentRequest(
76+
attachmentUploads: attachmentUploads,
77+
activityId: activity.activityId,
78+
comment: 'look at NYC',
79+
),
80+
);
81+
}
82+
83+
Future<void> howToDeleteAFileOrImage() async {
84+
// Delete an image from the CDN
85+
await client.deleteImage(url: 'https://mycdn.com/image.png');
86+
87+
// Delete a file from the CDN
88+
await client.deleteFile(url: 'https://mycdn.com/file.pdf');
89+
}
90+
91+
// Your custom implementation of CdnClient
92+
class CustomCDN implements CdnClient {
93+
@override
94+
Future<Result<UploadedFile>> uploadFile(
95+
AttachmentFile file, {
96+
ProgressCallback? onProgress,
97+
CancelToken? cancelToken,
98+
}) {
99+
// Use your own CDN upload logic here.
100+
// For example, you might upload the file to AWS S3, Google Cloud Storage, etc.
101+
// After uploading, return a Result<UploadedFile> with the file URLs.
102+
//
103+
// Note: Make sure to handle progress updates and cancellation if needed.
104+
return uploadToYourOwnCdn(
105+
file,
106+
onProgress: onProgress,
107+
cancelToken: cancelToken,
108+
);
109+
}
110+
111+
@override
112+
Future<Result<UploadedFile>> uploadImage(
113+
AttachmentFile image, {
114+
ProgressCallback? onProgress,
115+
CancelToken? cancelToken,
116+
}) {
117+
// Use your own CDN upload logic here.
118+
// For example, you might upload the image to AWS S3, Google Cloud Storage, etc.
119+
// After uploading, return a Result<UploadedFile> with the image URLs.
120+
//
121+
// Note: Make sure to handle progress updates and cancellation if needed.
122+
return uploadToYourOwnCdn(
123+
image,
124+
onProgress: onProgress,
125+
cancelToken: cancelToken,
126+
);
127+
}
128+
129+
@override
130+
Future<Result<void>> deleteFile(
131+
String url, {
132+
CancelToken? cancelToken,
133+
}) {
134+
// Use your own CDN deletion logic here.
135+
// For example, you might delete the file from AWS S3, Google Cloud Storage, etc.
136+
// After deleting, return a Result<void> indicating success or failure.
137+
//
138+
// Note: Make sure to handle cancellation if needed.
139+
return deleteFromYourOwnCdn(
140+
url,
141+
cancelToken: cancelToken,
142+
);
143+
}
144+
145+
@override
146+
Future<Result<void>> deleteImage(
147+
String url, {
148+
CancelToken? cancelToken,
149+
}) {
150+
// Use your own CDN deletion logic here.
151+
// For example, you might delete the image from AWS S3, Google Cloud Storage, etc.
152+
// After deleting, return a Result<void> indicating success or failure.
153+
//
154+
// Note: Make sure to handle cancellation if needed.
155+
return deleteFromYourOwnCdn(
156+
url,
157+
cancelToken: cancelToken,
158+
);
159+
}
160+
}
161+
162+
Future<void> usingYourOwnCdn() async {
163+
// Create a config with your custom CDN client
164+
final config = FeedsConfig(cdnClient: CustomCDN());
165+
166+
// Initialize the StreamFeedsClient with the custom config
167+
final client = StreamFeedsClient(
168+
apiKey: 'your_api_key',
169+
user: const User(id: 'user_id'),
170+
config: config,
171+
);
172+
}
173+
174+
// region Helper methods to simulate your own CDN logic
175+
176+
Future<Result<UploadedFile>> uploadToYourOwnCdn(
177+
AttachmentFile file, {
178+
ProgressCallback? onProgress,
179+
CancelToken? cancelToken,
180+
}) {
181+
// Implement your file upload logic here
182+
// Return a Result<UploadedFile> indicating success or failure
183+
throw UnimplementedError();
184+
}
185+
186+
Future<Result<void>> deleteFromYourOwnCdn(
187+
String url, {
188+
CancelToken? cancelToken,
189+
}) {
190+
// Implement your file deletion logic here
191+
// Return a Result<void> indicating success or failure
192+
throw UnimplementedError();
193+
}
194+
195+
// endregion

docs_code_snippets/04_01_feeds.dart

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ Future<void> creatingAFeed() async {
1919
visibility: FeedVisibility.public,
2020
),
2121
);
22+
2223
final feed2 = client.feedFromQuery(query);
2324
await feed2.getOrCreate();
2425
}
@@ -29,31 +30,37 @@ Future<void> readingAFeed() async {
2930
final feedData = feed.state.feed;
3031
final activities = feed.state.activities;
3132
final members = feed.state.members;
32-
// Always dispose the feed when you are done with it
33+
34+
// Note: Always dispose the feed when you are done with it
3335
feed.dispose();
3436
}
3537

3638
Future<void> readingAFeedMoreOptions() async {
3739
final query = FeedQuery(
3840
fid: const FeedId(group: 'user', id: 'john'),
39-
activityFilter: Filter.in_(ActivitiesFilterField.filterTags, const [
40-
'green',
41-
]), // filter activities with filter tag green
41+
// filter activities with filter tag green
42+
activityFilter: Filter.in_(
43+
ActivitiesFilterField.filterTags,
44+
const ['green'],
45+
),
4246
activityLimit: 10,
43-
externalRanking: {'user_score': 0.8}, // additional data used for ranking
47+
// additional data used for ranking
48+
externalRanking: {'user_score': 0.8},
4449
followerLimit: 10,
4550
followingLimit: 10,
4651
memberLimit: 10,
47-
view:
48-
'myview', // overwrite the default ranking or aggregation logic for this feed. good for split testing
49-
watch: true, // receive web-socket events with real-time updates
52+
// overwrite the default ranking or aggregation logic for this feed. good for split testing
53+
view: 'myview',
54+
// receive web-socket events with real-time updates
55+
watch: true,
5056
);
5157

5258
final feed = client.feedFromQuery(query);
5359
await feed.getOrCreate();
5460
final activities = feed.state.activities;
5561
final feedData = feed.state.feed;
56-
// Always dispose the feed when you are done with it
62+
63+
// Note: Always dispose the feed when you are done with it
5764
feed.dispose();
5865
}
5966

@@ -64,6 +71,7 @@ Future<void> feedPagination() async {
6471
activityLimit: 10,
6572
),
6673
);
74+
6775
// Page 1
6876
await feed.getOrCreate();
6977
final activities = feed.state.activities; // First 10 activities
@@ -97,16 +105,19 @@ Future<void> filteringExamples() async {
97105
type: 'activity',
98106
),
99107
]);
108+
100109
// Now read the feed, this will fetch activity 1 and 2
101110
final query = FeedQuery(
102111
fid: feedId,
103112
activityFilter: Filter.in_(ActivitiesFilterField.filterTags, const [
104113
'blue',
105114
]),
106115
);
116+
107117
final feed = client.feedFromQuery(query);
108118
await feed.getOrCreate();
109-
final activities = feed.state.activities; // contains first and second
119+
// contains first and second
120+
final activities = feed.state.activities;
110121
}
111122

112123
Future<void> moreComplexFilterExamples() async {
@@ -124,6 +135,7 @@ Future<void> moreComplexFilterExamples() async {
124135
]),
125136
]),
126137
);
138+
127139
await feed.getOrCreate();
128140
final activities = feed.state.activities;
129141
}
@@ -191,6 +203,7 @@ Future<void> queryMyFeeds() async {
191203
limit: 10,
192204
watch: true,
193205
);
206+
194207
final feedList = client.feedList(query);
195208

196209
// Page 1
@@ -217,14 +230,17 @@ Future<void> queryFeedsByNameOrVisibility() async {
217230
Filter.query(FeedsFilterField.name, 'Sports'),
218231
]),
219232
);
233+
220234
final sportsFeedList = client.feedList(sportsQuery);
221235
final sportsFeeds = await sportsFeedList.get();
236+
222237
final techQuery = FeedsQuery(
223238
filter: Filter.and([
224239
Filter.equal(FeedsFilterField.visibility, 'public'),
225240
Filter.autoComplete(FeedsFilterField.description, 'tech'),
226241
]),
227242
);
243+
228244
final techFeedList = client.feedList(techQuery);
229245
final techFeeds = await techFeedList.get();
230246
}
@@ -236,6 +252,7 @@ Future<void> queryFeedsByCreatorName() async {
236252
Filter.query(FeedsFilterField.createdByName, 'Thompson'),
237253
]),
238254
);
255+
239256
final feedList = client.feedList(query);
240257
final feeds = await feedList.get();
241258
}

0 commit comments

Comments
 (0)