Skip to content
Open
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
3 changes: 2 additions & 1 deletion lib/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -112,5 +112,6 @@
"eventsCount": "Events",
"followersCount": "Followers",
"followingCount": "Following",
"yourEvents": "Your events"
"yourEvents": "Your events",
"failedToLoadUserProfile": "Failed to load user's profile"
}
5 changes: 3 additions & 2 deletions lib/l10n/app_pl.arb
Original file line number Diff line number Diff line change
Expand Up @@ -112,5 +112,6 @@
"eventsCount": "Wydarzenia",
"followersCount": "Obserwujący",
"followingCount": "Obserwowani",
"yourEvents": "Twoje wydarzenia"
}
"yourEvents": "Twoje wydarzenia",
"failedToLoadUserProfile": "Nie udało się załadować profilu użytkownika"
}
2 changes: 2 additions & 0 deletions lib/src/core/presentation/app_initializer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import 'package:interns2025b_mobile/src/features/auth/presentation/pages/registe
import 'package:interns2025b_mobile/src/features/event/presentation/pages/event_creation_page.dart';
import 'package:interns2025b_mobile/src/features/event/presentation/pages/event_details_page.dart';
import 'package:interns2025b_mobile/src/features/event/presentation/pages/event_page.dart';
import 'package:interns2025b_mobile/src/features/profile/presentation/pages/profile_details_page.dart';
import 'package:interns2025b_mobile/src/features/profile/presentation/pages/profile_page.dart';
import 'package:interns2025b_mobile/src/features/profile/presentation/providers/profile_user_provider.dart';
import 'package:interns2025b_mobile/src/shared/presentation/providers/localization_controller_provider.dart';
Expand Down Expand Up @@ -44,6 +45,7 @@ class AppInitializer extends ConsumerWidget {
AppRoutes.addEvent: (context) => EventCreationPage(),
AppRoutes.events: (context) => EventPage(),
AppRoutes.eventDetails: (context) => EventDetails(),
AppRoutes.profileDetails: (context) => ProfileDetailsPage(),
},
);
}
Expand Down
1 change: 1 addition & 0 deletions lib/src/core/routes/app_routes.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ class AppRoutes {
static const String profile = '/profile';
static const String addEvent = '/add-event';
static const String eventDetails = '/event-details';
static const String profileDetails = '/profile-details';
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import 'package:interns2025b_mobile/src/features/auth/domain/usecases/forgot_pas
import 'package:interns2025b_mobile/src/features/auth/domain/usecases/login_usecase.dart';
import 'package:interns2025b_mobile/src/features/auth/domain/usecases/logout_usecase.dart';
import 'package:interns2025b_mobile/src/features/auth/domain/usecases/register_usecase.dart';
import 'package:interns2025b_mobile/src/features/profile/presentation/providers/profile_user_provider.dart';
import 'package:interns2025b_mobile/src/shared/domain/models/user_model.dart';
import 'package:interns2025b_mobile/src/shared/presentation/theme/app_colors.dart';
import 'package:shared_preferences/shared_preferences.dart';
Expand Down Expand Up @@ -49,6 +50,7 @@ class AuthController extends AsyncNotifier<User?> {
try {
await _loginUseCase(email, password);
final user = await _loadUser();
ref.read(profileUserProvider.notifier).state = user;
state = AsyncData(user);

if (context.mounted) {
Expand Down Expand Up @@ -112,6 +114,8 @@ class AuthController extends AsyncNotifier<User?> {
final prefs = await SharedPreferences.getInstance();
await prefs.remove('token');
await prefs.remove('user');
ref.read(profileUserProvider.notifier).state = null;


state = const AsyncData(null);

Expand Down
85 changes: 53 additions & 32 deletions lib/src/features/event/presentation/widgets/event_author_tile.dart
Original file line number Diff line number Diff line change
@@ -1,47 +1,68 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:interns2025b_mobile/src/core/routes/app_routes.dart';
import 'package:interns2025b_mobile/src/features/profile/presentation/providers/profile_user_provider.dart';
import 'package:interns2025b_mobile/src/shared/domain/models/event_owner.dart';
import 'package:interns2025b_mobile/src/shared/domain/models/user_model.dart';
import 'package:interns2025b_mobile/src/shared/presentation/theme/app_colors.dart';

class EventAuthorTile extends StatelessWidget {
class EventAuthorTile extends ConsumerWidget {
final EventOwner owner;

const EventAuthorTile({super.key, required this.owner});

@override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.only(bottom: 12),
padding: const EdgeInsets.all(12),
child: Row(
children: [
ClipRRect(
borderRadius: BorderRadius.circular(12),
child: Container(
width: 48,
height: 48,
color: AppColors.primary.withValues(alpha: 0.1),
child: owner.avatarUrl != null && owner.avatarUrl!.isNotEmpty
? Image.network(
owner.avatarUrl!,
fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) {
return _defaultAvatar();
},
)
: _defaultAvatar(),
Widget build(BuildContext context, WidgetRef ref) {
return InkWell(
onTap: () {
if (owner is User) {
final currentUser = ref.read(profileUserProvider);
final targetUserId = owner.id;

if (currentUser != null && currentUser.id == targetUserId) {
Navigator.of(context).pushNamed(AppRoutes.profile);
} else {
Navigator.of(
context,
).pushNamed(AppRoutes.profileDetails, arguments: targetUserId);
}
}
},
borderRadius: BorderRadius.circular(12),
child: Container(
margin: const EdgeInsets.only(bottom: 12),
padding: const EdgeInsets.all(12),
child: Row(
children: [
ClipRRect(
borderRadius: BorderRadius.circular(12),
child: Container(
width: 48,
height: 48,
color: AppColors.primary.withValues(alpha: 0.1),
child: owner.avatarUrl != null && owner.avatarUrl!.isNotEmpty
? Image.network(
owner.avatarUrl!,
fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) {
return _defaultAvatar();
},
)
: _defaultAvatar(),
),
),
),
const SizedBox(width: 12),
Expanded(
child: Text(
owner.displayName,
style: Theme.of(context).textTheme.labelLarge?.copyWith(
fontWeight: FontWeight.bold,
color: AppColors.black,
const SizedBox(width: 12),
Expanded(
child: Text(
owner.displayName,
style: Theme.of(context).textTheme.labelLarge?.copyWith(
fontWeight: FontWeight.bold,
color: AppColors.black,
),
),
),
),
],
],
),
),
);
}
Expand Down
7 changes: 5 additions & 2 deletions lib/src/features/event/presentation/widgets/event_card.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import 'package:interns2025b_mobile/src/shared/presentation/theme/app_colors.dar
class EventCard extends StatelessWidget {
final Event event;

const EventCard({super.key, required this.event,});
const EventCard({super.key, required this.event});

@override
Widget build(BuildContext context) {
Expand All @@ -34,7 +34,10 @@ class EventCard extends StatelessWidget {
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
EventDateTimeRow(date: event.start),
SizedBox(
width: MediaQuery.of(context).size.width * 0.6,
child: EventDateTimeRow(date: event.start),
),
EventPriceTag(
isPaid: event.isPaid,
paidText: localizations.paid,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,14 @@ class EventDateTimeRow extends StatelessWidget {
color: AppColors.grey,
),
const SizedBox(width: 8),
Text(
dateText,
style: Theme.of(
context,
).textTheme.bodyMedium?.copyWith(color: AppColors.grey),
Flexible(
child: Text(
dateText,
style: Theme.of(
context,
).textTheme.bodyMedium?.copyWith(color: AppColors.grey),
overflow: TextOverflow.ellipsis,
),
),
],
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,9 @@ class ProfileDataSource {
Future<void> deleteUserRequest() async {
await httpClient.post('/api/profile/delete-request');
}

Future<User> getUserProfile(int userId) async {
final response = await httpClient.get('/api/profile/$userId');
return User.fromJson(response['data']);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,8 @@ class RemoteProfileRepository implements ProfileRepository {

@override
Future<void> deleteUserRequest() => dataSource.deleteUserRequest();

@override
Future<User> getUserProfile(int userId) => dataSource.getUserProfile(userId);
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:interns2025b_mobile/src/features/profile/data/providers/profile_repository_provider.dart';
import 'package:interns2025b_mobile/src/features/profile/domain/usecases/get_user_profile_usecase.dart';

final getUserProfileUseCaseProvider = Provider<GetUserProfileUseCase>((ref) {
final repository = ref.watch(profileRepositoryProvider);
return GetUserProfileUseCase(repository);
});
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@ abstract class ProfileRepository {
});

Future<User> getProfile();
Future<User> getUserProfile(int id);
Future<void> deleteUserRequest();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import 'package:interns2025b_mobile/src/features/profile/domain/repositories/profile_repository.dart';
import 'package:interns2025b_mobile/src/shared/domain/models/user_model.dart';

class GetUserProfileUseCase {
final ProfileRepository repository;

GetUserProfileUseCase(this.repository);

Future<User> call(int id) {
return repository.getUserProfile(id);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,10 @@ class ProfileController extends ChangeNotifier {
}
}

Future<void> loadUser(BuildContext context) async {
await fetchUserProfile(context: context);
}

Future<void> deleteUser({required BuildContext context}) async {
_setLoading(true);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:interns2025b_mobile/src/features/profile/domain/usecases/get_profile_usecase.dart';
import 'package:interns2025b_mobile/src/features/profile/domain/usecases/get_user_profile_usecase.dart';
import 'package:interns2025b_mobile/src/features/profile/presentation/providers/profile_details_user_provider.dart';
import 'package:interns2025b_mobile/src/features/profile/presentation/providers/profile_user_provider.dart';

class ProfileDetailsController extends ChangeNotifier {
final Ref ref;
final GetUserProfileUseCase getUserProfileUseCase;
final GetProfileUseCase getProfileUseCase;

ProfileDetailsController(
this.ref,
this.getUserProfileUseCase,
this.getProfileUseCase,
);

bool _isLoading = false;
bool get isLoading => _isLoading;

Future<void> loadUserById(int userId, BuildContext context) async {
_isLoading = true;
notifyListeners();

final currentUser = ref.read(profileUserProvider);
final isSelf = currentUser != null && currentUser.id == userId;

final user = isSelf
? await getProfileUseCase()
: await getUserProfileUseCase(userId);

ref.read(profileDetailsUserProvider.notifier).state = user;

}
Comment on lines +34 to +35
Copy link

Copilot AI Jul 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The loadUserById method sets _isLoading to true but never sets it back to false. This will cause the loading state to persist indefinitely. Add _isLoading = false; notifyListeners(); before the method ends.

Suggested change
}
} finally {
_isLoading = false;
notifyListeners();
}
}

Copilot uses AI. Check for mistakes.
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:interns2025b_mobile/l10n/generated/app_localizations.dart';
import 'package:interns2025b_mobile/src/features/profile/presentation/providers/user_profile_by_id_provider.dart';
import 'package:interns2025b_mobile/src/features/profile/presentation/widgets/profile_info_card.dart';
import 'package:interns2025b_mobile/src/shared/presentation/theme/app_colors.dart';
import 'package:interns2025b_mobile/src/shared/presentation/widgets/navigation_bar.dart';

class ProfileDetailsPage extends ConsumerWidget {
const ProfileDetailsPage({super.key});

@override
Widget build(BuildContext context, WidgetRef ref) {
final userId = ModalRoute.of(context)!.settings.arguments as int;
final topPadding = MediaQuery.of(context).padding.top;
final localization = AppLocalizations.of(context)!.failedToLoadUserProfile;
Copy link

Copilot AI Jul 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The localization variable is assigned the specific error message string rather than the AppLocalizations object. This is inconsistent with usage elsewhere in the codebase and makes the variable name misleading. Consider renaming to errorMessage or assign the full AppLocalizations object.

Suggested change
final localization = AppLocalizations.of(context)!.failedToLoadUserProfile;
final errorMessage = AppLocalizations.of(context)!.failedToLoadUserProfile;

Copilot uses AI. Check for mistakes.

final userAsyncValue = ref.watch(userProfileByIdProvider(userId));

return userAsyncValue.when(
data: (user) {
return Scaffold(
body: Stack(
children: [
Container(color: AppColors.primary),
Positioned.fill(
top: 0,
child: SingleChildScrollView(
child: Column(
children: [
SizedBox(height: topPadding + 100),
ProfileInfoCard(user: user, editable: false),
],
),
),
),
],
),
bottomNavigationBar: const NavigationBarWidget(),
);
},
loading: () =>
const Scaffold(body: Center(child: CircularProgressIndicator())),
error: (error, _) => Scaffold(
body: Center(
child: Text(localization.replaceAll('{e}', error.toString())),
),
),
);
}
}
Loading