Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
15 changes: 8 additions & 7 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@ jobs:
packages/*/lib/src/app/*.gm.dart
packages/*/lib/src/l10n/app_localizations.dart
packages/*/lib/src/l10n/app_localizations_*.dart
key: ${{ runner.os }}-${{ steps.flutter.outputs.CHANNEL }}-dart-${{ hashFiles('**/build.yaml') }}
key: generated-${{ steps.flutter.outputs.CHANNEL }}-${{ runner.os }}-${{ hashFiles('**/build.yaml') }}-${{ hashFiles('packages/*/lib/**.dart') }}
restore-keys: generated-${{ steps.flutter.outputs.CHANNEL }}-${{ runner.os }}-
- name: 📦 Install dependencies
uses: bluefireteam/melos-action@c7dcb921b23cc520cace360b95d02b37bf09cdaa # v3
with:
Expand Down Expand Up @@ -143,7 +144,8 @@ jobs:
packages/*/lib/src/app/*.gm.dart
packages/*/lib/src/l10n/app_localizations.dart
packages/*/lib/src/l10n/app_localizations_*.dart
key: ${{ runner.os }}-${{ steps.flutter.outputs.CHANNEL }}-dart-${{ hashFiles('**/build.yaml') }}
key: generated-${{ steps.flutter.outputs.CHANNEL }}-${{ runner.os }}-${{ hashFiles('**/build.yaml') }}-${{ hashFiles('packages/*/lib/**.dart') }}
restore-keys: generated-${{ steps.flutter.outputs.CHANNEL }}-${{ runner.os }}-
- name: 📦 Install dependencies
run: dart pub get --enforce-lockfile
- name: 🔧 Build
Expand Down Expand Up @@ -195,7 +197,8 @@ jobs:
packages/*/lib/src/app/*.gm.dart
packages/*/lib/src/l10n/app_localizations.dart
packages/*/lib/src/l10n/app_localizations_*.dart
key: ${{ runner.os }}-${{ steps.flutter.outputs.CHANNEL }}-dart-${{ hashFiles('**/build.yaml') }}
key: generated-${{ steps.flutter.outputs.CHANNEL }}-${{ runner.os }}-${{ hashFiles('**/build.yaml') }}-${{ hashFiles('packages/*/lib/**.dart') }}
restore-keys: generated-${{ steps.flutter.outputs.CHANNEL }}-${{ runner.os }}-
- name: 🌋 Install Melos
uses: bluefireteam/melos-action@c7dcb921b23cc520cace360b95d02b37bf09cdaa # v3
with:
Expand Down Expand Up @@ -244,10 +247,8 @@ jobs:
packages/*/lib/src/app/*.gm.dart
packages/*/lib/src/l10n/app_localizations.dart
packages/*/lib/src/l10n/app_localizations_*.dart
key: ${{ runner.os }}-${{ steps.flutter.outputs.CHANNEL }}-dart-${{ hashFiles('**/build.yaml') }}
restore-keys: |
${{ runner.os }}-${{ steps.flutter.outputs.CHANNEL }}-dart-
${{ runner.os }}-
key: generated-${{ steps.flutter.outputs.CHANNEL }}-${{ runner.os }}-${{ hashFiles('**/build.yaml') }}-${{ hashFiles('packages/*/lib/**.dart') }}
restore-keys: generated-${{ steps.flutter.outputs.CHANNEL }}-${{ runner.os }}-
- name: 🌋 Install Melos
uses: bluefireteam/melos-action@c7dcb921b23cc520cace360b95d02b37bf09cdaa # v3
with:
Expand Down
2 changes: 2 additions & 0 deletions packages/app/lib/src/app/bootstrap.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import 'dart:developer';

import 'package:fast_immutable_collections/fast_immutable_collections.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
Expand Down Expand Up @@ -127,6 +128,7 @@
Uint8List _ => 'Uint8List(${value.length})',
AsyncData<Uint8List>(:final value) =>
'AsyncData<Uint8List>(value: ${_normalizedValue(value)})',
final IList<Uint8List> list => '[${list.map(_normalizedValue).join(', ')}]',

Check warning on line 131 in packages/app/lib/src/app/bootstrap.dart

View check run for this annotation

Codecov / codecov/patch

packages/app/lib/src/app/bootstrap.dart#L131

Added line #L131 was not covered by tests
_ => value.toString(),
};
}
17 changes: 13 additions & 4 deletions packages/app/lib/src/app/create_post.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,13 @@
import 'package:path_provider/path_provider.dart';

import '../features/auth/application/auth_service.dart';
import '../features/home/application/feed_service.dart';
import '../features/home/application/location_service.dart';
import '../features/home/application/uploaded_image_service.dart';
import '../features/home/data/post_repository.dart';
import '../features/home/domain/feed_entity.dart';
import '../features/home/domain/post_entity.dart';
import '../features/home/domain/post_id.dart';
import '../features/home/domain/uploaded_image_entity.dart';
import '../utils/hooks.dart';
import '../utils/responsive.dart';
Expand All @@ -33,11 +36,11 @@
final formKey = useGlobalKey<FormState>();
final title = useState('');
final description = useState('');
final userId = ref.watch(idProvider);
final userName = ref.watch(userNameProvider);

final handleSubmit = useCallback(() async {
final uploadedImages = ref.watch(uploadedImagesServiceProvider);
final userId = ref.read(idProvider);
final userName = ref.read(userNameProvider);
final uploadedImages = ref.read(uploadedImagesServiceProvider);

Check warning on line 43 in packages/app/lib/src/app/create_post.dart

View check run for this annotation

Codecov / codecov/patch

packages/app/lib/src/app/create_post.dart#L41-L43

Added lines #L41 - L43 were not covered by tests
final location = await ref.read(locationServiceProvider.future);
var lat = location.latitude.roundToDouble();
var lng = location.longitude.roundToDouble();
Expand All @@ -51,7 +54,7 @@

formKey.currentState?.save();

// Create a list off all uploaded images ids
// Create a list of all uploaded images ids

await ref
.read(postRepositoryProvider)
Expand All @@ -69,10 +72,16 @@
imageIds:
// Read in the list of uploaded images ids.
uploadedImages.map((image) => image.imageId).toIList(),
comments: const IList.empty(),
),
uploadedImages,
);

// Clear the uploaded images list.
ref
..invalidate(feedServiceProvider(FeedEntity.local(lat, lng)))
..invalidate(feedServiceProvider(const FeedEntity.world()));

Check warning on line 83 in packages/app/lib/src/app/create_post.dart

View check run for this annotation

Codecov / codecov/patch

packages/app/lib/src/app/create_post.dart#L82-L83

Added lines #L82 - L83 were not covered by tests

if (!context.mounted) return;
await context.router.maybePop();

Expand Down
2 changes: 1 addition & 1 deletion packages/app/lib/src/app/router.dart
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ class AppRouter extends RootStackRouter {
),
AutoRoute(
page: PostViewRoute.page,
path: '/post',
path: '/post/:id',
title: (context, data) => 'Post',
),
AutoRoute(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import '../domain/feed_entity.dart';
import '../domain/feed_model.dart';
import '../domain/post_entity.dart';
import '../domain/post_id.dart';

part 'feed_service.g.dart';

Expand Down Expand Up @@ -42,8 +43,12 @@
// Store the post in the provider
ref.watch(singlePostProvider(post.id).notifier).setPost(post);

// Collect the post ID
newPostIds.add(post.id);
// Collect the post ID if it's not already in the state
if (!state.ids.contains(post.id)) {
newPostIds.add(post.id);

Check warning on line 48 in packages/app/lib/src/features/home/application/feed_service.dart

View check run for this annotation

Codecov / codecov/patch

packages/app/lib/src/features/home/application/feed_service.dart#L47-L48

Added lines #L47 - L48 were not covered by tests
} else {
throw Exception('Post ${post.id} already exists in the feed state.');

Check warning on line 50 in packages/app/lib/src/features/home/application/feed_service.dart

View check run for this annotation

Codecov / codecov/patch

packages/app/lib/src/features/home/application/feed_service.dart#L50

Added line #L50 was not covered by tests
}
}

// Update the state with the new batch of post IDs and cursor
Expand All @@ -68,16 +73,20 @@
ref.watch(feedPostProvider(feed, postIndex - 1));
}

var next = ref
.read(feedServiceProvider(feed).select((s) => s.ids))
.elementAtOrNull(postIndex);
var moreToGet = true;
var next = ref.watch(
feedServiceProvider(feed).select((s) => s.ids.elementAtOrNull(postIndex)),
);

if (next == null) {
await ref.watch(feedServiceProvider(feed).notifier).fetchMore();

while (moreToGet && next == null) {
moreToGet = await ref.watch(feedServiceProvider(feed).notifier).fetchMore();
next = ref
.read(feedServiceProvider(feed).select((s) => s.ids))
.watch(feedServiceProvider(feed).select((s) => s.ids))

Check warning on line 84 in packages/app/lib/src/features/home/application/feed_service.dart

View check run for this annotation

Codecov / codecov/patch

packages/app/lib/src/features/home/application/feed_service.dart#L84

Added line #L84 was not covered by tests
.elementAtOrNull(postIndex);

if (next == null) {
return null;
}
}

return next;
Expand Down Expand Up @@ -139,6 +148,24 @@
}
}

/// Fetch a single post from the database.
@Riverpod(keepAlive: true)

Check warning on line 152 in packages/app/lib/src/features/home/application/feed_service.dart

View check run for this annotation

Codecov / codecov/patch

packages/app/lib/src/features/home/application/feed_service.dart#L152

Added line #L152 was not covered by tests
Future<PostEntity?> getPost(Ref ref, PostId postId) async {
var post = ref.watch(singlePostProvider(postId));

Check warning on line 154 in packages/app/lib/src/features/home/application/feed_service.dart

View check run for this annotation

Codecov / codecov/patch

packages/app/lib/src/features/home/application/feed_service.dart#L154

Added line #L154 was not covered by tests

if (post == null) {
final postRepo = ref.read(postRepositoryProvider);

Check warning on line 157 in packages/app/lib/src/features/home/application/feed_service.dart

View check run for this annotation

Codecov / codecov/patch

packages/app/lib/src/features/home/application/feed_service.dart#L157

Added line #L157 was not covered by tests

post = await postRepo.readPost(postId);

Check warning on line 159 in packages/app/lib/src/features/home/application/feed_service.dart

View check run for this annotation

Codecov / codecov/patch

packages/app/lib/src/features/home/application/feed_service.dart#L159

Added line #L159 was not covered by tests

if (post == null) return null;

ref.read(singlePostProvider(postId).notifier).setPost(post);

Check warning on line 163 in packages/app/lib/src/features/home/application/feed_service.dart

View check run for this annotation

Codecov / codecov/patch

packages/app/lib/src/features/home/application/feed_service.dart#L163

Added line #L163 was not covered by tests
}

return post;
}

/// Image provider for posts
@Riverpod(keepAlive: true)
Future<Uint8List> image(Ref ref, String id) async {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
import 'package:riverpod_annotation/riverpod_annotation.dart';

import '../../auth/domain/user.dart';
import '../domain/post_entity.dart';
import '../domain/comment_entity.dart';
import '../domain/post_id.dart';
import '../domain/post_model_entity.dart';
import 'avatar_service.dart';
import 'feed_service.dart';
Expand All @@ -20,19 +21,42 @@
/// This lets us emulate a "suspense"-like UI, where the UI doesn’t show until all data is loaded.
@Riverpod(keepAlive: true)
Future<PostModelEntity?> postService(Ref ref, PostId postId) async {
final post = ref.watch(singlePostProvider(postId));
final post = await ref.watch(getPostProvider(postId).future);

Check warning on line 24 in packages/app/lib/src/features/home/application/post_service.dart

View check run for this annotation

Codecov / codecov/patch

packages/app/lib/src/features/home/application/post_service.dart#L24

Added line #L24 was not covered by tests

if (post == null) return null;

final (avatar, images) =
final (avatar, images, commentsAvatars) =
await (
ref.watch(avatarServiceProvider(post.authorName).future),
Future.wait(
// TODO(MattsAttack): Could we grab all images with a single call?
post.imageIds.map((image) => ref.watch(imageProvider(image).future)),
),
Future.wait(
post.comments.map(
(comment) =>
ref.watch(avatarServiceProvider(comment.authorName).future),

Check warning on line 38 in packages/app/lib/src/features/home/application/post_service.dart

View check run for this annotation

Codecov / codecov/patch

packages/app/lib/src/features/home/application/post_service.dart#L35-L38

Added lines #L35 - L38 were not covered by tests
),
),
).wait;

if (post.comments.length != commentsAvatars.length) {
throw Exception('The number of comments and comment avatars do not match.');

Check warning on line 44 in packages/app/lib/src/features/home/application/post_service.dart

View check run for this annotation

Codecov / codecov/patch

packages/app/lib/src/features/home/application/post_service.dart#L43-L44

Added lines #L43 - L44 were not covered by tests
}

final commentsWithAvatars = post.comments.zip(commentsAvatars);

Check warning on line 47 in packages/app/lib/src/features/home/application/post_service.dart

View check run for this annotation

Codecov / codecov/patch

packages/app/lib/src/features/home/application/post_service.dart#L47

Added line #L47 was not covered by tests
final comments =
[
for (final (comment, commentAvatar) in commentsWithAvatars)
CommentEntity(
author: comment.author,
comment: comment.comment,

Check warning on line 53 in packages/app/lib/src/features/home/application/post_service.dart

View check run for this annotation

Codecov / codecov/patch

packages/app/lib/src/features/home/application/post_service.dart#L49-L53

Added lines #L49 - L53 were not covered by tests
avatar: commentAvatar,
authorName: comment.authorName,
timestamp: comment.timestamp,

Check warning on line 56 in packages/app/lib/src/features/home/application/post_service.dart

View check run for this annotation

Codecov / codecov/patch

packages/app/lib/src/features/home/application/post_service.dart#L55-L56

Added lines #L55 - L56 were not covered by tests
),
].lockUnsafe;

Check warning on line 58 in packages/app/lib/src/features/home/application/post_service.dart

View check run for this annotation

Codecov / codecov/patch

packages/app/lib/src/features/home/application/post_service.dart#L58

Added line #L58 was not covered by tests

return PostModelEntity(
id: post.id,
authorName: post.authorName,
Expand All @@ -42,6 +66,7 @@
description: post.description,
images: images.lockUnsafe,
likes: post.likes,
comments: comments,
);
}

Expand Down Expand Up @@ -100,3 +125,11 @@
IList<UserId> currentPostLikes(Ref ref) {
return ref.watch(currentPostProvider.select((value) => value.likes));
}

/// Provide the number comments of the [currentPost].
@Riverpod(dependencies: [currentPost])

Check warning on line 130 in packages/app/lib/src/features/home/application/post_service.dart

View check run for this annotation

Codecov / codecov/patch

packages/app/lib/src/features/home/application/post_service.dart#L130

Added line #L130 was not covered by tests
int currentPostCommentsCount(Ref ref) {
return ref.watch(
currentPostProvider.select((value) => value.comments.length),

Check warning on line 133 in packages/app/lib/src/features/home/application/post_service.dart

View check run for this annotation

Codecov / codecov/patch

packages/app/lib/src/features/home/application/post_service.dart#L132-L133

Added lines #L132 - L133 were not covered by tests
);
}
46 changes: 45 additions & 1 deletion packages/app/lib/src/features/home/data/post_repository.dart
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
/// This library contains post fetchers.
library;

import 'dart:typed_data';

import 'package:appwrite/appwrite.dart';
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
import 'package:flutter/foundation.dart' show Uint8List;
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';

Expand All @@ -12,6 +13,7 @@
import '../../auth/domain/user.dart';
import '../domain/feed_entity.dart';
import '../domain/post_entity.dart';
import '../domain/post_id.dart';
import '../domain/uploaded_image_entity.dart';

part 'post_repository.g.dart';
Expand All @@ -24,6 +26,9 @@
/// Read all the posts.
Future<IList<PostEntity>> readPosts(FeedEntity feed, PostId? cursor);

/// Read a single post.
Future<PostEntity?> readPost(PostId postId);

/// Create a new post.
///
/// Returns the created post.
Expand All @@ -40,6 +45,9 @@

/// Fetch images from Appwrite.
Future<Uint8List> getImage(String id);

/// Post a comment.
Future<void> updatePost(PostId postId, Map<String, Object?> updatedData);
}

final class _AppwritePostRepository implements PostRepository {
Expand Down Expand Up @@ -86,6 +94,32 @@
}).toIList();
}

@override

Check warning on line 97 in packages/app/lib/src/features/home/data/post_repository.dart

View check run for this annotation

Codecov / codecov/patch

packages/app/lib/src/features/home/data/post_repository.dart#L97

Added line #L97 was not covered by tests
Future<PostEntity?> readPost(PostId postId) async {
try {
final document = await database.getDocument(
databaseId: databaseId,
collectionId: collectionId,

Check warning on line 102 in packages/app/lib/src/features/home/data/post_repository.dart

View check run for this annotation

Codecov / codecov/patch

packages/app/lib/src/features/home/data/post_repository.dart#L100-L102

Added lines #L100 - L102 were not covered by tests
documentId: postId.id,
);

assert(
!document.data.containsKey('id'),

Check warning on line 107 in packages/app/lib/src/features/home/data/post_repository.dart

View check run for this annotation

Codecov / codecov/patch

packages/app/lib/src/features/home/data/post_repository.dart#L107

Added line #L107 was not covered by tests
'ID should not have been redundantly stored.',
);

document.data['id'] = document.$id;

Check warning on line 111 in packages/app/lib/src/features/home/data/post_repository.dart

View check run for this annotation

Codecov / codecov/patch

packages/app/lib/src/features/home/data/post_repository.dart#L111

Added line #L111 was not covered by tests

return PostEntity.fromJson(document.data);
} on AppwriteException catch (e) {
if (e.code == 404) {

Check warning on line 115 in packages/app/lib/src/features/home/data/post_repository.dart

View check run for this annotation

Codecov / codecov/patch

packages/app/lib/src/features/home/data/post_repository.dart#L113-L115

Added lines #L113 - L115 were not covered by tests
return null;
}

rethrow;
}
}

@override
Future<Uint8List> getImage(String id) async {
return await storage.getFileView(bucketId: 'post-media', fileId: id);
Expand Down Expand Up @@ -133,6 +167,16 @@
file: InputFile.fromBytes(bytes: bytes, filename: fileName),
);
}

@override

Check warning on line 171 in packages/app/lib/src/features/home/data/post_repository.dart

View check run for this annotation

Codecov / codecov/patch

packages/app/lib/src/features/home/data/post_repository.dart#L171

Added line #L171 was not covered by tests
Future<void> updatePost(PostId id, Map<String, Object?> updatedData) async {
await database.updateDocument(
databaseId: databaseId,
collectionId: collectionId,

Check warning on line 175 in packages/app/lib/src/features/home/data/post_repository.dart

View check run for this annotation

Codecov / codecov/patch

packages/app/lib/src/features/home/data/post_repository.dart#L173-L175

Added lines #L173 - L175 were not covered by tests
documentId: id.id,
data: updatedData,
);
}
}

/// Get a [PostRepository] for a specific author and feed.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import 'package:freezed_annotation/freezed_annotation.dart';

import '../../../utils/json.dart';
import '../../auth/domain/user.dart';

part 'comment_dto_entity.freezed.dart';
part 'comment_dto_entity.g.dart';

/// {@template nexus.features.home.domain.comment_dto_entity}
/// Represent a comment on a post.
/// {@endtemplate}
@immutable
@freezed
sealed class CommentDtoEntity with _$CommentDtoEntity {
/// {@macro nexus.features.home.domain.comment_dto_entity}
///
/// Create a new, immutable instance of [CommentDtoEntity].
const factory CommentDtoEntity({
/// The textual content of the comment.
required String comment,

/// The [UserId] of the author of the comment.
required UserId author,

/// The author of the comment’s display name.
required String authorName,

/// When the comment was created.
@DataTimeJsonConverter() required DateTime timestamp,
}) = _CommentDtoEntity;

/// Deserialize a JSON [Map] into a new, immutable instance of [CommentDtoEntity].
factory CommentDtoEntity.fromJson(Map<String, dynamic> json) =>
_$CommentDtoEntityFromJson(json);

Check warning on line 34 in packages/app/lib/src/features/home/domain/comment_dto_entity.dart

View check run for this annotation

Codecov / codecov/patch

packages/app/lib/src/features/home/domain/comment_dto_entity.dart#L33-L34

Added lines #L33 - L34 were not covered by tests
}
Loading
Loading