11import 'dart:io' ;
22import 'package:flutter/material.dart' ;
3+ import 'package:flutter/foundation.dart' show kIsWeb;
34import 'package:go_router/go_router.dart' ;
4- import 'package:image_picker/image_picker .dart' ;
5+ import 'package:file_picker/file_picker .dart' ;
56import 'package:material_symbols_icons/symbols.dart' ;
67import '../l10n/app_localizations.dart' ;
78import '../models/user_profile.dart' ;
@@ -18,10 +19,8 @@ class _ProfileEditScreenState extends State<ProfileEditScreen> {
1819 UserProfile ? _currentUser;
1920 bool _isSubmitting = false ;
2021
21- // Image pickers
22- final ImagePicker _imagePicker = ImagePicker ();
23- XFile ? _selectedAvatar;
24- XFile ? _selectedBackground;
22+ // File picker
23+ PlatformFile ? _selectedAvatar;
2524
2625 // Form fields
2726 late TextEditingController _emailController;
@@ -81,17 +80,30 @@ class _ProfileEditScreenState extends State<ProfileEditScreen> {
8180 );
8281
8382 if (choice == 'change' ) {
84- final XFile ? image = await _imagePicker.pickImage (
85- source: ImageSource .gallery,
86- maxWidth: 512 ,
87- maxHeight: 512 ,
88- imageQuality: 85 ,
89- );
83+ FilePickerResult ? result;
84+
85+ if (! kIsWeb && Platform .isAndroid) {
86+ result = await FilePicker .platform.pickFiles (
87+ type: FileType .custom,
88+ allowedExtensions: ['jpg' , 'jpeg' , 'png' , 'gif' , 'bmp' , 'webp' , 'heic' , 'heif' ],
89+ allowMultiple: false ,
90+ withData: false ,
91+ );
92+ } else {
93+ result = await FilePicker .platform.pickFiles (
94+ type: FileType .image,
95+ allowMultiple: false ,
96+ withData: true ,
97+ );
98+ }
9099
91- if (image != null ) {
92- setState (() {
93- _selectedAvatar = image;
94- });
100+ if (result != null ) {
101+ final file = result.files.single;
102+ if (file.bytes != null || file.path != null ) {
103+ setState (() {
104+ _selectedAvatar = file;
105+ });
106+ }
95107 }
96108 } else if (choice == 'remove' ) {
97109 setState (() {
@@ -100,27 +112,6 @@ class _ProfileEditScreenState extends State<ProfileEditScreen> {
100112 }
101113 }
102114
103- Future <void > _selectBackground () async {
104- final XFile ? image = await _imagePicker.pickImage (
105- source: ImageSource .gallery,
106- maxWidth: 1920 ,
107- maxHeight: 1080 ,
108- imageQuality: 85 ,
109- );
110-
111- if (image != null ) {
112- setState (() {
113- _selectedBackground = image;
114- });
115- }
116- }
117-
118- void _removeBackground () {
119- setState (() {
120- _selectedBackground = null ;
121- });
122- }
123-
124115 Future <void > _saveProfile () async {
125116 final l10n = AppLocalizations .of (context)! ;
126117
@@ -140,7 +131,7 @@ class _ProfileEditScreenState extends State<ProfileEditScreen> {
140131 createTime: _currentUser! .createTime,
141132 personalSign: _bioController.text,
142133 introduction: _introductionController.text,
143- avatar: _selectedAvatar? .path ?? _currentUser! .avatar,
134+ avatar: ( _selectedAvatar? .path ?? (_selectedAvatar ? .bytes != null ? 'bytes' : null )) ?? _currentUser! .avatar,
144135 );
145136 _isSubmitting = false ;
146137 });
@@ -181,116 +172,63 @@ class _ProfileEditScreenState extends State<ProfileEditScreen> {
181172 child: Column (
182173 crossAxisAlignment: CrossAxisAlignment .start,
183174 children: [
184- // Background and Avatar Section
185- AspectRatio (
186- aspectRatio: 16 / 7 ,
187- child: Stack (
188- clipBehavior: Clip .none,
189- fit: StackFit .expand,
190- children: [
191- // Background
192- GestureDetector (
193- onTap: _selectBackground,
194- child: Container (
195- color: colorScheme.surfaceContainerHigh,
196- child: _selectedBackground != null
197- ? Image .file (
198- File (_selectedBackground! .path),
199- fit: BoxFit .cover,
200- )
201- : Center (
202- child: Column (
203- mainAxisAlignment: MainAxisAlignment .center,
204- children: [
205- Icon (
206- Symbols .add_photo_alternate,
207- size: 48 ,
208- color: colorScheme.onSurfaceVariant,
209- ),
210- const SizedBox (height: 8 ),
211- Text (
212- l10n.profileEditChangeBackground,
213- style: TextStyle (
214- color: colorScheme.onSurfaceVariant,
215- ),
216- ),
217- ],
175+ // Avatar Section
176+ Center (
177+ child: Padding (
178+ padding: const EdgeInsets .symmetric (vertical: 32 ),
179+ child: GestureDetector (
180+ onTap: _selectAvatar,
181+ child: Stack (
182+ clipBehavior: Clip .none,
183+ children: [
184+ Container (
185+ decoration: BoxDecoration (
186+ shape: BoxShape .circle,
187+ border: Border .all (
188+ color: colorScheme.outline.withOpacity (0.2 ),
189+ width: 3 ,
190+ ),
191+ ),
192+ child: _selectedAvatar != null
193+ ? CircleAvatar (
194+ radius: 60 ,
195+ backgroundImage: kIsWeb && _selectedAvatar! .bytes != null
196+ ? MemoryImage (_selectedAvatar! .bytes! )
197+ : (_selectedAvatar! .path != null
198+ ? FileImage (File (_selectedAvatar! .path! )) as ImageProvider
199+ : null ),
200+ )
201+ : ProfilePictureWidget (
202+ avatarUrl: _currentUser! .avatar,
203+ radius: 60 ,
218204 ),
219- ),
220- ),
221- ),
222-
223- // Background remove button
224- if (_selectedBackground != null )
225- Positioned (
226- top: 8 ,
227- right: 8 ,
228- child: IconButton .filled (
229- icon: const Icon (Symbols .delete),
230- onPressed: _removeBackground,
231- tooltip: l10n.profileEditRemoveBackground,
232205 ),
233- ),
234-
235- // Avatar
236- Positioned (
237- left: 20 ,
238- bottom: - 32 ,
239- child: GestureDetector (
240- onTap: _selectAvatar,
241- child: Stack (
242- clipBehavior: Clip .none,
243- children: [
244- Container (
245- decoration: BoxDecoration (
246- shape: BoxShape .circle,
247- border: Border .all (
248- color: colorScheme.surface,
249- width: 4 ,
250- ),
206+ Positioned (
207+ right: 0 ,
208+ bottom: 0 ,
209+ child: Container (
210+ decoration: BoxDecoration (
211+ color: colorScheme.primaryContainer,
212+ shape: BoxShape .circle,
213+ border: Border .all (
214+ color: colorScheme.surface,
215+ width: 3 ,
251216 ),
252- child: _selectedAvatar != null
253- ? CircleAvatar (
254- radius: 40 ,
255- backgroundImage: FileImage (
256- File (_selectedAvatar! .path),
257- ),
258- )
259- : ProfilePictureWidget (
260- avatarUrl: _currentUser! .avatar,
261- radius: 40 ,
262- ),
263217 ),
264- Positioned (
265- right: - 4 ,
266- bottom: - 4 ,
267- child: Container (
268- decoration: BoxDecoration (
269- color: colorScheme.primaryContainer,
270- shape: BoxShape .circle,
271- border: Border .all (
272- color: colorScheme.surface,
273- width: 2 ,
274- ),
275- ),
276- padding: const EdgeInsets .all (6 ),
277- child: Icon (
278- Symbols .edit,
279- size: 16 ,
280- color: colorScheme.onPrimaryContainer,
281- ),
282- ),
218+ padding: const EdgeInsets .all (10 ),
219+ child: Icon (
220+ Symbols .edit,
221+ size: 20 ,
222+ color: colorScheme.onPrimaryContainer,
283223 ),
284- ] ,
224+ ) ,
285225 ),
286- ) ,
226+ ] ,
287227 ),
288- ] ,
228+ ) ,
289229 ),
290230 ),
291231
292- const SizedBox (height: 48 ),
293-
294232 // Basic Info Section
295233 Padding (
296234 padding: const EdgeInsets .symmetric (horizontal: 24 ),
0 commit comments