mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2025-12-28 05:34:41 +00:00
feat: support invite member by link on mobile (#7811)
* fix: invitation link issues * fix: cargo fmt * feat: support invite member by link on mobile * feat: implement new settings page design on mobile * feat: add leave workspace button * fix: flutter analyze * fix: bloc error * feat: add addMembers page
This commit is contained in:
parent
780ba51d87
commit
6a887fdca9
@ -1,9 +1,9 @@
|
||||
import 'package:appflowy/core/helpers/url_launcher.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/setting/widgets/mobile_setting_trailing.dart';
|
||||
import 'package:appflowy/startup/tasks/device_info_task.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/widgets/feature_flags/mobile_feature_flag_screen.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
@ -22,23 +22,23 @@ class AboutSettingGroup extends StatelessWidget {
|
||||
settingItemList: [
|
||||
MobileSettingItem(
|
||||
name: LocaleKeys.settings_mobile_privacyPolicy.tr(),
|
||||
trailing: const Icon(
|
||||
Icons.chevron_right,
|
||||
trailing: MobileSettingTrailing(
|
||||
text: '',
|
||||
),
|
||||
onTap: () => afLaunchUrlString('https://appflowy.com/privacy'),
|
||||
),
|
||||
MobileSettingItem(
|
||||
name: LocaleKeys.settings_mobile_termsAndConditions.tr(),
|
||||
trailing: const Icon(
|
||||
Icons.chevron_right,
|
||||
trailing: MobileSettingTrailing(
|
||||
text: '',
|
||||
),
|
||||
onTap: () => afLaunchUrlString('https://appflowy.com/terms'),
|
||||
),
|
||||
if (kDebugMode)
|
||||
MobileSettingItem(
|
||||
name: 'Feature Flags',
|
||||
trailing: const Icon(
|
||||
Icons.chevron_right,
|
||||
trailing: MobileSettingTrailing(
|
||||
text: '',
|
||||
),
|
||||
onTap: () {
|
||||
context.push(FeatureFlagScreen.routeName);
|
||||
@ -46,9 +46,10 @@ class AboutSettingGroup extends StatelessWidget {
|
||||
),
|
||||
MobileSettingItem(
|
||||
name: LocaleKeys.settings_mobile_version.tr(),
|
||||
trailing: FlowyText(
|
||||
'${ApplicationInfo.applicationVersion} (${ApplicationInfo.buildNumber})',
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
trailing: MobileSettingTrailing(
|
||||
text:
|
||||
'${ApplicationInfo.applicationVersion} (${ApplicationInfo.buildNumber})',
|
||||
showArrow: false,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@ -2,11 +2,11 @@ import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/bottom_sheet/show_mobile_bottom_sheet.dart';
|
||||
import 'package:appflowy/mobile/presentation/setting/widgets/mobile_setting_group_widget.dart';
|
||||
import 'package:appflowy/mobile/presentation/setting/widgets/mobile_setting_item_widget.dart';
|
||||
import 'package:appflowy/mobile/presentation/setting/widgets/mobile_setting_trailing.dart';
|
||||
import 'package:appflowy/mobile/presentation/widgets/flowy_option_tile.dart';
|
||||
import 'package:appflowy/workspace/application/settings/ai/settings_ai_bloc.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
@ -23,7 +23,6 @@ class AiSettingsGroup extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
return BlocProvider(
|
||||
create: (context) => SettingsAIBloc(
|
||||
userProfile,
|
||||
@ -36,21 +35,8 @@ class AiSettingsGroup extends StatelessWidget {
|
||||
settingItemList: [
|
||||
MobileSettingItem(
|
||||
name: LocaleKeys.settings_aiPage_keys_llmModelType.tr(),
|
||||
trailing: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 200),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Flexible(
|
||||
child: FlowyText(
|
||||
state.availableModels?.selectedModel.name ?? "",
|
||||
color: theme.colorScheme.onSurface,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
const Icon(Icons.chevron_right),
|
||||
],
|
||||
),
|
||||
trailing: MobileSettingTrailing(
|
||||
text: state.availableModels?.selectedModel.name ?? "",
|
||||
),
|
||||
onTap: () => _onLLMModelTypeTap(context, state),
|
||||
),
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
|
||||
import 'package:appflowy/mobile/presentation/setting/widgets/mobile_setting_trailing.dart';
|
||||
import 'package:appflowy/mobile/presentation/widgets/widgets.dart';
|
||||
import 'package:appflowy/plugins/document/application/document_appearance_cubit.dart';
|
||||
import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.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';
|
||||
|
||||
@ -17,20 +17,12 @@ class RTLSetting extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final textDirection =
|
||||
context.watch<AppearanceSettingsCubit>().state.textDirection;
|
||||
return MobileSettingItem(
|
||||
name: LocaleKeys.settings_appearance_textDirection_label.tr(),
|
||||
trailing: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
FlowyText(
|
||||
_textDirectionLabelText(textDirection),
|
||||
color: theme.colorScheme.onSurface,
|
||||
),
|
||||
const Icon(Icons.chevron_right),
|
||||
],
|
||||
trailing: MobileSettingTrailing(
|
||||
text: _textDirectionLabelText(textDirection),
|
||||
),
|
||||
onTap: () {
|
||||
showMobileBottomSheet(
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
|
||||
import 'package:appflowy/mobile/presentation/setting/widgets/mobile_setting_trailing.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/startup/tasks/app_window_size_manager.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/hotkeys.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/more_view_actions/widgets/font_size_stepper.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:scaled_app/scaled_app.dart';
|
||||
|
||||
@ -42,18 +42,10 @@ class _DisplaySizeSettingState extends State<DisplaySizeSetting> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
return MobileSettingItem(
|
||||
name: LocaleKeys.settings_appearance_displaySize.tr(),
|
||||
trailing: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
FlowyText(
|
||||
scaleFactor.toStringAsFixed(2),
|
||||
color: theme.colorScheme.onSurface,
|
||||
),
|
||||
const Icon(Icons.chevron_right),
|
||||
],
|
||||
trailing: MobileSettingTrailing(
|
||||
text: scaleFactor.toStringAsFixed(1),
|
||||
),
|
||||
onTap: () {
|
||||
showMobileBottomSheet(
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
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/setting/widgets/mobile_setting_trailing.dart';
|
||||
import 'package:appflowy/mobile/presentation/widgets/widgets.dart';
|
||||
import 'package:appflowy/util/theme_mode_extension.dart';
|
||||
import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.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';
|
||||
|
||||
@ -18,19 +18,11 @@ class ThemeSetting extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final themeMode = context.watch<AppearanceSettingsCubit>().state.themeMode;
|
||||
return MobileSettingItem(
|
||||
name: LocaleKeys.settings_appearance_themeMode_label.tr(),
|
||||
trailing: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
FlowyText(
|
||||
themeMode.labelText,
|
||||
color: theme.colorScheme.onSurface,
|
||||
),
|
||||
const Icon(Icons.chevron_right),
|
||||
],
|
||||
trailing: MobileSettingTrailing(
|
||||
text: themeMode.labelText,
|
||||
),
|
||||
onTap: () {
|
||||
showMobileBottomSheet(
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/env/cloud_env.dart';
|
||||
import 'package:appflowy/mobile/presentation/setting/cloud/appflowy_cloud_page.dart';
|
||||
import 'package:appflowy/mobile/presentation/setting/widgets/mobile_setting_group_widget.dart';
|
||||
import 'package:appflowy/mobile/presentation/setting/widgets/mobile_setting_item_widget.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:appflowy/mobile/presentation/setting/widgets/mobile_setting_trailing.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/widgets/setting_cloud.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
|
||||
class CloudSettingGroup extends StatelessWidget {
|
||||
const CloudSettingGroup({
|
||||
@ -15,19 +15,23 @@ class CloudSettingGroup extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FutureBuilder(
|
||||
future: PackageInfo.fromPlatform(),
|
||||
builder: (context, snapshot) => MobileSettingGroup(
|
||||
groupTitle: LocaleKeys.settings_menu_cloudSettings.tr(),
|
||||
settingItemList: [
|
||||
MobileSettingItem(
|
||||
name: LocaleKeys.settings_menu_cloudAppFlowy.tr(),
|
||||
trailing: const Icon(
|
||||
Icons.chevron_right,
|
||||
future: getAuthenticatorType(),
|
||||
builder: (context, snapshot) {
|
||||
final cloudType = snapshot.data ?? AuthenticatorType.appflowyCloud;
|
||||
final name = titleFromCloudType(cloudType);
|
||||
return MobileSettingGroup(
|
||||
groupTitle: 'Cloud settings',
|
||||
settingItemList: [
|
||||
MobileSettingItem(
|
||||
name: 'Cloud server',
|
||||
trailing: MobileSettingTrailing(
|
||||
text: name,
|
||||
),
|
||||
onTap: () => context.push(AppFlowyCloudPage.routeName),
|
||||
),
|
||||
onTap: () => context.push(AppFlowyCloudPage.routeName),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,11 +2,11 @@ import 'dart:async';
|
||||
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/setting/font/font_picker_screen.dart';
|
||||
import 'package:appflowy/mobile/presentation/setting/widgets/mobile_setting_trailing.dart';
|
||||
import 'package:appflowy/plugins/document/application/document_appearance_cubit.dart';
|
||||
import 'package:appflowy/util/font_family_extension.dart';
|
||||
import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.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:go_router/go_router.dart';
|
||||
@ -20,21 +20,12 @@ class FontSetting extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final selectedFont = context.watch<AppearanceSettingsCubit>().state.font;
|
||||
final name = selectedFont.fontFamilyDisplayName;
|
||||
return MobileSettingItem(
|
||||
name: LocaleKeys.settings_appearance_fontFamily_label.tr(),
|
||||
trailing: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
FlowyText(
|
||||
lineHeight: 1.0,
|
||||
name,
|
||||
color: theme.colorScheme.onSurface,
|
||||
),
|
||||
const Icon(Icons.chevron_right),
|
||||
],
|
||||
trailing: MobileSettingTrailing(
|
||||
text: name,
|
||||
),
|
||||
onTap: () async {
|
||||
final newFont = await context.push<String>(FontPickerScreen.routeName);
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/setting/language/language_picker_screen.dart';
|
||||
import 'package:appflowy/mobile/presentation/setting/widgets/mobile_setting_trailing.dart';
|
||||
import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/language.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
@ -28,22 +28,13 @@ class _LanguageSettingGroupState extends State<LanguageSettingGroup> {
|
||||
return state.locale;
|
||||
},
|
||||
builder: (context, locale) {
|
||||
final theme = Theme.of(context);
|
||||
return MobileSettingGroup(
|
||||
groupTitle: LocaleKeys.settings_menu_language.tr(),
|
||||
settingItemList: [
|
||||
MobileSettingItem(
|
||||
name: LocaleKeys.settings_menu_language.tr(),
|
||||
trailing: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
FlowyText(
|
||||
lineHeight: 1.0,
|
||||
languageFromLocale(locale),
|
||||
color: theme.colorScheme.onSurface,
|
||||
),
|
||||
const Icon(Icons.chevron_right),
|
||||
],
|
||||
trailing: MobileSettingTrailing(
|
||||
text: languageFromLocale(locale),
|
||||
),
|
||||
onTap: () async {
|
||||
final newLocale =
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
import 'package:appflowy/env/cloud_env.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
|
||||
import 'package:appflowy/mobile/presentation/setting/widgets/mobile_setting_trailing.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/workspace/application/user/prelude.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
|
||||
import 'package:appflowy_ui/appflowy_ui.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
@ -21,7 +22,7 @@ class PersonalInfoSettingGroup extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final theme = AppFlowyTheme.of(context);
|
||||
return BlocProvider<SettingsUserViewBloc>(
|
||||
create: (context) => getIt<SettingsUserViewBloc>(
|
||||
param1: userProfile,
|
||||
@ -33,16 +34,10 @@ class PersonalInfoSettingGroup extends StatelessWidget {
|
||||
groupTitle: LocaleKeys.settings_accountPage_title.tr(),
|
||||
settingItemList: [
|
||||
MobileSettingItem(
|
||||
name: userName,
|
||||
subtitle: isAuthEnabled && userProfile.email.isNotEmpty
|
||||
? Text(
|
||||
userProfile.email,
|
||||
style: theme.textTheme.bodyMedium?.copyWith(
|
||||
color: theme.colorScheme.onSurface,
|
||||
),
|
||||
)
|
||||
: null,
|
||||
trailing: const Icon(Icons.chevron_right),
|
||||
name: 'User name',
|
||||
trailing: MobileSettingTrailing(
|
||||
text: userName,
|
||||
),
|
||||
onTap: () {
|
||||
showMobileBottomSheet(
|
||||
context,
|
||||
@ -64,6 +59,15 @@ class PersonalInfoSettingGroup extends StatelessWidget {
|
||||
);
|
||||
},
|
||||
),
|
||||
MobileSettingItem(
|
||||
name: 'Email',
|
||||
trailing: Text(
|
||||
userProfile.email,
|
||||
style: theme.textStyle.heading4.standard(
|
||||
color: theme.textColorScheme.secondary,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
|
||||
@ -3,6 +3,7 @@ import 'dart:io';
|
||||
import 'package:appflowy/core/helpers/url_launcher.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/bottom_sheet/show_mobile_bottom_sheet.dart';
|
||||
import 'package:appflowy/mobile/presentation/setting/widgets/mobile_setting_trailing.dart';
|
||||
import 'package:appflowy/mobile/presentation/widgets/widgets.dart';
|
||||
import 'package:appflowy/shared/appflowy_cache_manager.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
@ -30,15 +31,15 @@ class SupportSettingGroup extends StatelessWidget {
|
||||
settingItemList: [
|
||||
MobileSettingItem(
|
||||
name: LocaleKeys.settings_mobile_joinDiscord.tr(),
|
||||
trailing: const Icon(
|
||||
Icons.chevron_right,
|
||||
trailing: MobileSettingTrailing(
|
||||
text: '',
|
||||
),
|
||||
onTap: () => afLaunchUrlString('https://discord.gg/JucBXeU2FE'),
|
||||
),
|
||||
MobileSettingItem(
|
||||
name: LocaleKeys.workspace_errorActions_reportIssue.tr(),
|
||||
trailing: const Icon(
|
||||
Icons.chevron_right,
|
||||
trailing: MobileSettingTrailing(
|
||||
text: '',
|
||||
),
|
||||
onTap: () {
|
||||
showMobileBottomSheet(
|
||||
@ -57,8 +58,8 @@ class SupportSettingGroup extends StatelessWidget {
|
||||
),
|
||||
MobileSettingItem(
|
||||
name: LocaleKeys.settings_files_clearCache.tr(),
|
||||
trailing: const Icon(
|
||||
Icons.chevron_right,
|
||||
trailing: MobileSettingTrailing(
|
||||
text: '',
|
||||
),
|
||||
onTap: () async {
|
||||
await showFlowyMobileConfirmDialog(
|
||||
|
||||
@ -9,6 +9,7 @@ import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/widget
|
||||
import 'package:appflowy/workspace/presentation/settings/pages/account/account_deletion.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
|
||||
import 'package:appflowy_ui/appflowy_ui.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@ -26,6 +27,7 @@ class UserSessionSettingGroup extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = AppFlowyTheme.of(context);
|
||||
return Column(
|
||||
children: [
|
||||
// third party sign in buttons
|
||||
@ -41,11 +43,15 @@ class UserSessionSettingGroup extends StatelessWidget {
|
||||
// delete account button
|
||||
// only show the delete account button in cloud mode
|
||||
if (userProfile.workspaceAuthType == AuthTypePB.Server) ...[
|
||||
const VSpace(16.0),
|
||||
MobileLogoutButton(
|
||||
VSpace(theme.spacing.l),
|
||||
AFOutlinedTextButton.destructive(
|
||||
alignment: Alignment.center,
|
||||
text: LocaleKeys.button_deleteAccount.tr(),
|
||||
textColor: Theme.of(context).colorScheme.error,
|
||||
onPressed: () => _showDeleteAccountDialog(context),
|
||||
textStyle: theme.textStyle.body.standard(
|
||||
color: theme.textColorScheme.error,
|
||||
),
|
||||
onTap: () => _showDeleteAccountDialog(context),
|
||||
size: AFButtonSize.l,
|
||||
),
|
||||
],
|
||||
],
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import 'package:appflowy_ui/appflowy_ui.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
@ -15,16 +16,22 @@ class MobileSettingGroup extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = AppFlowyTheme.of(context);
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const VSpace(4.0),
|
||||
FlowyText.semibold(
|
||||
VSpace(theme.spacing.s),
|
||||
Text(
|
||||
groupTitle,
|
||||
style: theme.textStyle.heading4.enhanced(
|
||||
color: theme.textColorScheme.primary,
|
||||
),
|
||||
),
|
||||
const VSpace(4.0),
|
||||
VSpace(theme.spacing.s),
|
||||
...settingItemList,
|
||||
showDivider ? const Divider() : const SizedBox.shrink(),
|
||||
showDivider
|
||||
? AFDivider(spacing: theme.spacing.m)
|
||||
: const SizedBox.shrink(),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import 'package:appflowy_ui/appflowy_ui.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
@ -26,17 +27,18 @@ class MobileSettingItem extends StatelessWidget {
|
||||
return Padding(
|
||||
padding: padding,
|
||||
child: ListTile(
|
||||
title: title ?? _buildDefaultTitle(name),
|
||||
title: title ?? _buildDefaultTitle(context, name),
|
||||
subtitle: subtitle,
|
||||
trailing: trailing,
|
||||
onTap: onTap,
|
||||
visualDensity: VisualDensity.compact,
|
||||
contentPadding: const EdgeInsets.only(left: 8.0),
|
||||
contentPadding: EdgeInsets.zero,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDefaultTitle(String? name) {
|
||||
Widget _buildDefaultTitle(BuildContext context, String? name) {
|
||||
final theme = AppFlowyTheme.of(context);
|
||||
return Row(
|
||||
children: [
|
||||
if (leadingIcon != null) ...[
|
||||
@ -44,9 +46,11 @@ class MobileSettingItem extends StatelessWidget {
|
||||
const HSpace(8),
|
||||
],
|
||||
Expanded(
|
||||
child: FlowyText.medium(
|
||||
child: Text(
|
||||
name ?? '',
|
||||
fontSize: 14.0,
|
||||
style: theme.textStyle.heading4.standard(
|
||||
color: theme.textColorScheme.primary,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
|
||||
@ -0,0 +1,42 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy_ui/appflowy_ui.dart';
|
||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class MobileSettingTrailing extends StatelessWidget {
|
||||
const MobileSettingTrailing({
|
||||
super.key,
|
||||
required this.text,
|
||||
this.showArrow = true,
|
||||
});
|
||||
|
||||
final String text;
|
||||
final bool showArrow;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = AppFlowyTheme.of(context);
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Text(
|
||||
text,
|
||||
style: theme.textStyle.heading4.standard(
|
||||
color: theme.textColorScheme.secondary,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
if (showArrow) ...[
|
||||
const HSpace(8),
|
||||
FlowySvg(
|
||||
FlowySvgs.toolbar_arrow_right_m,
|
||||
size: Size.square(24),
|
||||
color: theme.iconColorScheme.tertiary,
|
||||
),
|
||||
],
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,349 @@
|
||||
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/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart';
|
||||
import 'package:appflowy/shared/af_role_pb_extension.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/user/application/user_service.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/widgets/members/inivitation/m_invite_member_by_email.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:appflowy_ui/appflowy_ui.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 'member_list.dart';
|
||||
|
||||
class AddMembersScreen extends StatelessWidget {
|
||||
const AddMembersScreen({
|
||||
super.key,
|
||||
});
|
||||
|
||||
static const routeName = '/add_member';
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: FlowyAppBar(
|
||||
titleText: 'Add members',
|
||||
),
|
||||
body: const _InviteMemberPage(),
|
||||
resizeToAvoidBottomInset: false,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _InviteMemberPage extends StatefulWidget {
|
||||
const _InviteMemberPage();
|
||||
|
||||
@override
|
||||
State<_InviteMemberPage> createState() => _InviteMemberPageState();
|
||||
}
|
||||
|
||||
class _InviteMemberPageState extends State<_InviteMemberPage> {
|
||||
final emailController = TextEditingController();
|
||||
late final Future<UserProfilePB?> userProfile;
|
||||
bool exceededLimit = false;
|
||||
|
||||
@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) {
|
||||
final theme = AppFlowyTheme.of(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: [
|
||||
if (state.myRole.isOwner) ...[
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: EdgeInsets.all(theme.spacing.xl),
|
||||
child: const MInviteMemberByEmail(),
|
||||
),
|
||||
VSpace(theme.spacing.m),
|
||||
],
|
||||
if (state.members.isNotEmpty) ...[
|
||||
const AFDivider(),
|
||||
VSpace(theme.spacing.xl),
|
||||
MobileMemberList(
|
||||
members: state.members,
|
||||
userProfile: userProfile,
|
||||
myRole: state.myRole,
|
||||
),
|
||||
],
|
||||
if (state.myRole.isMember) ...[
|
||||
Spacer(),
|
||||
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),
|
||||
// if (exceededLimit) ...[
|
||||
// FlowyText.regular(
|
||||
// LocaleKeys.settings_appearance_members_inviteFailedMemberLimitMobile
|
||||
// .tr(),
|
||||
// fontSize: 14.0,
|
||||
// maxLines: 3,
|
||||
// color: Theme.of(context).colorScheme.error,
|
||||
// ),
|
||||
// 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;
|
||||
|
||||
// get keyboard height
|
||||
final keyboardHeight = MediaQuery.of(context).viewInsets.bottom;
|
||||
|
||||
// only show the result dialog when the action is WorkspaceMemberActionType.add
|
||||
if (actionType == WorkspaceMemberActionType.addByEmail) {
|
||||
result.fold(
|
||||
(s) {
|
||||
showToastNotification(
|
||||
message:
|
||||
LocaleKeys.settings_appearance_members_addMemberSuccess.tr(),
|
||||
bottomPadding: keyboardHeight,
|
||||
);
|
||||
},
|
||||
(f) {
|
||||
Log.error('add workspace member failed: $f');
|
||||
final message = f.code == ErrorCode.WorkspaceMemberLimitExceeded
|
||||
? LocaleKeys
|
||||
.settings_appearance_members_inviteFailedMemberLimitMobile
|
||||
.tr()
|
||||
: LocaleKeys.settings_appearance_members_failedToAddMember.tr();
|
||||
setState(() {
|
||||
exceededLimit = f.code == ErrorCode.WorkspaceMemberLimitExceeded;
|
||||
});
|
||||
showToastNotification(
|
||||
type: ToastificationType.error,
|
||||
bottomPadding: keyboardHeight,
|
||||
message: message,
|
||||
);
|
||||
},
|
||||
);
|
||||
} else if (actionType == WorkspaceMemberActionType.inviteByEmail) {
|
||||
result.fold(
|
||||
(s) {
|
||||
showToastNotification(
|
||||
message:
|
||||
LocaleKeys.settings_appearance_members_inviteMemberSuccess.tr(),
|
||||
bottomPadding: keyboardHeight,
|
||||
);
|
||||
},
|
||||
(f) {
|
||||
Log.error('invite workspace member failed: $f');
|
||||
final message = f.code == ErrorCode.WorkspaceMemberLimitExceeded
|
||||
? LocaleKeys
|
||||
.settings_appearance_members_inviteFailedMemberLimitMobile
|
||||
.tr()
|
||||
: LocaleKeys.settings_appearance_members_failedToInviteMember
|
||||
.tr();
|
||||
setState(() {
|
||||
exceededLimit = f.code == ErrorCode.WorkspaceMemberLimitExceeded;
|
||||
});
|
||||
showToastNotification(
|
||||
type: ToastificationType.error,
|
||||
message: message,
|
||||
bottomPadding: keyboardHeight,
|
||||
);
|
||||
},
|
||||
);
|
||||
} else if (actionType == WorkspaceMemberActionType.removeByEmail) {
|
||||
result.fold(
|
||||
(s) {
|
||||
showToastNotification(
|
||||
message: LocaleKeys
|
||||
.settings_appearance_members_removeFromWorkspaceSuccess
|
||||
.tr(),
|
||||
bottomPadding: keyboardHeight,
|
||||
);
|
||||
},
|
||||
(f) {
|
||||
showToastNotification(
|
||||
type: ToastificationType.error,
|
||||
message: LocaleKeys
|
||||
.settings_appearance_members_removeFromWorkspaceFailed
|
||||
.tr(),
|
||||
bottomPadding: keyboardHeight,
|
||||
);
|
||||
},
|
||||
);
|
||||
} else if (actionType == WorkspaceMemberActionType.generateInviteLink) {
|
||||
result.fold(
|
||||
(s) {
|
||||
showToastNotification(
|
||||
message: 'Invite link generated successfully',
|
||||
);
|
||||
|
||||
// copy the invite link to the clipboard
|
||||
final inviteLink = state.inviteLink;
|
||||
if (inviteLink != null) {
|
||||
getIt<ClipboardService>().setPlainText(inviteLink);
|
||||
}
|
||||
},
|
||||
(f) {
|
||||
Log.error('generate invite link failed: $f');
|
||||
showToastNotification(
|
||||
message: 'Failed to generate invite link',
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// void _inviteMember(BuildContext context) {
|
||||
// final email = emailController.text;
|
||||
// if (!isEmail(email)) {
|
||||
// showToastNotification(
|
||||
// type: ToastificationType.error,
|
||||
// message: LocaleKeys.settings_appearance_members_emailInvalidError.tr(),
|
||||
// );
|
||||
// return;
|
||||
// }
|
||||
// context
|
||||
// .read<WorkspaceMemberBloc>()
|
||||
// .add(WorkspaceMemberEvent.inviteWorkspaceMemberByEmail(email));
|
||||
// // clear the email field after inviting
|
||||
// emailController.clear();
|
||||
// }
|
||||
}
|
||||
|
||||
class _LeaveWorkspaceButton extends StatelessWidget {
|
||||
const _LeaveWorkspaceButton();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: AFOutlinedTextButton.destructive(
|
||||
alignment: Alignment.center,
|
||||
text: LocaleKeys.workspace_leaveCurrentWorkspace.tr(),
|
||||
onTap: () => _leaveWorkspace(context),
|
||||
size: AFButtonSize.l,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
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 {},
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,356 @@
|
||||
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:appflowy_ui/appflowy_ui.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 '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(),
|
||||
resizeToAvoidBottomInset: false,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _InviteMemberPage extends StatefulWidget {
|
||||
const _InviteMemberPage();
|
||||
|
||||
@override
|
||||
State<_InviteMemberPage> createState() => _InviteMemberPageState();
|
||||
}
|
||||
|
||||
class _InviteMemberPageState extends State<_InviteMemberPage> {
|
||||
final emailController = TextEditingController();
|
||||
late final Future<UserProfilePB?> userProfile;
|
||||
bool exceededLimit = false;
|
||||
|
||||
@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) {
|
||||
final theme = AppFlowyTheme.of(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: EdgeInsets.all(theme.spacing.xl),
|
||||
child: _buildInviteMemberArea(context),
|
||||
),
|
||||
const VSpace(16),
|
||||
],
|
||||
if (state.members.isNotEmpty) ...[
|
||||
const AFDivider(),
|
||||
VSpace(theme.spacing.xl),
|
||||
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),
|
||||
if (exceededLimit) ...[
|
||||
FlowyText.regular(
|
||||
LocaleKeys.settings_appearance_members_inviteFailedMemberLimitMobile
|
||||
.tr(),
|
||||
fontSize: 14.0,
|
||||
maxLines: 3,
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
),
|
||||
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;
|
||||
|
||||
// get keyboard height
|
||||
final keyboardHeight = MediaQuery.of(context).viewInsets.bottom;
|
||||
|
||||
// only show the result dialog when the action is WorkspaceMemberActionType.add
|
||||
if (actionType == WorkspaceMemberActionType.addByEmail) {
|
||||
result.fold(
|
||||
(s) {
|
||||
showToastNotification(
|
||||
message:
|
||||
LocaleKeys.settings_appearance_members_addMemberSuccess.tr(),
|
||||
bottomPadding: keyboardHeight,
|
||||
);
|
||||
},
|
||||
(f) {
|
||||
Log.error('add workspace member failed: $f');
|
||||
final message = f.code == ErrorCode.WorkspaceMemberLimitExceeded
|
||||
? LocaleKeys
|
||||
.settings_appearance_members_inviteFailedMemberLimitMobile
|
||||
.tr()
|
||||
: LocaleKeys.settings_appearance_members_failedToAddMember.tr();
|
||||
setState(() {
|
||||
exceededLimit = f.code == ErrorCode.WorkspaceMemberLimitExceeded;
|
||||
});
|
||||
showToastNotification(
|
||||
type: ToastificationType.error,
|
||||
bottomPadding: keyboardHeight,
|
||||
message: message,
|
||||
);
|
||||
},
|
||||
);
|
||||
} else if (actionType == WorkspaceMemberActionType.inviteByEmail) {
|
||||
result.fold(
|
||||
(s) {
|
||||
showToastNotification(
|
||||
message:
|
||||
LocaleKeys.settings_appearance_members_inviteMemberSuccess.tr(),
|
||||
bottomPadding: keyboardHeight,
|
||||
);
|
||||
},
|
||||
(f) {
|
||||
Log.error('invite workspace member failed: $f');
|
||||
final message = f.code == ErrorCode.WorkspaceMemberLimitExceeded
|
||||
? LocaleKeys
|
||||
.settings_appearance_members_inviteFailedMemberLimitMobile
|
||||
.tr()
|
||||
: LocaleKeys.settings_appearance_members_failedToInviteMember
|
||||
.tr();
|
||||
setState(() {
|
||||
exceededLimit = f.code == ErrorCode.WorkspaceMemberLimitExceeded;
|
||||
});
|
||||
showToastNotification(
|
||||
type: ToastificationType.error,
|
||||
message: message,
|
||||
bottomPadding: keyboardHeight,
|
||||
);
|
||||
},
|
||||
);
|
||||
} else if (actionType == WorkspaceMemberActionType.removeByEmail) {
|
||||
result.fold(
|
||||
(s) {
|
||||
showToastNotification(
|
||||
message: LocaleKeys
|
||||
.settings_appearance_members_removeFromWorkspaceSuccess
|
||||
.tr(),
|
||||
bottomPadding: keyboardHeight,
|
||||
);
|
||||
},
|
||||
(f) {
|
||||
showToastNotification(
|
||||
type: ToastificationType.error,
|
||||
message: LocaleKeys
|
||||
.settings_appearance_members_removeFromWorkspaceFailed
|
||||
.tr(),
|
||||
bottomPadding: keyboardHeight,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _inviteMember(BuildContext context) {
|
||||
final email = emailController.text;
|
||||
if (!isEmail(email)) {
|
||||
showToastNotification(
|
||||
type: ToastificationType.error,
|
||||
message: LocaleKeys.settings_appearance_members_emailInvalidError.tr(),
|
||||
);
|
||||
return;
|
||||
}
|
||||
context
|
||||
.read<WorkspaceMemberBloc>()
|
||||
.add(WorkspaceMemberEvent.inviteWorkspaceMemberByEmail(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;
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1,19 +1,25 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/base/app_bar/app_bar.dart';
|
||||
import 'package:appflowy/mobile/presentation/setting/workspace/add_members_screen.dart';
|
||||
import 'package:appflowy/mobile/presentation/widgets/show_flowy_mobile_confirm_dialog.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart';
|
||||
import 'package:appflowy/shared/af_role_pb_extension.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/user/application/user_service.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/widgets/members/inivitation/m_invite_member_by_link.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:appflowy_ui/appflowy_ui.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:go_router/go_router.dart';
|
||||
|
||||
import 'member_list.dart';
|
||||
|
||||
@ -31,11 +37,26 @@ class InviteMembersScreen extends StatelessWidget {
|
||||
return Scaffold(
|
||||
appBar: FlowyAppBar(
|
||||
titleText: LocaleKeys.settings_appearance_members_label.tr(),
|
||||
actions: [
|
||||
_buildAddMemberButton(context),
|
||||
],
|
||||
),
|
||||
body: const _InviteMemberPage(),
|
||||
resizeToAvoidBottomInset: false,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildAddMemberButton(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(right: 20),
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
context.push(AddMembersScreen.routeName);
|
||||
},
|
||||
child: FlowySvg(FlowySvgs.add_thin_s),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _InviteMemberPage extends StatefulWidget {
|
||||
@ -67,6 +88,7 @@ class _InviteMemberPageState extends State<_InviteMemberPage> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = AppFlowyTheme.of(context);
|
||||
return FutureBuilder(
|
||||
future: userProfile,
|
||||
builder: (context, snapshot) {
|
||||
@ -87,29 +109,27 @@ class _InviteMemberPageState extends State<_InviteMemberPage> {
|
||||
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.isOwner) ...[
|
||||
Container(
|
||||
width: double.infinity,
|
||||
padding: EdgeInsets.all(theme.spacing.xl),
|
||||
child: const MInviteMemberByLink(),
|
||||
),
|
||||
),
|
||||
if (state.myRole.isMember) const _LeaveWorkspaceButton(),
|
||||
VSpace(theme.spacing.m),
|
||||
],
|
||||
if (state.members.isNotEmpty) ...[
|
||||
const AFDivider(),
|
||||
VSpace(theme.spacing.xl),
|
||||
MobileMemberList(
|
||||
members: state.members,
|
||||
userProfile: userProfile,
|
||||
myRole: state.myRole,
|
||||
),
|
||||
],
|
||||
if (state.myRole.isMember) ...[
|
||||
Spacer(),
|
||||
const _LeaveWorkspaceButton(),
|
||||
],
|
||||
const VSpace(48),
|
||||
],
|
||||
);
|
||||
@ -120,41 +140,6 @@ class _InviteMemberPageState extends State<_InviteMemberPage> {
|
||||
);
|
||||
}
|
||||
|
||||
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),
|
||||
if (exceededLimit) ...[
|
||||
FlowyText.regular(
|
||||
LocaleKeys.settings_appearance_members_inviteFailedMemberLimitMobile
|
||||
.tr(),
|
||||
fontSize: 14.0,
|
||||
maxLines: 3,
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
),
|
||||
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(
|
||||
@ -270,23 +255,28 @@ class _InviteMemberPageState extends State<_InviteMemberPage> {
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
} else if (actionType == WorkspaceMemberActionType.generateInviteLink) {
|
||||
result.fold(
|
||||
(s) {
|
||||
showToastNotification(
|
||||
message: 'Invite link generated successfully',
|
||||
);
|
||||
|
||||
void _inviteMember(BuildContext context) {
|
||||
final email = emailController.text;
|
||||
if (!isEmail(email)) {
|
||||
showToastNotification(
|
||||
type: ToastificationType.error,
|
||||
message: LocaleKeys.settings_appearance_members_emailInvalidError.tr(),
|
||||
// copy the invite link to the clipboard
|
||||
final inviteLink = state.inviteLink;
|
||||
if (inviteLink != null) {
|
||||
getIt<ClipboardService>().setPlainText(inviteLink);
|
||||
}
|
||||
},
|
||||
(f) {
|
||||
Log.error('generate invite link failed: $f');
|
||||
showToastNotification(
|
||||
type: ToastificationType.error,
|
||||
message: 'Failed to generate invite link',
|
||||
);
|
||||
},
|
||||
);
|
||||
return;
|
||||
}
|
||||
context
|
||||
.read<WorkspaceMemberBloc>()
|
||||
.add(WorkspaceMemberEvent.inviteWorkspaceMemberByEmail(email));
|
||||
// clear the email field after inviting
|
||||
emailController.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@ -295,28 +285,13 @@ class _LeaveWorkspaceButton extends StatelessWidget {
|
||||
|
||||
@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,
|
||||
),
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: AFOutlinedTextButton.destructive(
|
||||
alignment: Alignment.center,
|
||||
text: LocaleKeys.workspace_leaveCurrentWorkspace.tr(),
|
||||
onTap: () => _leaveWorkspace(context),
|
||||
size: AFButtonSize.l,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@ -5,6 +5,7 @@ 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:appflowy_ui/appflowy_ui.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@ -27,31 +28,34 @@ class MobileMemberList extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SlidableAutoCloseBehavior(
|
||||
child: SeparatedColumn(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
separatorBuilder: () => const FlowyDivider(
|
||||
padding: EdgeInsets.symmetric(horizontal: 16.0),
|
||||
final theme = AppFlowyTheme.of(context);
|
||||
return SingleChildScrollView(
|
||||
child: SlidableAutoCloseBehavior(
|
||||
child: SeparatedColumn(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
separatorBuilder: () => SizedBox.shrink(),
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16.0,
|
||||
vertical: 8.0,
|
||||
),
|
||||
child: Text(
|
||||
'Joined',
|
||||
style: theme.textStyle.heading4.enhanced(
|
||||
color: theme.textColorScheme.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
...members.map(
|
||||
(member) => _MemberItem(
|
||||
member: member,
|
||||
myRole: myRole,
|
||||
userProfile: userProfile,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -70,8 +74,8 @@ class _MemberItem extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = AppFlowyTheme.of(context);
|
||||
final canDelete = myRole.canDelete && member.email != userProfile.email;
|
||||
final textColor = member.role.isOwner ? Theme.of(context).hintColor : null;
|
||||
|
||||
Widget child;
|
||||
|
||||
@ -79,17 +83,19 @@ class _MemberItem extends StatelessWidget {
|
||||
child = Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: FlowyText.medium(
|
||||
child: Text(
|
||||
member.name,
|
||||
color: textColor,
|
||||
fontSize: 15.0,
|
||||
style: theme.textStyle.heading4.standard(
|
||||
color: theme.textColorScheme.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: FlowyText.medium(
|
||||
child: Text(
|
||||
member.role.description,
|
||||
color: textColor,
|
||||
fontSize: 15.0,
|
||||
style: theme.textStyle.heading4.standard(
|
||||
color: theme.textColorScheme.secondary,
|
||||
),
|
||||
textAlign: TextAlign.end,
|
||||
),
|
||||
),
|
||||
@ -99,18 +105,19 @@ class _MemberItem extends StatelessWidget {
|
||||
child = Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: FlowyText.medium(
|
||||
child: Text(
|
||||
member.name,
|
||||
color: textColor,
|
||||
fontSize: 15.0,
|
||||
style: theme.textStyle.heading4.standard(
|
||||
color: theme.textColorScheme.primary,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
const HSpace(36.0),
|
||||
FlowyText.medium(
|
||||
Text(
|
||||
member.role.description,
|
||||
color: textColor,
|
||||
fontSize: 15.0,
|
||||
style: theme.textStyle.heading4.standard(
|
||||
color: theme.textColorScheme.secondary,
|
||||
),
|
||||
textAlign: TextAlign.end,
|
||||
),
|
||||
],
|
||||
@ -118,8 +125,10 @@ class _MemberItem extends StatelessWidget {
|
||||
}
|
||||
|
||||
child = Container(
|
||||
height: 48,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: theme.spacing.xl,
|
||||
vertical: theme.spacing.l,
|
||||
),
|
||||
child: child,
|
||||
);
|
||||
|
||||
|
||||
@ -1,7 +1,10 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/setting/widgets/mobile_setting_trailing.dart';
|
||||
import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../widgets/widgets.dart';
|
||||
import 'invite_members_screen.dart';
|
||||
@ -13,12 +16,21 @@ class WorkspaceSettingGroup extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final memberCount = context
|
||||
.read<UserWorkspaceBloc>()
|
||||
.state
|
||||
.currentWorkspace
|
||||
?.memberCount
|
||||
.toString() ??
|
||||
'';
|
||||
return MobileSettingGroup(
|
||||
groupTitle: LocaleKeys.settings_appearance_members_label.tr(),
|
||||
settingItemList: [
|
||||
MobileSettingItem(
|
||||
name: LocaleKeys.settings_appearance_members_label.tr(),
|
||||
trailing: const Icon(Icons.chevron_right),
|
||||
trailing: MobileSettingTrailing(
|
||||
text: memberCount,
|
||||
),
|
||||
onTap: () {
|
||||
context.push(InviteMembersScreen.routeName);
|
||||
},
|
||||
|
||||
@ -119,7 +119,9 @@ class MentionPageBloc extends Bloc<MentionPageEvent, MentionPageState> {
|
||||
final trash = trashOrFailed.toNullable();
|
||||
if (trash != null) {
|
||||
final isInTrash = trash.any((t) => t.id == pageId);
|
||||
add(MentionPageEvent.didUpdateTrashStatus(isInTrash: isInTrash));
|
||||
if (!isClosed) {
|
||||
add(MentionPageEvent.didUpdateTrashStatus(isInTrash: isInTrash));
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
@ -84,6 +84,9 @@ class FlowyRunner {
|
||||
IntegrationTestHelper.rustEnvsBuilder = rustEnvsBuilder;
|
||||
}
|
||||
|
||||
// Disable the log in test mode
|
||||
Log.shared.disableLog = mode.isTest;
|
||||
|
||||
// Clear and dispose tasks from previous AppLaunch
|
||||
if (getIt.isRegistered(instance: AppLauncher)) {
|
||||
await getIt<AppLauncher>().dispose();
|
||||
|
||||
@ -18,6 +18,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/add_members_screen.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';
|
||||
@ -104,6 +105,7 @@ GoRouter generateRouter(Widget child) {
|
||||
|
||||
// invite members
|
||||
_mobileInviteMembersPageRoute(),
|
||||
_mobileAddMembersPageRoute(),
|
||||
],
|
||||
|
||||
// Desktop and Mobile
|
||||
@ -215,6 +217,16 @@ GoRoute _mobileInviteMembersPageRoute() {
|
||||
);
|
||||
}
|
||||
|
||||
GoRoute _mobileAddMembersPageRoute() {
|
||||
return GoRoute(
|
||||
parentNavigatorKey: AppGlobals.rootNavKey,
|
||||
path: AddMembersScreen.routeName,
|
||||
pageBuilder: (context, state) {
|
||||
return const MaterialExtendedPage(child: AddMembersScreen());
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
GoRoute _mobileCloudSettingAppFlowyCloudPageRoute() {
|
||||
return GoRoute(
|
||||
parentNavigatorKey: AppGlobals.rootNavKey,
|
||||
|
||||
@ -1,36 +1,25 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy_ui/appflowy_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class MobileLogoutButton extends StatelessWidget {
|
||||
const MobileLogoutButton({
|
||||
super.key,
|
||||
this.icon,
|
||||
required this.text,
|
||||
this.textColor,
|
||||
required this.onPressed,
|
||||
});
|
||||
|
||||
final FlowySvgData? icon;
|
||||
final String text;
|
||||
final Color? textColor;
|
||||
final VoidCallback onPressed;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AFOutlinedIconTextButton.normal(
|
||||
return AFOutlinedTextButton.normal(
|
||||
alignment: Alignment.center,
|
||||
text: text,
|
||||
onTap: onPressed,
|
||||
size: AFButtonSize.l,
|
||||
iconBuilder: (context, isHovering, disabled) {
|
||||
if (icon == null) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
return FlowySvg(
|
||||
icon!,
|
||||
size: Size.square(18),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -189,6 +189,7 @@ class SpaceCancelOrConfirmButton extends StatelessWidget {
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
AFOutlinedTextButton.normal(
|
||||
size: UniversalPlatform.isDesktop ? AFButtonSize.m : AFButtonSize.l,
|
||||
text: LocaleKeys.button_cancel.tr(),
|
||||
textStyle: theme.textStyle.body.standard(
|
||||
color: theme.textColorScheme.primary,
|
||||
@ -335,6 +336,7 @@ class _ConfirmPopupState extends State<ConfirmPopup> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = AppFlowyTheme.of(context);
|
||||
return KeyboardListener(
|
||||
focusNode: focusNode,
|
||||
autofocus: true,
|
||||
@ -353,24 +355,28 @@ class _ConfirmPopupState extends State<ConfirmPopup> {
|
||||
}
|
||||
},
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(20),
|
||||
color: UniversalPlatform.isDesktop
|
||||
? null
|
||||
: Theme.of(context).colorScheme.surface,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(theme.borderRadius.xl),
|
||||
color: AppFlowyTheme.of(context).surfaceColorScheme.primary,
|
||||
),
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: theme.spacing.xxl,
|
||||
vertical: theme.spacing.xxl,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_buildTitle(),
|
||||
if (widget.description.isNotEmpty) ...[
|
||||
const VSpace(6),
|
||||
VSpace(theme.spacing.l),
|
||||
_buildDescription(),
|
||||
],
|
||||
if (widget.child != null) ...[
|
||||
const VSpace(12),
|
||||
widget.child!,
|
||||
],
|
||||
const VSpace(20),
|
||||
VSpace(theme.spacing.xxl),
|
||||
_buildStyledButton(context),
|
||||
],
|
||||
),
|
||||
|
||||
@ -0,0 +1,94 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/widgets/members/workspace_member_bloc.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
|
||||
import 'package:appflowy_ui/appflowy_ui.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';
|
||||
|
||||
class MInviteMemberByEmail extends StatefulWidget {
|
||||
const MInviteMemberByEmail({super.key});
|
||||
|
||||
@override
|
||||
State<MInviteMemberByEmail> createState() => _MInviteMemberByEmailState();
|
||||
}
|
||||
|
||||
class _MInviteMemberByEmailState extends State<MInviteMemberByEmail> {
|
||||
final _emailController = TextEditingController();
|
||||
|
||||
bool _isInviteButtonEnabled = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
_emailController.addListener(_onEmailChanged);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_emailController.dispose();
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = AppFlowyTheme.of(context);
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
AFTextField(
|
||||
autoFocus: true,
|
||||
controller: _emailController,
|
||||
hintText: LocaleKeys.settings_appearance_members_inviteHint.tr(),
|
||||
onSubmitted: (value) => _inviteMember(),
|
||||
),
|
||||
VSpace(theme.spacing.m),
|
||||
_isInviteButtonEnabled
|
||||
? AFFilledTextButton.primary(
|
||||
text: 'Send invite',
|
||||
alignment: Alignment.center,
|
||||
size: AFButtonSize.l,
|
||||
textStyle: theme.textStyle.heading4.enhanced(
|
||||
color: theme.textColorScheme.onFill,
|
||||
),
|
||||
onTap: _inviteMember,
|
||||
)
|
||||
: AFFilledTextButton.disabled(
|
||||
text: 'Send invite',
|
||||
alignment: Alignment.center,
|
||||
size: AFButtonSize.l,
|
||||
textStyle: theme.textStyle.heading4.enhanced(
|
||||
color: theme.textColorScheme.tertiary,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
void _inviteMember() {
|
||||
final email = _emailController.text;
|
||||
if (!isEmail(email)) {
|
||||
showToastNotification(
|
||||
type: ToastificationType.error,
|
||||
message: LocaleKeys.settings_appearance_members_emailInvalidError.tr(),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
context
|
||||
.read<WorkspaceMemberBloc>()
|
||||
.add(WorkspaceMemberEvent.inviteWorkspaceMemberByEmail(email));
|
||||
// clear the email field after inviting
|
||||
_emailController.clear();
|
||||
}
|
||||
|
||||
void _onEmailChanged() {
|
||||
setState(() {
|
||||
_isInviteButtonEnabled = _emailController.text.isNotEmpty;
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,155 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/shared_widget.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/widgets/members/workspace_member_bloc.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
|
||||
import 'package:appflowy_ui/appflowy_ui.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:universal_platform/universal_platform.dart';
|
||||
|
||||
class MInviteMemberByLink extends StatelessWidget {
|
||||
const MInviteMemberByLink({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = AppFlowyTheme.of(context);
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_Title(),
|
||||
VSpace(theme.spacing.l),
|
||||
_CopyLinkButton(),
|
||||
VSpace(theme.spacing.l),
|
||||
_Description(),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _Title extends StatelessWidget {
|
||||
const _Title();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = AppFlowyTheme.of(context);
|
||||
return Text(
|
||||
LocaleKeys.settings_appearance_members_inviteLinkToAddMember.tr(),
|
||||
style: theme.textStyle.heading4.enhanced(
|
||||
color: theme.textColorScheme.primary,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _Description extends StatelessWidget {
|
||||
const _Description();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = AppFlowyTheme.of(context);
|
||||
return Text.rich(
|
||||
TextSpan(
|
||||
children: [
|
||||
TextSpan(
|
||||
text: LocaleKeys.settings_appearance_members_clickToCopyLink.tr(),
|
||||
style: theme.textStyle.body.standard(
|
||||
color: theme.textColorScheme.primary,
|
||||
),
|
||||
),
|
||||
TextSpan(
|
||||
text: ' ${LocaleKeys.settings_appearance_members_or.tr()} ',
|
||||
style: theme.textStyle.body.standard(
|
||||
color: theme.textColorScheme.primary,
|
||||
),
|
||||
),
|
||||
TextSpan(
|
||||
text: LocaleKeys.settings_appearance_members_generateANewLink.tr(),
|
||||
style: theme.textStyle.body.standard(
|
||||
color: theme.textColorScheme.action,
|
||||
),
|
||||
mouseCursor: SystemMouseCursors.click,
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () => _onGenerateInviteLink(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onGenerateInviteLink(BuildContext context) async {
|
||||
final inviteLink = context.read<WorkspaceMemberBloc>().state.inviteLink;
|
||||
if (inviteLink != null) {
|
||||
// show a dialog to confirm if the user wants to copy the link to the clipboard
|
||||
await showConfirmDialog(
|
||||
context: context,
|
||||
style: ConfirmPopupStyle.cancelAndOk,
|
||||
title: LocaleKeys.settings_appearance_members_resetInviteLink.tr(),
|
||||
description: LocaleKeys
|
||||
.settings_appearance_members_resetInviteLinkDescription
|
||||
.tr(),
|
||||
confirmLabel: LocaleKeys.settings_appearance_members_reset.tr(),
|
||||
onConfirm: () {
|
||||
context.read<WorkspaceMemberBloc>().add(
|
||||
const WorkspaceMemberEvent.generateInviteLink(),
|
||||
);
|
||||
},
|
||||
confirmButtonBuilder: (dialogContext) => AFFilledTextButton.destructive(
|
||||
size: UniversalPlatform.isDesktop ? AFButtonSize.m : AFButtonSize.l,
|
||||
text: LocaleKeys.settings_appearance_members_reset.tr(),
|
||||
onTap: () {
|
||||
context.read<WorkspaceMemberBloc>().add(
|
||||
const WorkspaceMemberEvent.generateInviteLink(),
|
||||
);
|
||||
|
||||
Navigator.of(dialogContext).pop();
|
||||
},
|
||||
),
|
||||
);
|
||||
} else {
|
||||
context.read<WorkspaceMemberBloc>().add(
|
||||
const WorkspaceMemberEvent.generateInviteLink(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _CopyLinkButton extends StatelessWidget {
|
||||
const _CopyLinkButton();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = AppFlowyTheme.of(context);
|
||||
return AFOutlinedTextButton.normal(
|
||||
size: AFButtonSize.l,
|
||||
alignment: Alignment.center,
|
||||
text: LocaleKeys.button_copyLink.tr(),
|
||||
textStyle: theme.textStyle.heading4.enhanced(
|
||||
color: theme.textColorScheme.primary,
|
||||
),
|
||||
onTap: () {
|
||||
final link = context.read<WorkspaceMemberBloc>().state.inviteLink;
|
||||
if (link != null) {
|
||||
getIt<ClipboardService>().setData(
|
||||
ClipboardServiceData(
|
||||
plainText: link,
|
||||
),
|
||||
);
|
||||
|
||||
showToastNotification(
|
||||
message: LocaleKeys.document_inlineLink_copyLink.tr(),
|
||||
);
|
||||
} else {
|
||||
showToastNotification(
|
||||
message: 'You haven\'t generated an invite link yet.',
|
||||
type: ToastificationType.error,
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -66,7 +66,15 @@ class MemberHttpService {
|
||||
|
||||
try {
|
||||
return result.fold(
|
||||
(data) => FlowyResult.success(data['code'] as String),
|
||||
(data) {
|
||||
final code = data['data']['code'] as String;
|
||||
if (code.isEmpty) {
|
||||
return FlowyResult.failure(
|
||||
FlowyError(msg: 'Failed to get invite code: $code'),
|
||||
);
|
||||
}
|
||||
return FlowyResult.success(code);
|
||||
},
|
||||
(error) => FlowyResult.failure(error),
|
||||
);
|
||||
} catch (e) {
|
||||
|
||||
@ -52,6 +52,11 @@ class WorkspaceMemberBloc
|
||||
updateSubscriptionInfo: (info) async =>
|
||||
_onUpdateSubscriptionInfo(emit, info),
|
||||
upgradePlan: () async => _onUpgradePlan(),
|
||||
updateInviteLink: (inviteLink) async => emit(
|
||||
state.copyWith(
|
||||
inviteLink: inviteLink,
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
@ -90,7 +95,7 @@ class WorkspaceMemberBloc
|
||||
_memberHttpService?.getInviteCode(workspaceId: _workspaceId).fold(
|
||||
(s) async {
|
||||
final inviteLink = await _buildInviteLink(inviteCode: s);
|
||||
emit(state.copyWith(inviteLink: inviteLink));
|
||||
add(WorkspaceMemberEvent.updateInviteLink(inviteLink));
|
||||
},
|
||||
(e) => Log.info('Failed to get invite code: ${e.msg}', e),
|
||||
),
|
||||
@ -371,6 +376,9 @@ class WorkspaceMemberEvent with _$WorkspaceMemberEvent {
|
||||
) = UpdateSubscriptionInfo;
|
||||
|
||||
const factory WorkspaceMemberEvent.upgradePlan() = UpgradePlan;
|
||||
|
||||
const factory WorkspaceMemberEvent.updateInviteLink(String inviteLink) =
|
||||
UpdateInviteLink;
|
||||
}
|
||||
|
||||
enum WorkspaceMemberActionType {
|
||||
|
||||
@ -2,6 +2,7 @@ import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/util/built_in_svgs.dart';
|
||||
import 'package:appflowy/util/color_generator/color_generator.dart';
|
||||
import 'package:appflowy_ui/appflowy_ui.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/size.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
@ -42,6 +43,7 @@ class UserAvatar extends StatelessWidget {
|
||||
}
|
||||
|
||||
Widget _buildEmptyAvatar(BuildContext context) {
|
||||
final theme = AppFlowyTheme.of(context);
|
||||
final String nameOrDefault = _userName(name);
|
||||
final Color color = ColorGenerator(name).toColor();
|
||||
const initialsCount = 2;
|
||||
@ -69,10 +71,11 @@ class UserAvatar extends StatelessWidget {
|
||||
)
|
||||
: null,
|
||||
),
|
||||
child: FlowyText.medium(
|
||||
child: Text(
|
||||
nameInitials,
|
||||
color: Colors.black,
|
||||
fontSize: fontSize,
|
||||
style: theme.textStyle.caption
|
||||
.standard(color: theme.textColorScheme.primary)
|
||||
.copyWith(fontSize: fontSize),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@ -144,28 +144,28 @@ EXTERNAL SOURCES:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/window_manager/macos
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
app_links: 10e0a0ab602ffaf34d142cd4862f29d34b303b2a
|
||||
appflowy_backend: 865496343de667fc8c600e04b9fd05234e130cf9
|
||||
auto_updater_macos: 3e3462c418fe4e731917eacd8d28eef7af84086d
|
||||
bitsdojo_window_macos: 44e3b8fe3dd463820e0321f6256c5b1c16bb6a00
|
||||
connectivity_plus: 18d3c32514c886e046de60e9c13895109866c747
|
||||
desktop_drop: 69eeff437544aa619c8db7f4481b3a65f7696898
|
||||
device_info_plus: 1b14eed9bf95428983aed283a8d51cce3d8c4215
|
||||
file_selector_macos: cc3858c981fe6889f364731200d6232dac1d812d
|
||||
flowy_infra_ui: 03301a39ad118771adbf051a664265c61c507f38
|
||||
app_links: 9028728e32c83a0831d9db8cf91c526d16cc5468
|
||||
appflowy_backend: 464aeb3e5c6966a41641a2111e5ead72ce2695f7
|
||||
auto_updater_macos: 3a42f1a06be6981f1a18be37e6e7bf86aa732118
|
||||
bitsdojo_window_macos: 7959fb0ca65a3ccda30095c181ecb856fae48ea9
|
||||
connectivity_plus: e74b9f74717d2d99d45751750e266e55912baeb5
|
||||
desktop_drop: e0b672a7d84c0a6cbc378595e82cdb15f2970a43
|
||||
device_info_plus: 4fb280989f669696856f8b129e4a5e3cd6c48f76
|
||||
file_selector_macos: 6280b52b459ae6c590af5d78fc35c7267a3c4b31
|
||||
flowy_infra_ui: 8760ff42a789de40bf5007a5f176b454722a341e
|
||||
FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
|
||||
HotKey: 400beb7caa29054ea8d864c96f5ba7e5b4852277
|
||||
hotkey_manager: c32bf0bfe8f934b7bc17ab4ad5c4c142960b023c
|
||||
irondash_engine_context: da62996ee25616d2f01bbeb85dc115d813359478
|
||||
local_notifier: e9506bc66fc70311e8bc7291fb70f743c081e4ff
|
||||
package_info_plus: 12f1c5c2cfe8727ca46cbd0b26677728972d9a5b
|
||||
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
|
||||
hotkey_manager: b443f35f4d772162937aa73fd8995e579f8ac4e2
|
||||
irondash_engine_context: 893c7d96d20ce361d7e996f39d360c4c2f9869ba
|
||||
local_notifier: ebf072651e35ae5e47280ad52e2707375cb2ae4e
|
||||
package_info_plus: f0052d280d17aa382b932f399edf32507174e870
|
||||
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
|
||||
ReachabilitySwift: 32793e867593cfc1177f5d16491e3a197d2fccda
|
||||
screen_retriever_macos: 776e0fa5d42c6163d2bf772d22478df4b302b161
|
||||
screen_retriever_macos: 452e51764a9e1cdb74b3c541238795849f21557f
|
||||
Sentry: 1fe34e9c2cbba1e347623610d26db121dcb569f1
|
||||
sentry_flutter: a39c2a2d67d5e5b9cb0b94a4985c76dd5b3fc737
|
||||
share_plus: 1fa619de8392a4398bfaf176d441853922614e89
|
||||
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
|
||||
sentry_flutter: e24b397f9a61fa5bbefd8279c3b2242ca86faa90
|
||||
share_plus: 510bf0af1a42cd602274b4629920c9649c52f4cc
|
||||
shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
|
||||
Sparkle: 5f8960a7a119aa7d45dacc0d5837017170bc5675
|
||||
sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d
|
||||
super_native_extensions: 85efee3a7495b46b04befcfc86ed12069264ebf3
|
||||
|
||||
@ -15,8 +15,7 @@ class Log {
|
||||
bool enableFlutterLog = true;
|
||||
|
||||
// used to disable log in tests
|
||||
@visibleForTesting
|
||||
bool disableLog = true;
|
||||
bool disableLog = false;
|
||||
|
||||
Log() {
|
||||
_logger = Talker(
|
||||
|
||||
@ -25,7 +25,7 @@ class AFDivider extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = AppFlowyTheme.of(context);
|
||||
final color = this.color ?? theme.borderColorScheme.greyTertiary;
|
||||
final color = this.color ?? theme.borderColorScheme.primary;
|
||||
|
||||
return switch (axis) {
|
||||
Axis.horizontal => Container(
|
||||
|
||||
@ -1354,7 +1354,7 @@
|
||||
"inviteMemberByEmail": "Invite member by email",
|
||||
"inviteMemberHintText": "Invite by email",
|
||||
"resetInviteLink": "Reset the invite link?",
|
||||
"resetInviteLinkDescription": "Resetting will deactivate the current link for all space members and generate a new one. The previous link can only be managed through the",
|
||||
"resetInviteLinkDescription": "Resetting will deactivate the current link for all space members and generate a new one. The old link will no longer be available.",
|
||||
"adminPanel": "Admin Panel",
|
||||
"reset": "Reset",
|
||||
"resetInviteLinkSuccess": "Invite link reset successfully",
|
||||
|
||||
Loading…
Reference in New Issue
Block a user