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
27 changes: 27 additions & 0 deletions lib/app/app_router/app_router.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ import 'package:go_router/go_router.dart';
import 'package:magic_yeti/app/app_router/app_route.dart';
import 'package:magic_yeti/app/app_router/go_router_refresh_stream.dart';
import 'package:magic_yeti/app/bloc/app_bloc.dart';
import 'package:magic_yeti/friends_list/friends_list_page.dart';
import 'package:magic_yeti/friends_list/requests/friend_request_page.dart';
import 'package:magic_yeti/friends_list/search_user/search_user_page.dart';
import 'package:magic_yeti/home/home_page.dart';
import 'package:magic_yeti/life_counter/view/game_over_page.dart';
import 'package:magic_yeti/life_counter/view/view.dart';
Expand Down Expand Up @@ -74,6 +77,30 @@ class AppRouter {
child: MatchDetailsPage.pageBuilder(context, state),
),
),
AppRoute(
name: FriendsListPage.routeName,
path: FriendsListPage.routePath,
pageBuilder: (context, state) => NoTransitionPage(
name: FriendsListPage.routeName,
child: FriendsListPage.pageBuilder(context, state),
),
),
AppRoute(
name: FriendRequestsPage.routeName,
path: FriendRequestsPage.routePath,
pageBuilder: (context, state) => NoTransitionPage(
name: FriendRequestsPage.routeName,
child: FriendRequestsPage.pageBuilder(context, state),
),
),
AppRoute(
name: SearchUserPage.routeName,
path: SearchUserPage.routePath,
pageBuilder: (context, state) => NoTransitionPage(
name: SearchUserPage.routeName,
child: SearchUserPage.pageBuilder(context, state),
),
),
AppRoute(
name: GamePage.routeName,
path: GamePage.routePath,
Expand Down
54 changes: 54 additions & 0 deletions lib/friends_list/friends_list/bloc/friend_list_bloc.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:firebase_database_repository/firebase_database_repository.dart';

part 'friend_list_event.dart';
part 'friend_list_state.dart';

/// Bloc implementation for managing the user's friends list.
/// It handles loading the list of friends and removing friends.
///
/// Key features:
/// - Loads friends from Firestore
/// - Removes friends with confirmation
///
/// @dependencies
/// - FirebaseDatabaseRepository: For interacting with Firestore
/// - Flutter Bloc: For state management
///
/// @notes
/// - Implements error handling for network issues
/// - Ensures real-time updates using Firestore sync

class FriendBloc extends Bloc<FriendEvent, FriendState> {
FriendBloc({required this.repository}) : super(FriendsLoading()) {
on<LoadFriends>(_onLoadFriends);
on<RemoveFriend>(_onRemoveFriend);
}

final FirebaseDatabaseRepository repository;

Future<void> _onLoadFriends(
LoadFriends event,
Emitter<FriendState> emit,
) async {
try {
final friends = await repository.getFriends(event.userId);
emit(FriendsLoaded(friends));
} catch (e) {
emit(FriendsError('Failed to load friends: $e'));
}
}

Future<void> _onRemoveFriend(
RemoveFriend event,
Emitter<FriendState> emit,
) async {
try {
await repository.removeFriend(event.userId, event.friendId);
add(LoadFriends(event.userId)); // Reload friends after removal
} catch (e) {
emit(FriendsError('Failed to remove friend: $e'));
}
}
}
32 changes: 32 additions & 0 deletions lib/friends_list/friends_list/bloc/friend_list_event.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
part of 'friend_list_bloc.dart';

/// Events for the FriendsBloc.
/// Defines the actions that can be performed on the friends list.
///
/// Key events:
/// - LoadFriends: Triggered to load the friends list
/// - RemoveFriend: Triggered to remove a friend

sealed class FriendEvent extends Equatable {
const FriendEvent();

@override
List<Object> get props => [];
}

class LoadFriends extends FriendEvent {
const LoadFriends(this.userId);
final String userId;

@override
List<Object> get props => [userId];
}

class RemoveFriend extends FriendEvent {
const RemoveFriend(this.userId, this.friendId);
final String userId;
final String friendId;

@override
List<Object> get props => [userId, friendId];
}
34 changes: 34 additions & 0 deletions lib/friends_list/friends_list/bloc/friend_list_state.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
part of 'friend_list_bloc.dart';

/// States for the FriendsBloc.
/// Represents the different states of the friends list.
///
/// Key states:
/// - FriendsLoading: Indicates loading state
/// - FriendsLoaded: Indicates friends are successfully loaded
/// - FriendsError: Indicates an error occurred

abstract class FriendState extends Equatable {
const FriendState();

@override
List<Object> get props => [];
}

class FriendsLoading extends FriendState {}

class FriendsLoaded extends FriendState {
const FriendsLoaded(this.friends);
final List<FriendModel> friends;

@override
List<Object> get props => [friends];
}

class FriendsError extends FriendState {
const FriendsError(this.message);
final String message;

@override
List<Object> get props => [message];
}
104 changes: 104 additions & 0 deletions lib/friends_list/friends_list/friends_list.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import 'package:firebase_database_repository/firebase_database_repository.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:magic_yeti/app/bloc/app_bloc.dart';
import 'package:magic_yeti/friends_list/friends_list/bloc/friend_list_bloc.dart';

/// This file implements the UI for displaying and managing the user's friends list.
/// It allows users to view their friends and remove them if desired.
///
/// Key features:
/// - Display a list of current friends
/// - Remove friends with confirmation
///
/// @dependencies
/// - Flutter Bloc: For state management
/// - Firebase Database Repository: To interact with Firestore
///
/// @notes
/// - Handles network errors gracefully
/// - Ensures real-time updates using Firestore sync

class FriendsList extends StatelessWidget {
const FriendsList({super.key});

@override
Widget build(BuildContext context) {
final userId = context.read<AppBloc>().state.user.id;
return Scaffold(
body: BlocProvider(
create: (context) => FriendBloc(
repository: context.read<FirebaseDatabaseRepository>(),
)..add(LoadFriends(userId)),
child: const FriendsListView(),
),
);
}
}

class FriendsListView extends StatelessWidget {
const FriendsListView({super.key});

@override
Widget build(BuildContext context) {
final userId = context.read<AppBloc>().state.user.id;
return BlocBuilder<FriendBloc, FriendState>(
builder: (context, state) {
if (state is FriendsLoading) {
return const Center(child: CircularProgressIndicator());
} else if (state is FriendsLoaded) {
return state.friends.isEmpty
? const Center(child: Text('No friends found'))
: ListView.builder(
itemCount: state.friends.length,
itemBuilder: (context, index) {
final friend = state.friends[index];
return ListTile(
title: Text(friend.username),
trailing: IconButton(
icon: const Icon(Icons.delete),
onPressed: () =>
_confirmRemoveFriend(context, friend, userId),
),
);
},
);
} else if (state is FriendsError) {
return const Center(child: Text('Failed to load friends'));
}
return const Center(child: Text('No friends found'));
},
);
}

void _confirmRemoveFriend(
BuildContext context,
FriendModel friend,
String userId,
) {
showDialog<void>(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text('Remove Friend'),
content: Text('Are you sure you want to remove ${friend.username}?'),
actions: <Widget>[
TextButton(
child: const Text('Cancel'),
onPressed: () => Navigator.of(context).pop(),
),
TextButton(
child: const Text('Remove'),
onPressed: () {
context
.read<FriendBloc>()
.add(RemoveFriend(userId, friend.userId));
Navigator.of(context).pop();
},
),
],
);
},
);
}
}
57 changes: 57 additions & 0 deletions lib/friends_list/friends_list_page.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import 'package:app_ui/app_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:go_router/go_router.dart';
import 'package:magic_yeti/friends_list/friends_list/friends_list.dart';
import 'package:magic_yeti/friends_list/requests/friend_request_page.dart';
import 'package:magic_yeti/friends_list/search_user/search_user_page.dart';
import 'package:magic_yeti/l10n/l10n.dart';

class FriendsListPage extends StatelessWidget {
const FriendsListPage({super.key});
factory FriendsListPage.pageBuilder(_, __) {
return const FriendsListPage(key: Key('friends_list_page'));
}

static const routeName = 'friendsListPage';
static const routePath = '/friendsListPage';
@override
Widget build(BuildContext context) {
final l10n = context.l10n;
return DefaultTabController(
length: 2,
child: Scaffold(
appBar: AppBar(
backgroundColor: AppColors.quaternary,
title: Text(
l10n.friendsTitle,
style: Theme.of(context).textTheme.headlineMedium?.copyWith(
color: AppColors.onSurfaceVariant,
fontWeight: FontWeight.bold,
),
),
bottom: TabBar(
tabs: [
Tab(text: l10n.friendsTitle),
Tab(text: l10n.friendRequestsTitle),
],
indicatorColor: AppColors.tertiary,
labelColor: AppColors.onSurfaceVariant,
),
),
body: const TabBarView(
children: [
FriendsList(),
FriendRequestsPage(),
],
),
floatingActionButton: FloatingActionButton(
foregroundColor: AppColors.white,
backgroundColor: AppColors.tertiary,
onPressed: () => context.go(SearchUserPage.routePath),
child: const Icon(Icons.add),
),
),
);
}
}
61 changes: 61 additions & 0 deletions lib/friends_list/requests/bloc/friend_request_bloc.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:firebase_database_repository/firebase_database_repository.dart';

part 'friend_request_event.dart';
part 'friend_request_state.dart';

/// Bloc implementation for managing friend requests.
///
/// Handles:
/// - Loading friend requests from Firestore.
/// - Accepting and declining friend requests.
///
/// @dependencies
/// - Firebase Firestore: For data storage and retrieval.
/// - Flutter Bloc: For state management.
class FriendRequestBloc extends Bloc<FriendRequestEvent, FriendRequestState> {
FriendRequestBloc({required this.repository})
: super(FriendRequestLoading()) {
on<LoadFriendRequests>(_onLoadFriendRequests);
on<AcceptFriendRequest>(_onAcceptFriendRequest);
on<DeclineFriendRequest>(_onDeclineFriendRequest);
}
final FirebaseDatabaseRepository repository;

Future<void> _onLoadFriendRequests(
LoadFriendRequests event,
Emitter<FriendRequestState> emit,
) async {
try {
final requests = await repository.getFriendRequests(event.userId);
emit(FriendRequestLoaded(requests));
} catch (e) {
emit(const FriendRequestError('Failed to load friend requests'));
}
}

Future<void> _onAcceptFriendRequest(
AcceptFriendRequest event,
Emitter<FriendRequestState> emit,
) async {
try {
await repository.acceptFriendRequest(event.request, event.userId);
add(LoadFriendRequests(event.request.senderId));
} catch (e) {
emit(const FriendRequestError('Failed to accept friend request'));
}
}

Future<void> _onDeclineFriendRequest(
DeclineFriendRequest event,
Emitter<FriendRequestState> emit,
) async {
try {
await repository.declineFriendRequest(event.request.id);
add(LoadFriendRequests(event.request.senderId));
} catch (e) {
emit(const FriendRequestError('Failed to decline friend request'));
}
}
}
Loading