mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
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:
parent
d3d929b68e
commit
e6bf6a5c7d
@ -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
|
||||||
|
@ -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) {
|
||||||
|
@ -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(),
|
||||||
|
@ -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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
},
|
},
|
||||||
|
@ -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;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -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(
|
||||||
|
@ -410,7 +410,6 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
List<SelectionMenuItem> _customSlashMenuItems() {
|
List<SelectionMenuItem> _customSlashMenuItems() {
|
||||||
|
|
||||||
return [
|
return [
|
||||||
aiWriterSlashMenuItem,
|
aiWriterSlashMenuItem,
|
||||||
textSlashMenuItem,
|
textSlashMenuItem,
|
||||||
|
@ -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,
|
||||||
|
@ -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,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -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,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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": {
|
||||||
|
Loading…
Reference in New Issue
Block a user