Skip to content
Closed
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: 15 additions & 0 deletions lib/providers/auth_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -41,5 +41,20 @@ class AuthProvider with ChangeNotifier {
_isGuest = false;
notifyListeners();
}

Future<void> updateProfilePicture(String avatarUrl) async {
if (_user == null) return;
_isLoading = true;
notifyListeners();

try {
_user = await _service.updateProfilePicture(_user!.id, avatarUrl);
} catch (e) {
rethrow;
} finally {
_isLoading = false;
notifyListeners();
}
}
}

13 changes: 12 additions & 1 deletion lib/screens/profile/profile_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import 'package:provider/provider.dart';
import 'package:cached_network_image/cached_network_image.dart';
import '../../providers/auth_provider.dart';
import '../../providers/theme_provider.dart';
import 'profile_settings_screen.dart';

class ProfileScreen extends StatelessWidget {
const ProfileScreen({super.key});
Expand Down Expand Up @@ -51,7 +52,17 @@ class ProfileScreen extends StatelessWidget {
leading: const Icon(Icons.settings),
title: const Text('Settings'),
onTap: () {
// Navigate to settings
if (isGuest) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Login to access settings')),
);
return;
}
Navigator.of(context).push(
MaterialPageRoute(
builder: (ctx) => const ProfileSettingsScreen(),
),
);
},
),
ListTile(
Expand Down
187 changes: 187 additions & 0 deletions lib/screens/profile/profile_settings_screen.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../../providers/auth_provider.dart';

class ProfileSettingsScreen extends StatefulWidget {
const ProfileSettingsScreen({super.key});

@override
State<ProfileSettingsScreen> createState() => _ProfileSettingsScreenState();
}

class _ProfileSettingsScreenState extends State<ProfileSettingsScreen> {
late TextEditingController _avatarController;
String? _selectedAvatar;
bool _isSaving = false;

final List<String> _presetAvatars = const [
'https://picsum.photos/id/1005/200/200',
'https://picsum.photos/id/1011/200/200',
'https://picsum.photos/id/1027/200/200',
'https://picsum.photos/id/1035/200/200',
'https://picsum.photos/id/237/200/200',
'https://picsum.photos/id/64/200/200',
];

@override
void initState() {
super.initState();
final user = Provider.of<AuthProvider>(context, listen: false).user;
_avatarController = TextEditingController(text: user?.avatarUrl ?? '');
_selectedAvatar = user?.avatarUrl;
}

@override
void dispose() {
_avatarController.dispose();
super.dispose();
}

Future<void> _saveAvatar() async {
final auth = Provider.of<AuthProvider>(context, listen: false);
final avatarUrl = _avatarController.text.trim();

if (avatarUrl.isEmpty || auth.user == null) return;

setState(() => _isSaving = true);
try {
await auth.updateProfilePicture(avatarUrl);
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Profile picture updated')),
);
Navigator.of(context).pop();
} catch (e) {
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Failed to update picture: $e')),
);
} finally {
if (mounted) {
setState(() => _isSaving = false);
}
}
}

@override
Widget build(BuildContext context) {
final auth = Provider.of<AuthProvider>(context);
final user = auth.user;

if (user == null) {
return Scaffold(
appBar: AppBar(title: const Text('Profile Settings')),
body: const Center(child: Text('Login to update your profile.')),
);
}

return Scaffold(
appBar: AppBar(
title: const Text('Profile Settings'),
),
body: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Current Picture',
style: Theme.of(context).textTheme.titleMedium,
),
const SizedBox(height: 12),
Center(
child: CircleAvatar(
radius: 50,
backgroundImage:
CachedNetworkImageProvider(_selectedAvatar ?? user.avatarUrl),
),
),
const SizedBox(height: 24),
Text(
'Use a custom image URL',
style: Theme.of(context).textTheme.titleMedium,
),
const SizedBox(height: 8),
TextField(
controller: _avatarController,
decoration: const InputDecoration(
labelText: 'Image URL',
hintText: 'https://example.com/avatar.png',
border: OutlineInputBorder(),
),
onChanged: (value) {
setState(() {
_selectedAvatar = value.trim().isEmpty ? null : value.trim();
});
},
),
const SizedBox(height: 24),
Text(
'Or pick a preset',
style: Theme.of(context).textTheme.titleMedium,
),
const SizedBox(height: 8),
Expanded(
child: GridView.builder(
itemCount: _presetAvatars.length,
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
mainAxisSpacing: 12,
crossAxisSpacing: 12,
),
itemBuilder: (context, index) {
final avatar = _presetAvatars[index];
final isSelected = avatar == _selectedAvatar;
return GestureDetector(
onTap: () {
setState(() {
_selectedAvatar = avatar;
_avatarController.text = avatar;
});
},
child: Stack(
alignment: Alignment.center,
children: [
CircleAvatar(
radius: 40,
backgroundImage:
CachedNetworkImageProvider(avatar),
),
if (isSelected)
Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.black38,
),
child:
const Icon(Icons.check, color: Colors.white),
),
],
),
);
},
),
),
const SizedBox(height: 16),
SizedBox(
width: double.infinity,
child: ElevatedButton.icon(
onPressed: _isSaving ? null : _saveAvatar,
icon: _isSaving
? const SizedBox(
width: 16,
height: 16,
child: CircularProgressIndicator(strokeWidth: 2),
)
: const Icon(Icons.save),
label: Text(_isSaving ? 'Saving...' : 'Save Changes'),
),
),
],
),
),
);
}
}

14 changes: 13 additions & 1 deletion lib/services/mock_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ class MockService {
),
];

static final User _mockUser = User(
static User _mockUser = User(
id: 'u1',
name: 'John Doe',
email: 'john.doe@example.com',
Expand All @@ -100,6 +100,18 @@ class MockService {
return _mockUser;
}

Future<User> updateProfilePicture(String userId, String avatarUrl) async {
await Future.delayed(const Duration(milliseconds: 500));
if (_mockUser.id != userId) throw Exception('User not found');
_mockUser = User(
id: _mockUser.id,
name: _mockUser.name,
email: _mockUser.email,
avatarUrl: avatarUrl,
);
return _mockUser;
}

Future<List<Product>> searchProducts(String query) async {
await Future.delayed(const Duration(milliseconds: 600));
return _products.where((p) =>
Expand Down
8 changes: 4 additions & 4 deletions pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -244,10 +244,10 @@ packages:
dependency: transitive
description:
name: meta
sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
url: "https://pub.dev"
source: hosted
version: "1.16.0"
version: "1.17.0"
nested:
dependency: transitive
description:
Expand Down Expand Up @@ -505,10 +505,10 @@ packages:
dependency: transitive
description:
name: test_api
sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00"
sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55
url: "https://pub.dev"
source: hosted
version: "0.7.6"
version: "0.7.7"
typed_data:
dependency: transitive
description:
Expand Down