[READ ONLY MIRROR] Open Source TikTok alternative built on AT Protocol github.com/sprksocial/client
flutter atproto video dart
10
fork

Configure Feed

Select the types of activity you want to include in your feed.

fix: mutation retry logic and offline handling

+79 -62
+60 -52
lib/src/core/network/atproto/data/repositories/feed_repository_impl.dart
··· 778 778 Future<RepoStrongRef> likePost(String postCid, AtUri postUri) async { 779 779 _logger.d('Liking post with String: $postCid, URI: $postUri'); 780 780 781 - // Determine if this is a Bluesky post or Spark post 782 - final isBskyPost = postUri.collection.toString().startsWith( 783 - 'app.bsky.feed.post', 784 - ); 785 - final likeType = isBskyPost ? 'app.bsky.feed.like' : 'so.sprk.feed.like'; 781 + return _client.executeWithRetry(() async { 782 + // Determine if this is a Bluesky post or Spark post 783 + final isBskyPost = postUri.collection.toString().startsWith( 784 + 'app.bsky.feed.post', 785 + ); 786 + final likeType = isBskyPost ? 'app.bsky.feed.like' : 'so.sprk.feed.like'; 786 787 787 - _logger.d( 788 - 'Post type: ${isBskyPost ? 'Bluesky' : 'Spark'}, using collection: ' 789 - '$likeType', 790 - ); 788 + _logger.d( 789 + 'Post type: ${isBskyPost ? 'Bluesky' : 'Spark'}, using collection: ' 790 + '$likeType', 791 + ); 791 792 792 - final likeRecord = { 793 - r'$type': likeType, 794 - 'subject': {'cid': postCid, 'uri': postUri.toString()}, 795 - 'createdAt': DateTime.now().toUtc().toIso8601String(), 796 - }; 793 + final likeRecord = { 794 + r'$type': likeType, 795 + 'subject': {'cid': postCid, 'uri': postUri.toString()}, 796 + 'createdAt': DateTime.now().toUtc().toIso8601String(), 797 + }; 797 798 798 - final result = await _client.repo.createRecord( 799 - collection: likeType, 800 - record: likeRecord, 801 - ); 802 - _logger.i('Post liked successfully: ${result.uri}'); 799 + final result = await _client.repo.createRecord( 800 + collection: likeType, 801 + record: likeRecord, 802 + ); 803 + _logger.i('Post liked successfully: ${result.uri}'); 803 804 804 - return result; 805 + return result; 806 + }); 805 807 } 806 808 807 809 @override 808 810 Future<void> unlikePost(AtUri likeUri) async { 809 811 _logger.d('Unliking post with like URI: $likeUri'); 810 - await _client.repo.deleteRecord( 811 - uri: likeUri, 812 - skipBskyCrosspostCleanup: true, 813 - ); 814 - _logger.i('Post unliked successfully'); 812 + return _client.executeWithRetry(() async { 813 + await _client.repo.deleteRecord( 814 + uri: likeUri, 815 + skipBskyCrosspostCleanup: true, 816 + ); 817 + _logger.i('Post unliked successfully'); 818 + }); 815 819 } 816 820 817 821 @override 818 822 Future<RepoStrongRef> repostPost(String postCid, AtUri postUri) async { 819 823 _logger.d('Reposting post with CID: $postCid, URI: $postUri'); 820 824 821 - // Determine if this is a Bluesky post or Spark post 822 - final isBskyPost = postUri.collection.toString().startsWith( 823 - 'app.bsky.feed.post', 824 - ); 825 - final repostType = isBskyPost 826 - ? 'app.bsky.feed.repost' 827 - : 'so.sprk.feed.repost'; 825 + return _client.executeWithRetry(() async { 826 + // Determine if this is a Bluesky post or Spark post 827 + final isBskyPost = postUri.collection.toString().startsWith( 828 + 'app.bsky.feed.post', 829 + ); 830 + final repostType = isBskyPost 831 + ? 'app.bsky.feed.repost' 832 + : 'so.sprk.feed.repost'; 828 833 829 - _logger.d( 830 - 'Post type: ${isBskyPost ? 'Bluesky' : 'Spark'}, using collection: ' 831 - '$repostType', 832 - ); 834 + _logger.d( 835 + 'Post type: ${isBskyPost ? 'Bluesky' : 'Spark'}, using collection: ' 836 + '$repostType', 837 + ); 833 838 834 - final repostRecord = { 835 - r'$type': repostType, 836 - 'subject': {'cid': postCid, 'uri': postUri.toString()}, 837 - 'createdAt': DateTime.now().toUtc().toIso8601String(), 838 - }; 839 + final repostRecord = { 840 + r'$type': repostType, 841 + 'subject': {'cid': postCid, 'uri': postUri.toString()}, 842 + 'createdAt': DateTime.now().toUtc().toIso8601String(), 843 + }; 839 844 840 - final result = await _client.repo.createRecord( 841 - collection: repostType, 842 - record: repostRecord, 843 - ); 844 - _logger.i('Post reposted successfully: ${result.uri}'); 845 + final result = await _client.repo.createRecord( 846 + collection: repostType, 847 + record: repostRecord, 848 + ); 849 + _logger.i('Post reposted successfully: ${result.uri}'); 845 850 846 - return result; 851 + return result; 852 + }); 847 853 } 848 854 849 855 @override 850 856 Future<void> unrepostPost(AtUri repostUri) async { 851 857 _logger.d('Unreposting post with repost URI: $repostUri'); 852 - await _client.repo.deleteRecord( 853 - uri: repostUri, 854 - skipBskyCrosspostCleanup: true, 855 - ); 856 - _logger.i('Post unreposted successfully'); 858 + return _client.executeWithRetry(() async { 859 + await _client.repo.deleteRecord( 860 + uri: repostUri, 861 + skipBskyCrosspostCleanup: true, 862 + ); 863 + _logger.i('Post unreposted successfully'); 864 + }); 857 865 } 858 866 859 867 @override
+14 -10
lib/src/core/network/atproto/data/repositories/graph_repository_impl.dart
··· 158 158 @override 159 159 Future<void> unfollowUser(AtUri followUri) async { 160 160 _logger.d('Unfollowing user with follow URI: $followUri'); 161 - await _client.repo.deleteRecord( 162 - uri: followUri, 163 - skipBskyCrosspostCleanup: true, 164 - ); 165 - _logger.i('User unfollowed successfully'); 161 + return _client.executeWithRetry(() async { 162 + await _client.repo.deleteRecord( 163 + uri: followUri, 164 + skipBskyCrosspostCleanup: true, 165 + ); 166 + _logger.i('User unfollowed successfully'); 167 + }); 166 168 } 167 169 168 170 @override ··· 291 293 @override 292 294 Future<void> unblockUser(AtUri blockUri) async { 293 295 _logger.d('Unblocking user with block URI: $blockUri'); 294 - await _client.repo.deleteRecord( 295 - uri: blockUri, 296 - skipBskyCrosspostCleanup: true, 297 - ); 298 - _logger.i('User unblocked successfully'); 296 + return _client.executeWithRetry(() async { 297 + await _client.repo.deleteRecord( 298 + uri: blockUri, 299 + skipBskyCrosspostCleanup: true, 300 + ); 301 + _logger.i('User unblocked successfully'); 302 + }); 299 303 } 300 304 301 305 @override
+5
lib/src/core/network/messages/data/repository/messages_repository_xrpc.dart
··· 181 181 List<dynamic>? facets, 182 182 String? embed, 183 183 }) async { 184 + // NOTE: We intentionally do NOT retry sendMessage because it's not 185 + // idempotent. Retrying could create duplicate user-visible messages 186 + // if the first request succeeded but the connection dropped before 187 + // the client received the response. 188 + // See: https://docs.aws.amazon.com/general/latest/gr/api-retries.html 184 189 final body = <String, dynamic>{ 185 190 'convoId': convoId, 186 191 'message': <String, dynamic>{