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
|
||||
- permission_handler_apple (9.3.0):
|
||||
- Flutter
|
||||
- printing (1.0.0):
|
||||
- Flutter
|
||||
- ReachabilitySwift (5.0.0)
|
||||
- SDWebImage (5.14.2):
|
||||
- SDWebImage/Core (= 5.14.2)
|
||||
@ -105,7 +103,6 @@ DEPENDENCIES:
|
||||
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
|
||||
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
|
||||
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
|
||||
- printing (from `.symlinks/plugins/printing/ios`)
|
||||
- sentry_flutter (from `.symlinks/plugins/sentry_flutter/ios`)
|
||||
- share_plus (from `.symlinks/plugins/share_plus/ios`)
|
||||
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
|
||||
@ -154,8 +151,6 @@ EXTERNAL SOURCES:
|
||||
:path: ".symlinks/plugins/path_provider_foundation/darwin"
|
||||
permission_handler_apple:
|
||||
:path: ".symlinks/plugins/permission_handler_apple/ios"
|
||||
printing:
|
||||
:path: ".symlinks/plugins/printing/ios"
|
||||
sentry_flutter:
|
||||
:path: ".symlinks/plugins/sentry_flutter/ios"
|
||||
share_plus:
|
||||
@ -187,7 +182,6 @@ SPEC CHECKSUMS:
|
||||
package_info_plus: 58f0028419748fad15bf008b270aaa8e54380b1c
|
||||
path_provider_foundation: 3784922295ac71e43754bd15e0653ccfd36a147c
|
||||
permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2
|
||||
printing: 233e1b73bd1f4a05615548e9b5a324c98588640b
|
||||
ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825
|
||||
SDWebImage: b9a731e1d6307f44ca703b3976d18c24ca561e84
|
||||
Sentry: 8560050221424aef0bebc8e31eedf00af80f90a6
|
||||
|
@ -116,12 +116,18 @@ class _MobileViewItemBottomSheetState extends State<MobileViewItemBottomSheet> {
|
||||
Future<void> _showConfirmDialog({required VoidCallback onDelete}) async {
|
||||
await showFlowyCupertinoConfirmDialog(
|
||||
title: LocaleKeys.sideBar_removePageFromRecent.tr(),
|
||||
leftButton: FlowyText.regular(
|
||||
leftButton: FlowyText(
|
||||
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(),
|
||||
fontSize: 17.0,
|
||||
figmaLineHeight: 24.0,
|
||||
fontWeight: FontWeight.w400,
|
||||
color: const Color(0xFFFE0220),
|
||||
),
|
||||
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/setting/cloud/cloud_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/startup/startup.dart';
|
||||
import 'package:appflowy/user/application/auth/auth_service.dart';
|
||||
@ -79,8 +80,7 @@ class _MobileHomeSettingPageState extends State<MobileHomeSettingPage> {
|
||||
PersonalInfoSettingGroup(
|
||||
userProfile: userProfile,
|
||||
),
|
||||
// TODO: Enable and implement along with Push Notifications
|
||||
// const NotificationsSettingGroup(),
|
||||
const WorkspaceSettingGroup(),
|
||||
const AppearanceSettingGroup(),
|
||||
const LanguageSettingGroup(),
|
||||
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/space_order_bloc.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/sidebar/folder/folder_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-user/protobuf.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
@ -38,6 +41,7 @@ class _MobileSpaceTabState extends State<MobileSpaceTab>
|
||||
super.initState();
|
||||
|
||||
mobileCreateNewPageNotifier.addListener(_createNewPage);
|
||||
mobileLeaveWorkspaceNotifier.addListener(_leaveWorkspace);
|
||||
}
|
||||
|
||||
@override
|
||||
@ -45,6 +49,7 @@ class _MobileSpaceTabState extends State<MobileSpaceTab>
|
||||
tabController?.removeListener(_onTabChange);
|
||||
tabController?.dispose();
|
||||
mobileCreateNewPageNotifier.removeListener(_createNewPage);
|
||||
mobileLeaveWorkspaceNotifier.removeListener(_leaveWorkspace);
|
||||
|
||||
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(
|
||||
labelText: LocaleKeys.settings_menu_logout.tr(),
|
||||
onPressed: () async {
|
||||
await showFlowyMobileConfirmDialog(
|
||||
context,
|
||||
content: FlowyText(
|
||||
LocaleKeys.settings_menu_logoutPrompt.tr(),
|
||||
await showFlowyCupertinoConfirmDialog(
|
||||
title: LocaleKeys.settings_menu_logoutPrompt.tr(),
|
||||
leftButton: FlowyText(
|
||||
LocaleKeys.button_cancel.tr(),
|
||||
fontSize: 17.0,
|
||||
figmaLineHeight: 24.0,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: const Color(0xFF007AFF),
|
||||
),
|
||||
actionButtonTitle: LocaleKeys.button_yes.tr(),
|
||||
actionButtonColor: Theme.of(context).colorScheme.error,
|
||||
onActionButtonPressed: () async {
|
||||
rightButton: FlowyText(
|
||||
LocaleKeys.button_logout.tr(),
|
||||
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 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(
|
||||
context: context ?? AppGlobals.context,
|
||||
barrierColor: Colors.black.withOpacity(0.25),
|
||||
builder: (context) => CupertinoAlertDialog(
|
||||
title: FlowyText.medium(
|
||||
title,
|
||||
fontSize: 18,
|
||||
fontSize: 16,
|
||||
maxLines: 10,
|
||||
lineHeight: 1.3,
|
||||
figmaLineHeight: 22.0,
|
||||
),
|
||||
actions: [
|
||||
CupertinoDialogAction(
|
||||
|
@ -410,7 +410,6 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
|
||||
}
|
||||
|
||||
List<SelectionMenuItem> _customSlashMenuItems() {
|
||||
|
||||
return [
|
||||
aiWriterSlashMenuItem,
|
||||
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/language/language_picker_screen.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/emoji/emoji_picker_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
|
||||
_mobileNotificationMultiSelectPageRoute(),
|
||||
|
||||
// invite members
|
||||
_mobileInviteMembersPageRoute(),
|
||||
],
|
||||
|
||||
// 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() {
|
||||
return GoRoute(
|
||||
parentNavigatorKey: AppGlobals.rootNavKey,
|
||||
|
@ -1,5 +1,3 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.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/dialog/styled_dialogs.dart';
|
||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:toastification/toastification.dart';
|
||||
|
||||
export 'package:flowy_infra_ui/widget/dialog/styled_dialogs.dart';
|
||||
@ -354,9 +353,6 @@ class _MToast extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// only support success type
|
||||
assert(type == ToastificationType.success);
|
||||
|
||||
return Container(
|
||||
alignment: Alignment.bottomCenter,
|
||||
padding: const EdgeInsets.only(bottom: 100),
|
||||
@ -369,16 +365,19 @@ class _MToast extends StatelessWidget {
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const FlowySvg(
|
||||
FlowySvgs.success_s,
|
||||
blendMode: null,
|
||||
),
|
||||
const HSpace(8.0),
|
||||
if (type == ToastificationType.success) ...[
|
||||
const FlowySvg(
|
||||
FlowySvgs.success_s,
|
||||
blendMode: null,
|
||||
),
|
||||
const HSpace(8.0),
|
||||
],
|
||||
FlowyText.regular(
|
||||
message,
|
||||
fontSize: 16.0,
|
||||
figmaLineHeight: 18.0,
|
||||
color: Colors.white,
|
||||
maxLines: 3,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
@ -4,14 +4,20 @@ import 'package:flutter/material.dart';
|
||||
class FlowyDivider extends StatelessWidget {
|
||||
const FlowyDivider({
|
||||
super.key,
|
||||
this.padding,
|
||||
});
|
||||
|
||||
final EdgeInsets? padding;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Divider(
|
||||
height: 1.0,
|
||||
thickness: 1.0,
|
||||
color: AFThemeExtension.of(context).borderColor,
|
||||
return Padding(
|
||||
padding: padding ?? EdgeInsets.zero,
|
||||
child: Divider(
|
||||
height: 1.0,
|
||||
thickness: 1.0,
|
||||
color: AFThemeExtension.of(context).borderColor,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1086,6 +1086,8 @@
|
||||
"user": "User",
|
||||
"role": "Role",
|
||||
"removeFromWorkspace": "Remove from Workspace",
|
||||
"removeFromWorkspaceSuccess": "Remove from workspace successfully",
|
||||
"removeFromWorkspaceFailed": "Remove from workspace failed",
|
||||
"owner": "Owner",
|
||||
"guest": "Guest",
|
||||
"member": "Member",
|
||||
@ -1110,7 +1112,9 @@
|
||||
"removeMember": "Remove Member",
|
||||
"areYouSureToRemoveMember": "Are you sure you want to remove this member?",
|
||||
"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": {
|
||||
|
Loading…
Reference in New Issue
Block a user