mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2025-12-25 14:14:40 +00:00
feat: support workspace ops on mobile (#6449)
* feat: support workspace ops on mobile * chore: move the member bloc to workspace menu item widget * feat: support creating workspace on mobile * chore: add popToHome extension * fix: flutter analyze * feat: support renaming a workspace * feat: support deleting a workspace * feat: support leaving a workspace * feat: workspace icon ui revamp * feat: support updating workspace icon on mobile * feat: show a confirm dialog before deleting a workspace * fix: workspace name overflow * feat: support leaving a workspace * chore: update translations * feat: show a toast after renaming workspace * feat: update translations * feat: add workspace operation integration tests on mobile * test: add create workspace test on mobile
This commit is contained in:
parent
23e3650570
commit
813c8e6b86
36
.github/workflows/flutter_ci.yaml
vendored
36
.github/workflows/flutter_ci.yaml
vendored
@ -39,7 +39,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
os: [ ubuntu-latest ]
|
||||
os: [ubuntu-latest]
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
flutter_profile: development-linux-x86_64
|
||||
@ -73,7 +73,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
os: [ windows-latest ]
|
||||
os: [windows-latest]
|
||||
include:
|
||||
- os: windows-latest
|
||||
flutter_profile: development-windows-x86
|
||||
@ -100,7 +100,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
os: [ macos-latest ]
|
||||
os: [macos-latest]
|
||||
include:
|
||||
- os: macos-latest
|
||||
flutter_profile: development-mac-x86_64
|
||||
@ -122,12 +122,12 @@ jobs:
|
||||
flutter_profile: ${{ matrix.flutter_profile }}
|
||||
|
||||
unit_test:
|
||||
needs: [ prepare-linux ]
|
||||
needs: [prepare-linux]
|
||||
if: github.event.pull_request.draft != true
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ ubuntu-latest ]
|
||||
os: [ubuntu-latest]
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
flutter_profile: development-linux-x86_64
|
||||
@ -216,11 +216,11 @@ jobs:
|
||||
shell: bash
|
||||
|
||||
cloud_integration_test:
|
||||
needs: [ prepare-linux ]
|
||||
needs: [prepare-linux]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ ubuntu-latest ]
|
||||
os: [ubuntu-latest]
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
flutter_profile: development-linux-x86_64
|
||||
@ -317,20 +317,20 @@ jobs:
|
||||
sudo Xvfb -ac :99 -screen 0 1280x1024x24 > /dev/null 2>&1 &
|
||||
sudo apt-get install network-manager
|
||||
docker ps -a
|
||||
flutter test integration_test/cloud/cloud_runner.dart -d Linux --coverage
|
||||
flutter test integration_test/desktop/cloud/cloud_runner.dart -d Linux --coverage
|
||||
shell: bash
|
||||
|
||||
# split the integration tests into different machines to minimize the time
|
||||
integration_test_1:
|
||||
needs: [ prepare-linux ]
|
||||
needs: [prepare-linux]
|
||||
if: github.event.pull_request.draft != true
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ ubuntu-latest ]
|
||||
os: [ubuntu-latest]
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
target: 'x86_64-unknown-linux-gnu'
|
||||
target: "x86_64-unknown-linux-gnu"
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Checkout source code
|
||||
@ -352,15 +352,15 @@ jobs:
|
||||
rust_target: ${{ matrix.target }}
|
||||
|
||||
integration_test_2:
|
||||
needs: [ prepare-linux ]
|
||||
needs: [prepare-linux]
|
||||
if: github.event.pull_request.draft != true
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ ubuntu-latest ]
|
||||
os: [ubuntu-latest]
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
target: 'x86_64-unknown-linux-gnu'
|
||||
target: "x86_64-unknown-linux-gnu"
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Checkout source code
|
||||
@ -382,15 +382,15 @@ jobs:
|
||||
rust_target: ${{ matrix.target }}
|
||||
|
||||
integration_test_3:
|
||||
needs: [ prepare-linux ]
|
||||
needs: [prepare-linux]
|
||||
if: github.event.pull_request.draft != true
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ ubuntu-latest ]
|
||||
os: [ubuntu-latest]
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
target: 'x86_64-unknown-linux-gnu'
|
||||
target: "x86_64-unknown-linux-gnu"
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Checkout source code
|
||||
@ -409,4 +409,4 @@ jobs:
|
||||
flutter_version: ${{ env.FLUTTER_VERSION }}
|
||||
rust_toolchain: ${{ env.RUST_TOOLCHAIN }}
|
||||
cargo_make_version: ${{ env.CARGO_MAKE_VERSION }}
|
||||
rust_target: ${{ matrix.target }}
|
||||
rust_target: ${{ matrix.target }}
|
||||
|
||||
@ -1,93 +0,0 @@
|
||||
// import 'package:appflowy/env/cloud_env.dart';
|
||||
// import 'package:appflowy/workspace/application/settings/prelude.dart';
|
||||
// import 'package:appflowy/workspace/presentation/settings/pages/settings_account_view.dart';
|
||||
// import 'package:appflowy/workspace/presentation/settings/widgets/setting_supabase_cloud.dart';
|
||||
// import 'package:flutter_test/flutter_test.dart';
|
||||
// import 'package:integration_test/integration_test.dart';
|
||||
|
||||
// import '../shared/util.dart';
|
||||
|
||||
// void main() {
|
||||
// IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
// group('supabase auth', () {
|
||||
// testWidgets('sign in with supabase', (tester) async {
|
||||
// await tester.initializeAppFlowy(cloudType: AuthenticatorType.supabase);
|
||||
// await tester.tapGoogleLoginInButton();
|
||||
// await tester.expectToSeeHomePageWithGetStartedPage();
|
||||
// });
|
||||
|
||||
// testWidgets('sign out with supabase', (tester) async {
|
||||
// await tester.initializeAppFlowy(cloudType: AuthenticatorType.supabase);
|
||||
// await tester.tapGoogleLoginInButton();
|
||||
|
||||
// // Open the setting page and sign out
|
||||
// await tester.openSettings();
|
||||
// await tester.openSettingsPage(SettingsPage.account);
|
||||
// await tester.logout();
|
||||
|
||||
// // Go to the sign in page again
|
||||
// await tester.pumpAndSettle(const Duration(seconds: 1));
|
||||
// tester.expectToSeeGoogleLoginButton();
|
||||
// });
|
||||
|
||||
// testWidgets('sign in as anonymous', (tester) async {
|
||||
// await tester.initializeAppFlowy(cloudType: AuthenticatorType.supabase);
|
||||
// await tester.tapSignInAsGuest();
|
||||
|
||||
// // should not see the sync setting page when sign in as anonymous
|
||||
// await tester.openSettings();
|
||||
// await tester.openSettingsPage(SettingsPage.account);
|
||||
|
||||
// // Scroll to sign-out
|
||||
// await tester.scrollUntilVisible(
|
||||
// find.byType(SignInOutButton),
|
||||
// 100,
|
||||
// scrollable: find.findSettingsScrollable(),
|
||||
// );
|
||||
// await tester.tapButton(find.byType(SignInOutButton));
|
||||
|
||||
// tester.expectToSeeGoogleLoginButton();
|
||||
// });
|
||||
|
||||
// // testWidgets('enable encryption', (tester) async {
|
||||
// // await tester.initializeAppFlowy(cloudType: CloudType.supabase);
|
||||
// // await tester.tapGoogleLoginInButton();
|
||||
|
||||
// // // Open the setting page and sign out
|
||||
// // await tester.openSettings();
|
||||
// // await tester.openSettingsPage(SettingsPage.cloud);
|
||||
|
||||
// // // the switch should be off by default
|
||||
// // tester.assertEnableEncryptSwitchValue(false);
|
||||
// // await tester.toggleEnableEncrypt();
|
||||
|
||||
// // // the switch should be on after toggling
|
||||
// // tester.assertEnableEncryptSwitchValue(true);
|
||||
|
||||
// // // the switch can not be toggled back to off
|
||||
// // await tester.toggleEnableEncrypt();
|
||||
// // tester.assertEnableEncryptSwitchValue(true);
|
||||
// // });
|
||||
|
||||
// testWidgets('enable sync', (tester) async {
|
||||
// await tester.initializeAppFlowy(cloudType: AuthenticatorType.supabase);
|
||||
// await tester.tapGoogleLoginInButton();
|
||||
|
||||
// // Open the setting page and sign out
|
||||
// await tester.openSettings();
|
||||
// await tester.openSettingsPage(SettingsPage.cloud);
|
||||
|
||||
// // the switch should be on by default
|
||||
// tester.assertSupabaseEnableSyncSwitchValue(true);
|
||||
// await tester.toggleEnableSync(SupabaseEnableSync);
|
||||
|
||||
// // the switch should be off
|
||||
// tester.assertSupabaseEnableSyncSwitchValue(false);
|
||||
|
||||
// // the switch should be on after toggling
|
||||
// await tester.toggleEnableSync(SupabaseEnableSync);
|
||||
// tester.assertSupabaseEnableSyncSwitchValue(true);
|
||||
// });
|
||||
// });
|
||||
// }
|
||||
@ -1,9 +1,10 @@
|
||||
import 'anon_user_continue_test.dart' as anon_user_continue_test;
|
||||
import 'appflowy_cloud_auth_test.dart' as appflowy_cloud_auth_test;
|
||||
import 'document/document_drag_block_test.dart' as document_drag_block_test;
|
||||
import 'empty_test.dart' as preset_af_cloud_env_test;
|
||||
import 'sidebar/sidebar_move_page_test.dart' as sidebar_move_page_test;
|
||||
import 'user_setting_sync_test.dart' as user_sync_test;
|
||||
import 'uncategorized/anon_user_continue_test.dart' as anon_user_continue_test;
|
||||
import 'uncategorized/appflowy_cloud_auth_test.dart'
|
||||
as appflowy_cloud_auth_test;
|
||||
import 'uncategorized/empty_test.dart' as preset_af_cloud_env_test;
|
||||
import 'uncategorized/user_setting_sync_test.dart' as user_sync_test;
|
||||
import 'workspace/change_name_and_icon_test.dart'
|
||||
as change_workspace_name_and_icon_test;
|
||||
import 'workspace/collaborative_workspace_test.dart'
|
||||
@ -7,8 +7,8 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import '../../shared/constants.dart';
|
||||
import '../../shared/util.dart';
|
||||
import '../../../shared/constants.dart';
|
||||
import '../../../shared/util.dart';
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
@ -3,8 +3,8 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import '../../shared/constants.dart';
|
||||
import '../../shared/util.dart';
|
||||
import '../../../shared/constants.dart';
|
||||
import '../../../shared/util.dart';
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
@ -24,12 +24,12 @@ import 'package:integration_test/integration_test.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:universal_platform/universal_platform.dart';
|
||||
|
||||
import '../../shared/constants.dart';
|
||||
import '../../shared/database_test_op.dart';
|
||||
import '../../shared/dir.dart';
|
||||
import '../../shared/emoji.dart';
|
||||
import '../../shared/mock/mock_file_picker.dart';
|
||||
import '../../shared/util.dart';
|
||||
import '../../../shared/constants.dart';
|
||||
import '../../../shared/database_test_op.dart';
|
||||
import '../../../shared/dir.dart';
|
||||
import '../../../shared/emoji.dart';
|
||||
import '../../../shared/mock/mock_file_picker.dart';
|
||||
import '../../../shared/util.dart';
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
@ -24,10 +24,10 @@ import 'package:integration_test/integration_test.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
import '../desktop/board/board_hide_groups_test.dart';
|
||||
import '../shared/dir.dart';
|
||||
import '../shared/mock/mock_file_picker.dart';
|
||||
import '../shared/util.dart';
|
||||
import '../../../shared/dir.dart';
|
||||
import '../../../shared/mock/mock_file_picker.dart';
|
||||
import '../../../shared/util.dart';
|
||||
import '../../board/board_hide_groups_test.dart';
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
@ -15,8 +15,8 @@ import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
import '../shared/mock/mock_file_picker.dart';
|
||||
import '../shared/util.dart';
|
||||
import '../../../shared/mock/mock_file_picker.dart';
|
||||
import '../../../shared/util.dart';
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
@ -17,9 +17,9 @@ import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
import '../shared/dir.dart';
|
||||
import '../shared/mock/mock_file_picker.dart';
|
||||
import '../shared/util.dart';
|
||||
import '../../../shared/dir.dart';
|
||||
import '../../../shared/mock/mock_file_picker.dart';
|
||||
import '../../../shared/util.dart';
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
@ -2,7 +2,7 @@ import 'package:appflowy/env/cloud_env.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import '../shared/util.dart';
|
||||
import '../../../shared/util.dart';
|
||||
|
||||
// This test is meaningless, just for preventing the CI from failing.
|
||||
void main() {
|
||||
@ -22,12 +22,12 @@ import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
import '../desktop/board/board_hide_groups_test.dart';
|
||||
import '../shared/database_test_op.dart';
|
||||
import '../shared/dir.dart';
|
||||
import '../shared/emoji.dart';
|
||||
import '../shared/mock/mock_file_picker.dart';
|
||||
import '../shared/util.dart';
|
||||
import '../../../shared/database_test_op.dart';
|
||||
import '../../../shared/dir.dart';
|
||||
import '../../../shared/emoji.dart';
|
||||
import '../../../shared/mock/mock_file_picker.dart';
|
||||
import '../../../shared/util.dart';
|
||||
import '../../board/board_hide_groups_test.dart';
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
@ -15,9 +15,9 @@ import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
import '../../shared/mock/mock_file_picker.dart';
|
||||
import '../../shared/util.dart';
|
||||
import '../../shared/workspace.dart';
|
||||
import '../../../shared/mock/mock_file_picker.dart';
|
||||
import '../../../shared/util.dart';
|
||||
import '../../../shared/workspace.dart';
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
@ -23,11 +23,11 @@ import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
import '../../shared/database_test_op.dart';
|
||||
import '../../shared/dir.dart';
|
||||
import '../../shared/emoji.dart';
|
||||
import '../../shared/mock/mock_file_picker.dart';
|
||||
import '../../shared/util.dart';
|
||||
import '../../../shared/database_test_op.dart';
|
||||
import '../../../shared/dir.dart';
|
||||
import '../../../shared/emoji.dart';
|
||||
import '../../../shared/mock/mock_file_picker.dart';
|
||||
import '../../../shared/util.dart';
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
@ -27,11 +27,11 @@ import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
import '../../shared/database_test_op.dart';
|
||||
import '../../shared/dir.dart';
|
||||
import '../../shared/emoji.dart';
|
||||
import '../../shared/mock/mock_file_picker.dart';
|
||||
import '../../shared/util.dart';
|
||||
import '../../../shared/database_test_op.dart';
|
||||
import '../../../shared/dir.dart';
|
||||
import '../../../shared/emoji.dart';
|
||||
import '../../../shared/mock/mock_file_picker.dart';
|
||||
import '../../../shared/util.dart';
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
@ -0,0 +1,5 @@
|
||||
import 'workspace/workspace_operations_test.dart' as workspace_operations_test;
|
||||
|
||||
Future<void> main() async {
|
||||
workspace_operations_test.main();
|
||||
}
|
||||
@ -0,0 +1,67 @@
|
||||
// ignore_for_file: unused_import
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:appflowy/env/cloud_env.dart';
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/application/page_style/document_page_style_bloc.dart';
|
||||
import 'package:appflowy/mobile/presentation/base/app_bar/app_bar_actions.dart';
|
||||
import 'package:appflowy/mobile/presentation/base/view_page/app_bar_buttons.dart';
|
||||
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet_buttons.dart';
|
||||
import 'package:appflowy/mobile/presentation/home/home.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_page.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/cover/document_immersive_cover.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/cover/document_immersive_cover_bloc.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/page_style/_page_style_layout.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/user/application/auth/af_cloud_mock_auth_service.dart';
|
||||
import 'package:appflowy/user/application/auth/auth_service.dart';
|
||||
import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/widgets.dart';
|
||||
import 'package:appflowy/workspace/application/settings/prelude.dart';
|
||||
import 'package:appflowy/workspace/application/view/view_ext.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/widgets/setting_appflowy_cloud.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/uuid.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
import '../../../shared/constants.dart';
|
||||
import '../../../shared/dir.dart';
|
||||
import '../../../shared/mock/mock_file_picker.dart';
|
||||
import '../../../shared/util.dart';
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('workspace operations:', () {
|
||||
testWidgets('create a new workspace', (tester) async {
|
||||
await tester.initializeAppFlowy(
|
||||
cloudType: AuthenticatorType.appflowyCloudSelfHost,
|
||||
);
|
||||
await tester.tapGoogleLoginInButton();
|
||||
await tester.expectToSeeHomePageWithGetStartedPage();
|
||||
|
||||
// click the create a new workspace button
|
||||
await tester.tapButton(find.text(Constants.defaultWorkspaceName));
|
||||
await tester.tapButton(find.text(LocaleKeys.workspace_create.tr()));
|
||||
|
||||
// input the new workspace name
|
||||
final inputField = find.byType(TextFormField);
|
||||
const newWorkspaceName = 'AppFlowy';
|
||||
await tester.enterText(inputField, newWorkspaceName);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// wait for the workspace to be created
|
||||
await tester.pumpUntilFound(
|
||||
find.text(LocaleKeys.workspace_createSuccess.tr()),
|
||||
);
|
||||
|
||||
// expect to see the new workspace
|
||||
expect(find.text(newWorkspaceName), findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import 'mobile/document/page_style_test.dart' as page_style_test;
|
||||
import 'mobile/home_page/create_new_page_test.dart' as create_new_page_test;
|
||||
import 'mobile/sign_in/anonymous_sign_in_test.dart' as anonymous_sign_in_test;
|
||||
|
||||
@ -7,4 +8,5 @@ Future<void> runIntegrationOnMobile() async {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
anonymous_sign_in_test.main();
|
||||
create_new_page_test.main();
|
||||
page_style_test.main();
|
||||
}
|
||||
|
||||
@ -3,4 +3,6 @@ class Constants {
|
||||
static const gettingStartedPageName = 'Getting started';
|
||||
static const toDosPageName = 'To-dos';
|
||||
static const generalSpaceName = 'General';
|
||||
|
||||
static const defaultWorkspaceName = 'My Workspace';
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/page_item/mobile_view_item.dart';
|
||||
import 'package:appflowy/mobile/presentation/presentation.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/row/row_detail.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/banner.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/header/document_cover_widget.dart';
|
||||
@ -25,9 +26,15 @@ const String gettingStarted = 'Getting started';
|
||||
extension Expectation on WidgetTester {
|
||||
/// Expect to see the home page and with a default read me page.
|
||||
Future<void> expectToSeeHomePageWithGetStartedPage() async {
|
||||
final finder = find.byType(HomeStack);
|
||||
await pumpUntilFound(finder);
|
||||
expect(finder, findsOneWidget);
|
||||
if (UniversalPlatform.isDesktopOrWeb) {
|
||||
final finder = find.byType(HomeStack);
|
||||
await pumpUntilFound(finder);
|
||||
expect(finder, findsOneWidget);
|
||||
} else if (UniversalPlatform.isMobile) {
|
||||
final finder = find.byType(MobileHomePage);
|
||||
await pumpUntilFound(finder);
|
||||
expect(finder, findsOneWidget);
|
||||
}
|
||||
|
||||
final docFinder = find.textContaining(gettingStarted);
|
||||
await pumpUntilFound(docFinder);
|
||||
|
||||
@ -291,7 +291,44 @@ class _HomePageState extends State<_HomePage> {
|
||||
},
|
||||
);
|
||||
break;
|
||||
|
||||
case UserWorkspaceActionType.delete:
|
||||
message = result.fold(
|
||||
(s) {
|
||||
toastType = ToastificationType.success;
|
||||
return LocaleKeys.workspace_deleteSuccess.tr();
|
||||
},
|
||||
(e) {
|
||||
toastType = ToastificationType.error;
|
||||
return '${LocaleKeys.workspace_deleteFailed.tr()}: ${e.msg}';
|
||||
},
|
||||
);
|
||||
break;
|
||||
case UserWorkspaceActionType.leave:
|
||||
message = result.fold(
|
||||
(s) {
|
||||
toastType = ToastificationType.success;
|
||||
return LocaleKeys
|
||||
.settings_workspacePage_leaveWorkspacePrompt_success
|
||||
.tr();
|
||||
},
|
||||
(e) {
|
||||
toastType = ToastificationType.error;
|
||||
return '${LocaleKeys.settings_workspacePage_leaveWorkspacePrompt_fail.tr()}: ${e.msg}';
|
||||
},
|
||||
);
|
||||
break;
|
||||
case UserWorkspaceActionType.rename:
|
||||
message = result.fold(
|
||||
(s) {
|
||||
toastType = ToastificationType.success;
|
||||
return LocaleKeys.workspace_renameSuccess.tr();
|
||||
},
|
||||
(e) {
|
||||
toastType = ToastificationType.error;
|
||||
return '${LocaleKeys.workspace_renameFailed.tr()}: ${e.msg}';
|
||||
},
|
||||
);
|
||||
break;
|
||||
default:
|
||||
message = null;
|
||||
toastType = ToastificationType.error;
|
||||
|
||||
@ -121,30 +121,32 @@ class _MobileWorkspace extends StatelessWidget {
|
||||
},
|
||||
child: Row(
|
||||
children: [
|
||||
SizedBox.square(
|
||||
dimension: currentWorkspace.icon.isNotEmpty ? 34.0 : 26.0,
|
||||
child: WorkspaceIcon(
|
||||
workspace: currentWorkspace,
|
||||
iconSize: 26,
|
||||
fontSize: 16.0,
|
||||
enableEdit: false,
|
||||
alignment: Alignment.centerLeft,
|
||||
figmaLineHeight: 16.0,
|
||||
onSelected: (result) => context.read<UserWorkspaceBloc>().add(
|
||||
UserWorkspaceEvent.updateWorkspaceIcon(
|
||||
currentWorkspace.workspaceId,
|
||||
result.emoji,
|
||||
),
|
||||
WorkspaceIconV2(
|
||||
workspace: currentWorkspace,
|
||||
iconSize: 36,
|
||||
fontSize: 18.0,
|
||||
enableEdit: true,
|
||||
alignment: Alignment.centerLeft,
|
||||
figmaLineHeight: 26.0,
|
||||
emojiSize: 24.0,
|
||||
borderRadius: 12.0,
|
||||
showBorder: false,
|
||||
onSelected: (result) => context.read<UserWorkspaceBloc>().add(
|
||||
UserWorkspaceEvent.updateWorkspaceIcon(
|
||||
currentWorkspace.workspaceId,
|
||||
result.emoji,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
currentWorkspace.icon.isNotEmpty
|
||||
? const HSpace(2)
|
||||
: const HSpace(8),
|
||||
FlowyText.semibold(
|
||||
currentWorkspace.name,
|
||||
fontSize: 20.0,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
Flexible(
|
||||
child: FlowyText.semibold(
|
||||
currentWorkspace.name,
|
||||
fontSize: 20.0,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@ -0,0 +1,113 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
enum EditWorkspaceNameType {
|
||||
create,
|
||||
edit;
|
||||
|
||||
String get title {
|
||||
switch (this) {
|
||||
case EditWorkspaceNameType.create:
|
||||
return LocaleKeys.workspace_create.tr();
|
||||
case EditWorkspaceNameType.edit:
|
||||
return LocaleKeys.workspace_renameWorkspace.tr();
|
||||
}
|
||||
}
|
||||
|
||||
String get actionTitle {
|
||||
switch (this) {
|
||||
case EditWorkspaceNameType.create:
|
||||
return LocaleKeys.workspace_create.tr();
|
||||
case EditWorkspaceNameType.edit:
|
||||
return LocaleKeys.button_confirm.tr();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class EditWorkspaceNameBottomSheet extends StatefulWidget {
|
||||
const EditWorkspaceNameBottomSheet({
|
||||
super.key,
|
||||
required this.type,
|
||||
required this.onSubmitted,
|
||||
required this.workspaceName,
|
||||
});
|
||||
|
||||
final EditWorkspaceNameType type;
|
||||
final void Function(String) onSubmitted;
|
||||
|
||||
// if the workspace name is not empty, it will be used as the initial value of the text field.
|
||||
final String? workspaceName;
|
||||
|
||||
@override
|
||||
State<EditWorkspaceNameBottomSheet> createState() =>
|
||||
_EditWorkspaceNameBottomSheetState();
|
||||
}
|
||||
|
||||
class _EditWorkspaceNameBottomSheetState
|
||||
extends State<EditWorkspaceNameBottomSheet> {
|
||||
late final TextEditingController _textFieldController;
|
||||
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_textFieldController = TextEditingController(
|
||||
text: widget.workspaceName,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_textFieldController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
Form(
|
||||
key: _formKey,
|
||||
child: TextFormField(
|
||||
autofocus: true,
|
||||
controller: _textFieldController,
|
||||
keyboardType: TextInputType.text,
|
||||
decoration: InputDecoration(
|
||||
hintText: LocaleKeys.workspace_defaultName.tr(),
|
||||
),
|
||||
validator: (value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return LocaleKeys.workspace_workspaceNameCannotBeEmpty.tr();
|
||||
}
|
||||
return null;
|
||||
},
|
||||
onEditingComplete: _onSubmit,
|
||||
),
|
||||
),
|
||||
const VSpace(16),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: PrimaryRoundedButton(
|
||||
text: widget.type.actionTitle,
|
||||
fontSize: 16,
|
||||
margin: const EdgeInsets.symmetric(
|
||||
vertical: 16,
|
||||
),
|
||||
onTap: _onSubmit,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
void _onSubmit() {
|
||||
if (_formKey.currentState!.validate()) {
|
||||
final value = _textFieldController.text;
|
||||
widget.onSubmitted.call(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,16 +1,23 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/base/animated_gesture.dart';
|
||||
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
|
||||
import 'package:appflowy/mobile/presentation/widgets/widgets.dart';
|
||||
import 'package:appflowy/util/navigator_context_exntesion.dart';
|
||||
import 'package:appflowy/util/theme_extension.dart';
|
||||
import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_icon.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/widgets/members/workspace_member_bloc.dart';
|
||||
import 'package:appflowy_backend/log.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_bloc/flutter_bloc.dart';
|
||||
|
||||
import 'create_workspace_menu.dart';
|
||||
import 'workspace_more_options.dart';
|
||||
|
||||
// Only works on mobile.
|
||||
class MobileWorkspaceMenu extends StatelessWidget {
|
||||
const MobileWorkspaceMenu({
|
||||
@ -28,13 +35,13 @@ class MobileWorkspaceMenu extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// user profile
|
||||
final List<Widget> children = [
|
||||
_WorkspaceUserItem(userProfile: userProfile),
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 12, vertical: 10),
|
||||
child: Divider(height: 0.5),
|
||||
),
|
||||
_buildDivider(),
|
||||
];
|
||||
|
||||
// workspace list
|
||||
for (var i = 0; i < workspaces.length; i++) {
|
||||
final workspace = workspaces[i];
|
||||
children.add(
|
||||
@ -48,10 +55,97 @@ class MobileWorkspaceMenu extends StatelessWidget {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// create workspace button
|
||||
children.addAll([
|
||||
_buildDivider(),
|
||||
const _CreateWorkspaceButton(),
|
||||
]);
|
||||
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: children,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDivider() {
|
||||
return const Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 12, vertical: 10),
|
||||
child: Divider(height: 0.5),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _CreateWorkspaceButton extends StatelessWidget {
|
||||
const _CreateWorkspaceButton();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4.0),
|
||||
child: FlowyOptionTile.text(
|
||||
height: 60,
|
||||
showTopBorder: false,
|
||||
showBottomBorder: false,
|
||||
leftIcon: _buildLeftIcon(context),
|
||||
onTap: () => _showCreateWorkspaceBottomSheet(context),
|
||||
content: Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(left: 16.0),
|
||||
child: FlowyText.medium(
|
||||
LocaleKeys.workspace_create.tr(),
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _showCreateWorkspaceBottomSheet(BuildContext context) {
|
||||
showMobileBottomSheet(
|
||||
context,
|
||||
showHeader: true,
|
||||
title: LocaleKeys.workspace_create.tr(),
|
||||
showCloseButton: true,
|
||||
showDragHandle: true,
|
||||
showDivider: false,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
builder: (bottomSheetContext) {
|
||||
return EditWorkspaceNameBottomSheet(
|
||||
type: EditWorkspaceNameType.create,
|
||||
workspaceName: LocaleKeys.workspace_defaultName.tr(),
|
||||
onSubmitted: (name) {
|
||||
// create a new workspace
|
||||
Log.info('create a new workspace: $name');
|
||||
bottomSheetContext.popToHome();
|
||||
|
||||
context.read<UserWorkspaceBloc>().add(
|
||||
UserWorkspaceEvent.createWorkspace(
|
||||
name,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildLeftIcon(BuildContext context) {
|
||||
return Container(
|
||||
width: 36.0,
|
||||
height: 36.0,
|
||||
padding: const EdgeInsets.all(7.0),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(
|
||||
color: const Color(0x01717171).withOpacity(0.12),
|
||||
width: 0.8,
|
||||
),
|
||||
),
|
||||
child: const FlowySvg(FlowySvgs.add_workspace_s),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _WorkspaceUserItem extends StatelessWidget {
|
||||
@ -107,63 +201,286 @@ class _WorkspaceMenuItem extends StatelessWidget {
|
||||
)..add(const WorkspaceMemberEvent.initial()),
|
||||
child: BlocBuilder<WorkspaceMemberBloc, WorkspaceMemberState>(
|
||||
builder: (context, state) {
|
||||
final members = state.members;
|
||||
return FlowyOptionTile.text(
|
||||
content: Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(left: 12),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
FlowyText(
|
||||
workspace.name,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
FlowyText(
|
||||
state.isLoading
|
||||
? ''
|
||||
: LocaleKeys.settings_appearance_members_membersCount
|
||||
.plural(
|
||||
members.length,
|
||||
),
|
||||
fontSize: 10.0,
|
||||
color: Theme.of(context).hintColor,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
height: 60,
|
||||
showTopBorder: showTopBorder,
|
||||
showBottomBorder: false,
|
||||
leftIcon: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4.0),
|
||||
child: WorkspaceIcon(
|
||||
enableEdit: false,
|
||||
iconSize: 26,
|
||||
fontSize: 16.0,
|
||||
figmaLineHeight: 16.0,
|
||||
leftIcon: _WorkspaceMenuItemIcon(workspace: workspace),
|
||||
trailing: _WorkspaceMenuItemTrailing(
|
||||
workspace: workspace,
|
||||
currentWorkspace: currentWorkspace,
|
||||
),
|
||||
onTap: () => onWorkspaceSelected(workspace),
|
||||
content: Expanded(
|
||||
child: _WorkspaceMenuItemContent(
|
||||
workspace: workspace,
|
||||
onSelected: (result) => context.read<UserWorkspaceBloc>().add(
|
||||
UserWorkspaceEvent.updateWorkspaceIcon(
|
||||
workspace.workspaceId,
|
||||
result.emoji,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
trailing: workspace.workspaceId == currentWorkspace.workspaceId
|
||||
? const FlowySvg(
|
||||
FlowySvgs.m_blue_check_s,
|
||||
blendMode: null,
|
||||
)
|
||||
: null,
|
||||
onTap: () => onWorkspaceSelected(workspace),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// - Workspace name
|
||||
// - Workspace member count
|
||||
class _WorkspaceMenuItemContent extends StatelessWidget {
|
||||
const _WorkspaceMenuItemContent({
|
||||
required this.workspace,
|
||||
});
|
||||
|
||||
final UserWorkspacePB workspace;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(left: 12),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
FlowyText(
|
||||
workspace.name,
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
FlowyText(
|
||||
context.read<WorkspaceMemberBloc>().state.isLoading
|
||||
? ''
|
||||
: LocaleKeys.settings_appearance_members_membersCount.plural(
|
||||
context.read<WorkspaceMemberBloc>().state.members.length,
|
||||
),
|
||||
fontSize: 10.0,
|
||||
color: Theme.of(context).hintColor,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _WorkspaceMenuItemIcon extends StatelessWidget {
|
||||
const _WorkspaceMenuItemIcon({
|
||||
required this.workspace,
|
||||
});
|
||||
|
||||
final UserWorkspacePB workspace;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4.0),
|
||||
child: WorkspaceIconV2(
|
||||
enableEdit: false,
|
||||
iconSize: 36,
|
||||
emojiSize: 24.0,
|
||||
fontSize: 18.0,
|
||||
figmaLineHeight: 26.0,
|
||||
borderRadius: 12.0,
|
||||
workspace: workspace,
|
||||
onSelected: (result) => context.read<UserWorkspaceBloc>().add(
|
||||
UserWorkspaceEvent.updateWorkspaceIcon(
|
||||
workspace.workspaceId,
|
||||
result.emoji,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _WorkspaceMenuItemTrailing extends StatelessWidget {
|
||||
const _WorkspaceMenuItemTrailing({
|
||||
required this.workspace,
|
||||
required this.currentWorkspace,
|
||||
});
|
||||
|
||||
final UserWorkspacePB workspace;
|
||||
final UserWorkspacePB currentWorkspace;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
const iconSize = Size.square(20);
|
||||
return Row(
|
||||
children: [
|
||||
const HSpace(12.0),
|
||||
// show the check icon if the workspace is the current workspace
|
||||
if (workspace.workspaceId == currentWorkspace.workspaceId)
|
||||
const FlowySvg(
|
||||
FlowySvgs.m_blue_check_s,
|
||||
size: iconSize,
|
||||
blendMode: null,
|
||||
),
|
||||
const HSpace(15.0),
|
||||
// more options button
|
||||
AnimatedGestureDetector(
|
||||
onTapUp: () => _showMoreOptions(context),
|
||||
child: const FlowySvg(
|
||||
FlowySvgs.workspace_three_dots_s,
|
||||
size: iconSize,
|
||||
blendMode: null,
|
||||
),
|
||||
),
|
||||
const HSpace(8.0),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
void _showMoreOptions(BuildContext context) {
|
||||
final actions =
|
||||
context.read<WorkspaceMemberBloc>().state.myRole == AFRolePB.Owner
|
||||
? [
|
||||
// only the owner can update workspace properties
|
||||
WorkspaceMenuMoreOption.rename,
|
||||
WorkspaceMenuMoreOption.delete,
|
||||
]
|
||||
: [
|
||||
WorkspaceMenuMoreOption.leave,
|
||||
];
|
||||
|
||||
showMobileBottomSheet(
|
||||
context,
|
||||
showDragHandle: true,
|
||||
showDivider: false,
|
||||
useRootNavigator: true,
|
||||
backgroundColor: Theme.of(context).colorScheme.surface,
|
||||
builder: (bottomSheetContext) {
|
||||
return WorkspaceMenuMoreOptions(
|
||||
actions: actions,
|
||||
onAction: (action) => _onActions(context, bottomSheetContext, action),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _onActions(
|
||||
BuildContext context,
|
||||
BuildContext bottomSheetContext,
|
||||
WorkspaceMenuMoreOption action,
|
||||
) {
|
||||
Log.info('execute action in workspace menu bottom sheet: $action');
|
||||
|
||||
switch (action) {
|
||||
case WorkspaceMenuMoreOption.rename:
|
||||
_showRenameWorkspaceBottomSheet(context);
|
||||
break;
|
||||
case WorkspaceMenuMoreOption.invite:
|
||||
_pushToInviteMembersPage(context);
|
||||
break;
|
||||
case WorkspaceMenuMoreOption.delete:
|
||||
_deleteWorkspace(context, bottomSheetContext);
|
||||
break;
|
||||
case WorkspaceMenuMoreOption.leave:
|
||||
_leaveWorkspace(context, bottomSheetContext);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void _pushToInviteMembersPage(BuildContext context) {
|
||||
// empty implementation
|
||||
// we don't support invite members in workspace menu
|
||||
}
|
||||
|
||||
void _showRenameWorkspaceBottomSheet(BuildContext context) {
|
||||
showMobileBottomSheet(
|
||||
context,
|
||||
showHeader: true,
|
||||
title: LocaleKeys.workspace_renameWorkspace.tr(),
|
||||
showCloseButton: true,
|
||||
showDragHandle: true,
|
||||
showDivider: false,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
builder: (bottomSheetContext) {
|
||||
return EditWorkspaceNameBottomSheet(
|
||||
type: EditWorkspaceNameType.edit,
|
||||
workspaceName: workspace.name,
|
||||
onSubmitted: (name) {
|
||||
// rename the workspace
|
||||
Log.info('rename the workspace: $name');
|
||||
bottomSheetContext.popToHome();
|
||||
|
||||
context.read<UserWorkspaceBloc>().add(
|
||||
UserWorkspaceEvent.renameWorkspace(
|
||||
workspace.workspaceId,
|
||||
name,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _deleteWorkspace(BuildContext context, BuildContext bottomSheetContext) {
|
||||
Navigator.of(bottomSheetContext).pop();
|
||||
|
||||
_showConfirmDialog(
|
||||
context,
|
||||
'${LocaleKeys.space_delete.tr()}: ${workspace.name}',
|
||||
LocaleKeys.workspace_deleteWorkspaceHintText.tr(),
|
||||
LocaleKeys.button_delete.tr(),
|
||||
(_) async {
|
||||
context.read<UserWorkspaceBloc>().add(
|
||||
UserWorkspaceEvent.deleteWorkspace(
|
||||
workspace.workspaceId,
|
||||
),
|
||||
);
|
||||
context.popToHome();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _leaveWorkspace(BuildContext context, BuildContext bottomSheetContext) {
|
||||
Navigator.of(bottomSheetContext).pop();
|
||||
|
||||
_showConfirmDialog(
|
||||
context,
|
||||
'${LocaleKeys.settings_workspacePage_leaveWorkspacePrompt_title.tr()}: ${workspace.name}',
|
||||
LocaleKeys.settings_workspacePage_leaveWorkspacePrompt_content.tr(),
|
||||
LocaleKeys.button_confirm.tr(),
|
||||
(_) async {
|
||||
context.read<UserWorkspaceBloc>().add(
|
||||
UserWorkspaceEvent.leaveWorkspace(
|
||||
workspace.workspaceId,
|
||||
),
|
||||
);
|
||||
context.popToHome();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _showConfirmDialog(
|
||||
BuildContext context,
|
||||
String title,
|
||||
String content,
|
||||
String rightButtonText,
|
||||
void Function(BuildContext context)? onRightButtonPressed,
|
||||
) {
|
||||
showFlowyCupertinoConfirmDialog(
|
||||
title: title,
|
||||
content: FlowyText(
|
||||
content,
|
||||
fontSize: 14,
|
||||
color: Theme.of(context).hintColor,
|
||||
maxLines: 10,
|
||||
),
|
||||
leftButton: FlowyText(
|
||||
LocaleKeys.button_cancel.tr(),
|
||||
fontSize: 17.0,
|
||||
figmaLineHeight: 24.0,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: const Color(0xFF007AFF),
|
||||
),
|
||||
rightButton: FlowyText(
|
||||
rightButtonText,
|
||||
fontSize: 17.0,
|
||||
figmaLineHeight: 24.0,
|
||||
fontWeight: FontWeight.w400,
|
||||
color: const Color(0xFFFE0220),
|
||||
),
|
||||
onRightButtonPressed: onRightButtonPressed,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,108 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/widgets/widgets.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
enum WorkspaceMenuMoreOption {
|
||||
rename,
|
||||
invite,
|
||||
delete,
|
||||
leave,
|
||||
}
|
||||
|
||||
class WorkspaceMenuMoreOptions extends StatelessWidget {
|
||||
const WorkspaceMenuMoreOptions({
|
||||
super.key,
|
||||
this.isFavorite = false,
|
||||
required this.onAction,
|
||||
required this.actions,
|
||||
});
|
||||
|
||||
final bool isFavorite;
|
||||
final void Function(WorkspaceMenuMoreOption action) onAction;
|
||||
final List<WorkspaceMenuMoreOption> actions;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: actions
|
||||
.map(
|
||||
(action) => _buildActionButton(context, action),
|
||||
)
|
||||
.toList(),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildActionButton(
|
||||
BuildContext context,
|
||||
WorkspaceMenuMoreOption action,
|
||||
) {
|
||||
switch (action) {
|
||||
case WorkspaceMenuMoreOption.rename:
|
||||
return FlowyOptionTile.text(
|
||||
text: LocaleKeys.button_rename.tr(),
|
||||
height: 52.0,
|
||||
leftIcon: const FlowySvg(
|
||||
FlowySvgs.view_item_rename_s,
|
||||
size: Size.square(18),
|
||||
),
|
||||
showTopBorder: false,
|
||||
showBottomBorder: false,
|
||||
onTap: () => onAction(
|
||||
WorkspaceMenuMoreOption.rename,
|
||||
),
|
||||
);
|
||||
case WorkspaceMenuMoreOption.delete:
|
||||
return FlowyOptionTile.text(
|
||||
text: LocaleKeys.button_delete.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: () => onAction(
|
||||
WorkspaceMenuMoreOption.delete,
|
||||
),
|
||||
);
|
||||
case WorkspaceMenuMoreOption.invite:
|
||||
return FlowyOptionTile.text(
|
||||
// i18n
|
||||
text: 'Invite',
|
||||
height: 52.0,
|
||||
leftIcon: const FlowySvg(
|
||||
FlowySvgs.workspace_add_member_s,
|
||||
size: Size.square(18),
|
||||
),
|
||||
showTopBorder: false,
|
||||
showBottomBorder: false,
|
||||
onTap: () => onAction(
|
||||
WorkspaceMenuMoreOption.invite,
|
||||
),
|
||||
);
|
||||
case WorkspaceMenuMoreOption.leave:
|
||||
return FlowyOptionTile.text(
|
||||
text: LocaleKeys.workspace_leaveCurrentWorkspace.tr(),
|
||||
height: 52.0,
|
||||
textColor: Theme.of(context).colorScheme.error,
|
||||
leftIcon: FlowySvg(
|
||||
FlowySvgs.leave_workspace_s,
|
||||
size: const Size.square(18),
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
),
|
||||
showTopBorder: false,
|
||||
showBottomBorder: false,
|
||||
onTap: () => onAction(
|
||||
WorkspaceMenuMoreOption.leave,
|
||||
),
|
||||
);
|
||||
default:
|
||||
return const Placeholder();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -91,6 +91,7 @@ Future<T?> showFlowyMobileConfirmDialog<T>(
|
||||
Future<T?> showFlowyCupertinoConfirmDialog<T>({
|
||||
BuildContext? context,
|
||||
required String title,
|
||||
Widget? content,
|
||||
required Widget leftButton,
|
||||
required Widget rightButton,
|
||||
void Function(BuildContext context)? onLeftButtonPressed,
|
||||
@ -106,6 +107,7 @@ Future<T?> showFlowyCupertinoConfirmDialog<T>({
|
||||
maxLines: 10,
|
||||
figmaLineHeight: 22.0,
|
||||
),
|
||||
content: content,
|
||||
actions: [
|
||||
CupertinoDialogAction(
|
||||
onPressed: () {
|
||||
|
||||
@ -158,6 +158,7 @@ class _MobileThirdPartySignInState extends State<_MobileThirdPartySignIn> {
|
||||
const VSpace(padding),
|
||||
],
|
||||
MobileThirdPartySignInButton(
|
||||
key: signInWithGoogleButtonKey,
|
||||
type: ThirdPartySignInButtonType.google,
|
||||
onPressed: () => widget.onSignIn(ThirdPartySignInButtonType.google),
|
||||
),
|
||||
|
||||
@ -1,9 +1,28 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
// the color set generated from AI
|
||||
final _builtInColorSet = [
|
||||
(const Color(0xFF8A2BE2), const Color(0xFFF0E6FF)),
|
||||
(const Color(0xFF2E8B57), const Color(0xFFE0FFF0)),
|
||||
(const Color(0xFF1E90FF), const Color(0xFFE6F3FF)),
|
||||
(const Color(0xFFFF7F50), const Color(0xFFFFF0E6)),
|
||||
(const Color(0xFFFF69B4), const Color(0xFFFFE6F0)),
|
||||
(const Color(0xFF20B2AA), const Color(0xFFE0FFFF)),
|
||||
(const Color(0xFFDC143C), const Color(0xFFFFE6E6)),
|
||||
(const Color(0xFF8B4513), const Color(0xFFFFF0E6)),
|
||||
];
|
||||
|
||||
extension type ColorGenerator(String value) {
|
||||
Color toColor() {
|
||||
final int hash = value.codeUnits.fold(0, (int acc, int unit) => acc + unit);
|
||||
final double hue = (hash % 360).toDouble();
|
||||
return HSLColor.fromAHSL(1.0, hue, 0.5, 0.8).toColor();
|
||||
}
|
||||
|
||||
// shuffle a color from the built-in color set, for the same name, the result should be the same
|
||||
(Color, Color) randomColor() {
|
||||
final hash = value.codeUnits.fold(0, (int acc, int unit) => acc + unit);
|
||||
final index = hash % _builtInColorSet.length;
|
||||
return _builtInColorSet[index];
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,12 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
extension NavigatorContext on BuildContext {
|
||||
void popToHome() {
|
||||
Navigator.of(this).popUntil((route) {
|
||||
if (route.settings.name == '/') {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -1,11 +1,16 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/base/emoji/emoji_picker_screen.dart';
|
||||
import 'package:appflowy/shared/icon_emoji_picker/flowy_icon_emoji_picker.dart';
|
||||
import 'package:appflowy/util/color_generator/color_generator.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:universal_platform/universal_platform.dart';
|
||||
|
||||
class WorkspaceIcon extends StatefulWidget {
|
||||
const WorkspaceIcon({
|
||||
@ -95,3 +100,120 @@ class _WorkspaceIconState extends State<WorkspaceIcon> {
|
||||
return child;
|
||||
}
|
||||
}
|
||||
|
||||
// The v2 supports the built-in color set
|
||||
class WorkspaceIconV2 extends StatefulWidget {
|
||||
const WorkspaceIconV2({
|
||||
super.key,
|
||||
required this.workspace,
|
||||
required this.enableEdit,
|
||||
required this.iconSize,
|
||||
required this.fontSize,
|
||||
required this.onSelected,
|
||||
this.borderRadius = 4,
|
||||
this.emojiSize,
|
||||
this.alignment,
|
||||
required this.figmaLineHeight,
|
||||
this.showBorder = true,
|
||||
});
|
||||
|
||||
final UserWorkspacePB workspace;
|
||||
final double iconSize;
|
||||
final bool enableEdit;
|
||||
final double fontSize;
|
||||
final double? emojiSize;
|
||||
final void Function(EmojiPickerResult) onSelected;
|
||||
final double borderRadius;
|
||||
final Alignment? alignment;
|
||||
final double figmaLineHeight;
|
||||
final bool showBorder;
|
||||
|
||||
@override
|
||||
State<WorkspaceIconV2> createState() => _WorkspaceIconV2State();
|
||||
}
|
||||
|
||||
class _WorkspaceIconV2State extends State<WorkspaceIconV2> {
|
||||
final controller = PopoverController();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final color = ColorGenerator(widget.workspace.name).randomColor();
|
||||
Widget child = widget.workspace.icon.isNotEmpty
|
||||
? FlowyText.emoji(
|
||||
widget.workspace.icon,
|
||||
fontSize: widget.emojiSize,
|
||||
figmaLineHeight: widget.figmaLineHeight,
|
||||
optimizeEmojiAlign: true,
|
||||
)
|
||||
: FlowyText.semibold(
|
||||
widget.workspace.name.isEmpty
|
||||
? ''
|
||||
: widget.workspace.name.substring(0, 1),
|
||||
fontSize: widget.fontSize,
|
||||
color: color.$1,
|
||||
);
|
||||
|
||||
child = Container(
|
||||
alignment: Alignment.center,
|
||||
width: widget.iconSize,
|
||||
height: widget.iconSize,
|
||||
decoration: BoxDecoration(
|
||||
color: widget.workspace.icon.isNotEmpty ? null : color.$2,
|
||||
borderRadius: BorderRadius.circular(widget.borderRadius),
|
||||
border: widget.showBorder
|
||||
? Border.all(
|
||||
color: const Color(0x1A717171),
|
||||
)
|
||||
: null,
|
||||
),
|
||||
child: child,
|
||||
);
|
||||
|
||||
if (widget.enableEdit) {
|
||||
child = _buildEditableIcon(child);
|
||||
}
|
||||
|
||||
return child;
|
||||
}
|
||||
|
||||
Widget _buildEditableIcon(Widget child) {
|
||||
if (UniversalPlatform.isDesktopOrWeb) {
|
||||
AppFlowyPopover(
|
||||
offset: const Offset(0, 8),
|
||||
controller: controller,
|
||||
direction: PopoverDirection.bottomWithLeftAligned,
|
||||
constraints: BoxConstraints.loose(const Size(364, 356)),
|
||||
clickHandler: PopoverClickHandler.gestureDetector,
|
||||
margin: const EdgeInsets.all(0),
|
||||
popupBuilder: (_) => FlowyIconEmojiPicker(
|
||||
onSelectedEmoji: (result) {
|
||||
widget.onSelected(result);
|
||||
controller.close();
|
||||
},
|
||||
),
|
||||
child: MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
child: child,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return GestureDetector(
|
||||
onTap: () async {
|
||||
final result = await context.push<EmojiPickerResult>(
|
||||
Uri(
|
||||
path: MobileEmojiPickerScreen.routeName,
|
||||
queryParameters: {
|
||||
MobileEmojiPickerScreen.pageTitle:
|
||||
LocaleKeys.settings_workspacePage_workspaceIcon_title.tr(),
|
||||
},
|
||||
).toString(),
|
||||
);
|
||||
if (result != null) {
|
||||
widget.onSelected(result);
|
||||
}
|
||||
},
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,6 +3,7 @@ import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/widgets/loading.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/user/application/user_service.dart';
|
||||
import 'package:appflowy/util/navigator_context_exntesion.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/shared_widget.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
@ -100,12 +101,7 @@ class _AccountDeletionButtonState extends State<AccountDeletionButton> {
|
||||
textEditingController.text.trim(),
|
||||
isCheckedNotifier.value,
|
||||
onSuccess: () {
|
||||
Navigator.of(context).popUntil((route) {
|
||||
if (route.settings.name == '/') {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
context.popToHome();
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
@ -5,6 +5,7 @@ import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/user/application/auth/auth_service.dart';
|
||||
import 'package:appflowy/user/application/prelude.dart';
|
||||
import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/magic_link_sign_in_buttons.dart';
|
||||
import 'package:appflowy/util/navigator_context_exntesion.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/widgets/setting_third_party_login.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart';
|
||||
@ -89,13 +90,7 @@ class _SignInDialogContent extends StatelessWidget {
|
||||
const VSpace(10),
|
||||
SettingThirdPartyLogin(
|
||||
didLogin: () {
|
||||
// dismiss the setting dialog
|
||||
Navigator.of(context).popUntil((route) {
|
||||
if (route.settings.name == '/') {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
context.popToHome();
|
||||
},
|
||||
),
|
||||
],
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/util/navigator_context_exntesion.dart';
|
||||
import 'package:appflowy/workspace/application/export/document_exporter.dart';
|
||||
import 'package:appflowy/workspace/application/settings/settings_file_exporter_cubit.dart';
|
||||
import 'package:appflowy/workspace/application/settings/share/export_service.dart';
|
||||
@ -132,9 +133,7 @@ class _FileExporterWidgetState extends State<FileExporterWidget> {
|
||||
);
|
||||
}
|
||||
if (mounted) {
|
||||
Navigator.of(context).popUntil(
|
||||
(router) => router.settings.name == '/',
|
||||
);
|
||||
context.popToHome();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
@ -862,14 +862,6 @@ packages:
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
functions_client:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: functions_client
|
||||
sha256: e63f49cd3b41727f47b3bde284a11a4ac62839e0604f64077d4257487510e484
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.2"
|
||||
get_it:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -902,14 +894,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.2.1"
|
||||
gotrue:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: gotrue
|
||||
sha256: "8703db795511f69194fe77125a0c838bbb6befc2f95717b6e40331784a8bdecb"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.8.4"
|
||||
graphs:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -1139,14 +1123,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.8.0"
|
||||
jwt_decode:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: jwt_decode
|
||||
sha256: d2e9f68c052b2225130977429d30f187aa1981d789c76ad104a32243cfdebfbb
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.3.1"
|
||||
keyboard_height_plugin:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -1579,14 +1555,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.5.1"
|
||||
postgrest:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: postgrest
|
||||
sha256: c4197238601c7c3103b03a4bb77f2050b17d0064bf8b968309421abdebbb7f0e
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
process:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -1635,14 +1603,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.2"
|
||||
realtime_client:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: realtime_client
|
||||
sha256: d897a65ee3b1b5ddc1cf606f0b83792262d38fd5679c2df7e38da29c977513da
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.1"
|
||||
recase:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -1667,14 +1627,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.6.0"
|
||||
retry:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: retry
|
||||
sha256: "822e118d5b3aafed083109c72d5f484c6dc66707885e07c0fbcb8b986bba7efc"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.2"
|
||||
run_with_network_images:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
@ -1961,14 +1913,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.11.1"
|
||||
storage_client:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: storage_client
|
||||
sha256: "28c147c805304dbc2b762becd1fc26ee0cb621ace3732b9ae61ef979aab8b367"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.3"
|
||||
stream_channel:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -2009,23 +1953,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.4.1"
|
||||
supabase:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: supabase
|
||||
sha256: "4ed1cf3298f39865c05b2d8557f92eb131a9b9af70e32e218672a0afce01a6bc"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.0"
|
||||
supabase_flutter:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "packages/supabase_flutter"
|
||||
ref: "9b05eea"
|
||||
resolved-ref: "9b05eeac559a1f2da6289e1d70b3fa89e262fa3c"
|
||||
url: "https://github.com/supabase/supabase-flutter"
|
||||
source: git
|
||||
version: "2.3.1"
|
||||
super_clipboard:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -2403,14 +2330,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.2"
|
||||
yet_another_json_isolate:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: yet_another_json_isolate
|
||||
sha256: "47ed3900e6b0e4dfe378811a4402e85b7fc126a7daa94f840fef65ea9c8e46f4"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.2"
|
||||
sdks:
|
||||
dart: ">=3.4.0 <4.0.0"
|
||||
flutter: ">=3.22.0"
|
||||
|
||||
5
frontend/resources/flowy_icons/16x/leave_workspace.svg
Normal file
5
frontend/resources/flowy_icons/16x/leave_workspace.svg
Normal file
@ -0,0 +1,5 @@
|
||||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6.29688 5.66993C6.52938 2.96993 7.91688 1.86743 10.9544 1.86743H11.0519C14.4044 1.86743 15.7469 3.20993 15.7469 6.56243V11.4524C15.7469 14.8049 14.4044 16.1474 11.0519 16.1474H10.9544C7.93938 16.1474 6.55188 15.0599 6.30438 12.4049" stroke="#FB006D" stroke-width="1.125" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M1.5 9H11.16" stroke="#FB006D" stroke-width="1.125" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M9.48438 6.4873L11.9969 8.9998L9.48438 11.5123" stroke="#FB006D" stroke-width="1.125" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 687 B |
@ -0,0 +1,7 @@
|
||||
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M9 9C11.0711 9 12.75 7.32107 12.75 5.25C12.75 3.17893 11.0711 1.5 9 1.5C6.92893 1.5 5.25 3.17893 5.25 5.25C5.25 7.32107 6.92893 9 9 9Z" stroke="#171717" stroke-width="1.125" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M2.55859 16.5C2.55859 13.5975 5.44609 11.25 9.00109 11.25C9.72109 11.25 10.4186 11.3475 11.0711 11.5275" stroke="#171717" stroke-width="1.125" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M16.5 13.5C16.5 13.74 16.47 13.9725 16.41 14.1975C16.3425 14.4975 16.2225 14.79 16.065 15.045C15.5475 15.915 14.595 16.5 13.5 16.5C12.7275 16.5 12.03 16.2075 11.505 15.7275C11.28 15.5325 11.085 15.3 10.935 15.045C10.6575 14.595 10.5 14.0625 10.5 13.5C10.5 12.69 10.8225 11.9475 11.3475 11.4075C11.895 10.845 12.66 10.5 13.5 10.5C14.385 10.5 15.1875 10.8825 15.7275 11.4975C16.2075 12.03 16.5 12.735 16.5 13.5Z" stroke="#171717" stroke-width="1.125" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M14.6178 13.4849H12.3828" stroke="#171717" stroke-width="1.125" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M13.5 12.3899V14.6324" stroke="#171717" stroke-width="1.125" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
@ -72,9 +72,11 @@
|
||||
},
|
||||
"workspace": {
|
||||
"chooseWorkspace": "Choose your workspace",
|
||||
"defaultName": "My Workspace",
|
||||
"create": "Create workspace",
|
||||
"reset": "Reset workspace",
|
||||
"renameWorkspace": "Rename workspace",
|
||||
"workspaceNameCannotBeEmpty": "Workspace name cannot be empty",
|
||||
"resetWorkspacePrompt": "Resetting the workspace will delete all pages and data within it. Are you sure you want to reset the workspace? Alternatively, you can contact the support team to restore the workspace",
|
||||
"hint": "workspace",
|
||||
"notFoundError": "Workspace not found",
|
||||
@ -509,7 +511,9 @@
|
||||
},
|
||||
"leaveWorkspacePrompt": {
|
||||
"title": "Leave workspace",
|
||||
"content": "Are you sure you want to leave this workspace? You will lose access to all pages and data within it."
|
||||
"content": "Are you sure you want to leave this workspace? You will lose access to all pages and data within it.",
|
||||
"success": "You have left the workspace successfully.",
|
||||
"fail": "Failed to leave the workspace."
|
||||
},
|
||||
"manageWorkspace": {
|
||||
"title": "Manage workspace",
|
||||
@ -2661,4 +2665,4 @@
|
||||
"refreshNote": "After successful upgrade, click <refresh/> to activate your new features.",
|
||||
"refresh": "here"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user