Skip to content

Commit

Permalink
feat: show forum votes with voters name
Browse files Browse the repository at this point in the history
  • Loading branch information
khoadng committed Jun 29, 2023
1 parent 7222d1e commit 8ba9816
Show file tree
Hide file tree
Showing 15 changed files with 325 additions and 117 deletions.
14 changes: 14 additions & 0 deletions lib/boorus/core/feats/user_level_colors.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,19 @@ import 'package:flutter/material.dart';
// Project imports:
import 'package:boorusama/boorus/danbooru/feats/users/users.dart';

int getUserHexOnDarkColor(UserLevel level) => switch (level) {
UserLevel.member => 0xff009ae7,
UserLevel.gold => 0xffead084,
UserLevel.platinum => 0xff808080,
UserLevel.builder => 0xffc697ff,
UserLevel.contributor => 0xffc697ff,
UserLevel.approver => 0xffc697ff,
UserLevel.moderator => 0xff34c74a,
UserLevel.admin => 0xffff8b8a,
UserLevel.owner => 0xffff8b8a,
UserLevel.restricted => 0xff009ae7
};

int getUserHexColor(UserLevel level) => switch (level) {
UserLevel.member => 0xff0073ff,
UserLevel.gold => 0xffd0ba79,
Expand All @@ -19,4 +32,5 @@ int getUserHexColor(UserLevel level) => switch (level) {

extension UserColor on UserLevel {
Color toColor() => Color(getUserHexColor(this));
Color toOnDarkColor() => Color(getUserHexOnDarkColor(this));
}
4 changes: 4 additions & 0 deletions lib/boorus/danbooru/feats/forums/danbooru_forum_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import 'package:riverpod_infinite_scroll/riverpod_infinite_scroll.dart';
// Project imports:
import 'package:boorusama/boorus/danbooru/danbooru_provider.dart';
import 'package:boorusama/boorus/danbooru/feats/forums/forums.dart';
import 'package:boorusama/boorus/danbooru/feats/users/users.dart';

final danbooruForumTopicRepoProvider =
Provider<DanbooruForumTopicRepository>((ref) {
Expand All @@ -24,6 +25,9 @@ final danbooruForumPostRepoProvider =
Provider<DanbooruForumPostRepository>((ref) {
return DanbooruForumPostRepositoryApi(
api: ref.watch(danbooruApiProvider),
onFetched: (posts) => ref
.read(danbooruCreatorsProvider.notifier)
.load(posts.expand((e) => e.votes).map((e) => e.creatorId).toList()),
);
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,12 @@ const _forumPostParams =
class DanbooruForumPostRepositoryApi implements DanbooruForumPostRepository {
DanbooruForumPostRepositoryApi({
required this.api,
this.onFetched,
});

final DanbooruApi api;
final void Function(List<DanbooruForumPost> posts)? onFetched;

final limit = 20;

@override
Expand All @@ -53,6 +56,8 @@ class DanbooruForumPostRepositoryApi implements DanbooruForumPostRepository {

data.sort((a, b) => a.id.compareTo(b.id));

onFetched?.call(data);

return data;
});
}
37 changes: 37 additions & 0 deletions lib/boorus/danbooru/feats/users/converter.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Project imports:
import 'user.dart';
import 'user_dto.dart';
import 'user_level.dart';
import 'user_self.dart';
import 'user_self_dto.dart';

User userDtoToUser(
UserDto d,
) {
return User(
id: d.id ?? 0,
level: intToUserLevel(d.level ?? 0),
name: d.name ?? 'User',
joinedDate: d.createdAt ?? DateTime.now(),
uploadCount: d.uploadCount ?? 0,
tagEditCount: d.tagEditCount ?? 0,
noteEditCount: d.noteEditCount ?? 0,
commentCount: d.commentCount ?? 0,
forumPostCount: d.forumPostCount ?? 0,
favoriteGroupCount: d.favoriteGroupCount ?? 0,
);
}

UserSelf userDtoToUserSelf(
UserSelfDto d,
List<String> defaultBlacklistedTags,
) {
return UserSelf(
id: d.id ?? 0,
level: intToUserLevel(d.level ?? 0),
name: d.name ?? 'User',
blacklistedTags: d.blacklistedTags == null
? defaultBlacklistedTags
: tagStringToListTagString(d.blacklistedTags ?? ''),
);
}
30 changes: 29 additions & 1 deletion lib/boorus/danbooru/feats/users/creator.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
// Dart imports:
import 'dart:convert';

// Package imports:
import 'package:equatable/equatable.dart';

// Project imports:
import 'package:boorusama/boorus/danbooru/feats/users/users.dart';
import 'user.dart';
import 'user_level.dart';

class Creator extends Equatable {
const Creator({
Expand All @@ -11,16 +15,40 @@ class Creator extends Equatable {
required this.level,
});

factory Creator.fromUser(User user) => Creator(
id: user.id,
name: user.name,
level: user.level,
);

factory Creator.empty() => const Creator(
id: -1,
name: 'Creator',
level: UserLevel.member,
);

factory Creator.fromJson(String jsonStr) {
final jsonData = json.decode(jsonStr);
return Creator(
id: jsonData['id'],
name: jsonData['name'],
level: UserLevel.values[jsonData['level']],
);
}

final CreatorId id;
final CreatorName name;
final UserLevel level;

String toJson() {
final jsonData = {
'id': id,
'name': name,
'level': level.index,
};
return json.encode(jsonData);
}

@override
List<Object?> get props => [id, name, level];
}
Expand Down
74 changes: 74 additions & 0 deletions lib/boorus/danbooru/feats/users/creator_repository.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// Package imports:
import 'package:dio/dio.dart';
import 'package:hive/hive.dart';

// Project imports:
import 'creator.dart';
import 'user_repository.dart';

abstract interface class CreatorRepository {
Future<List<Creator>> getCreatorsByIdStringComma(
String idComma, {
CancelToken? cancelToken,
});
}

class CreatorRepositoryFromUserRepo implements CreatorRepository {
CreatorRepositoryFromUserRepo(
this.repo,
this.box,
);

final UserRepository repo;
final Box box;

@override
Future<List<Creator>> getCreatorsByIdStringComma(
String idComma, {
CancelToken? cancelToken,
}) async {
final now = DateTime.now();
final twoDaysAgo = now.subtract(const Duration(days: 2));

final ids = idComma.split(',');

// Identify IDs not in cache
final idsNotInCached = ids.where((e) => !box.containsKey(e)).toList();

// Fetch creators from cache
final creatorInCached = ids
.where((e) => box.containsKey(e))
.map((e) => Creator.fromJson(box.get(e)['creator']))
.toList();

List<Creator> creators = [];

// Only fetch creators from API when there are uncached IDs
if (idsNotInCached.isNotEmpty) {
try {
creators = await repo
.getUsersByIdStringComma(
idsNotInCached.join(','),
cancelToken: cancelToken,
)
.then((value) => value.map(Creator.fromUser).toList());
} catch (e) {
// handle the exception
}

for (var e in creators) {
if (!box.containsKey(e.id.toString()) ||
DateTime.parse(box.get(e.id.toString())['time'])
.isBefore(twoDaysAgo)) {
// Update cache with new creators
box.put(e.id.toString(), {
'time': now.toIso8601String(),
'creator': e.toJson(),
});
}
}
}

return [...creators, ...creatorInCached];
}
}
30 changes: 30 additions & 0 deletions lib/boorus/danbooru/feats/users/creators_notifier.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Package imports:
import 'package:flutter_riverpod/flutter_riverpod.dart';

// Project imports:
import 'package:boorusama/boorus/danbooru/feats/users/creator_repository.dart';
import 'package:boorusama/boorus/danbooru/feats/users/users.dart';
import 'package:boorusama/functional.dart';

class CreatorsNotifier extends Notifier<IMap<int, Creator>> {
CreatorRepository get repo => ref.watch(danbooruCreatorRepoProvider);

@override
IMap<int, Creator> build() {
return <int, Creator>{}.lock;
}

Future<void> load(List<int> ids) async {
// only load ids that are not already loaded
final notInCached = ids.where((id) => !state.containsKey(id)).toList();

final creators =
await repo.getCreatorsByIdStringComma(notInCached.join(','));

final map = {
for (final creator in creators) creator.id: creator,
}.lock;

state = state.addAll(map);
}
}
29 changes: 29 additions & 0 deletions lib/boorus/danbooru/feats/users/parser.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Package imports:
import 'package:retrofit/dio.dart';

// Project imports:
import 'package:boorusama/foundation/http/http.dart';
import 'converter.dart';
import 'user.dart';
import 'user_dto.dart';
import 'user_self.dart';
import 'user_self_dto.dart';

List<UserDto> parseUserDtos(
HttpResponse<dynamic> value,
) =>
parseResponse(
value: value,
converter: (item) => UserDto.fromJson(item),
);

List<User> parseUsers(List<UserDto> value) => value.map(userDtoToUser).toList();

List<UserSelf> parseUserSelf(
HttpResponse<dynamic> value,
List<String> defaultBlacklistedTags,
) =>
parseResponse(
value: value,
converter: (item) => UserSelfDto.fromJson(item),
).map((u) => userDtoToUserSelf(u, defaultBlacklistedTags)).toList();
Loading

0 comments on commit 8ba9816

Please sign in to comment.