feat: display joined at and user avatar in members page (#7809)

* feat: new settings page design

* feat: support display member join at and avatar

* chore: bump version 0.9.0

* chore: update translations
This commit is contained in:
Lucas 2025-04-23 13:41:52 +08:00 committed by GitHub
parent 4626c18ced
commit 1977cf6637
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 182 additions and 92 deletions

View File

@ -26,7 +26,7 @@ CARGO_MAKE_EXTEND_WORKSPACE_MAKEFILE = true
CARGO_MAKE_CRATE_FS_NAME = "dart_ffi" CARGO_MAKE_CRATE_FS_NAME = "dart_ffi"
CARGO_MAKE_CRATE_NAME = "dart-ffi" CARGO_MAKE_CRATE_NAME = "dart-ffi"
LIB_NAME = "dart_ffi" LIB_NAME = "dart_ffi"
APPFLOWY_VERSION = "0.8.9" APPFLOWY_VERSION = "0.9.0"
FLUTTER_DESKTOP_FEATURES = "dart" FLUTTER_DESKTOP_FEATURES = "dart"
PRODUCT_NAME = "AppFlowy" PRODUCT_NAME = "AppFlowy"
MACOSX_DEPLOYMENT_TARGET = "11.0" MACOSX_DEPLOYMENT_TARGET = "11.0"

View File

@ -27,6 +27,7 @@ import 'package:appflowy/workspace/presentation/settings/widgets/web_url_hint_wi
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart'; import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
import 'package:appflowy_backend/log.dart'; import 'package:appflowy_backend/log.dart';
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart'; import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
import 'package:appflowy_ui/appflowy_ui.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -60,6 +61,7 @@ class SettingsDialog extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final width = MediaQuery.of(context).size.width * 0.6; final width = MediaQuery.of(context).size.width * 0.6;
final theme = AppFlowyTheme.of(context);
return BlocProvider<SettingsDialogBloc>( return BlocProvider<SettingsDialogBloc>(
create: (context) => SettingsDialogBloc( create: (context) => SettingsDialogBloc(
user, user,
@ -72,12 +74,12 @@ class SettingsDialog extends StatelessWidget {
constraints: const BoxConstraints(minWidth: 564), constraints: const BoxConstraints(minWidth: 564),
child: ScaffoldMessenger( child: ScaffoldMessenger(
child: Scaffold( child: Scaffold(
backgroundColor: Colors.transparent, backgroundColor: theme.backgroundColorScheme.primary,
body: Row( body: Row(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
SizedBox( SizedBox(
width: 200, width: 204,
child: SettingsMenu( child: SettingsMenu(
userProfile: user, userProfile: user,
changeSelectedPage: (index) => context changeSelectedPage: (index) => context
@ -88,6 +90,10 @@ class SettingsDialog extends StatelessWidget {
isBillingEnabled: state.isBillingEnabled, isBillingEnabled: state.isBillingEnabled,
), ),
), ),
AFDivider(
axis: Axis.vertical,
color: theme.borderColorScheme.primary,
),
Expanded( Expanded(
child: getSettingsView( child: getSettingsView(
context context

View File

@ -88,17 +88,18 @@ class _Description extends StatelessWidget {
await showConfirmDialog( await showConfirmDialog(
context: context, context: context,
style: ConfirmPopupStyle.cancelAndOk, style: ConfirmPopupStyle.cancelAndOk,
title: 'Reset the invite link?', title: LocaleKeys.settings_appearance_members_resetInviteLink.tr(),
description: description: LocaleKeys
'Resetting will deactivate the current link for all space members and generate a new one. The old link will no longer be available.', .settings_appearance_members_resetInviteLinkDescription
confirmLabel: 'Reset', .tr(),
confirmLabel: LocaleKeys.settings_appearance_members_reset.tr(),
onConfirm: () { onConfirm: () {
context.read<WorkspaceMemberBloc>().add( context.read<WorkspaceMemberBloc>().add(
const WorkspaceMemberEvent.generateInviteLink(), const WorkspaceMemberEvent.generateInviteLink(),
); );
}, },
confirmButtonBuilder: (_) => AFFilledTextButton.destructive( confirmButtonBuilder: (_) => AFFilledTextButton.destructive(
text: 'Reset', text: LocaleKeys.settings_appearance_members_reset.tr(),
onTap: () { onTap: () {
context.read<WorkspaceMemberBloc>().add( context.read<WorkspaceMemberBloc>().add(
const WorkspaceMemberEvent.generateInviteLink(), const WorkspaceMemberEvent.generateInviteLink(),
@ -145,7 +146,8 @@ class _CopyLinkButton extends StatelessWidget {
); );
} else { } else {
showToastNotification( showToastNotification(
message: LocaleKeys.shareAction_copyLinkFailed.tr(), message: 'You haven\'t generated an invite link yet.',
type: ToastificationType.error,
); );
} }
}, },

View File

@ -10,6 +10,7 @@ import 'package:appflowy/workspace/presentation/settings/widgets/members/inivita
import 'package:appflowy/workspace/presentation/settings/widgets/members/workspace_member_bloc.dart'; import 'package:appflowy/workspace/presentation/settings/widgets/members/workspace_member_bloc.dart';
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart'; import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart'; import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart';
import 'package:appflowy/workspace/presentation/widgets/user_avatar.dart';
import 'package:appflowy_backend/log.dart'; import 'package:appflowy_backend/log.dart';
import 'package:appflowy_backend/protobuf/flowy-error/code.pbenum.dart'; import 'package:appflowy_backend/protobuf/flowy-error/code.pbenum.dart';
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart'; import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
@ -318,6 +319,7 @@ class _MemberListHeader extends StatelessWidget {
return Row( return Row(
children: [ children: [
Expanded( Expanded(
flex: 4,
child: Text( child: Text(
LocaleKeys.settings_appearance_members_user.tr(), LocaleKeys.settings_appearance_members_user.tr(),
style: theme.textStyle.body.standard( style: theme.textStyle.body.standard(
@ -326,6 +328,7 @@ class _MemberListHeader extends StatelessWidget {
), ),
), ),
Expanded( Expanded(
flex: 2,
child: Text( child: Text(
LocaleKeys.settings_appearance_members_role.tr(), LocaleKeys.settings_appearance_members_role.tr(),
style: theme.textStyle.body.standard( style: theme.textStyle.body.standard(
@ -334,6 +337,7 @@ class _MemberListHeader extends StatelessWidget {
), ),
), ),
Expanded( Expanded(
flex: 3,
child: Text( child: Text(
LocaleKeys.settings_accountPage_email_title.tr(), LocaleKeys.settings_accountPage_email_title.tr(),
style: theme.textStyle.body.standard( style: theme.textStyle.body.standard(
@ -364,14 +368,39 @@ class _MemberItem extends StatelessWidget {
return Row( return Row(
children: [ children: [
Expanded( Expanded(
child: Text( flex: 4,
child: Row(
children: [
UserAvatar(
iconUrl: member.avatarUrl,
name: member.name,
size: 24,
fontSize: 12,
emojiFontSize: 20,
),
HSpace(8),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
member.name, member.name,
style: theme.textStyle.body.enhanced( style: theme.textStyle.body.enhanced(
color: theme.textColorScheme.primary, color: theme.textColorScheme.primary,
), ),
), ),
Text(
_formatJoinedDate(member.joinedAt.toInt()),
style: theme.textStyle.caption.standard(
color: theme.textColorScheme.secondary,
),
),
],
),
],
),
), ),
Expanded( Expanded(
flex: 2,
child: member.role.isOwner || !myRole.canUpdate child: member.role.isOwner || !myRole.canUpdate
? Text( ? Text(
member.role.description, member.role.description,
@ -384,6 +413,7 @@ class _MemberItem extends StatelessWidget {
), ),
), ),
Expanded( Expanded(
flex: 3,
child: FlowyTooltip( child: FlowyTooltip(
message: member.email, message: member.email,
child: Text( child: Text(
@ -403,6 +433,11 @@ class _MemberItem extends StatelessWidget {
], ],
); );
} }
String _formatJoinedDate(int joinedAt) {
final date = DateTime.fromMillisecondsSinceEpoch(joinedAt * 1000);
return 'Joined on ${DateFormat('MMM d, y').format(date)}';
}
} }
enum _MemberMoreAction { enum _MemberMoreAction {
@ -428,7 +463,7 @@ class _MemberMoreActionList extends StatelessWidget {
return FlowyButton( return FlowyButton(
useIntrinsicWidth: true, useIntrinsicWidth: true,
text: const FlowySvg( text: const FlowySvg(
FlowySvgs.three_dots_vertical_s, FlowySvgs.three_dots_s,
), ),
onTap: () { onTap: () {
controller.show(); controller.show();

View File

@ -4,6 +4,7 @@ import 'package:appflowy/shared/feature_flags.dart';
import 'package:appflowy/workspace/application/settings/settings_dialog_bloc.dart'; import 'package:appflowy/workspace/application/settings/settings_dialog_bloc.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/settings_menu_element.dart'; import 'package:appflowy/workspace/presentation/settings/widgets/settings_menu_element.dart';
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart'; import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
import 'package:appflowy_ui/appflowy_ui.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
@ -25,28 +26,27 @@ class SettingsMenu extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final theme = AppFlowyTheme.of(context);
// Column > Expanded for full size no matter the content // Column > Expanded for full size no matter the content
return Column( return Column(
children: [ children: [
Expanded( Expanded(
child: Container( child: DecoratedBox(
padding: const EdgeInsets.symmetric(vertical: 8) +
const EdgeInsets.only(left: 8, right: 4),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surfaceContainerHighest, color: theme.backgroundColorScheme.secondary,
borderRadius: const BorderRadiusDirectional.only( borderRadius: const BorderRadiusDirectional.only(
topStart: Radius.circular(8), topStart: Radius.circular(8),
bottomStart: Radius.circular(8), bottomStart: Radius.circular(8),
), ),
), ),
child: SingleChildScrollView( child: SingleChildScrollView(
// Right padding is added to make the scrollbar centered padding: EdgeInsets.symmetric(
// in the space between the menu and the content vertical: 24,
padding: const EdgeInsets.only(right: 4) + horizontal: theme.spacing.l,
const EdgeInsets.symmetric(vertical: 16), ),
physics: const ClampingScrollPhysics(), physics: const ClampingScrollPhysics(),
child: SeparatedColumn( child: SeparatedColumn(
separatorBuilder: () => const VSpace(16), separatorBuilder: () => VSpace(theme.spacing.xs),
children: [ children: [
SettingsMenuElement( SettingsMenuElement(
page: SettingsPage.account, page: SettingsPage.account,

View File

@ -1,8 +1,6 @@
import 'package:appflowy/workspace/application/settings/settings_dialog_bloc.dart'; import 'package:appflowy/workspace/application/settings/settings_dialog_bloc.dart';
import 'package:flowy_infra/size.dart'; import 'package:appflowy_ui/appflowy_ui.dart';
import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra_ui/widget/spacing.dart';
import 'package:flowy_infra_ui/style_widget/hover.dart';
import 'package:flowy_infra_ui/style_widget/text.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class SettingsMenuElement extends StatelessWidget { class SettingsMenuElement extends StatelessWidget {
@ -23,42 +21,67 @@ class SettingsMenuElement extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return FlowyHover( final theme = AppFlowyTheme.of(context);
isSelected: () => page == selectedPage, return AFBaseButton(
resetHoverOnRebuild: false,
style: HoverStyle(
hoverColor: AFThemeExtension.of(context).greyHover,
borderRadius: BorderRadius.circular(4),
),
builder: (_, isHovering) => ListTile(
dense: true,
leading: iconWidget(
isHovering || page == selectedPage
? Theme.of(context).colorScheme.onSurface
: AFThemeExtension.of(context).textColor,
),
onTap: () => changeSelectedPage(page), onTap: () => changeSelectedPage(page),
selected: page == selectedPage, padding: EdgeInsets.all(theme.spacing.m),
selectedColor: Theme.of(context).colorScheme.onSurface, borderRadius: theme.borderRadius.m,
selectedTileColor: Theme.of(context).colorScheme.primary, borderColor: (_, __, ___, ____) => theme.fillColorScheme.transparent,
shape: RoundedRectangleBorder( backgroundColor: (_, isHovering, __) {
borderRadius: BorderRadius.circular(5), if (isHovering) {
), return theme.fillColorScheme.primaryAlpha5;
minLeadingWidth: 0, } else if (page == selectedPage) {
title: FlowyText.medium( return theme.fillColorScheme.themeSelect;
}
return theme.fillColorScheme.transparent;
},
builder: (_, __, ___) {
return Row(
children: [
icon,
HSpace(theme.spacing.m),
Text(
label, label,
fontSize: FontSizes.s14, style: theme.textStyle.body.standard(
overflow: TextOverflow.ellipsis, color: theme.textColorScheme.primary,
color: page == selectedPage
? Theme.of(context).colorScheme.onSurface
: null,
), ),
), ),
],
);
},
); );
} }
Widget iconWidget(Color color) => IconTheme( // return FlowyHover(
data: IconThemeData(color: color), // isSelected: () => page == selectedPage,
child: icon, // resetHoverOnRebuild: false,
); // style: HoverStyle(
// hoverColor: AFThemeExtension.of(context).greyHover,
// borderRadius: BorderRadius.circular(4),
// ),
// builder: (_, isHovering) => ListTile(
// dense: true,
// leading: iconWidget(
// isHovering || page == selectedPage
// ? Theme.of(context).colorScheme.onSurface
// : AFThemeExtension.of(context).textColor,
// ),
// onTap: () => changeSelectedPage(page),
// selected: page == selectedPage,
// selectedColor: Theme.of(context).colorScheme.onSurface,
// selectedTileColor: Theme.of(context).colorScheme.primary,
// shape: RoundedRectangleBorder(
// borderRadius: BorderRadius.circular(5),
// ),
// minLeadingWidth: 0,
// title: FlowyText.medium(
// label,
// fontSize: FontSizes.s14,
// overflow: TextOverflow.ellipsis,
// color: page == selectedPage
// ? Theme.of(context).colorScheme.onSurface
// : null,
// ),
// ),
// );
} }

View File

@ -17,12 +17,14 @@ class UserAvatar extends StatelessWidget {
required this.fontSize, required this.fontSize,
this.isHovering = false, this.isHovering = false,
this.decoration, this.decoration,
this.emojiFontSize,
}); });
final String iconUrl; final String iconUrl;
final String name; final String name;
final double size; final double size;
final double fontSize; final double fontSize;
final double? emojiFontSize;
final Decoration? decoration; final Decoration? decoration;
// If true, a border will be applied on top of the avatar // If true, a border will be applied on top of the avatar
@ -127,7 +129,10 @@ class UserAvatar extends StatelessWidget {
FlowySvgData('emoji/$iconUrl'), FlowySvgData('emoji/$iconUrl'),
blendMode: null, blendMode: null,
) )
: FlowyText.emoji(iconUrl, fontSize: fontSize), : FlowyText.emoji(
iconUrl,
fontSize: emojiFontSize ?? fontSize,
),
), ),
), ),
), ),

View File

@ -4,7 +4,7 @@ description: Bring projects, wikis, and teams together with AI. AppFlowy is an
your data. The best open source alternative to Notion. your data. The best open source alternative to Notion.
publish_to: "none" publish_to: "none"
version: 0.8.9 version: 0.9.0
environment: environment:
flutter: ">=3.27.4" flutter: ">=3.27.4"

View File

@ -1353,7 +1353,7 @@
"generateANewLink": "generate a new link", "generateANewLink": "generate a new link",
"inviteMemberByEmail": "Invite member by email", "inviteMemberByEmail": "Invite member by email",
"inviteMemberHintText": "Invite by email", "inviteMemberHintText": "Invite by email",
"resetInviteLink": "Reset the invite link", "resetInviteLink": "Reset the invite link?",
"resetInviteLinkDescription": "Resetting will deactivate the current link for all space members and generate a new one. The previous link can only be managed through the", "resetInviteLinkDescription": "Resetting will deactivate the current link for all space members and generate a new one. The previous link can only be managed through the",
"adminPanel": "Admin Panel", "adminPanel": "Admin Panel",
"reset": "Reset", "reset": "Reset",

View File

@ -493,7 +493,7 @@ checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f"
[[package]] [[package]]
name = "app-error" name = "app-error"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6c6f1c5cc3ce2161c247f63f6132361da8dc0a32#6c6f1c5cc3ce2161c247f63f6132361da8dc0a32" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=954c323#954c32332487f5e17a7fb5be0bc339db1cb00e17"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bincode", "bincode",
@ -513,7 +513,7 @@ dependencies = [
[[package]] [[package]]
name = "appflowy-ai-client" name = "appflowy-ai-client"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6c6f1c5cc3ce2161c247f63f6132361da8dc0a32#6c6f1c5cc3ce2161c247f63f6132361da8dc0a32" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=954c323#954c32332487f5e17a7fb5be0bc339db1cb00e17"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bytes", "bytes",
@ -1159,7 +1159,7 @@ dependencies = [
[[package]] [[package]]
name = "client-api" name = "client-api"
version = "0.2.0" version = "0.2.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6c6f1c5cc3ce2161c247f63f6132361da8dc0a32#6c6f1c5cc3ce2161c247f63f6132361da8dc0a32" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=954c323#954c32332487f5e17a7fb5be0bc339db1cb00e17"
dependencies = [ dependencies = [
"again", "again",
"anyhow", "anyhow",
@ -1214,7 +1214,7 @@ dependencies = [
[[package]] [[package]]
name = "client-api-entity" name = "client-api-entity"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6c6f1c5cc3ce2161c247f63f6132361da8dc0a32#6c6f1c5cc3ce2161c247f63f6132361da8dc0a32" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=954c323#954c32332487f5e17a7fb5be0bc339db1cb00e17"
dependencies = [ dependencies = [
"collab-entity", "collab-entity",
"collab-rt-entity", "collab-rt-entity",
@ -1227,7 +1227,7 @@ dependencies = [
[[package]] [[package]]
name = "client-websocket" name = "client-websocket"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6c6f1c5cc3ce2161c247f63f6132361da8dc0a32#6c6f1c5cc3ce2161c247f63f6132361da8dc0a32" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=954c323#954c32332487f5e17a7fb5be0bc339db1cb00e17"
dependencies = [ dependencies = [
"futures-channel", "futures-channel",
"futures-util", "futures-util",
@ -1499,7 +1499,7 @@ dependencies = [
[[package]] [[package]]
name = "collab-rt-entity" name = "collab-rt-entity"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6c6f1c5cc3ce2161c247f63f6132361da8dc0a32#6c6f1c5cc3ce2161c247f63f6132361da8dc0a32" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=954c323#954c32332487f5e17a7fb5be0bc339db1cb00e17"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bincode", "bincode",
@ -1521,7 +1521,7 @@ dependencies = [
[[package]] [[package]]
name = "collab-rt-protocol" name = "collab-rt-protocol"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6c6f1c5cc3ce2161c247f63f6132361da8dc0a32#6c6f1c5cc3ce2161c247f63f6132361da8dc0a32" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=954c323#954c32332487f5e17a7fb5be0bc339db1cb00e17"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
@ -1969,7 +1969,7 @@ checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308"
[[package]] [[package]]
name = "database-entity" name = "database-entity"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6c6f1c5cc3ce2161c247f63f6132361da8dc0a32#6c6f1c5cc3ce2161c247f63f6132361da8dc0a32" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=954c323#954c32332487f5e17a7fb5be0bc339db1cb00e17"
dependencies = [ dependencies = [
"bincode", "bincode",
"bytes", "bytes",
@ -3426,7 +3426,7 @@ dependencies = [
[[package]] [[package]]
name = "gotrue" name = "gotrue"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6c6f1c5cc3ce2161c247f63f6132361da8dc0a32#6c6f1c5cc3ce2161c247f63f6132361da8dc0a32" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=954c323#954c32332487f5e17a7fb5be0bc339db1cb00e17"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"getrandom 0.2.10", "getrandom 0.2.10",
@ -3441,7 +3441,7 @@ dependencies = [
[[package]] [[package]]
name = "gotrue-entity" name = "gotrue-entity"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6c6f1c5cc3ce2161c247f63f6132361da8dc0a32#6c6f1c5cc3ce2161c247f63f6132361da8dc0a32" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=954c323#954c32332487f5e17a7fb5be0bc339db1cb00e17"
dependencies = [ dependencies = [
"app-error", "app-error",
"jsonwebtoken", "jsonwebtoken",
@ -4065,7 +4065,7 @@ dependencies = [
[[package]] [[package]]
name = "infra" name = "infra"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6c6f1c5cc3ce2161c247f63f6132361da8dc0a32#6c6f1c5cc3ce2161c247f63f6132361da8dc0a32" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=954c323#954c32332487f5e17a7fb5be0bc339db1cb00e17"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bytes", "bytes",
@ -6643,7 +6643,7 @@ dependencies = [
[[package]] [[package]]
name = "shared-entity" name = "shared-entity"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=6c6f1c5cc3ce2161c247f63f6132361da8dc0a32#6c6f1c5cc3ce2161c247f63f6132361da8dc0a32" source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=954c323#954c32332487f5e17a7fb5be0bc339db1cb00e17"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"app-error", "app-error",

View File

@ -108,8 +108,8 @@ af-local-ai = { version = "0.1" }
# Run the script.add_workspace_members: # Run the script.add_workspace_members:
# scripts/tool/update_client_api_rev.sh new_rev_id # scripts/tool/update_client_api_rev.sh new_rev_id
# ⚠️⚠️⚠️️ # ⚠️⚠️⚠️️
client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "6c6f1c5cc3ce2161c247f63f6132361da8dc0a32" } client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "954c323" }
client-api-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "6c6f1c5cc3ce2161c247f63f6132361da8dc0a32" } client-api-entity = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "954c323" }
[profile.dev] [profile.dev]
opt-level = 0 opt-level = 0

View File

@ -71,6 +71,7 @@ pub fn from_af_workspace_member(member: AFWorkspaceMember) -> WorkspaceMember {
role: from_af_role(member.role), role: from_af_role(member.role),
name: member.name, name: member.name,
avatar_url: member.avatar_url, avatar_url: member.avatar_url,
joined_at: member.joined_at.map(|dt| dt.timestamp()),
} }
} }

View File

@ -248,6 +248,7 @@ impl UserCloudService for LocalServerUserServiceImpl {
uid, uid,
workspace_id: workspace_id.to_string(), workspace_id: workspace_id.to_string(),
updated_at: chrono::Utc::now().naive_utc(), updated_at: chrono::Utc::now().naive_utc(),
joined_at: None,
}; };
let member = WorkspaceMember::from(row.clone()); let member = WorkspaceMember::from(row.clone());

View File

@ -0,0 +1,3 @@
-- This file should undo anything in `up.sql`
ALTER TABLE workspace_members_table
DROP COLUMN joined_at;

View File

@ -0,0 +1,3 @@
-- Your SQL goes here
ALTER TABLE workspace_members_table
ADD COLUMN joined_at BIGINT DEFAULT NULL;

View File

@ -120,6 +120,7 @@ diesel::table! {
uid -> BigInt, uid -> BigInt,
workspace_id -> Text, workspace_id -> Text,
updated_at -> Timestamp, updated_at -> Timestamp,
joined_at -> Nullable<BigInt>,
} }
} }

View File

@ -370,6 +370,7 @@ pub struct WorkspaceMember {
pub role: Role, pub role: Role,
pub name: String, pub name: String,
pub avatar_url: Option<String>, pub avatar_url: Option<String>,
pub joined_at: Option<i64>,
} }
/// represent the user awareness object id for the workspace. /// represent the user awareness object id for the workspace.

View File

@ -16,6 +16,7 @@ pub struct WorkspaceMemberTable {
pub uid: i64, pub uid: i64,
pub workspace_id: String, pub workspace_id: String,
pub updated_at: chrono::NaiveDateTime, pub updated_at: chrono::NaiveDateTime,
pub joined_at: Option<i64>,
} }
impl From<WorkspaceMemberTable> for WorkspaceMember { impl From<WorkspaceMemberTable> for WorkspaceMember {
@ -25,6 +26,7 @@ impl From<WorkspaceMemberTable> for WorkspaceMember {
role: Role::from(value.role), role: Role::from(value.role),
name: value.name, name: value.name,
avatar_url: value.avatar_url, avatar_url: value.avatar_url,
joined_at: value.joined_at,
} }
} }
} }

View File

@ -111,6 +111,7 @@ pub fn insert_local_workspace(
uid, uid,
workspace_id: workspace_id.to_string(), workspace_id: workspace_id.to_string(),
updated_at: chrono::Utc::now().naive_utc(), updated_at: chrono::Utc::now().naive_utc(),
joined_at: None,
}; };
upsert_user_workspace(uid, AuthType::Local, user_workspace.clone(), conn)?; upsert_user_workspace(uid, AuthType::Local, user_workspace.clone(), conn)?;

View File

@ -24,6 +24,9 @@ pub struct WorkspaceMemberPB {
#[pb(index = 4, one_of)] #[pb(index = 4, one_of)]
pub avatar_url: Option<String>, pub avatar_url: Option<String>,
#[pb(index = 5, one_of)]
pub joined_at: Option<i64>,
} }
impl From<WorkspaceMember> for WorkspaceMemberPB { impl From<WorkspaceMember> for WorkspaceMemberPB {
@ -33,6 +36,7 @@ impl From<WorkspaceMember> for WorkspaceMemberPB {
name: value.name, name: value.name,
role: value.role.into(), role: value.role.into(),
avatar_url: value.avatar_url, avatar_url: value.avatar_url,
joined_at: value.joined_at,
} }
} }
} }

View File

@ -673,6 +673,7 @@ impl UserManager {
role: member_record.role.into(), role: member_record.role.into(),
name: member_record.name, name: member_record.name,
avatar_url: member_record.avatar_url, avatar_url: member_record.avatar_url,
joined_at: member_record.joined_at,
}); });
} }
@ -703,6 +704,7 @@ impl UserManager {
uid, uid,
workspace_id: workspace_id.to_string(), workspace_id: workspace_id.to_string(),
updated_at: Utc::now().naive_utc(), updated_at: Utc::now().naive_utc(),
joined_at: member.joined_at,
}; };
let mut db = self.authenticate_user.get_sqlite_connection(uid)?; let mut db = self.authenticate_user.get_sqlite_connection(uid)?;