Skip to content

Commit

Permalink
Added ability to favorite communities in drawer (#971)
Browse files Browse the repository at this point in the history
* added ability to favorite communities in drawer

* force cast to string for ids

* minor tweaks

* updated changelog

* added semantic labelling for favorite icon

* added try-catch for creating/upgrading database

* fixed issues with error handling
  • Loading branch information
hjiangsu authored Dec 15, 2023
1 parent bae2444 commit 2603ba7
Show file tree
Hide file tree
Showing 9 changed files with 556 additions and 237 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
- Added the ability to render SVGs in markdown bodies - contribution from @micahmo
- Added Safari extension to open Lemmy links in Thunder
- Added support for displaying comment origin instance - contribution from @ggichure.
- Aded ability to set keywords for filtering post titles/body
- Added ability to favorite communities on the drawer
- Added ability to set keywords for filtering post titles/body

## Changed
- Added new items to the Post and Comment actions sheet - contribution from @micahmo
Expand Down
21 changes: 20 additions & 1 deletion lib/account/bloc/account_bloc.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import 'package:lemmy_api_client/v3.dart';
import 'package:stream_transform/stream_transform.dart';

import 'package:thunder/account/models/account.dart';
import 'package:thunder/account/models/favourite.dart';
import 'package:thunder/core/auth/helpers/fetch_account.dart';
import 'package:thunder/core/singletons/lemmy_client.dart';

Expand Down Expand Up @@ -44,6 +45,7 @@ class AccountBloc extends Bloc<AccountEvent, AccountState> {
}

List<CommunityView> subsciptions = [];
List<CommunityView> favoritedCommunities = [];

while (!hasFetchedAllSubsciptions) {
ListCommunitiesResponse listCommunitiesResponse = await lemmy.run(
Expand All @@ -63,6 +65,9 @@ class AccountBloc extends Bloc<AccountEvent, AccountState> {
// Sort subscriptions by their name
subsciptions.sort((CommunityView a, CommunityView b) => a.community.name.compareTo(b.community.name));

List<Favorite> favorites = await Favorite.favorites(account.id);
favoritedCommunities = subsciptions.where((CommunityView communityView) => favorites.any((Favorite favorite) => favorite.communityId == communityView.community.id)).toList();

GetPersonDetailsResponse? getPersonDetailsResponse =
await lemmy.run(GetPersonDetails(username: account.username, auth: account.jwt, sort: SortType.new_, page: 1)).timeout(timeout, onTimeout: () {
throw Exception('Error: Timeout when attempting to fetch account details');
Expand All @@ -71,7 +76,7 @@ class AccountBloc extends Bloc<AccountEvent, AccountState> {
// This eliminates an issue which has plagued me a lot which is that there's a race condition
// with so many calls to GetAccountInformation, we can return success for the new and old account.
if (getPersonDetailsResponse.personView.person.id == (await fetchActiveProfileAccount())?.userId) {
return emit(state.copyWith(status: AccountStatus.success, subsciptions: subsciptions, personView: getPersonDetailsResponse.personView));
return emit(state.copyWith(status: AccountStatus.success, subsciptions: subsciptions, favorites: favoritedCommunities, personView: getPersonDetailsResponse.personView));
} else {
return emit(state.copyWith(status: AccountStatus.success));
}
Expand All @@ -85,5 +90,19 @@ class AccountBloc extends Bloc<AccountEvent, AccountState> {
emit(state.copyWith(status: AccountStatus.failure, errorMessage: e.toString()));
}
});

on<GetFavoritedCommunities>((event, emit) async {
Account? account = await fetchActiveProfileAccount();

if (account == null || account.jwt == null) {
return emit(state.copyWith(status: AccountStatus.success));
}

List<Favorite> favorites = await Favorite.favorites(account.id);
List<CommunityView> favoritedCommunities =
state.subsciptions.where((CommunityView communityView) => favorites.any((Favorite favorite) => favorite.communityId == communityView.community.id)).toList();

emit(state.copyWith(status: AccountStatus.success, favorites: favoritedCommunities));
});
}
}
2 changes: 2 additions & 0 deletions lib/account/bloc/account_event.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@ abstract class AccountEvent extends Equatable {
}

class GetAccountInformation extends AccountEvent {}

class GetFavoritedCommunities extends AccountEvent {}
8 changes: 7 additions & 1 deletion lib/account/bloc/account_state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ class AccountState extends Equatable {
const AccountState({
this.status = AccountStatus.initial,
this.subsciptions = const [],
this.favorites = const [],
this.personView,
this.errorMessage,
});
Expand All @@ -16,23 +17,28 @@ class AccountState extends Equatable {
/// The user's subscriptions if logged in
final List<CommunityView> subsciptions;

/// The user's favorites if logged in
final List<CommunityView> favorites;

/// The user's information
final PersonView? personView;

AccountState copyWith({
AccountStatus? status,
List<CommunityView>? subsciptions,
List<CommunityView>? favorites,
PersonView? personView,
String? errorMessage,
}) {
return AccountState(
status: status ?? this.status,
subsciptions: subsciptions ?? this.subsciptions,
favorites: favorites ?? this.favorites,
personView: personView ?? this.personView,
errorMessage: errorMessage ?? this.errorMessage,
);
}

@override
List<Object?> get props => [status, subsciptions, errorMessage];
List<Object?> get props => [status, subsciptions, favorites, errorMessage];
}
90 changes: 90 additions & 0 deletions lib/account/models/favourite.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import 'package:sqflite/sqflite.dart';

import 'package:thunder/core/singletons/database.dart';

class Favorite {
final String id;
final int communityId;
final String accountId;

const Favorite({
required this.id,
required this.communityId,
required this.accountId,
});

Map<String, dynamic> toMap() {
return {'id': id, 'communityId': communityId, 'accountId': accountId};
}

@override
String toString() {
return 'Favourite{id: $id, communityId: $communityId, accountId: $accountId}';
}

static Future<void> insertFavorite(Favorite favourite) async {
Database? database = await DB.instance.database;
if (database == null) return;

await database.insert(
'favorites',
favourite.toMap(),
conflictAlgorithm: ConflictAlgorithm.replace,
);
}

// A method that retrieves all favourites from the database
static Future<List<Favorite>> favorites(String accountId) async {
try {
Database? database = await DB.instance.database;
if (database == null) return [];

final List<Map<String, dynamic>> maps = await database.query('favorites', where: 'accountId = ?', whereArgs: [accountId]);

return List.generate(maps.length, (i) {
return Favorite(
id: maps[i]['id'].toString(),
communityId: maps[i]['communityId'],
accountId: maps[i]['accountId'].toString(),
);
});
} catch (e) {
return [];
}
}

static Future<Favorite?> fetchFavourite(String id) async {
Database? database = await DB.instance.database;

final List<Map<String, dynamic>>? maps = await database?.query('favorites', where: 'id = ?', whereArgs: [id]);
if (maps == null || maps.isEmpty) return null;

return Favorite(
id: maps.first['id'],
communityId: maps.first['communityId'],
accountId: maps.first['accountId'],
);
}

static Future<void> updateFavourite(Favorite favorite) async {
Database? database = await DB.instance.database;
if (database == null) return;

await database.update('favorites', favorite.toMap(), where: 'id = ?', whereArgs: [favorite.id]);
}

static Future<void> deleteFavorite({String? id, int? communityId}) async {
Database? database = await DB.instance.database;
if (database == null) return;

if (id != null) {
await database.delete('favorites', where: 'id = ?', whereArgs: [id]);
return;
}

if (communityId != null) {
await database.delete('favorites', where: 'communityId = ?', whereArgs: [communityId]);
return;
}
}
}
Loading

0 comments on commit 2603ba7

Please sign in to comment.