diff --git a/lib/components/user_tile.dart b/lib/components/user_tile.dart index f5b7fa7..d224a38 100644 --- a/lib/components/user_tile.dart +++ b/lib/components/user_tile.dart @@ -2,9 +2,15 @@ import 'package:flutter/material.dart'; class UserTile extends StatelessWidget { final String text; + final String? profileImageUrl; final void Function()? onTap; - const UserTile({super.key, required this.text, required this.onTap}); + const UserTile({ + super.key, + required this.text, + this.profileImageUrl, + required this.onTap, + }); @override Widget build(BuildContext context) { @@ -19,8 +25,15 @@ class UserTile extends StatelessWidget { padding: const EdgeInsets.all(20), child: Row( children: [ - // icon - const Icon(Icons.person), + // Profile image + if (profileImageUrl != null && profileImageUrl!.isNotEmpty) + CircleAvatar( + backgroundImage: NetworkImage(profileImageUrl!), + radius: 24, + ), + // Icon if profile image is not set + if (profileImageUrl == null || profileImageUrl!.isEmpty) + const Icon(Icons.person), const SizedBox(width: 20), diff --git a/lib/pages/conversations_page.dart b/lib/pages/conversations_page.dart index 66a8f2d..b4cba08 100644 --- a/lib/pages/conversations_page.dart +++ b/lib/pages/conversations_page.dart @@ -70,6 +70,7 @@ class ConversationsPage extends StatelessWidget { Map userData, BuildContext context) { return UserTile( text: userData[Constants.dbFieldUsersEmail], + profileImageUrl: userData[Constants.dbFieldUsersProfilePic], onTap: () { // tapped on a user -> go to chat page Navigator.push( diff --git a/lib/pages/edit_profile_page.dart b/lib/pages/edit_profile_page.dart index 1f12d0e..0f45013 100644 --- a/lib/pages/edit_profile_page.dart +++ b/lib/pages/edit_profile_page.dart @@ -6,10 +6,10 @@ import 'package:firebase_storage/firebase_storage.dart'; import 'package:image_cropper/image_cropper.dart'; import 'package:image_picker/image_picker.dart'; import 'dart:io' as io; +import 'dart:typed_data'; import '../constants.dart'; import '../models/user_profile.dart'; -import '../utils/helper.dart'; class EditProfilePage extends StatefulWidget { final UserProfile userData; @@ -25,7 +25,8 @@ class EditProfilePageState extends State { late TextEditingController _nameController; late TextEditingController _bioController; String? profileImageUrl; - io.File? _profileImage; + io.File? _profileImage; // for android/ios + Uint8List? _webProfileImage; // for web @override void initState() { @@ -38,12 +39,6 @@ class EditProfilePageState extends State { } Future _pickImage() async { - if (kIsWeb) { - showMsg(context, 'Limitation of the web version', - 'Due to limitations of the component used in the web version, setting a profile picture is currently available only on Android and iOS.'); - return; - } - final pickedFile = await ImagePicker().pickImage(source: ImageSource.gallery); @@ -57,7 +52,7 @@ class EditProfilePageState extends State { CropAspectRatioPreset.ratio3x2, CropAspectRatioPreset.original, CropAspectRatioPreset.ratio4x3, - CropAspectRatioPreset.ratio16x9 + CropAspectRatioPreset.ratio16x9, ], uiSettings: [ AndroidUiSettings( @@ -71,26 +66,35 @@ class EditProfilePageState extends State { title: 'Cropper', minimumAspectRatio: 1.0, ), -/* WebUiSettings( - context: context, - presentStyle: CropperPresentStyle.page, - boundary: const CroppieBoundary( - width: 400, - height: 400, + if (kIsWeb) + WebUiSettings( + context: context, + presentStyle: CropperPresentStyle.page, + boundary: const CroppieBoundary( + width: 400, + height: 400, + ), + viewPort: const CroppieViewPort( + width: 360, height: 360, type: 'circle'), + enableExif: true, + enableZoom: true, + showZoomer: true, ), - viewPort: - const CroppieViewPort(width: 360, height: 360, type: 'circle'), - enableExif: true, - enableZoom: true, - showZoomer: true, - ),*/ ], ); if (croppedFile != null) { - setState(() { - _profileImage = io.File(croppedFile.path); // convert cropped file - }); + if (kIsWeb) { + // web specific + Uint8List webProfileImage = await croppedFile.readAsBytes(); + setState(() { + _webProfileImage = webProfileImage; + }); + } else { + setState(() { + _profileImage = io.File(croppedFile.path); // convert cropped file + }); + } } } } @@ -98,6 +102,7 @@ class EditProfilePageState extends State { void _clearProfileImage() { setState(() { _profileImage = null; + _webProfileImage = null; // web only profileImageUrl = null; widget.userData.profilePictureUrl = null; }); @@ -110,13 +115,16 @@ class EditProfilePageState extends State { if (_formKey.currentState!.validate()) { String uid = FirebaseAuth.instance.currentUser!.uid; - if (_profileImage != null) { + if (_profileImage != null || _webProfileImage != null) { final storageRef = FirebaseStorage.instance .ref() .child(Constants.dbStoragePathProfiles) .child(uid); // filename = userid - if (!kIsWeb) { + if (kIsWeb && _webProfileImage != null) { + await storageRef.putData(_webProfileImage!); + profileImageUrl = await storageRef.getDownloadURL(); + } else if (_profileImage != null) { await storageRef.putFile(_profileImage!); profileImageUrl = await storageRef.getDownloadURL(); } @@ -165,6 +173,7 @@ class EditProfilePageState extends State { children: [ buildAvatar(context), if (_profileImage != null || + _webProfileImage != null || widget.userData.profilePictureUrl != null) IconButton( icon: Ink( @@ -221,29 +230,20 @@ class EditProfilePageState extends State { radius: 50, backgroundImage: _profileImage != null ? FileImage(_profileImage!) as ImageProvider - : ((widget.userData.profilePictureUrl != null && - widget.userData.profilePictureUrl!.isNotEmpty) - ? NetworkImage(widget.userData.profilePictureUrl!) - as ImageProvider - : null), + : _webProfileImage != null + ? MemoryImage(_webProfileImage!) + as ImageProvider // web specific + : (widget.userData.profilePictureUrl != null && + widget.userData.profilePictureUrl!.isNotEmpty + ? NetworkImage(widget.userData.profilePictureUrl!) + as ImageProvider + : null), child: ClipOval( child: _profileImage == null && + _webProfileImage == null && widget.userData.profilePictureUrl == null ? const Icon(Icons.person, size: 50) - : SizedBox( - width: 100, - height: 100, - child: _profileImage != null - ? (kIsWeb - ? Image.network(_profileImage!.path) - : Image.file(_profileImage!, fit: BoxFit.cover)) - : ((widget.userData.profilePictureUrl != null && - widget.userData.profilePictureUrl!.isNotEmpty) - ? Image.network( - widget.userData.profilePictureUrl!, - fit: BoxFit.cover) - : null), - ), + : null, ), ), Positioned( diff --git a/lib/pages/user_profile_page.dart b/lib/pages/user_profile_page.dart index 7da0dde..c7ba041 100644 --- a/lib/pages/user_profile_page.dart +++ b/lib/pages/user_profile_page.dart @@ -1,4 +1,3 @@ -import 'package:flutter/foundation.dart' show kIsWeb; import 'package:flutter/material.dart'; import 'package:firebase_auth/firebase_auth.dart'; @@ -610,13 +609,13 @@ class _UserProfilePageState extends State { ), CircleAvatar( radius: 50, - backgroundImage: (!kIsWeb && - (profileImageUrl != null && profileImageUrl!.isNotEmpty)) - ? NetworkImage(profileImageUrl!) - : null, + backgroundImage: + ((profileImageUrl != null && profileImageUrl!.isNotEmpty)) + ? NetworkImage(profileImageUrl!) + : null, child: (profileImageUrl == null || profileImageUrl!.isEmpty) ? const Icon(Icons.person, size: 50) - : (kIsWeb ? const Icon(Icons.broken_image, size: 50) : null), + : null, ), const SizedBox(height: 16), Text(myData.name, style: const TextStyle(fontSize: 24)), diff --git a/web/index.html b/web/index.html index f05ce2b..dc8d550 100644 --- a/web/index.html +++ b/web/index.html @@ -53,7 +53,8 @@ serviceWorkerVersion: serviceWorkerVersion, }, onEntrypointLoaded: function(engineInitializer) { - engineInitializer.initializeEngine().then(function(appRunner) { + let config = { renderer: 'html' }; + engineInitializer.initializeEngine(config).then(function(appRunner) { appRunner.runApp(); }); }