diff --git a/frontend/appflowy_flutter/.gitignore b/frontend/appflowy_flutter/.gitignore
index 82c35e91cd..a0886f8dbc 100644
--- a/frontend/appflowy_flutter/.gitignore
+++ b/frontend/appflowy_flutter/.gitignore
@@ -68,3 +68,5 @@ windows/flutter/dart_ffi/
**/**/Brewfile.lock.json
**/.sandbox
**/.vscode/
+
+*.env
diff --git a/frontend/appflowy_flutter/assets/images/common/open_folder.svg b/frontend/appflowy_flutter/assets/images/common/open_folder.svg
new file mode 100644
index 0000000000..cd81df9271
--- /dev/null
+++ b/frontend/appflowy_flutter/assets/images/common/open_folder.svg
@@ -0,0 +1,3 @@
+
diff --git a/frontend/appflowy_flutter/assets/images/common/recover.svg b/frontend/appflowy_flutter/assets/images/common/recover.svg
new file mode 100644
index 0000000000..38d77b51de
--- /dev/null
+++ b/frontend/appflowy_flutter/assets/images/common/recover.svg
@@ -0,0 +1,4 @@
+
diff --git a/frontend/appflowy_flutter/assets/images/editor/duplicate.svg b/frontend/appflowy_flutter/assets/images/editor/duplicate.svg
new file mode 100644
index 0000000000..40b5ed5a95
--- /dev/null
+++ b/frontend/appflowy_flutter/assets/images/editor/duplicate.svg
@@ -0,0 +1,4 @@
+
diff --git a/frontend/appflowy_flutter/assets/images/login/discord-mark.svg b/frontend/appflowy_flutter/assets/images/login/discord-mark.svg
new file mode 100644
index 0000000000..640e06af95
--- /dev/null
+++ b/frontend/appflowy_flutter/assets/images/login/discord-mark.svg
@@ -0,0 +1,3 @@
+
diff --git a/frontend/appflowy_flutter/assets/images/login/github-mark.svg b/frontend/appflowy_flutter/assets/images/login/github-mark.svg
new file mode 100644
index 0000000000..d5e6491854
--- /dev/null
+++ b/frontend/appflowy_flutter/assets/images/login/github-mark.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/frontend/appflowy_flutter/assets/images/login/google-mark.svg b/frontend/appflowy_flutter/assets/images/login/google-mark.svg
new file mode 100644
index 0000000000..5a65dae32d
--- /dev/null
+++ b/frontend/appflowy_flutter/assets/images/login/google-mark.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/frontend/appflowy_flutter/assets/translations/en.json b/frontend/appflowy_flutter/assets/translations/en.json
index a43c32e849..0c2b62ea5d 100644
--- a/frontend/appflowy_flutter/assets/translations/en.json
+++ b/frontend/appflowy_flutter/assets/translations/en.json
@@ -21,6 +21,7 @@
"signIn": {
"loginTitle": "Login to @:appName",
"loginButtonText": "Login",
+ "loginAsGuestButtonText": "Get Started",
"buttonText": "Sign In",
"forgotPassword": "Forgot Password?",
"emailHint": "Email",
@@ -184,7 +185,8 @@
"theme": "Theme"
},
"files": {
- "defaultLocation": "Where your data is stored now",
+ "copy": "Copy",
+ "defaultLocation": "Read files and data storage location",
"doubleTapToCopy": "Double tap to copy the path",
"restoreLocation": "Restore to AppFlowy default path",
"customizeLocation": "Open another folder",
@@ -203,7 +205,11 @@
"create": "Create",
"folderPath": "Path to store your folder",
"locationCannotBeEmpty": "Path cannot be empty",
- "pathCopiedSnackbar": "File storage path copied to clipboard!"
+ "pathCopiedSnackbar": "File storage path copied to clipboard!",
+ "changeLocationTooltips": "Change the files read the data directory",
+ "change": "Change",
+ "openLocationTooltips": "Open the files read the data directory",
+ "recoverLocationTooltips": "Recover the files read the data directory"
},
"user": {
"name": "Name",
diff --git a/frontend/appflowy_flutter/integration_test/util/base.dart b/frontend/appflowy_flutter/integration_test/util/base.dart
index 3525fc5871..acc4b64e7a 100644
--- a/frontend/appflowy_flutter/integration_test/util/base.dart
+++ b/frontend/appflowy_flutter/integration_test/util/base.dart
@@ -1,8 +1,8 @@
import 'dart:io';
+import 'package:appflowy/core/config/kv_keys.dart';
import 'package:appflowy/main.dart' as app;
import 'package:appflowy/startup/tasks/prelude.dart';
-import 'package:appflowy/workspace/application/settings/settings_location_cubit.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
@@ -21,7 +21,7 @@ class TestFolder {
static Future setTestLocation(String? name) async {
final location = await testLocation(name);
SharedPreferences.setMockInitialValues({
- kSettingsLocationDefaultLocation: location.path,
+ KVKeys.pathLocation: location.path,
});
return;
}
@@ -36,7 +36,7 @@ class TestFolder {
/// Get current using location.
static Future currentLocation() async {
final prefs = await SharedPreferences.getInstance();
- return prefs.getString(kSettingsLocationDefaultLocation)!;
+ return prefs.getString(KVKeys.pathLocation)!;
}
/// Get default location under development environment.
diff --git a/frontend/appflowy_flutter/integration_test/util/data.dart b/frontend/appflowy_flutter/integration_test/util/data.dart
index 0dd0961ee2..6725f2958b 100644
--- a/frontend/appflowy_flutter/integration_test/util/data.dart
+++ b/frontend/appflowy_flutter/integration_test/util/data.dart
@@ -1,6 +1,6 @@
import 'dart:io';
-import 'package:appflowy/workspace/application/settings/settings_location_cubit.dart';
+import 'package:appflowy/core/config/kv_keys.dart';
import 'package:archive/archive_io.dart';
import 'package:flutter/services.dart';
import 'package:path/path.dart' as p;
@@ -51,8 +51,7 @@ class TestWorkspaceService {
Future setUpAll() async {
SharedPreferences.setMockInitialValues(
{
- kSettingsLocationDefaultLocation:
- await workspace.root.then((value) => value.path),
+ KVKeys.pathLocation: await workspace.root.then((value) => value.path),
},
);
}
diff --git a/frontend/appflowy_flutter/lib/core/config/config.dart b/frontend/appflowy_flutter/lib/core/config/config.dart
index ad06ecb860..19c771c793 100644
--- a/frontend/appflowy_flutter/lib/core/config/config.dart
+++ b/frontend/appflowy_flutter/lib/core/config/config.dart
@@ -4,13 +4,15 @@ import 'package:appflowy_backend/protobuf/flowy-config/entities.pb.dart';
class Config {
static Future setSupabaseConfig({
required String url,
+ required String anonKey,
required String key,
required String secret,
}) async {
await ConfigEventSetSupabaseConfig(
SupabaseConfigPB.create()
..supabaseUrl = url
- ..supabaseKey = key
+ ..key = key
+ ..anonKey = anonKey
..jwtSecret = secret,
).send();
}
diff --git a/frontend/appflowy_flutter/lib/core/config/kv.dart b/frontend/appflowy_flutter/lib/core/config/kv.dart
index 692c691b57..4c0901cb8c 100644
--- a/frontend/appflowy_flutter/lib/core/config/kv.dart
+++ b/frontend/appflowy_flutter/lib/core/config/kv.dart
@@ -2,11 +2,61 @@ import 'package:appflowy_backend/dispatch/dispatch.dart';
import 'package:appflowy_backend/protobuf/flowy-config/entities.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
import 'package:dartz/dartz.dart';
+import 'package:shared_preferences/shared_preferences.dart';
+
+abstract class KeyValueStorage {
+ Future set(String key, String value);
+ Future> get(String key);
+ Future remove(String key);
+ Future clear();
+}
+
+class DartKeyValue implements KeyValueStorage {
+ SharedPreferences? _sharedPreferences;
+ SharedPreferences get sharedPreferences => _sharedPreferences!;
+
+ @override
+ Future> get(String key) async {
+ await _initSharedPreferencesIfNeeded();
+
+ final value = sharedPreferences.getString(key);
+ if (value != null) {
+ return Right(value);
+ }
+ return Left(FlowyError());
+ }
+
+ @override
+ Future remove(String key) async {
+ await _initSharedPreferencesIfNeeded();
+
+ await sharedPreferences.remove(key);
+ }
+
+ @override
+ Future set(String key, String value) async {
+ await _initSharedPreferencesIfNeeded();
+
+ await sharedPreferences.setString(key, value);
+ }
+
+ @override
+ Future clear() async {
+ await _initSharedPreferencesIfNeeded();
+
+ await sharedPreferences.clear();
+ }
+
+ Future _initSharedPreferencesIfNeeded() async {
+ _sharedPreferences ??= await SharedPreferences.getInstance();
+ }
+}
/// Key-value store
/// The data is stored in the local storage of the device.
-class KeyValue {
- static Future set(String key, String value) async {
+class RustKeyValue implements KeyValueStorage {
+ @override
+ Future set(String key, String value) async {
await ConfigEventSetKeyValue(
KeyValuePB.create()
..key = key
@@ -14,20 +64,23 @@ class KeyValue {
).send();
}
- static Future> get(String key) {
- return ConfigEventGetKeyValue(
- KeyPB.create()..key = key,
- ).send().then(
- (result) => result.fold(
- (pb) => left(pb.value),
- (error) => right(error),
- ),
- );
+ @override
+ Future> get(String key) async {
+ final payload = KeyPB.create()..key = key;
+ final response = await ConfigEventGetKeyValue(payload).send();
+ return response.swap().map((r) => r.value);
}
- static Future remove(String key) async {
+ @override
+ Future remove(String key) async {
await ConfigEventRemoveKeyValue(
KeyPB.create()..key = key,
).send();
}
+
+ @override
+ Future clear() {
+ // TODO: implement clear
+ throw UnimplementedError();
+ }
}
diff --git a/frontend/appflowy_flutter/lib/core/config/kv_keys.dart b/frontend/appflowy_flutter/lib/core/config/kv_keys.dart
new file mode 100644
index 0000000000..192a0e6960
--- /dev/null
+++ b/frontend/appflowy_flutter/lib/core/config/kv_keys.dart
@@ -0,0 +1,15 @@
+class KVKeys {
+ const KVKeys._();
+
+ static const String prefix = 'io.appflowy.appflowy_flutter';
+
+ /// The key for the path location of the local data for the whole app.
+ static const String pathLocation = '$prefix.path_location';
+
+ /// The key for the last time login type.
+ ///
+ /// The value is one of the following:
+ /// - local
+ /// - supabase
+ static const String loginType = '$prefix.login_type';
+}
diff --git a/frontend/appflowy_flutter/lib/env/env.dart b/frontend/appflowy_flutter/lib/env/env.dart
new file mode 100644
index 0000000000..311df8a3ce
--- /dev/null
+++ b/frontend/appflowy_flutter/lib/env/env.dart
@@ -0,0 +1,38 @@
+// lib/env/env.dart
+import 'package:envied/envied.dart';
+
+part 'env.g.dart';
+
+@Envied(path: '.env')
+abstract class Env {
+ @EnviedField(
+ obfuscate: true,
+ varName: 'SUPABASE_URL',
+ defaultValue: '',
+ )
+ static final supabaseUrl = _Env.supabaseUrl;
+ @EnviedField(
+ obfuscate: true,
+ varName: 'SUPABASE_ANON_KEY',
+ defaultValue: '',
+ )
+ static final supabaseAnonKey = _Env.supabaseAnonKey;
+ @EnviedField(
+ obfuscate: true,
+ varName: 'SUPABASE_KEY',
+ defaultValue: '',
+ )
+ static final supabaseKey = _Env.supabaseKey;
+ @EnviedField(
+ obfuscate: true,
+ varName: 'SUPABASE_JWT_SECRET',
+ defaultValue: '',
+ )
+ static final supabaseJwtSecret = _Env.supabaseJwtSecret;
+}
+
+bool get isSupabaseEnable =>
+ Env.supabaseUrl.isNotEmpty &&
+ Env.supabaseAnonKey.isNotEmpty &&
+ Env.supabaseKey.isNotEmpty &&
+ Env.supabaseJwtSecret.isNotEmpty;
diff --git a/frontend/appflowy_flutter/lib/plugins/document/application/editor_transaction_adapter.dart b/frontend/appflowy_flutter/lib/plugins/document/application/editor_transaction_adapter.dart
index 4fe2db1b07..2f7c0d78ce 100644
--- a/frontend/appflowy_flutter/lib/plugins/document/application/editor_transaction_adapter.dart
+++ b/frontend/appflowy_flutter/lib/plugins/document/application/editor_transaction_adapter.dart
@@ -65,7 +65,6 @@ extension on InsertOperation {
final parentId =
node.parent?.id ?? editorState.getNodeAtPath(path.parent)?.id ?? '';
final prevId = previousNode?.id ??
- node.previous?.id ??
editorState.getNodeAtPath(path.previous)?.id ??
'';
assert(parentId.isNotEmpty && prevId.isNotEmpty);
diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/actions/option_action.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/actions/option_action.dart
index eb6e861919..2da5eccc0d 100644
--- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/actions/option_action.dart
+++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/actions/option_action.dart
@@ -37,7 +37,7 @@ class ColorOptionAction extends PopoverActionCell {
@override
Widget? leftIcon(Color iconColor) {
return svgWidget(
- 'editor/delete', // todo: add color icon
+ 'editor/color_formatter',
color: iconColor,
);
}
@@ -100,10 +100,10 @@ class ColorOptionAction extends PopoverActionCell {
}
class OptionActionWrapper extends ActionCell {
- final OptionAction inner;
-
OptionActionWrapper(this.inner);
+ final OptionAction inner;
+
@override
Widget? leftIcon(Color iconColor) {
var name = '';
@@ -125,15 +125,13 @@ class OptionActionWrapper extends ActionCell {
name = 'editor/move_down';
break;
case OptionAction.color:
- name = 'editor/color';
- break;
+ throw UnimplementedError();
case OptionAction.divider:
throw UnimplementedError();
}
if (name.isEmpty) {
return null;
}
- name = 'editor/delete';
return svgWidget(
name,
color: iconColor,
diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/cover/cover_image_picker_bloc.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/cover/cover_image_picker_bloc.dart
index 3c375ad7ee..43993d3f47 100644
--- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/cover/cover_image_picker_bloc.dart
+++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/cover/cover_image_picker_bloc.dart
@@ -130,7 +130,7 @@ class CoverImagePickerBloc
}
Future _coverPath() async {
- final directory = await getIt().fetchLocation();
+ final directory = await getIt().getPath();
return Directory(p.join(directory, 'covers'))
.create(recursive: true)
.then((value) => value.path);
diff --git a/frontend/appflowy_flutter/lib/startup/deps_resolver.dart b/frontend/appflowy_flutter/lib/startup/deps_resolver.dart
index d060aabab1..5186646f53 100644
--- a/frontend/appflowy_flutter/lib/startup/deps_resolver.dart
+++ b/frontend/appflowy_flutter/lib/startup/deps_resolver.dart
@@ -1,3 +1,4 @@
+import 'package:appflowy/core/config/kv.dart';
import 'package:appflowy/core/network_monitor.dart';
import 'package:appflowy/plugins/database_view/application/field/field_action_sheet_bloc.dart';
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
@@ -5,6 +6,8 @@ import 'package:appflowy/plugins/database_view/application/field/field_service.d
import 'package:appflowy/plugins/database_view/application/setting/property_bloc.dart';
import 'package:appflowy/plugins/database_view/grid/application/grid_header_bloc.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/service/openai_client.dart';
+import 'package:appflowy/user/application/auth/auth_service.dart';
+import 'package:appflowy/user/application/auth/supabase_auth_service.dart';
import 'package:appflowy/user/application/user_listener.dart';
import 'package:appflowy/user/application/user_service.dart';
import 'package:appflowy/util/file_picker/file_picker_impl.dart';
@@ -45,7 +48,10 @@ class DependencyResolver {
}
void _resolveCommonService(GetIt getIt) async {
+ // getIt.registerFactory(() => RustKeyValue());
+ getIt.registerFactory(() => DartKeyValue());
getIt.registerFactory(() => FilePicker());
+ getIt.registerFactory(() => LocalFileStorage());
getIt.registerFactoryAsync(
() async {
@@ -66,11 +72,17 @@ void _resolveCommonService(GetIt getIt) async {
}
void _resolveUserDeps(GetIt getIt) {
- getIt.registerFactory(() => AuthService());
+ // getIt.registerFactory(() => AppFlowyAuthService());
+ getIt.registerFactory(() => SupabaseAuthService());
+
getIt.registerFactory(() => AuthRouter());
- getIt.registerFactory(() => SignInBloc(getIt()));
- getIt.registerFactory(() => SignUpBloc(getIt()));
+ getIt.registerFactory(
+ () => SignInBloc(getIt()),
+ );
+ getIt.registerFactory(
+ () => SignUpBloc(getIt()),
+ );
getIt.registerFactory(() => SplashRoute());
getIt.registerFactory(() => EditPanelBloc());
@@ -131,11 +143,6 @@ void _resolveFolderDeps(GetIt getIt) {
(user, _) => SettingsDialogBloc(user),
);
- // Location
- getIt.registerFactory(
- () => SettingsLocationCubit(),
- );
-
//User
getIt.registerFactoryParam(
(user, _) => SettingsUserViewBloc(user),
diff --git a/frontend/appflowy_flutter/lib/startup/startup.dart b/frontend/appflowy_flutter/lib/startup/startup.dart
index 0fa50120e8..aa7cd9d4a5 100644
--- a/frontend/appflowy_flutter/lib/startup/startup.dart
+++ b/frontend/appflowy_flutter/lib/startup/startup.dart
@@ -1,33 +1,17 @@
import 'dart:io';
+import 'package:appflowy/env/env.dart';
+import 'package:appflowy/workspace/application/settings/settings_location_cubit.dart';
import 'package:appflowy_backend/appflowy_backend.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:get_it/get_it.dart';
-import '../workspace/application/settings/settings_location_cubit.dart';
import 'deps_resolver.dart';
import 'launch_configuration.dart';
import 'plugin/plugin.dart';
import 'tasks/prelude.dart';
-// [[diagram: flowy startup flow]]
-// ┌──────────┐
-// │ FlowyApp │
-// └──────────┘
-// │ impl
-// ▼
-// ┌────────┐ 1.run ┌──────────┐
-// │ System │───┬───▶│EntryPoint│
-// └────────┘ │ └──────────┘ ┌─────────────────┐
-// │ ┌──▶ │ RustSDKInitTask │
-// │ ┌───────────┐ │ └─────────────────┘
-// └──▶ │AppLauncher│───┤
-// 2.launch └───────────┘ │ ┌─────────────┐ ┌──────────────────┐ ┌───────────────┐
-// └───▶│AppWidgetTask│────────▶│ApplicationWidget │─────▶│ SplashScreen │
-// └─────────────┘ └──────────────────┘ └───────────────┘
-//
-// 3.build MaterialApp
final getIt = GetIt.instance;
abstract class EntryPoint {
@@ -48,10 +32,12 @@ class FlowyRunner {
final env = integrationEnv();
initGetIt(getIt, env, f, config);
- final directory = getIt()
- .fetchLocation()
+ final directory = await getIt()
+ .getPath()
.then((value) => Directory(value));
+ // final directory = await appFlowyDocumentDirectory();
+
// add task
final launcher = getIt();
launcher.addTasks(
@@ -71,6 +57,12 @@ class FlowyRunner {
// ignore in test mode
if (!env.isTest()) ...[
const HotKeyTask(),
+ InitSupabaseTask(
+ url: Env.supabaseUrl,
+ anonKey: Env.supabaseAnonKey,
+ key: Env.supabaseKey,
+ jwtSecret: Env.supabaseJwtSecret,
+ ),
const InitAppWidgetTask(),
const InitPlatformServiceTask()
],
diff --git a/frontend/appflowy_flutter/lib/startup/tasks/prelude.dart b/frontend/appflowy_flutter/lib/startup/tasks/prelude.dart
index 208712b81a..d7d7993af7 100644
--- a/frontend/appflowy_flutter/lib/startup/tasks/prelude.dart
+++ b/frontend/appflowy_flutter/lib/startup/tasks/prelude.dart
@@ -6,3 +6,4 @@ export 'hot_key.dart';
export 'platform_error_catcher.dart';
export 'windows.dart';
export 'localization.dart';
+export 'supabase_task.dart';
diff --git a/frontend/appflowy_flutter/lib/startup/tasks/rust_sdk.dart b/frontend/appflowy_flutter/lib/startup/tasks/rust_sdk.dart
index 135012c298..7e389c491c 100644
--- a/frontend/appflowy_flutter/lib/startup/tasks/rust_sdk.dart
+++ b/frontend/appflowy_flutter/lib/startup/tasks/rust_sdk.dart
@@ -12,30 +12,23 @@ class InitRustSDKTask extends LaunchTask {
});
// Customize the RustSDK initialization path
- final Future? directory;
+ final Directory? directory;
@override
LaunchTaskType get type => LaunchTaskType.dataProcessing;
@override
Future initialize(LaunchContext context) async {
- // use the custom directory
- if (directory != null) {
- return directory!.then((directory) async {
- await context.getIt().init(directory);
- });
- } else {
- return appFlowyDocumentDirectory().then((directory) async {
- await context.getIt().init(directory);
- });
- }
+ final dir = directory ?? await appFlowyDocumentDirectory();
+ await context.getIt().init(dir);
}
}
Future appFlowyDocumentDirectory() async {
switch (integrationEnv()) {
case IntegrationMode.develop:
- Directory documentsDir = await getApplicationDocumentsDirectory();
+ Directory documentsDir = await getApplicationDocumentsDirectory()
+ ..create();
return Directory(path.join(documentsDir.path, 'data_dev')).create();
case IntegrationMode.release:
Directory documentsDir = await getApplicationDocumentsDirectory();
diff --git a/frontend/appflowy_flutter/lib/startup/tasks/supabase_task.dart b/frontend/appflowy_flutter/lib/startup/tasks/supabase_task.dart
new file mode 100644
index 0000000000..e7369cd331
--- /dev/null
+++ b/frontend/appflowy_flutter/lib/startup/tasks/supabase_task.dart
@@ -0,0 +1,46 @@
+import 'package:appflowy/core/config/config.dart';
+import 'package:appflowy_backend/log.dart';
+import 'package:supabase_flutter/supabase_flutter.dart';
+
+import '../startup.dart';
+
+bool isSupabaseEnable = false;
+bool isSupabaseInitialized = false;
+
+class InitSupabaseTask extends LaunchTask {
+ const InitSupabaseTask({
+ required this.url,
+ required this.anonKey,
+ required this.key,
+ required this.jwtSecret,
+ });
+
+ final String url;
+ final String anonKey;
+ final String key;
+ final String jwtSecret;
+
+ @override
+ Future initialize(LaunchContext context) async {
+ if (url.isEmpty || anonKey.isEmpty || jwtSecret.isEmpty || key.isEmpty) {
+ isSupabaseEnable = false;
+ Log.info('Supabase config is empty, skip init supabase.');
+ return;
+ }
+ if (isSupabaseInitialized) {
+ return;
+ }
+ await Supabase.initialize(
+ url: url,
+ anonKey: anonKey,
+ );
+ await Config.setSupabaseConfig(
+ url: url,
+ key: key,
+ secret: jwtSecret,
+ anonKey: anonKey,
+ );
+ isSupabaseEnable = true;
+ isSupabaseInitialized = true;
+ }
+}
diff --git a/frontend/appflowy_flutter/lib/startup/tasks/windows.dart b/frontend/appflowy_flutter/lib/startup/tasks/windows.dart
index 4fabfd962b..fcb80ecfe0 100644
--- a/frontend/appflowy_flutter/lib/startup/tasks/windows.dart
+++ b/frontend/appflowy_flutter/lib/startup/tasks/windows.dart
@@ -7,7 +7,7 @@ import 'package:window_manager/window_manager.dart';
class InitAppWindowTask extends LaunchTask {
const InitAppWindowTask({
- this.minimumSize = const Size(600, 400),
+ this.minimumSize = const Size(800, 600),
this.title = 'AppFlowy',
});
diff --git a/frontend/appflowy_flutter/lib/user/application/auth/appflowy_auth_service.dart b/frontend/appflowy_flutter/lib/user/application/auth/appflowy_auth_service.dart
new file mode 100644
index 0000000000..302d31a75c
--- /dev/null
+++ b/frontend/appflowy_flutter/lib/user/application/auth/appflowy_auth_service.dart
@@ -0,0 +1,87 @@
+import 'package:appflowy/user/application/auth/auth_service.dart';
+import 'package:appflowy/user/application/user_service.dart';
+import 'package:appflowy_backend/protobuf/flowy-user/auth.pb.dart';
+import 'package:dartz/dartz.dart';
+import 'package:easy_localization/easy_localization.dart';
+import 'package:flowy_infra/uuid.dart';
+import 'package:appflowy_backend/dispatch/dispatch.dart';
+import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart'
+ show SignInPayloadPB, SignUpPayloadPB, UserProfilePB;
+
+import '../../../generated/locale_keys.g.dart';
+
+class AppFlowyAuthService implements AuthService {
+ @override
+ Future> signIn({
+ required String email,
+ required String password,
+ AuthTypePB authType = AuthTypePB.Local,
+ Map map = const {},
+ }) async {
+ final request = SignInPayloadPB.create()
+ ..email = email
+ ..password = password
+ ..authType = authType;
+ final response = UserEventSignIn(request).send();
+ return response.then((value) => value.swap());
+ }
+
+ @override
+ Future> signUp({
+ required String name,
+ required String email,
+ required String password,
+ AuthTypePB authType = AuthTypePB.Local,
+ Map map = const {},
+ }) async {
+ final request = SignUpPayloadPB.create()
+ ..name = name
+ ..email = email
+ ..password = password
+ ..authType = authType;
+ final response = await UserEventSignUp(request).send().then(
+ (value) => value.swap(),
+ );
+ return response;
+ }
+
+ @override
+ Future signOut({
+ AuthTypePB authType = AuthTypePB.Local,
+ Map map = const {},
+ }) async {
+ final payload = SignOutPB()..authType = authType;
+ await UserEventSignOut(payload).send();
+ return;
+ }
+
+ @override
+ Future> signUpAsGuest({
+ AuthTypePB authType = AuthTypePB.Local,
+ Map map = const {},
+ }) {
+ const password = "AppFlowy123@";
+ final uid = uuid();
+ final userEmail = "$uid@appflowy.io";
+ return signUp(
+ name: LocaleKeys.defaultUsername.tr(),
+ password: password,
+ email: userEmail,
+ );
+ }
+
+ @override
+ Future> signUpWithOAuth({
+ required String platform,
+ AuthTypePB authType = AuthTypePB.Local,
+ Map map = const {},
+ }) {
+ throw UnimplementedError();
+ }
+
+ @override
+ Future> getUser() async {
+ return UserBackendService.getCurrentUserProfile();
+ }
+}
diff --git a/frontend/appflowy_flutter/lib/user/application/auth/auth_error.dart b/frontend/appflowy_flutter/lib/user/application/auth/auth_error.dart
new file mode 100644
index 0000000000..f9629e1547
--- /dev/null
+++ b/frontend/appflowy_flutter/lib/user/application/auth/auth_error.dart
@@ -0,0 +1,19 @@
+import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
+
+class AuthError {
+ static final supabaseSignInError = FlowyError()
+ ..msg = 'supabase sign in error'
+ ..code = -10001;
+
+ static final supabaseSignUpError = FlowyError()
+ ..msg = 'supabase sign up error'
+ ..code = -10002;
+
+ static final supabaseSignInWithOauthError = FlowyError()
+ ..msg = 'supabase sign in with oauth error'
+ ..code = -10003;
+
+ static final supabaseGetUserError = FlowyError()
+ ..msg = 'supabase sign in with oauth error'
+ ..code = -10003;
+}
diff --git a/frontend/appflowy_flutter/lib/user/application/auth/auth_service.dart b/frontend/appflowy_flutter/lib/user/application/auth/auth_service.dart
new file mode 100644
index 0000000000..70c7fd73f9
--- /dev/null
+++ b/frontend/appflowy_flutter/lib/user/application/auth/auth_service.dart
@@ -0,0 +1,51 @@
+import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-user/auth.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pbserver.dart';
+import 'package:dartz/dartz.dart';
+
+class AuthServiceMapKeys {
+ const AuthServiceMapKeys._();
+
+ // for supabase auth use only.
+ static const String uuid = 'uuid';
+}
+
+abstract class AuthService {
+ /// Returns [UserProfilePB] if the user is authenticated, otherwise returns [FlowyError].
+ Future> signIn({
+ required String email,
+ required String password,
+ AuthTypePB authType,
+ Map map,
+ });
+
+ /// Returns [UserProfilePB] if the user is authenticated, otherwise returns [FlowyError].
+ Future> signUp({
+ required String name,
+ required String email,
+ required String password,
+ AuthTypePB authType,
+ Map map,
+ });
+
+ ///
+ Future> signUpWithOAuth({
+ required String platform,
+ AuthTypePB authType,
+ Map map,
+ });
+
+ /// Returns a default [UserProfilePB]
+ Future> signUpAsGuest({
+ AuthTypePB authType,
+ Map map,
+ });
+
+ ///
+ Future signOut({
+ AuthTypePB authType,
+ });
+
+ /// Returns [UserProfilePB] if the user has sign in, otherwise returns null.
+ Future> getUser();
+}
diff --git a/frontend/appflowy_flutter/lib/user/application/auth/supabase_auth_service.dart b/frontend/appflowy_flutter/lib/user/application/auth/supabase_auth_service.dart
new file mode 100644
index 0000000000..6a28a44981
--- /dev/null
+++ b/frontend/appflowy_flutter/lib/user/application/auth/supabase_auth_service.dart
@@ -0,0 +1,221 @@
+import 'dart:async';
+
+import 'package:appflowy/core/config/kv.dart';
+import 'package:appflowy/core/config/kv_keys.dart';
+import 'package:appflowy/startup/startup.dart';
+import 'package:appflowy/startup/tasks/prelude.dart';
+import 'package:appflowy/user/application/auth/appflowy_auth_service.dart';
+import 'package:appflowy/user/application/auth/auth_service.dart';
+import 'package:appflowy_backend/dispatch/dispatch.dart';
+import 'package:appflowy_backend/log.dart';
+import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
+import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
+import 'package:dartz/dartz.dart';
+import 'package:supabase_flutter/supabase_flutter.dart';
+import 'auth_error.dart';
+
+class SupabaseAuthService implements AuthService {
+ SupabaseAuthService();
+
+ SupabaseClient get _client => Supabase.instance.client;
+ GoTrueClient get _auth => _client.auth;
+
+ final AppFlowyAuthService _appFlowyAuthService = AppFlowyAuthService();
+
+ @override
+ Future> signUp({
+ required String name,
+ required String email,
+ required String password,
+ AuthTypePB authType = AuthTypePB.Supabase,
+ Map map = const {},
+ }) async {
+ if (!isSupabaseEnable) {
+ return _appFlowyAuthService.signUp(
+ name: name,
+ email: email,
+ password: password,
+ );
+ }
+
+ // fetch the uuid from supabase.
+ final response = await _auth.signUp(
+ email: email,
+ password: password,
+ );
+ final uuid = response.user?.id;
+ if (uuid == null) {
+ return left(AuthError.supabaseSignUpError);
+ }
+ // assign the uuid to our backend service.
+ // and will transfer this logic to backend later.
+ return _appFlowyAuthService.signUp(
+ name: name,
+ email: email,
+ password: password,
+ authType: authType,
+ map: {
+ AuthServiceMapKeys.uuid: uuid,
+ },
+ );
+ }
+
+ @override
+ Future> signIn({
+ required String email,
+ required String password,
+ AuthTypePB authType = AuthTypePB.Supabase,
+ Map map = const {},
+ }) async {
+ if (!isSupabaseEnable) {
+ return _appFlowyAuthService.signIn(
+ email: email,
+ password: password,
+ );
+ }
+
+ try {
+ final response = await _auth.signInWithPassword(
+ email: email,
+ password: password,
+ );
+ final uuid = response.user?.id;
+ if (uuid == null) {
+ return Left(AuthError.supabaseSignInError);
+ }
+ return _appFlowyAuthService.signIn(
+ email: email,
+ password: password,
+ authType: authType,
+ map: {
+ AuthServiceMapKeys.uuid: uuid,
+ },
+ );
+ } on AuthException catch (e) {
+ Log.error(e);
+ return Left(AuthError.supabaseSignInError);
+ }
+ }
+
+ @override
+ Future> signUpWithOAuth({
+ required String platform,
+ AuthTypePB authType = AuthTypePB.Supabase,
+ Map map = const {},
+ }) async {
+ if (!isSupabaseEnable) {
+ return _appFlowyAuthService.signUpWithOAuth(
+ platform: platform,
+ );
+ }
+ final provider = platform.toProvider();
+ final completer = Completer>();
+ late final StreamSubscription subscription;
+ subscription = _auth.onAuthStateChange.listen((event) async {
+ if (event.event != AuthChangeEvent.signedIn) {
+ completer.complete(left(AuthError.supabaseSignInWithOauthError));
+ } else {
+ final user = await getSupabaseUser();
+ final Either response = await user.fold(
+ (l) => left(l),
+ (r) async => await setupAuth(map: {AuthServiceMapKeys.uuid: r.id}),
+ );
+ completer.complete(response);
+ }
+ subscription.cancel();
+ });
+ final Map query = {};
+ if (provider == Provider.google) {
+ query['access_type'] = 'offline';
+ query['prompt'] = 'consent';
+ }
+ final response = await _auth.signInWithOAuth(
+ provider,
+ queryParams: query,
+ redirectTo:
+ 'io.appflowy.appflowy-flutter://login-callback', // can't use underscore here.
+ );
+ if (!response) {
+ completer.complete(left(AuthError.supabaseSignInWithOauthError));
+ }
+ return completer.future;
+ }
+
+ @override
+ Future signOut({
+ AuthTypePB authType = AuthTypePB.Supabase,
+ }) async {
+ if (!isSupabaseEnable) {
+ return _appFlowyAuthService.signOut();
+ }
+ await _auth.signOut();
+ await _appFlowyAuthService.signOut(
+ authType: authType,
+ );
+ }
+
+ @override
+ Future> signUpAsGuest({
+ AuthTypePB authType = AuthTypePB.Supabase,
+ Map map = const {},
+ }) async {
+ // supabase don't support guest login.
+ // so, just forward to our backend.
+ return _appFlowyAuthService.signUpAsGuest();
+ }
+
+ @override
+ Future> getUser() async {
+ final loginType = await getIt()
+ .get(KVKeys.loginType)
+ .then((value) => value.toOption().toNullable());
+ if (!isSupabaseEnable || (loginType != null && loginType != 'supabase')) {
+ return _appFlowyAuthService.getUser();
+ }
+ final user = await getSupabaseUser();
+ return user.map((r) => r.toUserProfile());
+ }
+
+ Future> getSupabaseUser() async {
+ final user = _auth.currentUser;
+ if (user == null) {
+ return left(AuthError.supabaseGetUserError);
+ }
+ return Right(user);
+ }
+
+ Future> setupAuth({
+ required Map map,
+ }) async {
+ final payload = ThirdPartyAuthPB(
+ authType: AuthTypePB.Supabase,
+ map: map,
+ );
+ return UserEventThirdPartyAuth(payload)
+ .send()
+ .then((value) => value.swap());
+ }
+}
+
+extension on User {
+ UserProfilePB toUserProfile() {
+ return UserProfilePB()
+ ..email = email ?? ''
+ ..token = this.id;
+ }
+}
+
+extension on String {
+ Provider toProvider() {
+ switch (this) {
+ case 'github':
+ return Provider.github;
+ case 'google':
+ return Provider.google;
+ case 'discord':
+ return Provider.discord;
+ default:
+ throw UnimplementedError();
+ }
+ }
+}
diff --git a/frontend/appflowy_flutter/lib/user/application/auth_service.dart b/frontend/appflowy_flutter/lib/user/application/auth_service.dart
deleted file mode 100644
index c6e684d611..0000000000
--- a/frontend/appflowy_flutter/lib/user/application/auth_service.dart
+++ /dev/null
@@ -1,63 +0,0 @@
-import 'package:dartz/dartz.dart';
-import 'package:easy_localization/easy_localization.dart';
-import 'package:flowy_infra/uuid.dart';
-import 'package:appflowy_backend/dispatch/dispatch.dart';
-import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
-import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart'
- show SignInPayloadPB, SignUpPayloadPB, UserProfilePB;
-
-import '../../generated/locale_keys.g.dart';
-
-class AuthService {
- Future> signIn({
- required String? email,
- required String? password,
- }) {
- //
- final request = SignInPayloadPB.create()
- ..email = email ?? ''
- ..password = password ?? '';
-
- return UserEventSignIn(request).send();
- }
-
- Future> signUp({
- required String? name,
- required String? password,
- required String? email,
- }) {
- final request = SignUpPayloadPB.create()
- ..email = email ?? ''
- ..name = name ?? ''
- ..password = password ?? '';
-
- return UserEventSignUp(request).send();
-
- // return UserEventSignUp(request).send().then((result) {
- // return result.fold((userProfile) async {
- // return await FolderEventCreateDefaultWorkspace().send().then((result) {
- // return result.fold((workspaceIdentifier) {
- // return left(Tuple2(userProfile, workspaceIdentifier.workspaceId));
- // }, (error) {
- // throw UnimplementedError;
- // });
- // });
- // }, (error) => right(error));
- // });
- }
-
- Future> signOut() {
- return UserEventSignOut().send();
- }
-
- Future> autoSignUp() {
- const password = "AppFlowy123@";
- final uid = uuid();
- final userEmail = "$uid@appflowy.io";
- return signUp(
- name: LocaleKeys.defaultUsername.tr(),
- password: password,
- email: userEmail,
- );
- }
-}
diff --git a/frontend/appflowy_flutter/lib/user/application/prelude.dart b/frontend/appflowy_flutter/lib/user/application/prelude.dart
index 74b644808e..ddacb654d3 100644
--- a/frontend/appflowy_flutter/lib/user/application/prelude.dart
+++ b/frontend/appflowy_flutter/lib/user/application/prelude.dart
@@ -1,4 +1,4 @@
-export './auth_service.dart';
+export 'auth/appflowy_auth_service.dart';
export './sign_in_bloc.dart';
export './sign_up_bloc.dart';
export './splash_bloc.dart';
diff --git a/frontend/appflowy_flutter/lib/user/application/sign_in_bloc.dart b/frontend/appflowy_flutter/lib/user/application/sign_in_bloc.dart
index 69b0fe7a62..a95530a5c0 100644
--- a/frontend/appflowy_flutter/lib/user/application/sign_in_bloc.dart
+++ b/frontend/appflowy_flutter/lib/user/application/sign_in_bloc.dart
@@ -1,4 +1,4 @@
-import 'package:appflowy/user/application/auth_service.dart';
+import 'package:appflowy/user/application/auth/auth_service.dart';
import 'package:dartz/dartz.dart';
import 'package:appflowy_backend/protobuf/flowy-error/code.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
@@ -20,6 +20,16 @@ class SignInBloc extends Bloc {
emit,
);
},
+ signedInWithOAuth: (value) async =>
+ await _performActionOnSignInWithOAuth(
+ state,
+ emit,
+ value.platform,
+ ),
+ signedInAsGuest: (value) async => await _performActionOnSignInAsGuest(
+ state,
+ emit,
+ ),
emailChanged: (EmailChanged value) async {
emit(
state.copyWith(
@@ -45,6 +55,26 @@ class SignInBloc extends Bloc {
Future _performActionOnSignIn(
SignInState state,
Emitter emit,
+ ) async {
+ final result = await authService.signIn(
+ email: state.email ?? '',
+ password: state.password ?? '',
+ );
+ emit(
+ result.fold(
+ (error) => stateFromCode(error),
+ (userProfile) => state.copyWith(
+ isSubmitting: false,
+ successOrFail: some(left(userProfile)),
+ ),
+ ),
+ );
+ }
+
+ Future _performActionOnSignInWithOAuth(
+ SignInState state,
+ Emitter emit,
+ String platform,
) async {
emit(
state.copyWith(
@@ -55,17 +85,41 @@ class SignInBloc extends Bloc {
),
);
- final result = await authService.signIn(
- email: state.email,
- password: state.password,
+ final result = await authService.signUpWithOAuth(
+ platform: platform,
);
emit(
result.fold(
+ (error) => stateFromCode(error),
(userProfile) => state.copyWith(
isSubmitting: false,
successOrFail: some(left(userProfile)),
),
+ ),
+ );
+ }
+
+ Future _performActionOnSignInAsGuest(
+ SignInState state,
+ Emitter emit,
+ ) async {
+ emit(
+ state.copyWith(
+ isSubmitting: true,
+ emailError: none(),
+ passwordError: none(),
+ successOrFail: none(),
+ ),
+ );
+
+ final result = await authService.signUpAsGuest();
+ emit(
+ result.fold(
(error) => stateFromCode(error),
+ (userProfile) => state.copyWith(
+ isSubmitting: false,
+ successOrFail: some(left(userProfile)),
+ ),
),
);
}
@@ -97,6 +151,9 @@ class SignInBloc extends Bloc {
class SignInEvent with _$SignInEvent {
const factory SignInEvent.signedInWithUserEmailAndPassword() =
SignedInWithUserEmailAndPassword;
+ const factory SignInEvent.signedInWithOAuth(String platform) =
+ SignedInWithOAuth;
+ const factory SignInEvent.signedInAsGuest() = SignedInAsGuest;
const factory SignInEvent.emailChanged(String email) = EmailChanged;
const factory SignInEvent.passwordChanged(String password) = PasswordChanged;
}
diff --git a/frontend/appflowy_flutter/lib/user/application/sign_up_bloc.dart b/frontend/appflowy_flutter/lib/user/application/sign_up_bloc.dart
index bb3e99f51c..ca884cdecc 100644
--- a/frontend/appflowy_flutter/lib/user/application/sign_up_bloc.dart
+++ b/frontend/appflowy_flutter/lib/user/application/sign_up_bloc.dart
@@ -1,4 +1,4 @@
-import 'package:appflowy/user/application/auth_service.dart';
+import 'package:appflowy/user/application/auth/auth_service.dart';
import 'package:dartz/dartz.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:appflowy_backend/protobuf/flowy-error/code.pb.dart';
@@ -100,12 +100,13 @@ class SignUpBloc extends Bloc {
);
final result = await authService.signUp(
- name: state.email,
- password: state.password,
- email: state.email,
+ name: state.email ?? '',
+ password: state.password ?? '',
+ email: state.email ?? '',
);
emit(
result.fold(
+ (error) => stateFromCode(error),
(profile) => state.copyWith(
isSubmitting: false,
successOrFail: some(left(profile)),
@@ -113,7 +114,6 @@ class SignUpBloc extends Bloc {
passwordError: none(),
repeatPasswordError: none(),
),
- (error) => stateFromCode(error),
),
);
}
diff --git a/frontend/appflowy_flutter/lib/user/application/splash_bloc.dart b/frontend/appflowy_flutter/lib/user/application/splash_bloc.dart
index 56448ad538..f603e1cf53 100644
--- a/frontend/appflowy_flutter/lib/user/application/splash_bloc.dart
+++ b/frontend/appflowy_flutter/lib/user/application/splash_bloc.dart
@@ -1,5 +1,6 @@
+import 'package:appflowy/startup/startup.dart';
+import 'package:appflowy/user/application/auth/auth_service.dart';
import 'package:appflowy/user/domain/auth_state.dart';
-import 'package:appflowy_backend/dispatch/dispatch.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
@@ -10,16 +11,11 @@ class SplashBloc extends Bloc {
on((event, emit) async {
await event.map(
getUser: (val) async {
- final result = await UserEventCheckUser().send();
- final authState = result.fold(
- (userProfile) {
- return AuthState.authenticated(userProfile);
- },
- (error) {
- return AuthState.unauthenticated(error);
- },
+ final response = await getIt().getUser();
+ final authState = response.fold(
+ (error) => AuthState.unauthenticated(error),
+ (user) => AuthState.authenticated(user),
);
-
emit(state.copyWith(auth: authState));
},
);
diff --git a/frontend/appflowy_flutter/lib/user/application/user_listener.dart b/frontend/appflowy_flutter/lib/user/application/user_listener.dart
index 91185b8f45..a06e398bc5 100644
--- a/frontend/appflowy_flutter/lib/user/application/user_listener.dart
+++ b/frontend/appflowy_flutter/lib/user/application/user_listener.dart
@@ -40,7 +40,7 @@ class UserListener {
}
_userParser = UserNotificationParser(
- id: _userProfile.token,
+ id: _userProfile.id.toString(),
callback: _userNotificationCallback,
);
_subscription = RustStreamReceiver.listen((observable) {
diff --git a/frontend/appflowy_flutter/lib/user/application/user_service.dart b/frontend/appflowy_flutter/lib/user/application/user_service.dart
index eacb938de1..60ec1e26f8 100644
--- a/frontend/appflowy_flutter/lib/user/application/user_service.dart
+++ b/frontend/appflowy_flutter/lib/user/application/user_service.dart
@@ -1,10 +1,10 @@
import 'dart:async';
+import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
import 'package:dartz/dartz.dart';
import 'package:appflowy_backend/dispatch/dispatch.dart';
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-folder2/workspace.pb.dart';
-import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart';
import 'package:fixnum/fixnum.dart';
class UserBackendService {
@@ -58,8 +58,9 @@ class UserBackendService {
throw UnimplementedError();
}
- Future> signOut() {
- return UserEventSignOut().send();
+ Future> signOut(AuthTypePB authType) {
+ final payload = SignOutPB()..authType = authType;
+ return UserEventSignOut(payload).send();
}
Future> initUser() async {
diff --git a/frontend/appflowy_flutter/lib/user/presentation/folder/folder_widget.dart b/frontend/appflowy_flutter/lib/user/presentation/folder/folder_widget.dart
index 4033d59e13..20eb65fbcd 100644
--- a/frontend/appflowy_flutter/lib/user/presentation/folder/folder_widget.dart
+++ b/frontend/appflowy_flutter/lib/user/presentation/folder/folder_widget.dart
@@ -61,9 +61,9 @@ class _FolderWidgetState extends State {
}
Future _openFolder() async {
- final directory = await getIt().getDirectoryPath();
- if (directory != null) {
- await getIt().setLocation(directory);
+ final path = await getIt().getDirectoryPath();
+ if (path != null) {
+ await getIt().setPath(path);
await widget.createFolderCallback();
}
}
@@ -188,7 +188,7 @@ class CreateFolderWidgetState extends State {
LocaleKeys.settings_files_locationCannotBeEmpty.tr(),
);
} else {
- await getIt().setLocation(_path);
+ await getIt().setPath(_path);
await widget.onPressedCreate();
}
},
diff --git a/frontend/appflowy_flutter/lib/user/presentation/router.dart b/frontend/appflowy_flutter/lib/user/presentation/router.dart
index b03f3ef042..d0845d41d7 100644
--- a/frontend/appflowy_flutter/lib/user/presentation/router.dart
+++ b/frontend/appflowy_flutter/lib/user/presentation/router.dart
@@ -1,5 +1,5 @@
import 'package:appflowy/startup/startup.dart';
-import 'package:appflowy/user/application/auth_service.dart';
+import 'package:appflowy/user/application/auth/auth_service.dart';
import 'package:appflowy/user/presentation/sign_in_screen.dart';
import 'package:appflowy/user/presentation/sign_up_screen.dart';
import 'package:appflowy/user/presentation/skip_log_in_screen.dart';
@@ -28,7 +28,7 @@ class AuthRouter {
);
}
- void pushHomeScreen(
+ void pushHomeScreenWithWorkSpace(
BuildContext context,
UserProfilePB profile,
WorkspaceSettingPB workspaceSetting,
@@ -45,6 +45,21 @@ class AuthRouter {
),
);
}
+
+ Future pushHomeScreen(
+ BuildContext context,
+ UserProfilePB userProfile,
+ ) async {
+ final result = await FolderEventReadCurrentWorkspace().send();
+ result.fold(
+ (workspaceSettingPB) => pushHomeScreenWithWorkSpace(
+ context,
+ userProfile,
+ workspaceSettingPB,
+ ),
+ (r) => pushWelcomeScreen(context, userProfile),
+ );
+ }
}
class SplashRoute {
diff --git a/frontend/appflowy_flutter/lib/user/presentation/sign_in_screen.dart b/frontend/appflowy_flutter/lib/user/presentation/sign_in_screen.dart
index 44b339a31e..fef663f104 100644
--- a/frontend/appflowy_flutter/lib/user/presentation/sign_in_screen.dart
+++ b/frontend/appflowy_flutter/lib/user/presentation/sign_in_screen.dart
@@ -1,13 +1,15 @@
+import 'package:appflowy/core/config/kv.dart';
+import 'package:appflowy/core/config/kv_keys.dart';
+import 'package:appflowy/core/frameless_window.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/user/application/sign_in_bloc.dart';
import 'package:appflowy/user/presentation/router.dart';
import 'package:appflowy/user/presentation/widgets/background.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/size.dart';
-import 'package:flowy_infra_ui/style_widget/text.dart';
+import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/widget/rounded_button.dart';
import 'package:flowy_infra_ui/widget/rounded_input_field.dart';
-import 'package:flowy_infra_ui/widget/spacing.dart';
import 'package:flowy_infra_ui/style_widget/snap_bar.dart';
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart'
@@ -19,21 +21,29 @@ import 'package:flowy_infra/image.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
class SignInScreen extends StatelessWidget {
+ const SignInScreen({
+ super.key,
+ required this.router,
+ });
+
final AuthRouter router;
- const SignInScreen({Key? key, required this.router}) : super(key: key);
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => getIt(),
- child: BlocListener(
+ child: BlocConsumer(
listener: (context, state) {
state.successOrFail.fold(
() => null,
(result) => _handleSuccessOrFail(result, context),
);
},
- child: Scaffold(
+ builder: (_, __) => Scaffold(
+ appBar: const PreferredSize(
+ preferredSize: Size(double.infinity, 60),
+ child: MoveWindowDetector(),
+ ),
body: SignInForm(router: router),
),
),
@@ -45,18 +55,19 @@ class SignInScreen extends StatelessWidget {
BuildContext context,
) {
result.fold(
- (user) => router.pushWelcomeScreen(context, user),
+ (user) => router.pushHomeScreen(context, user),
(error) => showSnapBar(context, error.msg),
);
}
}
class SignInForm extends StatelessWidget {
- final AuthRouter router;
const SignInForm({
- Key? key,
+ super.key,
required this.router,
- }) : super(key: key);
+ });
+
+ final AuthRouter router;
@override
Widget build(BuildContext context) {
@@ -64,22 +75,42 @@ class SignInForm extends StatelessWidget {
alignment: Alignment.center,
child: AuthFormContainer(
children: [
+ // Email.
FlowyLogoTitle(
title: LocaleKeys.signIn_loginTitle.tr(),
logoSize: const Size(60, 60),
),
const VSpace(30),
- const EmailTextField(),
- const PasswordTextField(),
- ForgetPasswordButton(router: router),
- const VSpace(30),
- const LoginButton(),
+ // Email and password. don't support yet.
+ /*
+ ...[
+ const EmailTextField(),
+ const VSpace(5),
+ const PasswordTextField(),
+ const VSpace(20),
+ const LoginButton(),
+ const VSpace(10),
+
+ const VSpace(10),
+ SignUpPrompt(router: router),
+ ],
+ */
+
+ const SignInAsGuestButton(),
+
+ // third-party sign in.
+ const VSpace(20),
+ const OrContinueWith(),
const VSpace(10),
- SignUpPrompt(router: router),
+ const ThirdPartySignInButtons(),
+ const VSpace(20),
+
+ // loading status
if (context.read().state.isSubmitting) ...[
const SizedBox(height: 8),
const LinearProgressIndicator(value: null),
- ]
+ const VSpace(20),
+ ],
],
),
);
@@ -113,6 +144,7 @@ class SignUpPrompt extends StatelessWidget {
style: TextStyle(color: Theme.of(context).colorScheme.primary),
),
),
+ ForgetPasswordButton(router: router),
],
);
}
@@ -136,6 +168,25 @@ class LoginButton extends StatelessWidget {
}
}
+class SignInAsGuestButton extends StatelessWidget {
+ const SignInAsGuestButton({
+ Key? key,
+ }) : super(key: key);
+
+ @override
+ Widget build(BuildContext context) {
+ return RoundedTextButton(
+ title: LocaleKeys.signIn_loginAsGuestButtonText.tr(),
+ height: 48,
+ borderRadius: Corners.s6Border,
+ onPressed: () {
+ getIt().set(KVKeys.loginType, 'local');
+ context.read().add(const SignInEvent.signedInAsGuest());
+ },
+ );
+ }
+}
+
class ForgetPasswordButton extends StatelessWidget {
const ForgetPasswordButton({
Key? key,
@@ -150,7 +201,9 @@ class ForgetPasswordButton extends StatelessWidget {
style: TextButton.styleFrom(
textStyle: Theme.of(context).textTheme.bodyMedium,
),
- onPressed: () => router.pushForgetPasswordScreen(context),
+ onPressed: () {
+ throw UnimplementedError();
+ },
child: Text(
LocaleKeys.signIn_forgotPassword.tr(),
style: TextStyle(color: Theme.of(context).colorScheme.primary),
@@ -214,3 +267,98 @@ class EmailTextField extends StatelessWidget {
);
}
}
+
+class OrContinueWith extends StatelessWidget {
+ const OrContinueWith({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return Row(
+ mainAxisAlignment: MainAxisAlignment.spaceEvenly,
+ children: const [
+ Flexible(
+ child: Divider(
+ color: Colors.white,
+ height: 10,
+ ),
+ ),
+ FlowyText.regular(' Or continue with '),
+ Flexible(
+ child: Divider(
+ color: Colors.white,
+ height: 10,
+ ),
+ ),
+ ],
+ );
+ }
+}
+
+class ThirdPartySignInButton extends StatelessWidget {
+ const ThirdPartySignInButton({
+ Key? key,
+ required this.icon,
+ required this.onPressed,
+ }) : super(key: key);
+
+ final String icon;
+ final VoidCallback onPressed;
+
+ @override
+ Widget build(BuildContext context) {
+ return FlowyIconButton(
+ height: 48,
+ width: 48,
+ iconPadding: const EdgeInsets.all(8.0),
+ radius: Corners.s10Border,
+ onPressed: onPressed,
+ icon: svgWidget(
+ icon,
+ ),
+ );
+ }
+}
+
+class ThirdPartySignInButtons extends StatelessWidget {
+ const ThirdPartySignInButtons({
+ super.key,
+ });
+
+ @override
+ Widget build(BuildContext context) {
+ return Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ ThirdPartySignInButton(
+ icon: 'login/google-mark',
+ onPressed: () {
+ getIt().set(KVKeys.loginType, 'supabase');
+ context
+ .read()
+ .add(const SignInEvent.signedInWithOAuth('google'));
+ },
+ ),
+ const SizedBox(width: 20),
+ ThirdPartySignInButton(
+ icon: 'login/github-mark',
+ onPressed: () {
+ getIt().set(KVKeys.loginType, 'supabase');
+ context
+ .read()
+ .add(const SignInEvent.signedInWithOAuth('github'));
+ },
+ ),
+ const SizedBox(width: 20),
+ ThirdPartySignInButton(
+ icon: 'login/discord-mark',
+ onPressed: () {
+ getIt().set(KVKeys.loginType, 'supabase');
+ context
+ .read()
+ .add(const SignInEvent.signedInWithOAuth('discord'));
+ },
+ ),
+ ],
+ );
+ }
+}
diff --git a/frontend/appflowy_flutter/lib/user/presentation/sign_up_screen.dart b/frontend/appflowy_flutter/lib/user/presentation/sign_up_screen.dart
index 7b8b796fd1..95f9c2fef8 100644
--- a/frontend/appflowy_flutter/lib/user/presentation/sign_up_screen.dart
+++ b/frontend/appflowy_flutter/lib/user/presentation/sign_up_screen.dart
@@ -18,8 +18,12 @@ import 'package:flowy_infra/image.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
class SignUpScreen extends StatelessWidget {
+ const SignUpScreen({
+ super.key,
+ required this.router,
+ });
+
final AuthRouter router;
- const SignUpScreen({Key? key, required this.router}) : super(key: key);
@override
Widget build(BuildContext context) {
@@ -65,7 +69,9 @@ class SignUpForm extends StatelessWidget {
),
const VSpace(30),
const EmailTextField(),
+ const VSpace(5),
const PasswordTextField(),
+ const VSpace(5),
const RepeatPasswordTextField(),
const VSpace(30),
const SignUpButton(),
diff --git a/frontend/appflowy_flutter/lib/user/presentation/skip_log_in_screen.dart b/frontend/appflowy_flutter/lib/user/presentation/skip_log_in_screen.dart
index fb9662bc8c..8f69763603 100644
--- a/frontend/appflowy_flutter/lib/user/presentation/skip_log_in_screen.dart
+++ b/frontend/appflowy_flutter/lib/user/presentation/skip_log_in_screen.dart
@@ -1,5 +1,6 @@
import 'package:appflowy/core/frameless_window.dart';
import 'package:appflowy/startup/entry_point.dart';
+import 'package:appflowy/user/application/auth/auth_service.dart';
import 'package:dartz/dartz.dart' as dartz;
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/size.dart';
@@ -15,7 +16,6 @@ import 'package:url_launcher/url_launcher.dart';
import '../../generated/locale_keys.g.dart';
import '../../startup/launch_configuration.dart';
import '../../startup/startup.dart';
-import '../application/auth_service.dart';
import 'folder/folder_widget.dart';
import 'router.dart';
import 'widgets/background.dart';
@@ -120,16 +120,16 @@ class _SkipLogInScreenState extends State {
}
Future _autoRegister(BuildContext context) async {
- final result = await widget.authService.autoSignUp();
+ final result = await widget.authService.signUpAsGuest();
result.fold(
+ (error) {
+ Log.error(error);
+ },
(user) {
FolderEventReadCurrentWorkspace().send().then((result) {
_openCurrentWorkspace(context, user, result);
});
},
- (error) {
- Log.error(error);
- },
);
}
@@ -140,7 +140,8 @@ class _SkipLogInScreenState extends State {
) {
workspacesOrError.fold(
(workspaceSetting) {
- widget.router.pushHomeScreen(context, user, workspaceSetting);
+ widget.router
+ .pushHomeScreenWithWorkSpace(context, user, workspaceSetting);
},
(error) {
Log.error(error);
diff --git a/frontend/appflowy_flutter/lib/user/presentation/splash_screen.dart b/frontend/appflowy_flutter/lib/user/presentation/splash_screen.dart
index e2c60447ce..aefcada89d 100644
--- a/frontend/appflowy_flutter/lib/user/presentation/splash_screen.dart
+++ b/frontend/appflowy_flutter/lib/user/presentation/splash_screen.dart
@@ -1,10 +1,11 @@
+import 'package:appflowy/startup/tasks/supabase_task.dart';
+import 'package:appflowy/user/application/auth/auth_service.dart';
import 'package:appflowy_backend/dispatch/dispatch.dart';
import 'package:appflowy_backend/log.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../startup/startup.dart';
-import '../application/auth_service.dart';
import '../application/splash_bloc.dart';
import '../domain/auth_state.dart';
import 'router.dart';
@@ -64,33 +65,40 @@ class SplashScreen extends StatelessWidget {
);
}
- void _handleAuthenticated(BuildContext context, Authenticated result) {
- final userProfile = result.userProfile;
- FolderEventReadCurrentWorkspace().send().then(
- (result) {
- return result.fold(
- (workspaceSetting) {
- getIt()
- .pushHomeScreen(context, userProfile, workspaceSetting);
- },
- (error) async {
- Log.error(error);
- getIt().pushWelcomeScreen(context, userProfile);
- },
+ Future _handleAuthenticated(
+ BuildContext context,
+ Authenticated authenticated,
+ ) async {
+ final userProfile = authenticated.userProfile;
+ final result = await FolderEventReadCurrentWorkspace().send();
+ result.fold(
+ (workspaceSetting) {
+ getIt().pushHomeScreen(
+ context,
+ userProfile,
+ workspaceSetting,
);
},
+ (error) async {
+ Log.error(error);
+ getIt().pushWelcomeScreen(context, userProfile);
+ },
);
}
void _handleUnauthenticated(BuildContext context, Unauthenticated result) {
- // getIt().pushSignInScreen(context);
- getIt().pushSkipLoginScreen(context);
+ // if the env is not configured, we will skip to the 'skip login screen'.
+ if (isSupabaseEnable) {
+ getIt().pushSignInScreen(context);
+ } else {
+ getIt().pushSkipLoginScreen(context);
+ }
}
Future _registerIfNeeded() async {
final result = await UserEventCheckUser().send();
if (!result.isLeft()) {
- await getIt().autoSignUp();
+ await getIt().signUpAsGuest();
}
}
}
diff --git a/frontend/appflowy_flutter/lib/workspace/application/settings/settings_location_cubit.dart b/frontend/appflowy_flutter/lib/workspace/application/settings/settings_location_cubit.dart
index 44b32dfe5d..344ffc2887 100644
--- a/frontend/appflowy_flutter/lib/workspace/application/settings/settings_location_cubit.dart
+++ b/frontend/appflowy_flutter/lib/workspace/application/settings/settings_location_cubit.dart
@@ -1,71 +1,83 @@
import 'dart:io';
-import 'package:bloc/bloc.dart';
-import 'package:flutter/material.dart';
-import 'package:shared_preferences/shared_preferences.dart';
+import 'package:appflowy/core/config/kv.dart';
+import 'package:appflowy/core/config/kv_keys.dart';
+import 'package:appflowy/startup/startup.dart';
+import 'package:appflowy_backend/log.dart';
+import 'package:flutter/foundation.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:freezed_annotation/freezed_annotation.dart';
import '../../../startup/tasks/prelude.dart';
-@visibleForTesting
-const String kSettingsLocationDefaultLocation =
- 'kSettingsLocationDefaultLocation';
+part 'settings_location_cubit.freezed.dart';
-class SettingsLocation {
- SettingsLocation({
- String? path,
- }) : _path = path;
+@freezed
+class SettingsLocationState with _$SettingsLocationState {
+ const factory SettingsLocationState.initial() = _Initial;
+ const factory SettingsLocationState.didReceivedPath(String path) =
+ _DidReceivedPath;
+}
- String? _path;
-
- set path(String? path) {
- _path = path;
+class SettingsLocationCubit extends Cubit {
+ SettingsLocationCubit() : super(const SettingsLocationState.initial()) {
+ _init();
}
- String? get path {
+ Future setPath(String path) async {
+ await getIt().setPath(path);
+ emit(SettingsLocationState.didReceivedPath(path));
+ }
+
+ Future _init() async {
+ final path = await getIt().getPath();
+ emit(SettingsLocationState.didReceivedPath(path));
+ }
+}
+
+class LocalFileStorage {
+ LocalFileStorage();
+ String? _cachePath;
+
+ Future setPath(String path) async {
+ if (kIsWeb || Platform.isAndroid || Platform.isIOS) {
+ Log.info('LocalFileStorage is not supported on this platform.');
+ return;
+ }
+
if (Platform.isMacOS) {
// remove the prefix `/Volumes/*`
- return _path?.replaceFirst(RegExp(r'^/Volumes/[^/]+'), '');
+ path = path.replaceFirst(RegExp(r'^/Volumes/[^/]+'), '');
} else if (Platform.isWindows) {
- return _path?.replaceAll("/", "\\");
+ path = path.replaceAll('/', '\\');
}
- return _path;
+
+ await getIt().set(KVKeys.pathLocation, path);
+ // clear the cache path, and not set the cache path to the new path because the set path may be invalid
+ _cachePath = null;
}
- SettingsLocation copyWith({String? path}) {
- return SettingsLocation(
- path: path ?? this.path,
+ Future getPath() async {
+ if (_cachePath != null) {
+ return _cachePath!;
+ }
+
+ final response = await getIt().get(KVKeys.pathLocation);
+ final String path = await response.fold(
+ (error) async {
+ // return the default path if the path is not set
+ final directory = await appFlowyDocumentDirectory();
+ return directory.path;
+ },
+ (path) => path,
);
- }
-}
+ _cachePath = path;
-class SettingsLocationCubit extends Cubit {
- SettingsLocationCubit() : super(SettingsLocation(path: null));
-
- /// Returns a path that used to store user data
- Future fetchLocation() async {
- final prefs = await SharedPreferences.getInstance();
-
- /// Use the [appFlowyDocumentDirectory] instead if there is no user
- /// preference location
- final path = prefs.getString(kSettingsLocationDefaultLocation) ??
- (await appFlowyDocumentDirectory()).path;
-
- emit(state.copyWith(path: path));
- return Future.value(path);
- }
-
- /// Saves the user preference local data store location
- Future setLocation(String? path) async {
- path = path ?? (await appFlowyDocumentDirectory()).path;
-
- assert(path.isNotEmpty);
- if (path.isEmpty) {
- path = (await appFlowyDocumentDirectory()).path;
+ // if the path is not exists means the path is invalid, so we should clear the kv store
+ if (!Directory(path).existsSync()) {
+ await getIt().clear();
}
- final prefs = await SharedPreferences.getInstance();
- prefs.setString(kSettingsLocationDefaultLocation, path);
- await Directory(path).create(recursive: true);
- emit(state.copyWith(path: path));
+ return path;
}
}
diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_file_customize_location_view.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_file_customize_location_view.dart
index 5ed0a07f13..8a7e9af1f7 100644
--- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_file_customize_location_view.dart
+++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_file_customize_location_view.dart
@@ -1,154 +1,258 @@
+import 'dart:io';
+
import 'package:appflowy/startup/entry_point.dart';
import 'package:appflowy/util/file_picker/file_picker_service.dart';
import 'package:appflowy/workspace/application/settings/settings_location_cubit.dart';
+import 'package:flowy_infra/image.dart';
+import 'package:flowy_infra_ui/style_widget/hover.dart';
+import 'package:flowy_infra_ui/widget/buttons/secondary_button.dart';
import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
-import 'package:flutter/services.dart';
-
+import 'package:styled_widget/styled_widget.dart';
+import 'package:url_launcher/url_launcher.dart';
import '../../../../generated/locale_keys.g.dart';
import '../../../../startup/launch_configuration.dart';
import '../../../../startup/startup.dart';
import '../../../../startup/tasks/prelude.dart';
-class SettingsFileLocationCustomzier extends StatefulWidget {
- const SettingsFileLocationCustomzier({
+class SettingsFileLocationCustomizer extends StatefulWidget {
+ const SettingsFileLocationCustomizer({
super.key,
- required this.cubit,
});
- final SettingsLocationCubit cubit;
-
@override
- State createState() =>
- SettingsFileLocationCustomzierState();
+ State createState() =>
+ SettingsFileLocationCustomizerState();
}
@visibleForTesting
-class SettingsFileLocationCustomzierState
- extends State {
+class SettingsFileLocationCustomizerState
+ extends State {
@override
Widget build(BuildContext context) {
- return BlocProvider.value(
- value: widget.cubit,
- child: BlocBuilder(
+ return BlocProvider(
+ create: (_) => SettingsLocationCubit(),
+ child: BlocBuilder(
builder: (context, state) {
- return ListTile(
- title: FlowyText.medium(
- LocaleKeys.settings_files_defaultLocation.tr(),
- overflow: TextOverflow.ellipsis,
+ return state.when(
+ initial: () => const Center(
+ child: CircularProgressIndicator(),
),
- subtitle: Tooltip(
- message: LocaleKeys.settings_files_doubleTapToCopy.tr(),
- child: GestureDetector(
- onDoubleTap: () {
- Clipboard.setData(
- ClipboardData(
- text: state.path,
+ didReceivedPath: (path) {
+ return Row(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ // display file paths.
+ Flexible(
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ mainAxisAlignment: MainAxisAlignment.spaceEvenly,
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ FlowyText.medium(
+ LocaleKeys.settings_files_defaultLocation.tr(),
+ fontSize: 13,
+ overflow: TextOverflow.visible,
+ ).padding(horizontal: 5),
+ const VSpace(5),
+ _CopyableText(
+ usingPath: path,
+ ),
+ ],
),
- ).then((_) {
- if (mounted) {
- ScaffoldMessenger.of(context).showSnackBar(
- SnackBar(
- content: FlowyText(
- LocaleKeys.settings_files_pathCopiedSnackbar.tr(),
- color: Theme.of(context).colorScheme.onSurface,
- ),
- ),
- );
- }
- });
- },
- child: FlowyText.regular(
- state.path ?? '',
- overflow: TextOverflow.ellipsis,
- ),
- ),
- ),
- trailing: Row(
- mainAxisSize: MainAxisSize.min,
- children: [
- Tooltip(
- message: LocaleKeys.settings_files_restoreLocation.tr(),
- child: FlowyIconButton(
- height: 40,
- width: 40,
- icon: const Icon(Icons.restore_outlined),
- hoverColor:
- Theme.of(context).colorScheme.secondaryContainer,
- onPressed: () async {
- final result = await appFlowyDocumentDirectory();
- await _setCustomLocation(result.path);
- await FlowyRunner.run(
- FlowyApp(),
- config: const LaunchConfiguration(
- autoRegistrationSupported: true,
- ),
- );
- if (mounted) {
- Navigator.of(context).pop();
- }
- },
),
- ),
- const SizedBox(
- width: 5,
- ),
- Tooltip(
- message: LocaleKeys.settings_files_customizeLocation.tr(),
- child: FlowyIconButton(
- height: 40,
- width: 40,
- icon: const Icon(Icons.folder_open_outlined),
- hoverColor:
- Theme.of(context).colorScheme.secondaryContainer,
- onPressed: () async {
- final result =
- await getIt().getDirectoryPath();
- if (result != null) {
- await _setCustomLocation(result);
- await reloadApp();
- }
- },
+
+ // display the icons
+ Flexible(
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.end,
+ children: [
+ _ChangeStoragePathButton(
+ usingPath: path,
+ ),
+ const HSpace(10),
+ _OpenStorageButton(
+ usingPath: path,
+ ),
+ _RecoverDefaultStorageButton(
+ usingPath: path,
+ ),
+ ],
+ ),
),
- )
- ],
- ),
+ ],
+ );
+ },
);
},
),
);
}
+}
- Future _setCustomLocation(String? path) async {
- // Using default location if path equals null.
- final location = path ?? (await appFlowyDocumentDirectory()).path;
- if (mounted) {
- widget.cubit.setLocation(location);
- }
+class _CopyableText extends StatelessWidget {
+ const _CopyableText({
+ required this.usingPath,
+ });
- // The location could not save into the KV db, because the db initialize is later than the rust sdk initialize.
- /*
- final prefs = await SharedPreferences.getInstance();
- if (mounted) {
- context
- .read()
- .setKeyValue(AppearanceKeys.defaultLocation, location);
- }
- */
- }
+ final String usingPath;
- Future reloadApp() async {
- await FlowyRunner.run(
- FlowyApp(),
- config: const LaunchConfiguration(
- autoRegistrationSupported: true,
- ),
+ @override
+ Widget build(BuildContext context) {
+ return FlowyHover(
+ builder: (_, onHover) {
+ return GestureDetector(
+ onTap: () {
+ Clipboard.setData(ClipboardData(text: usingPath));
+ ScaffoldMessenger.of(context).showSnackBar(
+ SnackBar(
+ content: FlowyText(
+ LocaleKeys.settings_files_pathCopiedSnackbar.tr(),
+ color: Theme.of(context).colorScheme.onSurface,
+ ),
+ ),
+ );
+ },
+ child: Container(
+ height: 20,
+ padding: const EdgeInsets.symmetric(horizontal: 5),
+ child: Row(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ Flexible(
+ child: FlowyText.regular(
+ usingPath,
+ fontSize: 12,
+ overflow: TextOverflow.ellipsis,
+ ),
+ ),
+ if (onHover)
+ FlowyText.regular(
+ LocaleKeys.settings_files_copy.tr(),
+ fontSize: 12,
+ color: Theme.of(context).colorScheme.primary,
+ )
+ ],
+ ),
+ ),
+ );
+ },
+ );
+ }
+}
+
+class _ChangeStoragePathButton extends StatefulWidget {
+ const _ChangeStoragePathButton({
+ required this.usingPath,
+ });
+
+ final String usingPath;
+
+ @override
+ State<_ChangeStoragePathButton> createState() =>
+ _ChangeStoragePathButtonState();
+}
+
+class _ChangeStoragePathButtonState extends State<_ChangeStoragePathButton> {
+ @override
+ Widget build(BuildContext context) {
+ return Tooltip(
+ message: LocaleKeys.settings_files_changeLocationTooltips.tr(),
+ child: SecondaryTextButton(
+ LocaleKeys.settings_files_change.tr(),
+ mode: SecondaryTextButtonMode.small,
+ onPressed: () async {
+ // pick the new directory and reload app
+ final path = await getIt().getDirectoryPath();
+ if (path == null || !mounted || widget.usingPath == path) {
+ return;
+ }
+ await context.read().setPath(path);
+ await FlowyRunner.run(
+ FlowyApp(),
+ config: const LaunchConfiguration(
+ autoRegistrationSupported: true,
+ ),
+ );
+ if (mounted) {
+ Navigator.of(context).pop();
+ }
+ },
+ ),
+ );
+ }
+}
+
+class _OpenStorageButton extends StatelessWidget {
+ const _OpenStorageButton({
+ required this.usingPath,
+ });
+
+ final String usingPath;
+
+ @override
+ Widget build(BuildContext context) {
+ return FlowyIconButton(
+ hoverColor: Theme.of(context).colorScheme.secondaryContainer,
+ tooltipText: LocaleKeys.settings_files_openLocationTooltips.tr(),
+ icon: svgWidget(
+ 'common/open_folder',
+ color: Theme.of(context).iconTheme.color,
+ ),
+ onPressed: () async {
+ final uri = Directory(usingPath).uri;
+ if (await canLaunchUrl(uri)) {
+ launchUrl(uri);
+ }
+ },
+ );
+ }
+}
+
+class _RecoverDefaultStorageButton extends StatefulWidget {
+ const _RecoverDefaultStorageButton({
+ required this.usingPath,
+ });
+
+ final String usingPath;
+
+ @override
+ State<_RecoverDefaultStorageButton> createState() =>
+ _RecoverDefaultStorageButtonState();
+}
+
+class _RecoverDefaultStorageButtonState
+ extends State<_RecoverDefaultStorageButton> {
+ @override
+ Widget build(BuildContext context) {
+ return FlowyIconButton(
+ hoverColor: Theme.of(context).colorScheme.secondaryContainer,
+ tooltipText: LocaleKeys.settings_files_recoverLocationTooltips.tr(),
+ icon: svgWidget(
+ 'common/recover',
+ color: Theme.of(context).iconTheme.color,
+ ),
+ onPressed: () async {
+ // reset to the default directory and reload app
+ final directory = await appFlowyDocumentDirectory();
+ final path = directory.path;
+ if (!mounted || widget.usingPath == path) {
+ return;
+ }
+ await context.read().setPath(path);
+ await FlowyRunner.run(
+ FlowyApp(),
+ config: const LaunchConfiguration(
+ autoRegistrationSupported: true,
+ ),
+ );
+ if (mounted) {
+ Navigator.of(context).pop();
+ }
+ },
);
- if (mounted) {
- Navigator.of(context).pop();
- }
- return;
}
}
diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_file_system_view.dart b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_file_system_view.dart
index c90793fbfd..507efbe37b 100644
--- a/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_file_system_view.dart
+++ b/frontend/appflowy_flutter/lib/workspace/presentation/settings/widgets/settings_file_system_view.dart
@@ -1,8 +1,6 @@
import 'package:appflowy/workspace/presentation/settings/widgets/settings_file_customize_location_view.dart';
import 'package:flutter/material.dart';
-import '../../../application/settings/settings_location_cubit.dart';
-
class SettingsFileSystemView extends StatefulWidget {
const SettingsFileSystemView({
super.key,
@@ -13,16 +11,15 @@ class SettingsFileSystemView extends StatefulWidget {
}
class _SettingsFileSystemViewState extends State {
- final _locationCubit = SettingsLocationCubit()..fetchLocation();
-
@override
Widget build(BuildContext context) {
+ // return Column(
+ // children: [],
+ // );
return ListView.separated(
itemBuilder: (context, index) {
if (index == 0) {
- return SettingsFileLocationCustomzier(
- cubit: _locationCubit,
- );
+ return const SettingsFileLocationCustomizer();
} else if (index == 1) {
// return _buildExportDatabaseButton();
}
diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/widgets/dialogs.dart b/frontend/appflowy_flutter/lib/workspace/presentation/widgets/dialogs.dart
index 2e3c8b0622..5b62a726bc 100644
--- a/frontend/appflowy_flutter/lib/workspace/presentation/widgets/dialogs.dart
+++ b/frontend/appflowy_flutter/lib/workspace/presentation/widgets/dialogs.dart
@@ -227,7 +227,7 @@ class OkCancelButton extends StatelessWidget {
SecondaryTextButton(
cancelTitle ?? LocaleKeys.button_Cancel.tr(),
onPressed: onCancelPressed,
- bigMode: true,
+ mode: SecondaryTextButtonMode.big,
),
HSpace(Insets.m),
if (onOkPressed != null)
diff --git a/frontend/appflowy_flutter/macos/Runner/Info.plist b/frontend/appflowy_flutter/macos/Runner/Info.plist
index 4628404f6e..6c91d53468 100644
--- a/frontend/appflowy_flutter/macos/Runner/Info.plist
+++ b/frontend/appflowy_flutter/macos/Runner/Info.plist
@@ -40,5 +40,16 @@
MainMenu
NSPrincipalClass
NSApplication
+ CFBundleURLTypes
+
+
+ CFBundleURLName
+
+ CFBundleURLSchemes
+
+ io.appflowy.appflowy-flutter
+
+
+
diff --git a/frontend/appflowy_flutter/packages/appflowy_popover/pubspec.yaml b/frontend/appflowy_flutter/packages/appflowy_popover/pubspec.yaml
index 80e808f890..7f83c6aee8 100644
--- a/frontend/appflowy_flutter/packages/appflowy_popover/pubspec.yaml
+++ b/frontend/appflowy_flutter/packages/appflowy_popover/pubspec.yaml
@@ -4,8 +4,8 @@ version: 0.0.1
homepage:
environment:
- sdk: ">=2.17.0 <3.0.0"
- flutter: ">=1.17.0"
+ sdk: ">=2.19.0 <3.0.0"
+ flutter: ">=3.7.0"
dependencies:
flutter:
diff --git a/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/text.dart b/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/text.dart
index 4dceb85710..972a330ef2 100644
--- a/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/text.dart
+++ b/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/text.dart
@@ -65,9 +65,12 @@ class FlowyText extends StatelessWidget {
@override
Widget build(BuildContext context) {
+ final text = overflow == TextOverflow.ellipsis
+ ? title.replaceAll('', '\u200B')
+ : title;
if (selectable) {
return SelectableText(
- title,
+ text,
maxLines: maxLines,
textAlign: textAlign,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
@@ -79,7 +82,7 @@ class FlowyText extends StatelessWidget {
);
} else {
return Text(
- title,
+ text,
maxLines: maxLines,
textAlign: textAlign,
overflow: overflow ?? TextOverflow.clip,
diff --git a/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/widget/buttons/secondary_button.dart b/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/widget/buttons/secondary_button.dart
index eae3c58a2a..d45e6affbd 100644
--- a/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/widget/buttons/secondary_button.dart
+++ b/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/widget/buttons/secondary_button.dart
@@ -4,19 +4,50 @@ import 'package:flowy_infra/size.dart';
import 'base_styled_button.dart';
+enum SecondaryTextButtonMode {
+ normal,
+ big,
+ small;
+
+ Size get size {
+ switch (this) {
+ case SecondaryTextButtonMode.normal:
+ return const Size(80, 38);
+ case SecondaryTextButtonMode.big:
+ return const Size(100, 40);
+ case SecondaryTextButtonMode.small:
+ return const Size(100, 30);
+ }
+ }
+
+ BorderRadius get borderRadius {
+ switch (this) {
+ case SecondaryTextButtonMode.normal:
+ return Corners.s8Border;
+ case SecondaryTextButtonMode.big:
+ return Corners.s12Border;
+ case SecondaryTextButtonMode.small:
+ return Corners.s6Border;
+ }
+ }
+}
+
class SecondaryTextButton extends StatelessWidget {
+ const SecondaryTextButton(
+ this.label, {
+ super.key,
+ this.onPressed,
+ this.mode = SecondaryTextButtonMode.normal,
+ });
+
final String label;
final VoidCallback? onPressed;
- final bool bigMode;
-
- const SecondaryTextButton(this.label,
- {Key? key, this.onPressed, this.bigMode = false})
- : super(key: key);
+ final SecondaryTextButtonMode mode;
@override
Widget build(BuildContext context) {
return SecondaryButton(
- bigMode: bigMode,
+ mode: mode,
onPressed: onPressed,
child: FlowyText.regular(
label,
@@ -27,23 +58,27 @@ class SecondaryTextButton extends StatelessWidget {
}
class SecondaryButton extends StatelessWidget {
+ const SecondaryButton({
+ super.key,
+ required this.child,
+ this.onPressed,
+ this.mode = SecondaryTextButtonMode.normal,
+ });
+
final Widget child;
final VoidCallback? onPressed;
- final bool bigMode;
-
- const SecondaryButton(
- {Key? key, required this.child, this.onPressed, this.bigMode = false})
- : super(key: key);
+ final SecondaryTextButtonMode mode;
@override
Widget build(BuildContext context) {
+ final size = mode.size;
return BaseStyledButton(
- minWidth: bigMode ? 100 : 80,
- minHeight: bigMode ? 40 : 38,
+ minWidth: size.width,
+ minHeight: size.height,
contentPadding: EdgeInsets.zero,
bgColor: Theme.of(context).colorScheme.surface,
outlineColor: Theme.of(context).colorScheme.primary,
- borderRadius: bigMode ? Corners.s12Border : Corners.s8Border,
+ borderRadius: mode.borderRadius,
onPressed: onPressed,
child: child,
);
diff --git a/frontend/appflowy_flutter/packages/flowy_infra_ui/pubspec.yaml b/frontend/appflowy_flutter/packages/flowy_infra_ui/pubspec.yaml
index 59d0c48b7e..03547676a8 100644
--- a/frontend/appflowy_flutter/packages/flowy_infra_ui/pubspec.yaml
+++ b/frontend/appflowy_flutter/packages/flowy_infra_ui/pubspec.yaml
@@ -5,8 +5,8 @@ homepage:
publish_to: "none"
environment:
- sdk: ">=2.12.0 <3.0.0"
- flutter: ">=1.20.0"
+ sdk: ">=2.19.0 <3.0.0"
+ flutter: ">=3.7.0"
dependencies:
flutter:
diff --git a/frontend/appflowy_flutter/pubspec.lock b/frontend/appflowy_flutter/pubspec.lock
index 99cecdcfac..065c49f0ac 100644
--- a/frontend/appflowy_flutter/pubspec.lock
+++ b/frontend/appflowy_flutter/pubspec.lock
@@ -5,18 +5,18 @@ packages:
dependency: transitive
description:
name: _fe_analyzer_shared
- sha256: "4897882604d919befd350648c7f91926a9d5de99e67b455bf0917cc2362f4bb8"
+ sha256: "405666cd3cf0ee0a48d21ec67e65406aad2c726d9fa58840d3375e7bdcd32a07"
url: "https://pub.dev"
source: hosted
- version: "47.0.0"
+ version: "60.0.0"
analyzer:
dependency: transitive
description:
name: analyzer
- sha256: "690e335554a8385bc9d787117d9eb52c0c03ee207a607e593de3c9d71b1cfe80"
+ sha256: "1952250bd005bacb895a01bf1b4dc00e3ba1c526cf47dca54dfe24979c65f5b3"
url: "https://pub.dev"
source: hosted
- version: "4.7.0"
+ version: "5.12.0"
animations:
dependency: transitive
description:
@@ -149,10 +149,10 @@ packages:
dependency: "direct dev"
description:
name: build_runner
- sha256: "93f05c041932674be039b0a2323d6cf57e5f2bbf884a3c0382f9e53fc45ebace"
+ sha256: b0a8a7b8a76c493e85f1b84bffa0588859a06197863dba8c9036b15581fd9727
url: "https://pub.dev"
source: hosted
- version: "2.3.0"
+ version: "2.3.3"
build_runner_core:
dependency: transitive
description:
@@ -393,6 +393,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.0.2"
+ envied:
+ dependency: "direct main"
+ description:
+ name: envied
+ sha256: "60d3f5606c7b35bc6ef493e650d916b34351d8af2e58b7ac45881ba59dfcf039"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.3.0+3"
+ envied_generator:
+ dependency: "direct dev"
+ description:
+ name: envied_generator
+ sha256: dfdbe5dc52863e54c036a4c4042afbdf1bd528cb4c1e638ecba26228ba72e9e5
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.3.0+3"
equatable:
dependency: "direct main"
description:
@@ -579,10 +595,10 @@ packages:
dependency: transitive
description:
name: frontend_server_client
- sha256: "4f4a162323c86ffc1245765cfe138872b8f069deb42f7dbb36115fa27f31469b"
+ sha256: "408e3ca148b31c20282ad6f37ebfa6f4bdc8fede5b74bc2f08d9d92b55db3612"
url: "https://pub.dev"
source: hosted
- version: "2.1.3"
+ version: "3.2.0"
fuchsia_remote_debug_protocol:
dependency: transitive
description: flutter
@@ -717,10 +733,10 @@ packages:
dependency: transitive
description:
name: intl_utils
- sha256: "856baa08d4735ee3476901827d52671c1a2b6f9ccb8b848d73bc5b8dd28b21e5"
+ sha256: db392393fbf891e3eb32f6beb1928b00cdb33e3c54597fd5f5dc5c43e5ba601c
url: "https://pub.dev"
source: hosted
- version: "2.7.0"
+ version: "2.8.2"
io:
dependency: transitive
description:
diff --git a/frontend/appflowy_flutter/pubspec.yaml b/frontend/appflowy_flutter/pubspec.yaml
index b0320c7953..0f2afa8481 100644
--- a/frontend/appflowy_flutter/pubspec.yaml
+++ b/frontend/appflowy_flutter/pubspec.yaml
@@ -108,6 +108,7 @@ dependencies:
flutter_svg: ^2.0.5
nanoid: ^1.0.0
supabase_flutter: ^1.9.1
+ envied: ^0.3.0+3
dev_dependencies:
flutter_lints: ^2.0.1
@@ -116,10 +117,11 @@ dev_dependencies:
sdk: flutter
integration_test:
sdk: flutter
- build_runner: ^2.2.0
+ build_runner: ^2.3.3
freezed: ^2.1.0+1
bloc_test: ^9.0.2
json_serializable: ^6.5.4
+ envied_generator: ^0.3.0+3
# The "flutter_lints" package below contains a set of recommended lints to
# encourage good coding practices. The lint set provided by the package is
@@ -174,6 +176,7 @@ flutter:
- assets/images/emoji/
- assets/images/grid/field/
- assets/images/common/
+ - assets/images/login/
- assets/images/grid/setting/
- assets/translations/
diff --git a/frontend/appflowy_flutter/test/util.dart b/frontend/appflowy_flutter/test/util.dart
index 72606d5796..4278ea2a1b 100644
--- a/frontend/appflowy_flutter/test/util.dart
+++ b/frontend/appflowy_flutter/test/util.dart
@@ -1,6 +1,6 @@
import 'package:appflowy/startup/launch_configuration.dart';
import 'package:appflowy/startup/startup.dart';
-import 'package:appflowy/user/application/auth_service.dart';
+import 'package:appflowy/user/application/auth/auth_service.dart';
import 'package:appflowy/user/application/user_service.dart';
import 'package:appflowy/workspace/application/workspace/workspace_service.dart';
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
@@ -55,11 +55,11 @@ class AppFlowyUnitTest {
email: userEmail,
);
return result.fold(
+ (error) {},
(user) {
userProfile = user;
userService = UserBackendService(userId: userProfile.id);
},
- (error) {},
);
}
diff --git a/frontend/appflowy_tauri/src-tauri/Cargo.lock b/frontend/appflowy_tauri/src-tauri/Cargo.lock
index ed921f33f2..d0055f78ff 100644
--- a/frontend/appflowy_tauri/src-tauri/Cargo.lock
+++ b/frontend/appflowy_tauri/src-tauri/Cargo.lock
@@ -423,10 +423,10 @@ dependencies = [
"http",
"http-body",
"hyper",
- "hyper-rustls",
+ "hyper-rustls 0.23.2",
"lazy_static",
"pin-project-lite",
- "rustls",
+ "rustls 0.20.8",
"tokio",
"tower",
"tracing",
@@ -1791,6 +1791,7 @@ dependencies = [
"flowy-error",
"flowy-folder2",
"flowy-net",
+ "flowy-server",
"flowy-sqlite",
"flowy-task",
"flowy-user",
@@ -1932,6 +1933,7 @@ dependencies = [
"tokio",
"tracing",
"unicode-segmentation",
+ "uuid",
]
[[package]]
@@ -1939,33 +1941,14 @@ name = "flowy-net"
version = "0.1.0"
dependencies = [
"anyhow",
- "async-stream",
"bytes",
- "config",
- "dashmap",
"flowy-codegen",
"flowy-derive",
- "flowy-document2",
"flowy-error",
- "flowy-folder2",
- "flowy-user",
- "futures-util",
- "hyper",
- "lazy_static",
"lib-dispatch",
- "lib-infra",
- "lib-ws",
- "nanoid",
- "parking_lot 0.12.1",
"protobuf",
- "reqwest",
- "serde",
- "serde-aux",
- "serde_json",
- "strum",
"strum_macros",
"thiserror",
- "tokio",
"tracing",
]
@@ -1983,6 +1966,34 @@ dependencies = [
"tracing",
]
+[[package]]
+name = "flowy-server"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "bytes",
+ "config",
+ "flowy-config",
+ "flowy-error",
+ "flowy-user",
+ "futures-util",
+ "hyper",
+ "lazy_static",
+ "lib-infra",
+ "nanoid",
+ "parking_lot 0.12.1",
+ "postgrest",
+ "reqwest",
+ "serde",
+ "serde-aux",
+ "serde_json",
+ "thiserror",
+ "tokio",
+ "tokio-retry",
+ "tracing",
+ "uuid",
+]
+
[[package]]
name = "flowy-sqlite"
version = "0.1.0"
@@ -2036,6 +2047,7 @@ dependencies = [
"protobuf",
"serde",
"serde_json",
+ "serde_repr",
"strum",
"strum_macros",
"tokio",
@@ -2706,10 +2718,23 @@ dependencies = [
"http",
"hyper",
"log",
- "rustls",
+ "rustls 0.20.8",
"rustls-native-certs",
"tokio",
- "tokio-rustls",
+ "tokio-rustls 0.23.4",
+]
+
+[[package]]
+name = "hyper-rustls"
+version = "0.24.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0646026eb1b3eea4cd9ba47912ea5ce9cc07713d105b1a14698f4e6433d348b7"
+dependencies = [
+ "http",
+ "hyper",
+ "rustls 0.21.1",
+ "tokio",
+ "tokio-rustls 0.24.0",
]
[[package]]
@@ -3021,6 +3046,7 @@ dependencies = [
name = "lib-infra"
version = "0.1.0"
dependencies = [
+ "anyhow",
"async-trait",
"bytes",
"chrono",
@@ -3994,6 +4020,15 @@ dependencies = [
"miniz_oxide 0.7.1",
]
+[[package]]
+name = "postgrest"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e66400cb23a379592bc8c8bdc9adda652eef4a969b74ab78454a8e8c11330c2b"
+dependencies = [
+ "reqwest",
+]
+
[[package]]
name = "ppv-lite86"
version = "0.2.17"
@@ -4419,6 +4454,7 @@ dependencies = [
"http",
"http-body",
"hyper",
+ "hyper-rustls 0.24.0",
"hyper-tls",
"ipnet",
"js-sys",
@@ -4428,16 +4464,20 @@ dependencies = [
"once_cell",
"percent-encoding",
"pin-project-lite",
+ "rustls 0.21.1",
+ "rustls-pemfile",
"serde",
"serde_json",
"serde_urlencoded",
"tokio",
"tokio-native-tls",
+ "tokio-rustls 0.24.0",
"tower-service",
"url",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
+ "webpki-roots",
"winreg 0.10.1",
]
@@ -4587,6 +4627,18 @@ dependencies = [
"webpki",
]
+[[package]]
+name = "rustls"
+version = "0.21.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c911ba11bc8433e811ce56fde130ccf32f5127cab0e0194e9c68c5a5b671791e"
+dependencies = [
+ "log",
+ "ring",
+ "rustls-webpki",
+ "sct",
+]
+
[[package]]
name = "rustls-native-certs"
version = "0.6.2"
@@ -4608,6 +4660,16 @@ dependencies = [
"base64 0.21.0",
]
+[[package]]
+name = "rustls-webpki"
+version = "0.100.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d6207cd5ed3d8dca7816f8f3725513a34609c0c765bf652b8c3cb4cfd87db46b"
+dependencies = [
+ "ring",
+ "untrusted",
+]
+
[[package]]
name = "rustversion"
version = "1.0.12"
@@ -4754,9 +4816,9 @@ dependencies = [
[[package]]
name = "serde-aux"
-version = "1.1.0"
+version = "4.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "905f2fc9f3d1574e8b5923a58118240021f01d4e239673937ffb9f42707a4f22"
+checksum = "c3dfe1b7eb6f9dcf011bd6fad169cdeaae75eda0d61b1a99a3f015b41b0cae39"
dependencies = [
"chrono",
"serde",
@@ -5641,11 +5703,21 @@ version = "0.23.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59"
dependencies = [
- "rustls",
+ "rustls 0.20.8",
"tokio",
"webpki",
]
+[[package]]
+name = "tokio-rustls"
+version = "0.24.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e0d409377ff5b1e3ca6437aa86c1eb7d40c134bfec254e44c830defa92669db5"
+dependencies = [
+ "rustls 0.21.1",
+ "tokio",
+]
+
[[package]]
name = "tokio-stream"
version = "0.1.14"
@@ -6329,6 +6401,15 @@ dependencies = [
"untrusted",
]
+[[package]]
+name = "webpki-roots"
+version = "0.22.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87"
+dependencies = [
+ "webpki",
+]
+
[[package]]
name = "webview2-com"
version = "0.19.1"
diff --git a/frontend/appflowy_tauri/src-tauri/src/init.rs b/frontend/appflowy_tauri/src-tauri/src/init.rs
index fbffd072c5..3043d0c785 100644
--- a/frontend/appflowy_tauri/src-tauri/src/init.rs
+++ b/frontend/appflowy_tauri/src-tauri/src/init.rs
@@ -1,5 +1,4 @@
-use flowy_core::{ AppFlowyCore, AppFlowyCoreConfig, DEFAULT_NAME};
-use flowy_net::http_server::self_host::configuration::get_client_server_configuration;
+use flowy_core::{AppFlowyCore, AppFlowyCoreConfig, DEFAULT_NAME};
pub fn init_flowy_core() -> AppFlowyCore {
let config_json = include_str!("../tauri.conf.json");
@@ -12,12 +11,7 @@ pub fn init_flowy_core() -> AppFlowyCore {
data_path.push("data");
std::env::set_var("RUST_LOG", "trace");
- let server_config = get_client_server_configuration().unwrap();
- let config = AppFlowyCoreConfig::new(
- data_path.to_str().unwrap(),
- DEFAULT_NAME.to_string(),
- server_config,
- )
- .log_filter("trace", vec!["appflowy_tauri".to_string()]);
+ let config = AppFlowyCoreConfig::new(data_path.to_str().unwrap(), DEFAULT_NAME.to_string())
+ .log_filter("trace", vec!["appflowy_tauri".to_string()]);
AppFlowyCore::new(config)
}
diff --git a/frontend/appflowy_tauri/src/appflowy_app/stores/effects/user/user_bd_svc.ts b/frontend/appflowy_tauri/src/appflowy_app/stores/effects/user/user_bd_svc.ts
index 7c9e50d3fc..2159dc761c 100644
--- a/frontend/appflowy_tauri/src/appflowy_app/stores/effects/user/user_bd_svc.ts
+++ b/frontend/appflowy_tauri/src/appflowy_app/stores/effects/user/user_bd_svc.ts
@@ -1,5 +1,7 @@
import { nanoid } from '@reduxjs/toolkit';
import {
+ AuthTypePB,
+ SignOutPB,
UserEventCheckUser,
UserEventGetUserProfile,
UserEventSignIn,
@@ -8,13 +10,13 @@ import {
UserEventUpdateUserProfile,
} from '@/services/backend/events/flowy-user';
import {
+ CreateWorkspacePayloadPB,
SignInPayloadPB,
SignUpPayloadPB,
UpdateUserProfilePayloadPB,
WorkspaceIdPB,
- CreateWorkspacePayloadPB,
- WorkspaceSettingPB,
WorkspacePB,
+ WorkspaceSettingPB,
} from '@/services/backend';
import {
FolderEventCreateWorkspace,
@@ -81,7 +83,8 @@ export class UserBackendService {
};
signOut = () => {
- return UserEventSignOut();
+ const payload = SignOutPB.fromObject({ auth_type: AuthTypePB.Local });
+ return UserEventSignOut(payload);
};
}
@@ -97,7 +100,8 @@ export class AuthBackendService {
};
signOut = () => {
- return UserEventSignOut();
+ const payload = SignOutPB.fromObject({ auth_type: AuthTypePB.Local });
+ return UserEventSignOut(payload);
};
autoSignUp = () => {
diff --git a/frontend/rust-lib/.gitignore b/frontend/rust-lib/.gitignore
index 740f8d77a7..5e19a3e66b 100644
--- a/frontend/rust-lib/.gitignore
+++ b/frontend/rust-lib/.gitignore
@@ -13,4 +13,5 @@ bin/
**/src/protobuf
**/resources/proto
.idea/
-AppFlowy-Collab/
\ No newline at end of file
+AppFlowy-Collab/
+.env
\ No newline at end of file
diff --git a/frontend/rust-lib/Cargo.lock b/frontend/rust-lib/Cargo.lock
index bf8f64793c..591497edb9 100644
--- a/frontend/rust-lib/Cargo.lock
+++ b/frontend/rust-lib/Cargo.lock
@@ -1262,6 +1262,7 @@ dependencies = [
"flowy-derive",
"flowy-net",
"flowy-notification",
+ "flowy-server",
"lazy_static",
"lib-dispatch",
"log",
@@ -1376,6 +1377,12 @@ dependencies = [
"winapi",
]
+[[package]]
+name = "dotenv"
+version = "0.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f"
+
[[package]]
name = "dyn-clone"
version = "1.0.11"
@@ -1563,6 +1570,7 @@ dependencies = [
"flowy-error",
"flowy-folder2",
"flowy-net",
+ "flowy-server",
"flowy-sqlite",
"flowy-task",
"flowy-user",
@@ -1709,6 +1717,7 @@ dependencies = [
"tokio",
"tracing",
"unicode-segmentation",
+ "uuid",
]
[[package]]
@@ -1716,33 +1725,14 @@ name = "flowy-net"
version = "0.1.0"
dependencies = [
"anyhow",
- "async-stream",
"bytes",
- "config",
- "dashmap",
"flowy-codegen",
"flowy-derive",
- "flowy-document2",
"flowy-error",
- "flowy-folder2",
- "flowy-user",
- "futures-util",
- "hyper",
- "lazy_static",
"lib-dispatch",
- "lib-infra",
- "lib-ws",
- "nanoid",
- "parking_lot 0.12.1",
"protobuf",
- "reqwest",
- "serde",
- "serde-aux",
- "serde_json",
- "strum",
"strum_macros",
"thiserror",
- "tokio",
"tracing",
]
@@ -1760,6 +1750,35 @@ dependencies = [
"tracing",
]
+[[package]]
+name = "flowy-server"
+version = "0.1.0"
+dependencies = [
+ "anyhow",
+ "bytes",
+ "config",
+ "dotenv",
+ "flowy-config",
+ "flowy-error",
+ "flowy-user",
+ "futures-util",
+ "hyper",
+ "lazy_static",
+ "lib-infra",
+ "nanoid",
+ "parking_lot 0.12.1",
+ "postgrest",
+ "reqwest",
+ "serde",
+ "serde-aux",
+ "serde_json",
+ "thiserror",
+ "tokio",
+ "tokio-retry",
+ "tracing",
+ "uuid",
+]
+
[[package]]
name = "flowy-sqlite"
version = "0.1.0"
@@ -1804,6 +1823,7 @@ dependencies = [
"flowy-core",
"flowy-folder2",
"flowy-net",
+ "flowy-server",
"flowy-user",
"futures",
"futures-util",
@@ -1853,6 +1873,7 @@ dependencies = [
"rand_core 0.6.4",
"serde",
"serde_json",
+ "serde_repr",
"strum",
"strum_macros",
"tokio",
@@ -2488,6 +2509,7 @@ dependencies = [
name = "lib-infra"
version = "0.1.0"
dependencies = [
+ "anyhow",
"async-trait",
"bytes",
"chrono",
@@ -3237,6 +3259,15 @@ version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160"
+[[package]]
+name = "postgrest"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e66400cb23a379592bc8c8bdc9adda652eef4a969b74ab78454a8e8c11330c2b"
+dependencies = [
+ "reqwest",
+]
+
[[package]]
name = "ppv-lite86"
version = "0.2.17"
@@ -3727,6 +3758,7 @@ dependencies = [
"http",
"http-body",
"hyper",
+ "hyper-rustls",
"hyper-tls",
"ipnet",
"js-sys",
@@ -3736,16 +3768,20 @@ dependencies = [
"once_cell",
"percent-encoding",
"pin-project-lite",
+ "rustls",
+ "rustls-pemfile",
"serde",
"serde_json",
"serde_urlencoded",
"tokio",
"tokio-native-tls",
+ "tokio-rustls",
"tower-service",
"url",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
+ "webpki-roots",
"winreg",
]
@@ -4030,9 +4066,9 @@ dependencies = [
[[package]]
name = "serde-aux"
-version = "1.1.0"
+version = "4.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "905f2fc9f3d1574e8b5923a58118240021f01d4e239673937ffb9f42707a4f22"
+checksum = "c3dfe1b7eb6f9dcf011bd6fad169cdeaae75eda0d61b1a99a3f015b41b0cae39"
dependencies = [
"chrono",
"serde",
@@ -4970,6 +5006,15 @@ version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
+[[package]]
+name = "uuid"
+version = "1.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "345444e32442451b267fc254ae85a209c64be56d2890e601a0c37ff0c3c5ecd2"
+dependencies = [
+ "getrandom 0.2.9",
+]
+
[[package]]
name = "validator"
version = "0.16.0"
@@ -5133,6 +5178,15 @@ dependencies = [
"untrusted",
]
+[[package]]
+name = "webpki-roots"
+version = "0.22.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87"
+dependencies = [
+ "webpki",
+]
+
[[package]]
name = "which"
version = "4.4.0"
diff --git a/frontend/rust-lib/Cargo.toml b/frontend/rust-lib/Cargo.toml
index f9232200d6..d613ae3bea 100644
--- a/frontend/rust-lib/Cargo.toml
+++ b/frontend/rust-lib/Cargo.toml
@@ -14,6 +14,7 @@ members = [
"flowy-error",
"flowy-database2",
"flowy-task",
+ "flowy-server",
"flowy-config",
]
diff --git a/frontend/rust-lib/dart-ffi/Cargo.toml b/frontend/rust-lib/dart-ffi/Cargo.toml
index 5c2d0b71e9..0b0380d9e6 100644
--- a/frontend/rust-lib/dart-ffi/Cargo.toml
+++ b/frontend/rust-lib/dart-ffi/Cargo.toml
@@ -31,6 +31,7 @@ flowy-core = { path = "../flowy-core" }
flowy-notification = { path = "../flowy-notification" }
flowy-net = { path = "../flowy-net" }
flowy-derive = { path = "../../../shared-lib/flowy-derive" }
+flowy-server = { path = "../flowy-server" }
[features]
default = ["dart", "rev-sqlite"]
diff --git a/frontend/rust-lib/dart-ffi/src/lib.rs b/frontend/rust-lib/dart-ffi/src/lib.rs
index e86227aa1e..4b18368f90 100644
--- a/frontend/rust-lib/dart-ffi/src/lib.rs
+++ b/frontend/rust-lib/dart-ffi/src/lib.rs
@@ -6,7 +6,6 @@ use lazy_static::lazy_static;
use parking_lot::RwLock;
use flowy_core::*;
-use flowy_net::http_server::self_host::configuration::get_client_server_configuration;
use flowy_notification::register_notification_sender;
use lib_dispatch::prelude::ToBytes;
use lib_dispatch::prelude::*;
@@ -32,10 +31,9 @@ pub extern "C" fn init_sdk(path: *mut c_char) -> i64 {
let c_str: &CStr = unsafe { CStr::from_ptr(path) };
let path: &str = c_str.to_str().unwrap();
- let server_config = get_client_server_configuration().unwrap();
let log_crates = vec!["flowy-ffi".to_string()];
- let config = AppFlowyCoreConfig::new(path, DEFAULT_NAME.to_string(), server_config)
- .log_filter("info", log_crates);
+ let config =
+ AppFlowyCoreConfig::new(path, DEFAULT_NAME.to_string()).log_filter("info", log_crates);
*APPFLOWY_CORE.write() = Some(AppFlowyCore::new(config));
0
diff --git a/frontend/rust-lib/flowy-config/src/entities.rs b/frontend/rust-lib/flowy-config/src/entities.rs
index d3b1bad2e1..625e45f7d0 100644
--- a/frontend/rust-lib/flowy-config/src/entities.rs
+++ b/frontend/rust-lib/flowy-config/src/entities.rs
@@ -15,18 +15,35 @@ pub struct KeyPB {
pub key: String,
}
+pub const SUPABASE_URL: &str = "SUPABASE_URL";
+pub const SUPABASE_ANON_KEY: &str = "SUPABASE_ANON_KEY";
+pub const SUPABASE_KEY: &str = "SUPABASE_KEY";
+pub const SUPABASE_JWT_SECRET: &str = "SUPABASE_JWT_SECRET";
+
#[derive(Default, ProtoBuf)]
pub struct SupabaseConfigPB {
#[pb(index = 1)]
supabase_url: String,
#[pb(index = 2)]
- supabase_key: String,
+ anon_key: String,
#[pb(index = 3)]
+ key: String,
+
+ #[pb(index = 4)]
jwt_secret: String,
}
+impl SupabaseConfigPB {
+ pub(crate) fn write_to_env(self) {
+ std::env::set_var(SUPABASE_URL, self.supabase_url);
+ std::env::set_var(SUPABASE_ANON_KEY, self.anon_key);
+ std::env::set_var(SUPABASE_KEY, self.key);
+ std::env::set_var(SUPABASE_JWT_SECRET, self.jwt_secret);
+ }
+}
+
#[derive(Default, ProtoBuf)]
pub struct AppFlowyCollabConfigPB {
#[pb(index = 1, one_of)]
diff --git a/frontend/rust-lib/flowy-config/src/event_handler.rs b/frontend/rust-lib/flowy-config/src/event_handler.rs
index 9bd3b1eda4..9915e83526 100644
--- a/frontend/rust-lib/flowy-config/src/event_handler.rs
+++ b/frontend/rust-lib/flowy-config/src/event_handler.rs
@@ -35,6 +35,7 @@ pub(crate) async fn remove_key_value_handler(data: AFPluginData) -> Flowy
pub(crate) async fn set_supabase_config_handler(
data: AFPluginData,
) -> FlowyResult<()> {
- let _config = data.into_inner();
+ let config = data.into_inner();
+ config.write_to_env();
Ok(())
}
diff --git a/frontend/rust-lib/flowy-config/src/event_map.rs b/frontend/rust-lib/flowy-config/src/event_map.rs
index 1a4068acdb..e9c5392547 100644
--- a/frontend/rust-lib/flowy-config/src/event_map.rs
+++ b/frontend/rust-lib/flowy-config/src/event_map.rs
@@ -26,6 +26,8 @@ pub enum ConfigEvent {
#[event(input = "KeyPB")]
RemoveKeyValue = 2,
+ /// Set the supabase config. It will be written to the environment variables.
+ /// Check out the `write_to_env` of [SupabaseConfigPB].
#[event(input = "SupabaseConfigPB")]
SetSupabaseConfig = 3,
}
diff --git a/frontend/rust-lib/flowy-config/src/lib.rs b/frontend/rust-lib/flowy-config/src/lib.rs
index 0c11fb9c5a..e08a6c9ce6 100644
--- a/frontend/rust-lib/flowy-config/src/lib.rs
+++ b/frontend/rust-lib/flowy-config/src/lib.rs
@@ -1,4 +1,4 @@
-mod entities;
+pub mod entities;
mod event_handler;
pub mod event_map;
mod protobuf;
diff --git a/frontend/rust-lib/flowy-core/Cargo.toml b/frontend/rust-lib/flowy-core/Cargo.toml
index bee70ed1a5..05597d4c2a 100644
--- a/frontend/rust-lib/flowy-core/Cargo.toml
+++ b/frontend/rust-lib/flowy-core/Cargo.toml
@@ -19,6 +19,7 @@ flowy-document2 = { path = "../flowy-document2" }
#flowy-revision = { path = "../flowy-revision" }
flowy-error = { path = "../flowy-error" }
flowy-task = { path = "../flowy-task" }
+flowy-server = { path = "../flowy-server" }
flowy-config = { path = "../flowy-config" }
appflowy-integrate = { version = "0.1.0" }
diff --git a/frontend/rust-lib/flowy-core/src/deps_resolve/database_deps.rs b/frontend/rust-lib/flowy-core/src/deps_resolve/database_deps.rs
index db8bc84e45..d388be3b1e 100644
--- a/frontend/rust-lib/flowy-core/src/deps_resolve/database_deps.rs
+++ b/frontend/rust-lib/flowy-core/src/deps_resolve/database_deps.rs
@@ -31,7 +31,7 @@ impl DatabaseUser2 for DatabaseUserImpl {
.map_err(|e| FlowyError::internal().context(e))
}
- fn token(&self) -> Result {
+ fn token(&self) -> Result