feat: support inviting members on mobile (#5986)

* feat: support inviting members on mobile

* feat: support workspace member list on mobile

* feat: support leave workspace on mobile

* chore: adjust member list ui

* fix: flutter analyze
This commit is contained in:
Lucas.Xu 2024-08-17 11:04:43 +08:00 committed by GitHub
parent d3d929b68e
commit e6bf6a5c7d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 610 additions and 36 deletions

View File

@ -63,8 +63,6 @@ PODS:
- FlutterMacOS - FlutterMacOS
- permission_handler_apple (9.3.0): - permission_handler_apple (9.3.0):
- Flutter - Flutter
- printing (1.0.0):
- Flutter
- ReachabilitySwift (5.0.0) - ReachabilitySwift (5.0.0)
- SDWebImage (5.14.2): - SDWebImage (5.14.2):
- SDWebImage/Core (= 5.14.2) - SDWebImage/Core (= 5.14.2)
@ -105,7 +103,6 @@ DEPENDENCIES:
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`) - permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
- printing (from `.symlinks/plugins/printing/ios`)
- sentry_flutter (from `.symlinks/plugins/sentry_flutter/ios`) - sentry_flutter (from `.symlinks/plugins/sentry_flutter/ios`)
- share_plus (from `.symlinks/plugins/share_plus/ios`) - share_plus (from `.symlinks/plugins/share_plus/ios`)
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
@ -154,8 +151,6 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/path_provider_foundation/darwin" :path: ".symlinks/plugins/path_provider_foundation/darwin"
permission_handler_apple: permission_handler_apple:
:path: ".symlinks/plugins/permission_handler_apple/ios" :path: ".symlinks/plugins/permission_handler_apple/ios"
printing:
:path: ".symlinks/plugins/printing/ios"
sentry_flutter: sentry_flutter:
:path: ".symlinks/plugins/sentry_flutter/ios" :path: ".symlinks/plugins/sentry_flutter/ios"
share_plus: share_plus:
@ -187,7 +182,6 @@ SPEC CHECKSUMS:
package_info_plus: 58f0028419748fad15bf008b270aaa8e54380b1c package_info_plus: 58f0028419748fad15bf008b270aaa8e54380b1c
path_provider_foundation: 3784922295ac71e43754bd15e0653ccfd36a147c path_provider_foundation: 3784922295ac71e43754bd15e0653ccfd36a147c
permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2 permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2
printing: 233e1b73bd1f4a05615548e9b5a324c98588640b
ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825 ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825
SDWebImage: b9a731e1d6307f44ca703b3976d18c24ca561e84 SDWebImage: b9a731e1d6307f44ca703b3976d18c24ca561e84
Sentry: 8560050221424aef0bebc8e31eedf00af80f90a6 Sentry: 8560050221424aef0bebc8e31eedf00af80f90a6

View File

@ -116,12 +116,18 @@ class _MobileViewItemBottomSheetState extends State<MobileViewItemBottomSheet> {
Future<void> _showConfirmDialog({required VoidCallback onDelete}) async { Future<void> _showConfirmDialog({required VoidCallback onDelete}) async {
await showFlowyCupertinoConfirmDialog( await showFlowyCupertinoConfirmDialog(
title: LocaleKeys.sideBar_removePageFromRecent.tr(), title: LocaleKeys.sideBar_removePageFromRecent.tr(),
leftButton: FlowyText.regular( leftButton: FlowyText(
LocaleKeys.button_cancel.tr(), LocaleKeys.button_cancel.tr(),
color: const Color(0xFF1456F0), fontSize: 17.0,
figmaLineHeight: 24.0,
fontWeight: FontWeight.w500,
color: const Color(0xFF007AFF),
), ),
rightButton: FlowyText.medium( rightButton: FlowyText(
LocaleKeys.button_delete.tr(), LocaleKeys.button_delete.tr(),
fontSize: 17.0,
figmaLineHeight: 24.0,
fontWeight: FontWeight.w400,
color: const Color(0xFFFE0220), color: const Color(0xFFFE0220),
), ),
onRightButtonPressed: (context) { onRightButtonPressed: (context) {

View File

@ -5,6 +5,7 @@ import 'package:appflowy/mobile/presentation/base/app_bar/app_bar.dart';
import 'package:appflowy/mobile/presentation/presentation.dart'; import 'package:appflowy/mobile/presentation/presentation.dart';
import 'package:appflowy/mobile/presentation/setting/cloud/cloud_setting_group.dart'; import 'package:appflowy/mobile/presentation/setting/cloud/cloud_setting_group.dart';
import 'package:appflowy/mobile/presentation/setting/user_session_setting_group.dart'; import 'package:appflowy/mobile/presentation/setting/user_session_setting_group.dart';
import 'package:appflowy/mobile/presentation/setting/workspace/workspace_setting_group.dart';
import 'package:appflowy/mobile/presentation/widgets/widgets.dart'; import 'package:appflowy/mobile/presentation/widgets/widgets.dart';
import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/user/application/auth/auth_service.dart'; import 'package:appflowy/user/application/auth/auth_service.dart';
@ -79,8 +80,7 @@ class _MobileHomeSettingPageState extends State<MobileHomeSettingPage> {
PersonalInfoSettingGroup( PersonalInfoSettingGroup(
userProfile: userProfile, userProfile: userProfile,
), ),
// TODO: Enable and implement along with Push Notifications const WorkspaceSettingGroup(),
// const NotificationsSettingGroup(),
const AppearanceSettingGroup(), const AppearanceSettingGroup(),
const LanguageSettingGroup(), const LanguageSettingGroup(),
if (Env.enableCustomCloud) const CloudSettingGroup(), if (Env.enableCustomCloud) const CloudSettingGroup(),

View File

@ -6,9 +6,12 @@ import 'package:appflowy/mobile/presentation/home/recent_folder/recent_space.dar
import 'package:appflowy/mobile/presentation/home/tab/_tab_bar.dart'; import 'package:appflowy/mobile/presentation/home/tab/_tab_bar.dart';
import 'package:appflowy/mobile/presentation/home/tab/space_order_bloc.dart'; import 'package:appflowy/mobile/presentation/home/tab/space_order_bloc.dart';
import 'package:appflowy/mobile/presentation/presentation.dart'; import 'package:appflowy/mobile/presentation/presentation.dart';
import 'package:appflowy/mobile/presentation/setting/workspace/invite_members_screen.dart';
import 'package:appflowy/workspace/application/menu/sidebar_sections_bloc.dart'; import 'package:appflowy/workspace/application/menu/sidebar_sections_bloc.dart';
import 'package:appflowy/workspace/application/sidebar/folder/folder_bloc.dart'; import 'package:appflowy/workspace/application/sidebar/folder/folder_bloc.dart';
import 'package:appflowy/workspace/application/sidebar/space/space_bloc.dart'; import 'package:appflowy/workspace/application/sidebar/space/space_bloc.dart';
import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart';
import 'package:appflowy_backend/log.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart'; import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
@ -38,6 +41,7 @@ class _MobileSpaceTabState extends State<MobileSpaceTab>
super.initState(); super.initState();
mobileCreateNewPageNotifier.addListener(_createNewPage); mobileCreateNewPageNotifier.addListener(_createNewPage);
mobileLeaveWorkspaceNotifier.addListener(_leaveWorkspace);
} }
@override @override
@ -45,6 +49,7 @@ class _MobileSpaceTabState extends State<MobileSpaceTab>
tabController?.removeListener(_onTabChange); tabController?.removeListener(_onTabChange);
tabController?.dispose(); tabController?.dispose();
mobileCreateNewPageNotifier.removeListener(_createNewPage); mobileCreateNewPageNotifier.removeListener(_createNewPage);
mobileLeaveWorkspaceNotifier.removeListener(_leaveWorkspace);
super.dispose(); super.dispose();
} }
@ -171,4 +176,16 @@ class _MobileSpaceTabState extends State<MobileSpaceTab>
); );
} }
} }
void _leaveWorkspace() {
final workspaceId =
context.read<UserWorkspaceBloc>().state.currentWorkspace?.workspaceId;
if (workspaceId == null) {
Log.error('Workspace ID is null');
return;
}
context
.read<UserWorkspaceBloc>()
.add(UserWorkspaceEvent.leaveWorkspace(workspaceId));
}
} }

View File

@ -42,14 +42,24 @@ class UserSessionSettingGroup extends StatelessWidget {
MobileSignInOrLogoutButton( MobileSignInOrLogoutButton(
labelText: LocaleKeys.settings_menu_logout.tr(), labelText: LocaleKeys.settings_menu_logout.tr(),
onPressed: () async { onPressed: () async {
await showFlowyMobileConfirmDialog( await showFlowyCupertinoConfirmDialog(
context, title: LocaleKeys.settings_menu_logoutPrompt.tr(),
content: FlowyText( leftButton: FlowyText(
LocaleKeys.settings_menu_logoutPrompt.tr(), LocaleKeys.button_cancel.tr(),
fontSize: 17.0,
figmaLineHeight: 24.0,
fontWeight: FontWeight.w500,
color: const Color(0xFF007AFF),
), ),
actionButtonTitle: LocaleKeys.button_yes.tr(), rightButton: FlowyText(
actionButtonColor: Theme.of(context).colorScheme.error, LocaleKeys.button_logout.tr(),
onActionButtonPressed: () async { fontSize: 17.0,
figmaLineHeight: 24.0,
fontWeight: FontWeight.w400,
color: const Color(0xFFFE0220),
),
onRightButtonPressed: (context) async {
Navigator.of(context).pop();
await getIt<AuthService>().signOut(); await getIt<AuthService>().signOut();
await runAppFlowy(); await runAppFlowy();
}, },

View File

@ -0,0 +1,329 @@
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/presentation/base/app_bar/app_bar.dart';
import 'package:appflowy/mobile/presentation/widgets/show_flowy_mobile_confirm_dialog.dart';
import 'package:appflowy/shared/af_role_pb_extension.dart';
import 'package:appflowy/user/application/user_service.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/members/workspace_member_bloc.dart';
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
import 'package:appflowy_backend/log.dart';
import 'package:appflowy_backend/protobuf/flowy-error/code.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
import 'package:appflowy_result/appflowy_result.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:string_validator/string_validator.dart';
import 'package:toastification/toastification.dart';
import 'member_list.dart';
ValueNotifier<int> mobileLeaveWorkspaceNotifier = ValueNotifier(0);
class InviteMembersScreen extends StatelessWidget {
const InviteMembersScreen({
super.key,
});
static const routeName = '/invite_member';
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: FlowyAppBar(
titleText: LocaleKeys.settings_appearance_members_label.tr(),
),
body: const _InviteMemberPage(),
);
}
}
class _InviteMemberPage extends StatefulWidget {
const _InviteMemberPage();
@override
State<_InviteMemberPage> createState() => _InviteMemberPageState();
}
class _InviteMemberPageState extends State<_InviteMemberPage> {
final emailController = TextEditingController();
late final Future<UserProfilePB?> userProfile;
@override
void initState() {
super.initState();
userProfile = UserBackendService.getCurrentUserProfile().fold(
(s) => s,
(f) => null,
);
}
@override
void dispose() {
emailController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return FutureBuilder(
future: userProfile,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const SizedBox.shrink();
}
if (snapshot.hasError || snapshot.data == null) {
return _buildError(context);
}
final userProfile = snapshot.data!;
return BlocProvider<WorkspaceMemberBloc>(
create: (context) => WorkspaceMemberBloc(userProfile: userProfile)
..add(const WorkspaceMemberEvent.initial()),
child: BlocConsumer<WorkspaceMemberBloc, WorkspaceMemberState>(
listener: _onListener,
builder: (context, state) {
return Column(
children: [
Expanded(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
if (state.myRole.isOwner) ...[
Padding(
padding: const EdgeInsets.all(16.0),
child: _buildInviteMemberArea(context),
),
const VSpace(16),
],
if (state.members.isNotEmpty) ...[
const VSpace(8),
MobileMemberList(
members: state.members,
userProfile: userProfile,
myRole: state.myRole,
),
],
],
),
),
if (state.myRole.isMember) const _LeaveWorkspaceButton(),
const VSpace(48),
],
);
},
),
);
},
);
}
Widget _buildInviteMemberArea(BuildContext context) {
return Column(
children: [
TextFormField(
autofocus: true,
controller: emailController,
keyboardType: TextInputType.text,
decoration: InputDecoration(
hintText: LocaleKeys.settings_appearance_members_inviteHint.tr(),
),
),
const VSpace(16),
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: () => _inviteMember(context),
child: Text(
LocaleKeys.settings_appearance_members_sendInvite.tr(),
),
),
),
],
);
}
Widget _buildError(BuildContext context) {
return Center(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 48.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
FlowyText.medium(
LocaleKeys.settings_appearance_members_workspaceMembersError.tr(),
fontSize: 18.0,
textAlign: TextAlign.center,
),
const VSpace(8.0),
FlowyText.regular(
LocaleKeys
.settings_appearance_members_workspaceMembersErrorDescription
.tr(),
fontSize: 17.0,
maxLines: 10,
textAlign: TextAlign.center,
lineHeight: 1.3,
color: Theme.of(context).hintColor,
),
],
),
),
);
}
void _onListener(BuildContext context, WorkspaceMemberState state) {
final actionResult = state.actionResult;
if (actionResult == null) {
return;
}
final actionType = actionResult.actionType;
final result = actionResult.result;
// only show the result dialog when the action is WorkspaceMemberActionType.add
if (actionType == WorkspaceMemberActionType.add) {
result.fold(
(s) {
showToastNotification(
context,
message:
LocaleKeys.settings_appearance_members_addMemberSuccess.tr(),
);
},
(f) {
Log.error('add workspace member failed: $f');
final message = f.code == ErrorCode.WorkspaceMemberLimitExceeded
? LocaleKeys.settings_appearance_members_memberLimitExceeded.tr()
: LocaleKeys.settings_appearance_members_failedToAddMember.tr();
showToastNotification(
context,
type: ToastificationType.error,
message: message,
);
},
);
} else if (actionType == WorkspaceMemberActionType.invite) {
result.fold(
(s) {
showToastNotification(
context,
message:
LocaleKeys.settings_appearance_members_inviteMemberSuccess.tr(),
);
},
(f) {
Log.error('invite workspace member failed: $f');
final message = f.code == ErrorCode.WorkspaceMemberLimitExceeded
? LocaleKeys.settings_appearance_members_inviteFailedMemberLimit
.tr()
: LocaleKeys.settings_appearance_members_failedToInviteMember
.tr();
showToastNotification(
context,
type: ToastificationType.error,
message: message,
);
},
);
} else if (actionType == WorkspaceMemberActionType.remove) {
result.fold(
(s) {
showToastNotification(
context,
message: LocaleKeys
.settings_appearance_members_removeFromWorkspaceSuccess
.tr(),
);
},
(f) {
showToastNotification(
context,
type: ToastificationType.error,
message: LocaleKeys
.settings_appearance_members_removeFromWorkspaceFailed
.tr(),
);
},
);
}
}
void _inviteMember(BuildContext context) {
final email = emailController.text;
if (!isEmail(email)) {
return showToastNotification(
context,
message: LocaleKeys.settings_appearance_members_emailInvalidError.tr(),
);
}
context
.read<WorkspaceMemberBloc>()
.add(WorkspaceMemberEvent.inviteWorkspaceMember(email));
// clear the email field after inviting
emailController.clear();
}
}
class _LeaveWorkspaceButton extends StatelessWidget {
const _LeaveWorkspaceButton();
@override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
margin: const EdgeInsets.symmetric(horizontal: 16),
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.transparent,
foregroundColor: Theme.of(context).colorScheme.error,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(4),
side: BorderSide(
color: Theme.of(context).colorScheme.error,
width: 0.5,
),
),
),
onPressed: () => _leaveWorkspace(context),
child: FlowyText(
LocaleKeys.workspace_leaveCurrentWorkspace.tr(),
fontSize: 14.0,
color: Theme.of(context).colorScheme.error,
fontWeight: FontWeight.w500,
),
),
);
}
void _leaveWorkspace(BuildContext context) {
showFlowyCupertinoConfirmDialog(
title: LocaleKeys.workspace_leaveCurrentWorkspacePrompt.tr(),
leftButton: FlowyText(
LocaleKeys.button_cancel.tr(),
fontSize: 17.0,
figmaLineHeight: 24.0,
fontWeight: FontWeight.w500,
color: const Color(0xFF007AFF),
),
rightButton: FlowyText(
LocaleKeys.button_confirm.tr(),
fontSize: 17.0,
figmaLineHeight: 24.0,
fontWeight: FontWeight.w400,
color: const Color(0xFFFE0220),
),
onRightButtonPressed: (buttonContext) async {
// try to use popUntil with a specific route name but failed
// so use pop twice as a workaround
Navigator.of(buttonContext).pop();
Navigator.of(context).pop();
Navigator.of(context).pop();
mobileLeaveWorkspaceNotifier.value =
mobileLeaveWorkspaceNotifier.value + 1;
},
);
}
}

View File

@ -0,0 +1,164 @@
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
import 'package:appflowy/mobile/presentation/widgets/widgets.dart';
import 'package:appflowy/shared/af_role_pb_extension.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/members/workspace_member_bloc.dart';
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_slidable/flutter_slidable.dart';
class MobileMemberList extends StatelessWidget {
const MobileMemberList({
super.key,
required this.members,
required this.myRole,
required this.userProfile,
});
final List<WorkspaceMemberPB> members;
final AFRolePB myRole;
final UserProfilePB userProfile;
@override
Widget build(BuildContext context) {
return SlidableAutoCloseBehavior(
child: SeparatedColumn(
crossAxisAlignment: CrossAxisAlignment.start,
separatorBuilder: () => const FlowyDivider(
padding: EdgeInsets.symmetric(horizontal: 16.0),
),
children: [
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 16.0,
vertical: 8.0,
),
child: FlowyText.semibold(
LocaleKeys.settings_appearance_members_label.tr(),
fontSize: 16.0,
),
),
...members.map(
(member) => _MemberItem(
member: member,
myRole: myRole,
userProfile: userProfile,
),
),
],
),
);
}
}
class _MemberItem extends StatelessWidget {
const _MemberItem({
required this.member,
required this.myRole,
required this.userProfile,
});
final WorkspaceMemberPB member;
final AFRolePB myRole;
final UserProfilePB userProfile;
@override
Widget build(BuildContext context) {
final canDelete = myRole.canDelete && member.email != userProfile.email;
final textColor = member.role.isOwner ? Theme.of(context).hintColor : null;
Widget child = Container(
height: 48,
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Row(
children: [
Expanded(
child: FlowyText.medium(
member.name,
color: textColor,
fontSize: 15.0,
),
),
Expanded(
child: FlowyText.medium(
member.role.description,
color: textColor,
fontSize: 15.0,
textAlign: TextAlign.end,
),
),
],
),
);
if (canDelete) {
child = Slidable(
key: ValueKey(member.email),
endActionPane: ActionPane(
extentRatio: 1 / 6.0,
motion: const ScrollMotion(),
children: [
CustomSlidableAction(
backgroundColor: const Color(0xE5515563),
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(10),
bottomLeft: Radius.circular(10),
),
onPressed: (context) {
HapticFeedback.mediumImpact();
_showDeleteMenu(context);
},
padding: EdgeInsets.zero,
child: const FlowySvg(
FlowySvgs.three_dots_s,
size: Size.square(24),
color: Colors.white,
),
),
],
),
child: child,
);
}
return child;
}
void _showDeleteMenu(BuildContext context) {
final workspaceMemberBloc = context.read<WorkspaceMemberBloc>();
showMobileBottomSheet(
context,
showDragHandle: true,
showDivider: false,
useRootNavigator: true,
backgroundColor: Theme.of(context).colorScheme.surface,
builder: (context) {
return FlowyOptionTile.text(
text: LocaleKeys.settings_appearance_members_removeFromWorkspace.tr(),
height: 52.0,
textColor: Theme.of(context).colorScheme.error,
leftIcon: FlowySvg(
FlowySvgs.trash_s,
size: const Size.square(18),
color: Theme.of(context).colorScheme.error,
),
showTopBorder: false,
showBottomBorder: false,
onTap: () {
workspaceMemberBloc.add(
WorkspaceMemberEvent.removeWorkspaceMember(
member.email,
),
);
Navigator.of(context).pop();
},
);
},
);
}
}

View File

@ -0,0 +1,29 @@
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import '../widgets/widgets.dart';
import 'invite_members_screen.dart';
class WorkspaceSettingGroup extends StatelessWidget {
const WorkspaceSettingGroup({
super.key,
});
@override
Widget build(BuildContext context) {
return MobileSettingGroup(
groupTitle: LocaleKeys.settings_appearance_members_label.tr(),
settingItemList: [
MobileSettingItem(
name: LocaleKeys.settings_appearance_members_label.tr(),
trailing: const Icon(Icons.chevron_right),
onTap: () {
context.push(InviteMembersScreen.routeName);
},
),
],
);
}
}

View File

@ -98,12 +98,13 @@ Future<T?> showFlowyCupertinoConfirmDialog<T>({
}) { }) {
return showDialog( return showDialog(
context: context ?? AppGlobals.context, context: context ?? AppGlobals.context,
barrierColor: Colors.black.withOpacity(0.25),
builder: (context) => CupertinoAlertDialog( builder: (context) => CupertinoAlertDialog(
title: FlowyText.medium( title: FlowyText.medium(
title, title,
fontSize: 18, fontSize: 16,
maxLines: 10, maxLines: 10,
lineHeight: 1.3, figmaLineHeight: 22.0,
), ),
actions: [ actions: [
CupertinoDialogAction( CupertinoDialogAction(

View File

@ -410,7 +410,6 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
} }
List<SelectionMenuItem> _customSlashMenuItems() { List<SelectionMenuItem> _customSlashMenuItems() {
return [ return [
aiWriterSlashMenuItem, aiWriterSlashMenuItem,
textSlashMenuItem, textSlashMenuItem,

View File

@ -17,6 +17,7 @@ import 'package:appflowy/mobile/presentation/setting/cloud/appflowy_cloud_page.d
import 'package:appflowy/mobile/presentation/setting/font/font_picker_screen.dart'; import 'package:appflowy/mobile/presentation/setting/font/font_picker_screen.dart';
import 'package:appflowy/mobile/presentation/setting/language/language_picker_screen.dart'; import 'package:appflowy/mobile/presentation/setting/language/language_picker_screen.dart';
import 'package:appflowy/mobile/presentation/setting/launch_settings_page.dart'; import 'package:appflowy/mobile/presentation/setting/launch_settings_page.dart';
import 'package:appflowy/mobile/presentation/setting/workspace/invite_members_screen.dart';
import 'package:appflowy/plugins/base/color/color_picker_screen.dart'; import 'package:appflowy/plugins/base/color/color_picker_screen.dart';
import 'package:appflowy/plugins/base/emoji/emoji_picker_screen.dart'; import 'package:appflowy/plugins/base/emoji/emoji_picker_screen.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/code_block/code_language_screen.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/code_block/code_language_screen.dart';
@ -97,6 +98,9 @@ GoRouter generateRouter(Widget child) {
// notifications // notifications
_mobileNotificationMultiSelectPageRoute(), _mobileNotificationMultiSelectPageRoute(),
// invite members
_mobileInviteMembersPageRoute(),
], ],
// Desktop and Mobile // Desktop and Mobile
@ -198,6 +202,18 @@ GoRoute _mobileNotificationMultiSelectPageRoute() {
); );
} }
GoRoute _mobileInviteMembersPageRoute() {
return GoRoute(
parentNavigatorKey: AppGlobals.rootNavKey,
path: InviteMembersScreen.routeName,
pageBuilder: (context, state) {
return const MaterialExtendedPage(
child: InviteMembersScreen(),
);
},
);
}
GoRoute _mobileCloudSettingAppFlowyCloudPageRoute() { GoRoute _mobileCloudSettingAppFlowyCloudPageRoute() {
return GoRoute( return GoRoute(
parentNavigatorKey: AppGlobals.rootNavKey, parentNavigatorKey: AppGlobals.rootNavKey,

View File

@ -1,5 +1,3 @@
import 'package:flutter/material.dart';
import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/startup/tasks/app_widget.dart'; import 'package:appflowy/startup/tasks/app_widget.dart';
@ -13,6 +11,7 @@ import 'package:flowy_infra_ui/widget/buttons/primary_button.dart';
import 'package:flowy_infra_ui/widget/buttons/secondary_button.dart'; import 'package:flowy_infra_ui/widget/buttons/secondary_button.dart';
import 'package:flowy_infra_ui/widget/dialog/styled_dialogs.dart'; import 'package:flowy_infra_ui/widget/dialog/styled_dialogs.dart';
import 'package:flowy_infra_ui/widget/spacing.dart'; import 'package:flowy_infra_ui/widget/spacing.dart';
import 'package:flutter/material.dart';
import 'package:toastification/toastification.dart'; import 'package:toastification/toastification.dart';
export 'package:flowy_infra_ui/widget/dialog/styled_dialogs.dart'; export 'package:flowy_infra_ui/widget/dialog/styled_dialogs.dart';
@ -354,9 +353,6 @@ class _MToast extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
// only support success type
assert(type == ToastificationType.success);
return Container( return Container(
alignment: Alignment.bottomCenter, alignment: Alignment.bottomCenter,
padding: const EdgeInsets.only(bottom: 100), padding: const EdgeInsets.only(bottom: 100),
@ -369,16 +365,19 @@ class _MToast extends StatelessWidget {
child: Row( child: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
if (type == ToastificationType.success) ...[
const FlowySvg( const FlowySvg(
FlowySvgs.success_s, FlowySvgs.success_s,
blendMode: null, blendMode: null,
), ),
const HSpace(8.0), const HSpace(8.0),
],
FlowyText.regular( FlowyText.regular(
message, message,
fontSize: 16.0, fontSize: 16.0,
figmaLineHeight: 18.0, figmaLineHeight: 18.0,
color: Colors.white, color: Colors.white,
maxLines: 3,
), ),
], ],
), ),

View File

@ -4,14 +4,20 @@ import 'package:flutter/material.dart';
class FlowyDivider extends StatelessWidget { class FlowyDivider extends StatelessWidget {
const FlowyDivider({ const FlowyDivider({
super.key, super.key,
this.padding,
}); });
final EdgeInsets? padding;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Divider( return Padding(
padding: padding ?? EdgeInsets.zero,
child: Divider(
height: 1.0, height: 1.0,
thickness: 1.0, thickness: 1.0,
color: AFThemeExtension.of(context).borderColor, color: AFThemeExtension.of(context).borderColor,
),
); );
} }
} }

View File

@ -1086,6 +1086,8 @@
"user": "User", "user": "User",
"role": "Role", "role": "Role",
"removeFromWorkspace": "Remove from Workspace", "removeFromWorkspace": "Remove from Workspace",
"removeFromWorkspaceSuccess": "Remove from workspace successfully",
"removeFromWorkspaceFailed": "Remove from workspace failed",
"owner": "Owner", "owner": "Owner",
"guest": "Guest", "guest": "Guest",
"member": "Member", "member": "Member",
@ -1110,7 +1112,9 @@
"removeMember": "Remove Member", "removeMember": "Remove Member",
"areYouSureToRemoveMember": "Are you sure you want to remove this member?", "areYouSureToRemoveMember": "Are you sure you want to remove this member?",
"inviteMemberSuccess": "The invitation has been sent successfully", "inviteMemberSuccess": "The invitation has been sent successfully",
"failedToInviteMember": "Failed to invite member" "failedToInviteMember": "Failed to invite member",
"workspaceMembersError": "Oops, something went wrong",
"workspaceMembersErrorDescription": "We couldn't load the member list at this time. Please try again later"
} }
}, },
"files": { "files": {