diff --git a/frontend/app_flowy/assets/images/emoji/1F42F.svg b/frontend/app_flowy/assets/images/emoji/1F42F.svg
new file mode 100644
index 0000000000..a6e8e3e81f
--- /dev/null
+++ b/frontend/app_flowy/assets/images/emoji/1F42F.svg
@@ -0,0 +1,32 @@
+
diff --git a/frontend/app_flowy/assets/images/emoji/1F431.svg b/frontend/app_flowy/assets/images/emoji/1F431.svg
new file mode 100644
index 0000000000..26aa279abc
--- /dev/null
+++ b/frontend/app_flowy/assets/images/emoji/1F431.svg
@@ -0,0 +1,24 @@
+
diff --git a/frontend/app_flowy/assets/images/emoji/1F435.svg b/frontend/app_flowy/assets/images/emoji/1F435.svg
new file mode 100644
index 0000000000..0220a6e58e
--- /dev/null
+++ b/frontend/app_flowy/assets/images/emoji/1F435.svg
@@ -0,0 +1,28 @@
+
diff --git a/frontend/app_flowy/assets/images/emoji/1F43A.svg b/frontend/app_flowy/assets/images/emoji/1F43A.svg
new file mode 100644
index 0000000000..3e29b3a6a9
--- /dev/null
+++ b/frontend/app_flowy/assets/images/emoji/1F43A.svg
@@ -0,0 +1,28 @@
+
diff --git a/frontend/app_flowy/assets/images/emoji/1F600.svg b/frontend/app_flowy/assets/images/emoji/1F600.svg
new file mode 100644
index 0000000000..e9e1d0ea88
--- /dev/null
+++ b/frontend/app_flowy/assets/images/emoji/1F600.svg
@@ -0,0 +1,17 @@
+
diff --git a/frontend/app_flowy/assets/images/emoji/1F984.svg b/frontend/app_flowy/assets/images/emoji/1F984.svg
new file mode 100644
index 0000000000..a5f8206cbc
--- /dev/null
+++ b/frontend/app_flowy/assets/images/emoji/1F984.svg
@@ -0,0 +1,21 @@
+
diff --git a/frontend/app_flowy/assets/images/emoji/1F9CC.svg b/frontend/app_flowy/assets/images/emoji/1F9CC.svg
new file mode 100644
index 0000000000..eb30038228
--- /dev/null
+++ b/frontend/app_flowy/assets/images/emoji/1F9CC.svg
@@ -0,0 +1,33 @@
+
diff --git a/frontend/app_flowy/assets/images/emoji/1F9DB.svg b/frontend/app_flowy/assets/images/emoji/1F9DB.svg
new file mode 100644
index 0000000000..590829d25c
--- /dev/null
+++ b/frontend/app_flowy/assets/images/emoji/1F9DB.svg
@@ -0,0 +1,26 @@
+
diff --git a/frontend/app_flowy/assets/images/emoji/1F9DD-200D-2642-FE0F.svg b/frontend/app_flowy/assets/images/emoji/1F9DD-200D-2642-FE0F.svg
new file mode 100644
index 0000000000..62e5101f53
--- /dev/null
+++ b/frontend/app_flowy/assets/images/emoji/1F9DD-200D-2642-FE0F.svg
@@ -0,0 +1,39 @@
+
diff --git a/frontend/app_flowy/assets/images/emoji/1F9DE-200D-2642-FE0F.svg b/frontend/app_flowy/assets/images/emoji/1F9DE-200D-2642-FE0F.svg
new file mode 100644
index 0000000000..e662de70a6
--- /dev/null
+++ b/frontend/app_flowy/assets/images/emoji/1F9DE-200D-2642-FE0F.svg
@@ -0,0 +1,28 @@
+
diff --git a/frontend/app_flowy/assets/images/emoji/1F9DF.svg b/frontend/app_flowy/assets/images/emoji/1F9DF.svg
new file mode 100644
index 0000000000..e2ea11f33a
--- /dev/null
+++ b/frontend/app_flowy/assets/images/emoji/1F9DF.svg
@@ -0,0 +1,28 @@
+
diff --git a/frontend/app_flowy/lib/user/application/user_service.dart b/frontend/app_flowy/lib/user/application/user_service.dart
index 48bea6aa41..35e32f2eb1 100644
--- a/frontend/app_flowy/lib/user/application/user_service.dart
+++ b/frontend/app_flowy/lib/user/application/user_service.dart
@@ -11,7 +11,8 @@ class UserService {
UserService({
required this.userId,
});
- Future> getUserProfile({required String userId}) {
+ Future> getUserProfile(
+ {required String userId}) {
return UserEventGetUserProfile().send();
}
@@ -19,6 +20,7 @@ class UserService {
String? name,
String? password,
String? email,
+ String? iconUrl,
}) {
var payload = UpdateUserProfilePayloadPB.create()..id = userId;
@@ -34,10 +36,15 @@ class UserService {
payload.email = email;
}
+ if (iconUrl != null) {
+ payload.iconUrl = iconUrl;
+ }
+
return UserEventUpdateUserProfile(payload).send();
}
- Future> deleteWorkspace({required String workspaceId}) {
+ Future> deleteWorkspace(
+ {required String workspaceId}) {
throw UnimplementedError();
}
@@ -70,7 +77,8 @@ class UserService {
});
}
- Future> createWorkspace(String name, String desc) {
+ Future> createWorkspace(
+ String name, String desc) {
final request = CreateWorkspacePayloadPB.create()
..name = name
..desc = desc;
diff --git a/frontend/app_flowy/lib/workspace/application/user/settings_user_bloc.dart b/frontend/app_flowy/lib/workspace/application/user/settings_user_bloc.dart
index 7435778471..de63777812 100644
--- a/frontend/app_flowy/lib/workspace/application/user/settings_user_bloc.dart
+++ b/frontend/app_flowy/lib/workspace/application/user/settings_user_bloc.dart
@@ -35,6 +35,14 @@ class SettingsUserViewBloc extends Bloc {
);
});
},
+ updateUserIcon: (String iconUrl) {
+ _userService.updateUserProfile(iconUrl: iconUrl).then((result) {
+ result.fold(
+ (l) => null,
+ (err) => Log.error(err),
+ );
+ });
+ },
);
});
}
@@ -52,7 +60,8 @@ class SettingsUserViewBloc extends Bloc {
void _profileUpdated(Either userProfileOrFailed) {
userProfileOrFailed.fold(
- (newUserProfile) => add(SettingsUserEvent.didReceiveUserProfile(newUserProfile)),
+ (newUserProfile) =>
+ add(SettingsUserEvent.didReceiveUserProfile(newUserProfile)),
(err) => Log.error(err),
);
}
@@ -62,7 +71,10 @@ class SettingsUserViewBloc extends Bloc {
class SettingsUserEvent with _$SettingsUserEvent {
const factory SettingsUserEvent.initial() = _Initial;
const factory SettingsUserEvent.updateUserName(String name) = _UpdateUserName;
- const factory SettingsUserEvent.didReceiveUserProfile(UserProfilePB newUserProfile) = _DidReceiveUserProfile;
+ const factory SettingsUserEvent.updateUserIcon(String iconUrl) =
+ _UpdateUserIcon;
+ const factory SettingsUserEvent.didReceiveUserProfile(
+ UserProfilePB newUserProfile) = _DidReceiveUserProfile;
}
@freezed
@@ -72,7 +84,8 @@ class SettingsUserState with _$SettingsUserState {
required Either successOrFailure,
}) = _SettingsUserState;
- factory SettingsUserState.initial(UserProfilePB userProfile) => SettingsUserState(
+ factory SettingsUserState.initial(UserProfilePB userProfile) =>
+ SettingsUserState(
userProfile: userProfile,
successOrFailure: left(unit),
);
diff --git a/frontend/app_flowy/lib/workspace/presentation/home/menu/menu_user.dart b/frontend/app_flowy/lib/workspace/presentation/home/menu/menu_user.dart
index d7b8dce4af..baf9dbffab 100644
--- a/frontend/app_flowy/lib/workspace/presentation/home/menu/menu_user.dart
+++ b/frontend/app_flowy/lib/workspace/presentation/home/menu/menu_user.dart
@@ -19,7 +19,8 @@ class MenuUser extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocProvider(
- create: (context) => getIt(param1: user)..add(const MenuUserEvent.initial()),
+ create: (context) =>
+ getIt(param1: user)..add(const MenuUserEvent.initial()),
child: BlocBuilder(
builder: (context, state) => Row(
children: [
@@ -39,20 +40,16 @@ class MenuUser extends StatelessWidget {
}
Widget _renderAvatar(BuildContext context) {
- return const SizedBox(
+ String iconUrl = context.read().state.userProfile.iconUrl;
+
+ return SizedBox(
width: 25,
height: 25,
child: ClipRRect(
borderRadius: Corners.s5Border,
child: CircleAvatar(
- backgroundColor: Color.fromRGBO(132, 39, 224, 1.0),
- child: Text(
- 'M',
- style: TextStyle(
- fontSize: 18,
- fontWeight: FontWeight.w300,
- ),
- ),
+ backgroundColor: Colors.transparent,
+ child: svgWidget('emoji/$iconUrl'),
)),
);
}
diff --git a/frontend/app_flowy/lib/workspace/presentation/settings/widgets/settings_user_view.dart b/frontend/app_flowy/lib/workspace/presentation/settings/widgets/settings_user_view.dart
index f8f094d1b0..a235fe7dcf 100644
--- a/frontend/app_flowy/lib/workspace/presentation/settings/widgets/settings_user_view.dart
+++ b/frontend/app_flowy/lib/workspace/presentation/settings/widgets/settings_user_view.dart
@@ -2,7 +2,11 @@ import 'package:app_flowy/startup/startup.dart';
import 'package:flutter/material.dart';
import 'package:app_flowy/workspace/application/user/settings_user_bloc.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:flowy_infra_ui/widget/spacing.dart';
import 'package:flowy_sdk/protobuf/flowy-user/user_profile.pb.dart';
+import 'package:flowy_infra/image.dart';
+
+import 'dart:convert';
class SettingsUserView extends StatelessWidget {
final UserProfilePB user;
@@ -11,12 +15,17 @@ class SettingsUserView extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocProvider(
- create: (context) => getIt(param1: user)..add(const SettingsUserEvent.initial()),
+ create: (context) => getIt(param1: user)
+ ..add(const SettingsUserEvent.initial()),
child: BlocBuilder(
builder: (context, state) => SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
- children: [_renderUserNameInput(context)],
+ children: [
+ _renderUserNameInput(context),
+ const VSpace(20),
+ _renderCurrentIcon(context)
+ ],
),
),
),
@@ -27,6 +36,12 @@ class SettingsUserView extends StatelessWidget {
String name = context.read().state.userProfile.name;
return _UserNameInput(name);
}
+
+ Widget _renderCurrentIcon(BuildContext context) {
+ String iconUrl =
+ context.read().state.userProfile.iconUrl;
+ return _CurrentIcon(iconUrl);
+ }
}
class _UserNameInput extends StatelessWidget {
@@ -44,7 +59,121 @@ class _UserNameInput extends StatelessWidget {
labelText: 'Name',
),
onSubmitted: (val) {
- context.read().add(SettingsUserEvent.updateUserName(val));
+ context
+ .read()
+ .add(SettingsUserEvent.updateUserName(val));
});
}
}
+
+class _CurrentIcon extends StatelessWidget {
+ final String iconUrl;
+ const _CurrentIcon(this.iconUrl, {Key? key}) : super(key: key);
+
+ @override
+ Widget build(BuildContext context) {
+ _setIcon(String iconUrl) {
+ context
+ .read()
+ .add(SettingsUserEvent.updateUserIcon(iconUrl));
+ Navigator.of(context).pop();
+ }
+
+ return Material(
+ color: Colors.transparent,
+ child: GestureDetector(
+ onTap: () {
+ showDialog(
+ context: context,
+ builder: (BuildContext context) {
+ return SimpleDialog(
+ title: const Text('Select an Icon'),
+ children: [
+ SizedBox(
+ height: 300, width: 300, child: IconGallery(_setIcon))
+ ]);
+ },
+ );
+ },
+ child: Column(children: [
+ const Align(
+ alignment: Alignment.topLeft,
+ child: Text(
+ "Icon",
+ style: TextStyle(color: Colors.grey),
+ )),
+ Align(
+ alignment: Alignment.centerLeft,
+ child: Container(
+ margin: const EdgeInsets.all(5.0),
+ decoration:
+ BoxDecoration(border: Border.all(color: Colors.grey)),
+ child: svgWithSize('emoji/$iconUrl', const Size(60, 60)),
+ )),
+ ])),
+ );
+ }
+}
+
+class IconGallery extends StatelessWidget {
+ final Function setIcon;
+ const IconGallery(this.setIcon, {Key? key}) : super(key: key);
+
+ Future> _getIcons(BuildContext context) async {
+ final manifestContent =
+ await DefaultAssetBundle.of(context).loadString('AssetManifest.json');
+
+ final Map manifestMap = json.decode(manifestContent);
+
+ final iconUrls = manifestMap.keys
+ .where((String key) =>
+ key.startsWith('assets/images/emoji/') && key.endsWith('.svg'))
+ .map((String key) => key.split('/').last.split('.').first)
+ .toList();
+
+ return iconUrls;
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return FutureBuilder>(
+ future: _getIcons(context),
+ builder: (BuildContext context, AsyncSnapshot> snapshot) {
+ if (snapshot.hasData) {
+ return GridView.count(
+ padding: const EdgeInsets.all(20),
+ crossAxisCount: 5,
+ children: (snapshot.data ?? []).map((String iconUrl) {
+ return IconOption(iconUrl, setIcon);
+ }).toList(),
+ );
+ } else {
+ return const Center(
+ child: CircularProgressIndicator(),
+ );
+ }
+ },
+ );
+ }
+}
+
+class IconOption extends StatelessWidget {
+ final String iconUrl;
+ final Function setIcon;
+
+ IconOption(this.iconUrl, this.setIcon, {Key? key})
+ : super(key: ValueKey(iconUrl));
+
+ @override
+ Widget build(BuildContext context) {
+ return Material(
+ color: Colors.transparent,
+ child: GestureDetector(
+ onTap: () {
+ setIcon(iconUrl);
+ },
+ child: svgWidget('emoji/$iconUrl'),
+ ),
+ );
+ }
+}
diff --git a/frontend/app_flowy/pubspec.yaml b/frontend/app_flowy/pubspec.yaml
index 56e96e96dd..d04277a8b4 100644
--- a/frontend/app_flowy/pubspec.yaml
+++ b/frontend/app_flowy/pubspec.yaml
@@ -120,6 +120,7 @@ flutter:
- assets/images/home/
- assets/images/editor/
- assets/images/grid/
+ - assets/images/emoji/
- assets/images/grid/field/
- assets/images/grid/setting/
- assets/translations/
diff --git a/frontend/rust-lib/flowy-database/migrations/2022-08-08-110959_user-add-icon/down.sql b/frontend/rust-lib/flowy-database/migrations/2022-08-08-110959_user-add-icon/down.sql
new file mode 100644
index 0000000000..505fbd4b2f
--- /dev/null
+++ b/frontend/rust-lib/flowy-database/migrations/2022-08-08-110959_user-add-icon/down.sql
@@ -0,0 +1 @@
+ALTER TABLE user_table DROP COLUMN icon_url;
diff --git a/frontend/rust-lib/flowy-database/migrations/2022-08-08-110959_user-add-icon/up.sql b/frontend/rust-lib/flowy-database/migrations/2022-08-08-110959_user-add-icon/up.sql
new file mode 100644
index 0000000000..c2aee5e3de
--- /dev/null
+++ b/frontend/rust-lib/flowy-database/migrations/2022-08-08-110959_user-add-icon/up.sql
@@ -0,0 +1 @@
+ALTER TABLE user_table ADD COLUMN icon_url TEXT NOT NULL DEFAULT '';
diff --git a/frontend/rust-lib/flowy-database/src/schema.rs b/frontend/rust-lib/flowy-database/src/schema.rs
index e41fd6d865..eda9cd888b 100644
--- a/frontend/rust-lib/flowy-database/src/schema.rs
+++ b/frontend/rust-lib/flowy-database/src/schema.rs
@@ -88,6 +88,7 @@ table! {
token -> Text,
email -> Text,
workspace -> Text,
+ icon_url -> Text,
}
}
diff --git a/frontend/rust-lib/flowy-user/src/entities/parser/mod.rs b/frontend/rust-lib/flowy-user/src/entities/parser/mod.rs
index 71259509f2..792af0c146 100644
--- a/frontend/rust-lib/flowy-user/src/entities/parser/mod.rs
+++ b/frontend/rust-lib/flowy-user/src/entities/parser/mod.rs
@@ -1,11 +1,13 @@
// https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-validate/
mod user_email;
+mod user_icon;
mod user_id;
mod user_name;
mod user_password;
mod user_workspace;
pub use user_email::*;
+pub use user_icon::*;
pub use user_id::*;
pub use user_name::*;
pub use user_password::*;
diff --git a/frontend/rust-lib/flowy-user/src/entities/parser/user_icon.rs b/frontend/rust-lib/flowy-user/src/entities/parser/user_icon.rs
new file mode 100644
index 0000000000..69258ca848
--- /dev/null
+++ b/frontend/rust-lib/flowy-user/src/entities/parser/user_icon.rs
@@ -0,0 +1,16 @@
+use crate::errors::ErrorCode;
+
+#[derive(Debug)]
+pub struct UserIcon(pub String);
+
+impl UserIcon {
+ pub fn parse(s: String) -> Result {
+ Ok(Self(s))
+ }
+}
+
+impl AsRef for UserIcon {
+ fn as_ref(&self) -> &str {
+ &self.0
+ }
+}
diff --git a/frontend/rust-lib/flowy-user/src/entities/user_profile.rs b/frontend/rust-lib/flowy-user/src/entities/user_profile.rs
index 276894ffc8..4b423db3af 100644
--- a/frontend/rust-lib/flowy-user/src/entities/user_profile.rs
+++ b/frontend/rust-lib/flowy-user/src/entities/user_profile.rs
@@ -2,7 +2,7 @@ use flowy_derive::ProtoBuf;
use std::convert::TryInto;
use crate::{
- entities::parser::{UserEmail, UserId, UserName, UserPassword},
+ entities::parser::{UserEmail, UserIcon, UserId, UserName, UserPassword},
errors::ErrorCode,
};
@@ -25,6 +25,9 @@ pub struct UserProfilePB {
#[pb(index = 4)]
pub token: String,
+
+ #[pb(index = 5)]
+ pub icon_url: String,
}
#[derive(ProtoBuf, Default)]
@@ -40,6 +43,9 @@ pub struct UpdateUserProfilePayloadPB {
#[pb(index = 4, one_of)]
pub password: Option,
+
+ #[pb(index = 5, one_of)]
+ pub icon_url: Option,
}
impl UpdateUserProfilePayloadPB {
@@ -64,6 +70,11 @@ impl UpdateUserProfilePayloadPB {
self.password = Some(password.to_owned());
self
}
+
+ pub fn icon_url(mut self, icon_url: &str) -> Self {
+ self.icon_url = Some(icon_url.to_owned());
+ self
+ }
}
#[derive(ProtoBuf, Default, Clone, Debug)]
@@ -79,6 +90,9 @@ pub struct UpdateUserProfileParams {
#[pb(index = 4, one_of)]
pub password: Option,
+
+ #[pb(index = 5, one_of)]
+ pub icon_url: Option,
}
impl UpdateUserProfileParams {
@@ -88,6 +102,7 @@ impl UpdateUserProfileParams {
name: None,
email: None,
password: None,
+ icon_url: None,
}
}
@@ -105,6 +120,11 @@ impl UpdateUserProfileParams {
self.password = Some(password.to_owned());
self
}
+
+ pub fn icon_url(mut self, icon_url: &str) -> Self {
+ self.icon_url = Some(icon_url.to_owned());
+ self
+ }
}
impl TryInto for UpdateUserProfilePayloadPB {
@@ -128,11 +148,17 @@ impl TryInto for UpdateUserProfilePayloadPB {
Some(password) => Some(UserPassword::parse(password)?.0),
};
+ let icon_url = match self.icon_url {
+ None => None,
+ Some(icon_url) => Some(UserIcon::parse(icon_url)?.0),
+ };
+
Ok(UpdateUserProfileParams {
id,
name,
email,
password,
+ icon_url,
})
}
}
diff --git a/frontend/rust-lib/flowy-user/src/services/database.rs b/frontend/rust-lib/flowy-user/src/services/database.rs
index 64ebd705a7..62b74c9f22 100644
--- a/frontend/rust-lib/flowy-user/src/services/database.rs
+++ b/frontend/rust-lib/flowy-user/src/services/database.rs
@@ -82,6 +82,7 @@ pub struct UserTable {
pub(crate) token: String,
pub(crate) email: String,
pub(crate) workspace: String, // deprecated
+ pub(crate) icon_url: String,
}
impl UserTable {
@@ -91,6 +92,7 @@ impl UserTable {
name,
email,
token,
+ icon_url: "".to_owned(),
workspace: "".to_owned(),
}
}
@@ -120,6 +122,7 @@ impl std::convert::From for UserProfilePB {
email: table.email,
name: table.name,
token: table.token,
+ icon_url: table.icon_url,
}
}
}
@@ -131,6 +134,7 @@ pub struct UserTableChangeset {
pub workspace: Option, // deprecated
pub name: Option,
pub email: Option,
+ pub icon_url: Option,
}
impl UserTableChangeset {
@@ -140,6 +144,7 @@ impl UserTableChangeset {
workspace: None,
name: params.name,
email: params.email,
+ icon_url: params.icon_url,
}
}
}