mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
feat: Integrate supabase (#2551)
* feat: integrate supabase auth service * chore: ignore the sercet * feat: separate and inject the auth service * chore: integrate auth service into sign in/up page * feat: integrate github and google sign in * chore: update user trait * feat: box any params in UserCloudService trait * feat: add flowy-server crate * refactor: user trait * docs: doc ThirdPartyAuthPB * feat: server provider * feat: pass the uuid to rust side * feat: pass supabase config to rust side * feat: integrate env file * feat: implement login as guest * feat: impl postgrest * test: use env * chore: upper case key * feat: optimize the file storage * fix: don't call set auth when user login in local * docs: add docs * feat: create/update/get user using postgrest * feat: optimize the login as guest * feat: create user workspace * feat: create user default workspace * feat: redesign the setting path location page * feat: use uuid as view id * feat: send auth info to rust backend * fix: sign up * fix: skip to wrong page after login * fix: integrate test error * fix: indent command error * feat: add discord login in type * fix: flutter analyze * ci: fix rust tests * ci: fix tauri build * ci: fix tauri build --------- Co-authored-by: nathan <nathan@appflowy.io>
This commit is contained in:
parent
5cc8340e05
commit
d842f228e8
2
frontend/appflowy_flutter/.gitignore
vendored
2
frontend/appflowy_flutter/.gitignore
vendored
@ -68,3 +68,5 @@ windows/flutter/dart_ffi/
|
||||
**/**/Brewfile.lock.json
|
||||
**/.sandbox
|
||||
**/.vscode/
|
||||
|
||||
*.env
|
||||
|
@ -0,0 +1,3 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path opacity="0.7" d="M14.1579 9L14.6673 8.57546C14.5413 8.42426 14.3547 8.33684 14.1579 8.33684V9ZM16 11.2105L15.4905 11.6351C15.6165 11.7863 15.8032 11.8737 16 11.8737V11.2105ZM9.66316 12.6843C9.66316 12.318 9.36625 12.0211 9 12.0211C8.63375 12.0211 8.33684 12.318 8.33684 12.6843H9.66316ZM8.33684 17.1053C8.33684 17.4716 8.63375 17.7685 9 17.7685C9.36625 17.7685 9.66316 17.4716 9.66316 17.1053H8.33684ZM23.6632 12.6843C23.6632 12.318 23.3663 12.0211 23 12.0211C22.6337 12.0211 22.3368 12.318 22.3368 12.6843H23.6632ZM22.3368 17.1053C22.3368 17.4716 22.6337 17.7685 23 17.7685C23.3663 17.7685 23.6632 17.4716 23.6632 17.1053H22.3368ZM9.66316 9.73684C9.66316 9.69614 9.69614 9.66316 9.73684 9.66316V8.33684C8.96364 8.33684 8.33684 8.96364 8.33684 9.73684H9.66316ZM9.73684 9.66316H14.1579V8.33684H9.73684V9.66316ZM13.6484 9.42454L15.4905 11.6351L16.5094 10.786L14.6673 8.57546L13.6484 9.42454ZM16 11.8737H22.2631V10.5474H16V11.8737ZM22.2631 11.8737C22.3038 11.8737 22.3368 11.9067 22.3368 11.9474H23.6631C23.6631 11.1742 23.0363 10.5474 22.2631 10.5474V11.8737ZM22.3368 11.9474V21.5263H23.6631V11.9474H22.3368ZM22.3368 21.5263C22.3368 21.567 22.3038 21.6 22.2631 21.6V22.9263C23.0363 22.9263 23.6631 22.2995 23.6631 21.5263H22.3368ZM22.2631 21.6H9.73684V22.9263H22.2631V21.6ZM9.73684 21.6C9.69614 21.6 9.66316 21.567 9.66316 21.5263H8.33684C8.33684 22.2995 8.96365 22.9263 9.73684 22.9263V21.6ZM9.66316 21.5263V9.73684H8.33684V21.5263H9.66316ZM22.9999 14.2317H9V15.558H22.9999V14.2317ZM8.33684 12.6843V17.1053H9.66316V12.6843H8.33684ZM22.3368 12.6843V17.1053H23.6632V12.6843H22.3368Z" fill="#333333"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.7 KiB |
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 11 KiB |
@ -0,0 +1,4 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11.974 6.33301H7.35865C6.7922 6.33301 6.33301 6.7922 6.33301 7.35865V11.974C6.33301 12.5405 6.7922 12.9997 7.35865 12.9997H11.974C12.5405 12.9997 12.9997 12.5405 12.9997 11.974V7.35865C12.9997 6.7922 12.5405 6.33301 11.974 6.33301Z" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M4.53846 9.66667H4.02564C3.75362 9.66667 3.49275 9.55861 3.3004 9.36626C3.10806 9.17392 3 8.91304 3 8.64103V4.02564C3 3.75362 3.10806 3.49275 3.3004 3.3004C3.49275 3.10806 3.75362 3 4.02564 3H8.64103C8.91304 3 9.17392 3.10806 9.36626 3.3004C9.55861 3.49275 9.66667 3.75362 9.66667 4.02564V4.53846" stroke="#333333" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 785 B |
@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="127px" height="96px" style="shape-rendering:geometricPrecision; text-rendering:geometricPrecision; image-rendering:optimizeQuality; fill-rule:evenodd; clip-rule:evenodd" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g><path style="opacity:0.982" fill="#5864f2" d="M 40.5,-0.5 C 42.5,-0.5 44.5,-0.5 46.5,-0.5C 46.7534,3.03777 48.5868,5.03777 52,5.5C 59.3333,4.16667 66.6667,4.16667 74,5.5C 77.4132,5.03777 79.2466,3.03777 79.5,-0.5C 81.8333,-0.5 84.1667,-0.5 86.5,-0.5C 93.6784,1.84095 100.845,4.50762 108,7.5C 117.556,21.9281 123.723,37.5947 126.5,54.5C 126.5,63.1667 126.5,71.8333 126.5,80.5C 117.234,86.2996 107.567,91.2996 97.5,95.5C 95.8333,95.5 94.1667,95.5 92.5,95.5C 90.2583,92.0205 88.2583,88.3538 86.5,84.5C 89.9621,82.9349 93.2954,81.1016 96.5,79C 95.8333,78.8333 95.1667,78.6667 94.5,78.5C 80.6757,84.0674 66.3424,85.9007 51.5,84C 44.0284,82.4993 36.6951,80.8327 29.5,79C 32.7046,81.1016 36.0379,82.9349 39.5,84.5C 37.7417,88.3538 35.7417,92.0205 33.5,95.5C 31.8333,95.5 30.1667,95.5 28.5,95.5C 18.4329,91.2996 8.76625,86.2996 -0.5,80.5C -0.5,71.8333 -0.5,63.1667 -0.5,54.5C 2.27734,37.5947 8.44401,21.9281 18,7.5C 25.419,4.30685 32.919,1.64019 40.5,-0.5 Z M 38.5,40.5 C 51.8373,41.4956 55.6706,48.1623 50,60.5C 42.3899,66.5776 36.3899,65.2442 32,56.5C 30.062,49.3891 32.2287,44.0558 38.5,40.5 Z M 80.5,40.5 C 93.8373,41.4956 97.6706,48.1623 92,60.5C 84.3899,66.5776 78.3899,65.2442 74,56.5C 72.062,49.3891 74.2287,44.0558 80.5,40.5 Z"/></g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.5 KiB |
@ -0,0 +1 @@
|
||||
<svg width="98" height="96" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M48.854 0C21.839 0 0 22 0 49.217c0 21.756 13.993 40.172 33.405 46.69 2.427.49 3.316-1.059 3.316-2.362 0-1.141-.08-5.052-.08-9.127-13.59 2.934-16.42-5.867-16.42-5.867-2.184-5.704-5.42-7.17-5.42-7.17-4.448-3.015.324-3.015.324-3.015 4.934.326 7.523 5.052 7.523 5.052 4.367 7.496 11.404 5.378 14.235 4.074.404-3.178 1.699-5.378 3.074-6.6-10.839-1.141-22.243-5.378-22.243-24.283 0-5.378 1.94-9.778 5.014-13.2-.485-1.222-2.184-6.275.486-13.038 0 0 4.125-1.304 13.426 5.052a46.97 46.97 0 0 1 12.214-1.63c4.125 0 8.33.571 12.213 1.63 9.302-6.356 13.427-5.052 13.427-5.052 2.67 6.763.97 11.816.485 13.038 3.155 3.422 5.015 7.822 5.015 13.2 0 18.905-11.404 23.06-22.324 24.283 1.78 1.548 3.316 4.481 3.316 9.126 0 6.6-.08 11.897-.08 13.526 0 1.304.89 2.853 3.316 2.364 19.412-6.52 33.405-24.935 33.405-46.691C97.707 22 75.788 0 48.854 0z" fill="#fff"/></svg>
|
After Width: | Height: | Size: 960 B |
@ -0,0 +1 @@
|
||||
<svg xmlns:xlink="http://www.w3.org/1999/xlink" data-e2e="" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg" width="1" height="1" ><path fill-rule="evenodd" clip-rule="evenodd" d="M43 24.4313C43 23.084 42.8767 21.7885 42.6475 20.5449H24.3877V27.8945H34.8219C34.3724 30.2695 33.0065 32.2818 30.9532 33.6291V38.3964H37.2189C40.885 35.0886 43 30.2177 43 24.4313Z" fill="#4285F4"></path><path fill-rule="evenodd" clip-rule="evenodd" d="M24.3872 43.001C29.6219 43.001 34.0107 41.2996 37.2184 38.3978L30.9527 33.6305C29.2165 34.7705 26.9958 35.4441 24.3872 35.4441C19.3375 35.4441 15.0633 32.1018 13.5388 27.6108H7.06152V32.5337C10.2517 38.7433 16.8082 43.001 24.3872 43.001Z" fill="#34A853"></path><path fill-rule="evenodd" clip-rule="evenodd" d="M13.5395 27.6094C13.1516 26.4695 12.9313 25.2517 12.9313 23.9994C12.9313 22.7472 13.1516 21.5295 13.5395 20.3894V15.4668H7.06217C5.74911 18.0318 5 20.9336 5 23.9994C5 27.0654 5.74911 29.9673 7.06217 32.5323L13.5395 27.6094Z" fill="#FBBC04"></path><path fill-rule="evenodd" clip-rule="evenodd" d="M24.3872 12.5568C27.2336 12.5568 29.7894 13.5155 31.7987 15.3982L37.3595 9.94866C34.0018 6.88281 29.6131 5 24.3872 5C16.8082 5 10.2517 9.25777 7.06152 15.4674L13.5388 20.39C15.0633 15.8991 19.3375 12.5568 24.3872 12.5568Z" fill="#EA4335"></path></svg>
|
After Width: | Height: | Size: 1.3 KiB |
@ -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",
|
||||
|
@ -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<void> 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<String> currentLocation() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
return prefs.getString(kSettingsLocationDefaultLocation)!;
|
||||
return prefs.getString(KVKeys.pathLocation)!;
|
||||
}
|
||||
|
||||
/// Get default location under development environment.
|
||||
|
@ -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<void> setUpAll() async {
|
||||
SharedPreferences.setMockInitialValues(
|
||||
{
|
||||
kSettingsLocationDefaultLocation:
|
||||
await workspace.root.then((value) => value.path),
|
||||
KVKeys.pathLocation: await workspace.root.then((value) => value.path),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
@ -4,13 +4,15 @@ import 'package:appflowy_backend/protobuf/flowy-config/entities.pb.dart';
|
||||
class Config {
|
||||
static Future<void> 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();
|
||||
}
|
||||
|
@ -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<void> set(String key, String value);
|
||||
Future<Either<FlowyError, String>> get(String key);
|
||||
Future<void> remove(String key);
|
||||
Future<void> clear();
|
||||
}
|
||||
|
||||
class DartKeyValue implements KeyValueStorage {
|
||||
SharedPreferences? _sharedPreferences;
|
||||
SharedPreferences get sharedPreferences => _sharedPreferences!;
|
||||
|
||||
@override
|
||||
Future<Either<FlowyError, String>> get(String key) async {
|
||||
await _initSharedPreferencesIfNeeded();
|
||||
|
||||
final value = sharedPreferences.getString(key);
|
||||
if (value != null) {
|
||||
return Right(value);
|
||||
}
|
||||
return Left(FlowyError());
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> remove(String key) async {
|
||||
await _initSharedPreferencesIfNeeded();
|
||||
|
||||
await sharedPreferences.remove(key);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> set(String key, String value) async {
|
||||
await _initSharedPreferencesIfNeeded();
|
||||
|
||||
await sharedPreferences.setString(key, value);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> clear() async {
|
||||
await _initSharedPreferencesIfNeeded();
|
||||
|
||||
await sharedPreferences.clear();
|
||||
}
|
||||
|
||||
Future<void> _initSharedPreferencesIfNeeded() async {
|
||||
_sharedPreferences ??= await SharedPreferences.getInstance();
|
||||
}
|
||||
}
|
||||
|
||||
/// Key-value store
|
||||
/// The data is stored in the local storage of the device.
|
||||
class KeyValue {
|
||||
static Future<void> set(String key, String value) async {
|
||||
class RustKeyValue implements KeyValueStorage {
|
||||
@override
|
||||
Future<void> set(String key, String value) async {
|
||||
await ConfigEventSetKeyValue(
|
||||
KeyValuePB.create()
|
||||
..key = key
|
||||
@ -14,20 +64,23 @@ class KeyValue {
|
||||
).send();
|
||||
}
|
||||
|
||||
static Future<Either<String, FlowyError>> get(String key) {
|
||||
return ConfigEventGetKeyValue(
|
||||
KeyPB.create()..key = key,
|
||||
).send().then(
|
||||
(result) => result.fold(
|
||||
(pb) => left(pb.value),
|
||||
(error) => right(error),
|
||||
),
|
||||
);
|
||||
@override
|
||||
Future<Either<FlowyError, String>> 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<void> remove(String key) async {
|
||||
@override
|
||||
Future<void> remove(String key) async {
|
||||
await ConfigEventRemoveKeyValue(
|
||||
KeyPB.create()..key = key,
|
||||
).send();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> clear() {
|
||||
// TODO: implement clear
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
|
15
frontend/appflowy_flutter/lib/core/config/kv_keys.dart
Normal file
15
frontend/appflowy_flutter/lib/core/config/kv_keys.dart
Normal file
@ -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';
|
||||
}
|
38
frontend/appflowy_flutter/lib/env/env.dart
vendored
Normal file
38
frontend/appflowy_flutter/lib/env/env.dart
vendored
Normal file
@ -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;
|
@ -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);
|
||||
|
@ -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,
|
||||
|
@ -130,7 +130,7 @@ class CoverImagePickerBloc
|
||||
}
|
||||
|
||||
Future<String> _coverPath() async {
|
||||
final directory = await getIt<SettingsLocationCubit>().fetchLocation();
|
||||
final directory = await getIt<LocalFileStorage>().getPath();
|
||||
return Directory(p.join(directory, 'covers'))
|
||||
.create(recursive: true)
|
||||
.then((value) => value.path);
|
||||
|
@ -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<KeyValueStorage>(() => RustKeyValue());
|
||||
getIt.registerFactory<KeyValueStorage>(() => DartKeyValue());
|
||||
getIt.registerFactory<FilePickerService>(() => FilePicker());
|
||||
getIt.registerFactory<LocalFileStorage>(() => LocalFileStorage());
|
||||
|
||||
getIt.registerFactoryAsync<OpenAIRepository>(
|
||||
() async {
|
||||
@ -66,11 +72,17 @@ void _resolveCommonService(GetIt getIt) async {
|
||||
}
|
||||
|
||||
void _resolveUserDeps(GetIt getIt) {
|
||||
getIt.registerFactory<AuthService>(() => AuthService());
|
||||
// getIt.registerFactory<AuthService>(() => AppFlowyAuthService());
|
||||
getIt.registerFactory<AuthService>(() => SupabaseAuthService());
|
||||
|
||||
getIt.registerFactory<AuthRouter>(() => AuthRouter());
|
||||
|
||||
getIt.registerFactory<SignInBloc>(() => SignInBloc(getIt<AuthService>()));
|
||||
getIt.registerFactory<SignUpBloc>(() => SignUpBloc(getIt<AuthService>()));
|
||||
getIt.registerFactory<SignInBloc>(
|
||||
() => SignInBloc(getIt<AuthService>()),
|
||||
);
|
||||
getIt.registerFactory<SignUpBloc>(
|
||||
() => SignUpBloc(getIt<AuthService>()),
|
||||
);
|
||||
|
||||
getIt.registerFactory<SplashRoute>(() => SplashRoute());
|
||||
getIt.registerFactory<EditPanelBloc>(() => EditPanelBloc());
|
||||
@ -131,11 +143,6 @@ void _resolveFolderDeps(GetIt getIt) {
|
||||
(user, _) => SettingsDialogBloc(user),
|
||||
);
|
||||
|
||||
// Location
|
||||
getIt.registerFactory<SettingsLocationCubit>(
|
||||
() => SettingsLocationCubit(),
|
||||
);
|
||||
|
||||
//User
|
||||
getIt.registerFactoryParam<SettingsUserViewBloc, UserProfilePB, void>(
|
||||
(user, _) => SettingsUserViewBloc(user),
|
||||
|
@ -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<SettingsLocationCubit>()
|
||||
.fetchLocation()
|
||||
final directory = await getIt<LocalFileStorage>()
|
||||
.getPath()
|
||||
.then((value) => Directory(value));
|
||||
|
||||
// final directory = await appFlowyDocumentDirectory();
|
||||
|
||||
// add task
|
||||
final launcher = getIt<AppLauncher>();
|
||||
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()
|
||||
],
|
||||
|
@ -6,3 +6,4 @@ export 'hot_key.dart';
|
||||
export 'platform_error_catcher.dart';
|
||||
export 'windows.dart';
|
||||
export 'localization.dart';
|
||||
export 'supabase_task.dart';
|
||||
|
@ -12,30 +12,23 @@ class InitRustSDKTask extends LaunchTask {
|
||||
});
|
||||
|
||||
// Customize the RustSDK initialization path
|
||||
final Future<Directory>? directory;
|
||||
final Directory? directory;
|
||||
|
||||
@override
|
||||
LaunchTaskType get type => LaunchTaskType.dataProcessing;
|
||||
|
||||
@override
|
||||
Future<void> initialize(LaunchContext context) async {
|
||||
// use the custom directory
|
||||
if (directory != null) {
|
||||
return directory!.then((directory) async {
|
||||
await context.getIt<FlowySDK>().init(directory);
|
||||
});
|
||||
} else {
|
||||
return appFlowyDocumentDirectory().then((directory) async {
|
||||
await context.getIt<FlowySDK>().init(directory);
|
||||
});
|
||||
}
|
||||
final dir = directory ?? await appFlowyDocumentDirectory();
|
||||
await context.getIt<FlowySDK>().init(dir);
|
||||
}
|
||||
}
|
||||
|
||||
Future<Directory> 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();
|
||||
|
@ -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<void> 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;
|
||||
}
|
||||
}
|
@ -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',
|
||||
});
|
||||
|
||||
|
@ -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<Either<FlowyError, UserProfilePB>> signIn({
|
||||
required String email,
|
||||
required String password,
|
||||
AuthTypePB authType = AuthTypePB.Local,
|
||||
Map<String, String> 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<Either<FlowyError, UserProfilePB>> signUp({
|
||||
required String name,
|
||||
required String email,
|
||||
required String password,
|
||||
AuthTypePB authType = AuthTypePB.Local,
|
||||
Map<String, String> 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<void> signOut({
|
||||
AuthTypePB authType = AuthTypePB.Local,
|
||||
Map<String, String> map = const {},
|
||||
}) async {
|
||||
final payload = SignOutPB()..authType = authType;
|
||||
await UserEventSignOut(payload).send();
|
||||
return;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<FlowyError, UserProfilePB>> signUpAsGuest({
|
||||
AuthTypePB authType = AuthTypePB.Local,
|
||||
Map<String, String> 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<Either<FlowyError, UserProfilePB>> signUpWithOAuth({
|
||||
required String platform,
|
||||
AuthTypePB authType = AuthTypePB.Local,
|
||||
Map<String, String> map = const {},
|
||||
}) {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<FlowyError, UserProfilePB>> getUser() async {
|
||||
return UserBackendService.getCurrentUserProfile();
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
@ -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<Either<FlowyError, UserProfilePB>> signIn({
|
||||
required String email,
|
||||
required String password,
|
||||
AuthTypePB authType,
|
||||
Map<String, String> map,
|
||||
});
|
||||
|
||||
/// Returns [UserProfilePB] if the user is authenticated, otherwise returns [FlowyError].
|
||||
Future<Either<FlowyError, UserProfilePB>> signUp({
|
||||
required String name,
|
||||
required String email,
|
||||
required String password,
|
||||
AuthTypePB authType,
|
||||
Map<String, String> map,
|
||||
});
|
||||
|
||||
///
|
||||
Future<Either<FlowyError, UserProfilePB>> signUpWithOAuth({
|
||||
required String platform,
|
||||
AuthTypePB authType,
|
||||
Map<String, String> map,
|
||||
});
|
||||
|
||||
/// Returns a default [UserProfilePB]
|
||||
Future<Either<FlowyError, UserProfilePB>> signUpAsGuest({
|
||||
AuthTypePB authType,
|
||||
Map<String, String> map,
|
||||
});
|
||||
|
||||
///
|
||||
Future<void> signOut({
|
||||
AuthTypePB authType,
|
||||
});
|
||||
|
||||
/// Returns [UserProfilePB] if the user has sign in, otherwise returns null.
|
||||
Future<Either<FlowyError, UserProfilePB>> getUser();
|
||||
}
|
@ -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<Either<FlowyError, UserProfilePB>> signUp({
|
||||
required String name,
|
||||
required String email,
|
||||
required String password,
|
||||
AuthTypePB authType = AuthTypePB.Supabase,
|
||||
Map<String, String> 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<Either<FlowyError, UserProfilePB>> signIn({
|
||||
required String email,
|
||||
required String password,
|
||||
AuthTypePB authType = AuthTypePB.Supabase,
|
||||
Map<String, String> 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<Either<FlowyError, UserProfilePB>> signUpWithOAuth({
|
||||
required String platform,
|
||||
AuthTypePB authType = AuthTypePB.Supabase,
|
||||
Map<String, String> map = const {},
|
||||
}) async {
|
||||
if (!isSupabaseEnable) {
|
||||
return _appFlowyAuthService.signUpWithOAuth(
|
||||
platform: platform,
|
||||
);
|
||||
}
|
||||
final provider = platform.toProvider();
|
||||
final completer = Completer<Either<FlowyError, UserProfilePB>>();
|
||||
late final StreamSubscription<AuthState> subscription;
|
||||
subscription = _auth.onAuthStateChange.listen((event) async {
|
||||
if (event.event != AuthChangeEvent.signedIn) {
|
||||
completer.complete(left(AuthError.supabaseSignInWithOauthError));
|
||||
} else {
|
||||
final user = await getSupabaseUser();
|
||||
final Either<FlowyError, UserProfilePB> response = await user.fold(
|
||||
(l) => left(l),
|
||||
(r) async => await setupAuth(map: {AuthServiceMapKeys.uuid: r.id}),
|
||||
);
|
||||
completer.complete(response);
|
||||
}
|
||||
subscription.cancel();
|
||||
});
|
||||
final Map<String, String> 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<void> signOut({
|
||||
AuthTypePB authType = AuthTypePB.Supabase,
|
||||
}) async {
|
||||
if (!isSupabaseEnable) {
|
||||
return _appFlowyAuthService.signOut();
|
||||
}
|
||||
await _auth.signOut();
|
||||
await _appFlowyAuthService.signOut(
|
||||
authType: authType,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<FlowyError, UserProfilePB>> signUpAsGuest({
|
||||
AuthTypePB authType = AuthTypePB.Supabase,
|
||||
Map<String, String> map = const {},
|
||||
}) async {
|
||||
// supabase don't support guest login.
|
||||
// so, just forward to our backend.
|
||||
return _appFlowyAuthService.signUpAsGuest();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Either<FlowyError, UserProfilePB>> getUser() async {
|
||||
final loginType = await getIt<KeyValueStorage>()
|
||||
.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<Either<FlowyError, User>> getSupabaseUser() async {
|
||||
final user = _auth.currentUser;
|
||||
if (user == null) {
|
||||
return left(AuthError.supabaseGetUserError);
|
||||
}
|
||||
return Right(user);
|
||||
}
|
||||
|
||||
Future<Either<FlowyError, UserProfilePB>> setupAuth({
|
||||
required Map<String, String> 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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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<Either<UserProfilePB, FlowyError>> signIn({
|
||||
required String? email,
|
||||
required String? password,
|
||||
}) {
|
||||
//
|
||||
final request = SignInPayloadPB.create()
|
||||
..email = email ?? ''
|
||||
..password = password ?? '';
|
||||
|
||||
return UserEventSignIn(request).send();
|
||||
}
|
||||
|
||||
Future<Either<UserProfilePB, FlowyError>> 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<Either<Unit, FlowyError>> signOut() {
|
||||
return UserEventSignOut().send();
|
||||
}
|
||||
|
||||
Future<Either<UserProfilePB, FlowyError>> autoSignUp() {
|
||||
const password = "AppFlowy123@";
|
||||
final uid = uuid();
|
||||
final userEmail = "$uid@appflowy.io";
|
||||
return signUp(
|
||||
name: LocaleKeys.defaultUsername.tr(),
|
||||
password: password,
|
||||
email: userEmail,
|
||||
);
|
||||
}
|
||||
}
|
@ -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';
|
||||
|
@ -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<SignInEvent, SignInState> {
|
||||
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<SignInEvent, SignInState> {
|
||||
Future<void> _performActionOnSignIn(
|
||||
SignInState state,
|
||||
Emitter<SignInState> 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<void> _performActionOnSignInWithOAuth(
|
||||
SignInState state,
|
||||
Emitter<SignInState> emit,
|
||||
String platform,
|
||||
) async {
|
||||
emit(
|
||||
state.copyWith(
|
||||
@ -55,17 +85,41 @@ class SignInBloc extends Bloc<SignInEvent, SignInState> {
|
||||
),
|
||||
);
|
||||
|
||||
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<void> _performActionOnSignInAsGuest(
|
||||
SignInState state,
|
||||
Emitter<SignInState> 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<SignInEvent, SignInState> {
|
||||
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;
|
||||
}
|
||||
|
@ -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<SignUpEvent, SignUpState> {
|
||||
);
|
||||
|
||||
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<SignUpEvent, SignUpState> {
|
||||
passwordError: none(),
|
||||
repeatPasswordError: none(),
|
||||
),
|
||||
(error) => stateFromCode(error),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -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<SplashEvent, SplashState> {
|
||||
on<SplashEvent>((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<AuthService>().getUser();
|
||||
final authState = response.fold(
|
||||
(error) => AuthState.unauthenticated(error),
|
||||
(user) => AuthState.authenticated(user),
|
||||
);
|
||||
|
||||
emit(state.copyWith(auth: authState));
|
||||
},
|
||||
);
|
||||
|
@ -40,7 +40,7 @@ class UserListener {
|
||||
}
|
||||
|
||||
_userParser = UserNotificationParser(
|
||||
id: _userProfile.token,
|
||||
id: _userProfile.id.toString(),
|
||||
callback: _userNotificationCallback,
|
||||
);
|
||||
_subscription = RustStreamReceiver.listen((observable) {
|
||||
|
@ -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<Either<Unit, FlowyError>> signOut() {
|
||||
return UserEventSignOut().send();
|
||||
Future<Either<Unit, FlowyError>> signOut(AuthTypePB authType) {
|
||||
final payload = SignOutPB()..authType = authType;
|
||||
return UserEventSignOut(payload).send();
|
||||
}
|
||||
|
||||
Future<Either<Unit, FlowyError>> initUser() async {
|
||||
|
@ -61,9 +61,9 @@ class _FolderWidgetState extends State<FolderWidget> {
|
||||
}
|
||||
|
||||
Future<void> _openFolder() async {
|
||||
final directory = await getIt<FilePickerService>().getDirectoryPath();
|
||||
if (directory != null) {
|
||||
await getIt<SettingsLocationCubit>().setLocation(directory);
|
||||
final path = await getIt<FilePickerService>().getDirectoryPath();
|
||||
if (path != null) {
|
||||
await getIt<LocalFileStorage>().setPath(path);
|
||||
await widget.createFolderCallback();
|
||||
}
|
||||
}
|
||||
@ -188,7 +188,7 @@ class CreateFolderWidgetState extends State<CreateFolderWidget> {
|
||||
LocaleKeys.settings_files_locationCannotBeEmpty.tr(),
|
||||
);
|
||||
} else {
|
||||
await getIt<SettingsLocationCubit>().setLocation(_path);
|
||||
await getIt<LocalFileStorage>().setPath(_path);
|
||||
await widget.onPressedCreate();
|
||||
}
|
||||
},
|
||||
|
@ -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<void> pushHomeScreen(
|
||||
BuildContext context,
|
||||
UserProfilePB userProfile,
|
||||
) async {
|
||||
final result = await FolderEventReadCurrentWorkspace().send();
|
||||
result.fold(
|
||||
(workspaceSettingPB) => pushHomeScreenWithWorkSpace(
|
||||
context,
|
||||
userProfile,
|
||||
workspaceSettingPB,
|
||||
),
|
||||
(r) => pushWelcomeScreen(context, userProfile),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SplashRoute {
|
||||
|
@ -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<SignInBloc>(),
|
||||
child: BlocListener<SignInBloc, SignInState>(
|
||||
child: BlocConsumer<SignInBloc, SignInState>(
|
||||
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<SignInBloc>().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<KeyValueStorage>().set(KVKeys.loginType, 'local');
|
||||
context.read<SignInBloc>().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<KeyValueStorage>().set(KVKeys.loginType, 'supabase');
|
||||
context
|
||||
.read<SignInBloc>()
|
||||
.add(const SignInEvent.signedInWithOAuth('google'));
|
||||
},
|
||||
),
|
||||
const SizedBox(width: 20),
|
||||
ThirdPartySignInButton(
|
||||
icon: 'login/github-mark',
|
||||
onPressed: () {
|
||||
getIt<KeyValueStorage>().set(KVKeys.loginType, 'supabase');
|
||||
context
|
||||
.read<SignInBloc>()
|
||||
.add(const SignInEvent.signedInWithOAuth('github'));
|
||||
},
|
||||
),
|
||||
const SizedBox(width: 20),
|
||||
ThirdPartySignInButton(
|
||||
icon: 'login/discord-mark',
|
||||
onPressed: () {
|
||||
getIt<KeyValueStorage>().set(KVKeys.loginType, 'supabase');
|
||||
context
|
||||
.read<SignInBloc>()
|
||||
.add(const SignInEvent.signedInWithOAuth('discord'));
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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(),
|
||||
|
@ -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<SkipLogInScreen> {
|
||||
}
|
||||
|
||||
Future<void> _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<SkipLogInScreen> {
|
||||
) {
|
||||
workspacesOrError.fold(
|
||||
(workspaceSetting) {
|
||||
widget.router.pushHomeScreen(context, user, workspaceSetting);
|
||||
widget.router
|
||||
.pushHomeScreenWithWorkSpace(context, user, workspaceSetting);
|
||||
},
|
||||
(error) {
|
||||
Log.error(error);
|
||||
|
@ -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<SplashRoute>()
|
||||
.pushHomeScreen(context, userProfile, workspaceSetting);
|
||||
},
|
||||
(error) async {
|
||||
Log.error(error);
|
||||
getIt<SplashRoute>().pushWelcomeScreen(context, userProfile);
|
||||
},
|
||||
Future<void> _handleAuthenticated(
|
||||
BuildContext context,
|
||||
Authenticated authenticated,
|
||||
) async {
|
||||
final userProfile = authenticated.userProfile;
|
||||
final result = await FolderEventReadCurrentWorkspace().send();
|
||||
result.fold(
|
||||
(workspaceSetting) {
|
||||
getIt<SplashRoute>().pushHomeScreen(
|
||||
context,
|
||||
userProfile,
|
||||
workspaceSetting,
|
||||
);
|
||||
},
|
||||
(error) async {
|
||||
Log.error(error);
|
||||
getIt<SplashRoute>().pushWelcomeScreen(context, userProfile);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _handleUnauthenticated(BuildContext context, Unauthenticated result) {
|
||||
// getIt<SplashRoute>().pushSignInScreen(context);
|
||||
getIt<SplashRoute>().pushSkipLoginScreen(context);
|
||||
// if the env is not configured, we will skip to the 'skip login screen'.
|
||||
if (isSupabaseEnable) {
|
||||
getIt<SplashRoute>().pushSignInScreen(context);
|
||||
} else {
|
||||
getIt<SplashRoute>().pushSkipLoginScreen(context);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _registerIfNeeded() async {
|
||||
final result = await UserEventCheckUser().send();
|
||||
if (!result.isLeft()) {
|
||||
await getIt<AuthService>().autoSignUp();
|
||||
await getIt<AuthService>().signUpAsGuest();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<SettingsLocationState> {
|
||||
SettingsLocationCubit() : super(const SettingsLocationState.initial()) {
|
||||
_init();
|
||||
}
|
||||
|
||||
String? get path {
|
||||
Future<void> setPath(String path) async {
|
||||
await getIt<LocalFileStorage>().setPath(path);
|
||||
emit(SettingsLocationState.didReceivedPath(path));
|
||||
}
|
||||
|
||||
Future<void> _init() async {
|
||||
final path = await getIt<LocalFileStorage>().getPath();
|
||||
emit(SettingsLocationState.didReceivedPath(path));
|
||||
}
|
||||
}
|
||||
|
||||
class LocalFileStorage {
|
||||
LocalFileStorage();
|
||||
String? _cachePath;
|
||||
|
||||
Future<void> 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<KeyValueStorage>().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<String> getPath() async {
|
||||
if (_cachePath != null) {
|
||||
return _cachePath!;
|
||||
}
|
||||
|
||||
final response = await getIt<KeyValueStorage>().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<SettingsLocation> {
|
||||
SettingsLocationCubit() : super(SettingsLocation(path: null));
|
||||
|
||||
/// Returns a path that used to store user data
|
||||
Future<String> 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<void> 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<KeyValueStorage>().clear();
|
||||
}
|
||||
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
prefs.setString(kSettingsLocationDefaultLocation, path);
|
||||
await Directory(path).create(recursive: true);
|
||||
emit(state.copyWith(path: path));
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
@ -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<SettingsFileLocationCustomzier> createState() =>
|
||||
SettingsFileLocationCustomzierState();
|
||||
State<SettingsFileLocationCustomizer> createState() =>
|
||||
SettingsFileLocationCustomizerState();
|
||||
}
|
||||
|
||||
@visibleForTesting
|
||||
class SettingsFileLocationCustomzierState
|
||||
extends State<SettingsFileLocationCustomzier> {
|
||||
class SettingsFileLocationCustomizerState
|
||||
extends State<SettingsFileLocationCustomizer> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider<SettingsLocationCubit>.value(
|
||||
value: widget.cubit,
|
||||
child: BlocBuilder<SettingsLocationCubit, SettingsLocation>(
|
||||
return BlocProvider<SettingsLocationCubit>(
|
||||
create: (_) => SettingsLocationCubit(),
|
||||
child: BlocBuilder<SettingsLocationCubit, SettingsLocationState>(
|
||||
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<FilePickerService>().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<void> _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<AppearanceSettingsCubit>()
|
||||
.setKeyValue(AppearanceKeys.defaultLocation, location);
|
||||
}
|
||||
*/
|
||||
}
|
||||
final String usingPath;
|
||||
|
||||
Future<void> 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<FilePickerService>().getDirectoryPath();
|
||||
if (path == null || !mounted || widget.usingPath == path) {
|
||||
return;
|
||||
}
|
||||
await context.read<SettingsLocationCubit>().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<SettingsLocationCubit>().setPath(path);
|
||||
await FlowyRunner.run(
|
||||
FlowyApp(),
|
||||
config: const LaunchConfiguration(
|
||||
autoRegistrationSupported: true,
|
||||
),
|
||||
);
|
||||
if (mounted) {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
},
|
||||
);
|
||||
if (mounted) {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -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<SettingsFileSystemView> {
|
||||
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();
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -40,5 +40,16 @@
|
||||
<string>MainMenu</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string>NSApplication</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleURLName</key>
|
||||
<string></string>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>io.appflowy.appflowy-flutter</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
|
@ -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:
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
);
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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/
|
||||
|
||||
|
@ -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) {},
|
||||
);
|
||||
}
|
||||
|
||||
|
133
frontend/appflowy_tauri/src-tauri/Cargo.lock
generated
133
frontend/appflowy_tauri/src-tauri/Cargo.lock
generated
@ -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"
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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 = () => {
|
||||
|
3
frontend/rust-lib/.gitignore
vendored
3
frontend/rust-lib/.gitignore
vendored
@ -13,4 +13,5 @@ bin/
|
||||
**/src/protobuf
|
||||
**/resources/proto
|
||||
.idea/
|
||||
AppFlowy-Collab/
|
||||
AppFlowy-Collab/
|
||||
.env
|
96
frontend/rust-lib/Cargo.lock
generated
96
frontend/rust-lib/Cargo.lock
generated
@ -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"
|
||||
|
@ -14,6 +14,7 @@ members = [
|
||||
"flowy-error",
|
||||
"flowy-database2",
|
||||
"flowy-task",
|
||||
"flowy-server",
|
||||
"flowy-config",
|
||||
]
|
||||
|
||||
|
@ -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"]
|
||||
|
@ -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
|
||||
|
@ -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)]
|
||||
|
@ -35,6 +35,7 @@ pub(crate) async fn remove_key_value_handler(data: AFPluginData<KeyPB>) -> Flowy
|
||||
pub(crate) async fn set_supabase_config_handler(
|
||||
data: AFPluginData<SupabaseConfigPB>,
|
||||
) -> FlowyResult<()> {
|
||||
let _config = data.into_inner();
|
||||
let config = data.into_inner();
|
||||
config.write_to_env();
|
||||
Ok(())
|
||||
}
|
||||
|
@ -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,
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
mod entities;
|
||||
pub mod entities;
|
||||
mod event_handler;
|
||||
pub mod event_map;
|
||||
mod protobuf;
|
||||
|
@ -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" }
|
||||
|
||||
|
@ -31,7 +31,7 @@ impl DatabaseUser2 for DatabaseUserImpl {
|
||||
.map_err(|e| FlowyError::internal().context(e))
|
||||
}
|
||||
|
||||
fn token(&self) -> Result<String, FlowyError> {
|
||||
fn token(&self) -> Result<Option<String>, FlowyError> {
|
||||
self
|
||||
.0
|
||||
.token()
|
||||
|
@ -29,7 +29,7 @@ impl DocumentUser for DocumentUserImpl {
|
||||
.map_err(|e| FlowyError::internal().context(e))
|
||||
}
|
||||
|
||||
fn token(&self) -> Result<String, FlowyError> {
|
||||
fn token(&self) -> Result<Option<String>, FlowyError> {
|
||||
self
|
||||
.0
|
||||
.token()
|
||||
|
@ -63,7 +63,7 @@ impl FolderUser for FolderUserImpl {
|
||||
.map_err(|e| FlowyError::internal().context(e))
|
||||
}
|
||||
|
||||
fn token(&self) -> Result<String, FlowyError> {
|
||||
fn token(&self) -> Result<Option<String>, FlowyError> {
|
||||
self
|
||||
.0
|
||||
.token()
|
||||
|
@ -117,7 +117,7 @@ impl WorkspaceUser for WorkspaceUserImpl {
|
||||
.map_err(|e| FlowyError::internal().context(e))
|
||||
}
|
||||
|
||||
fn token(&self) -> Result<String, FlowyError> {
|
||||
fn token(&self) -> Result<Option<String>, FlowyError> {
|
||||
self
|
||||
.0
|
||||
.token()
|
||||
|
@ -1,11 +1,9 @@
|
||||
pub use database_deps::*;
|
||||
pub use document2_deps::*;
|
||||
pub use folder2_deps::*;
|
||||
pub use user_deps::*;
|
||||
|
||||
mod document2_deps;
|
||||
mod folder2_deps;
|
||||
mod user_deps;
|
||||
mod util;
|
||||
|
||||
mod database_deps;
|
||||
|
@ -1,19 +0,0 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use flowy_net::http_server::self_host::configuration::ClientServerConfiguration;
|
||||
use flowy_net::http_server::self_host::user::UserHttpCloudService;
|
||||
use flowy_net::local_server::LocalServer;
|
||||
use flowy_user::event_map::UserCloudService;
|
||||
|
||||
pub struct UserDepsResolver();
|
||||
impl UserDepsResolver {
|
||||
pub fn resolve(
|
||||
local_server: &Option<Arc<LocalServer>>,
|
||||
server_config: &ClientServerConfiguration,
|
||||
) -> Arc<dyn UserCloudService> {
|
||||
match local_server.clone() {
|
||||
None => Arc::new(UserHttpCloudService::new(server_config)),
|
||||
Some(local_server) => local_server,
|
||||
}
|
||||
}
|
||||
}
|
1
frontend/rust-lib/flowy-core/src/integrate/mod.rs
Normal file
1
frontend/rust-lib/flowy-core/src/integrate/mod.rs
Normal file
@ -0,0 +1 @@
|
||||
pub(crate) mod server;
|
68
frontend/rust-lib/flowy-core/src/integrate/server.rs
Normal file
68
frontend/rust-lib/flowy-core/src/integrate/server.rs
Normal file
@ -0,0 +1,68 @@
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
use parking_lot::RwLock;
|
||||
|
||||
use flowy_error::{ErrorCode, FlowyError};
|
||||
use flowy_server::local_server::LocalServer;
|
||||
use flowy_server::self_host::configuration::self_host_server_configuration;
|
||||
use flowy_server::self_host::SelfHostServer;
|
||||
use flowy_server::supabase::{SupabaseConfiguration, SupabaseServer};
|
||||
use flowy_server::AppFlowyServer;
|
||||
use flowy_user::event_map::{UserAuthService, UserCloudServiceProvider};
|
||||
use flowy_user::services::AuthType;
|
||||
|
||||
/// The [AppFlowyServerProvider] provides list of [AppFlowyServer] base on the [AuthType]. Using
|
||||
/// the auth type, the [AppFlowyServerProvider] will create a new [AppFlowyServer] if it doesn't
|
||||
/// exist.
|
||||
/// Each server implements the [AppFlowyServer] trait, which provides the [UserAuthService], etc.
|
||||
#[derive(Default)]
|
||||
pub struct AppFlowyServerProvider {
|
||||
providers: RwLock<HashMap<AuthType, Arc<dyn AppFlowyServer>>>,
|
||||
}
|
||||
|
||||
impl AppFlowyServerProvider {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl UserCloudServiceProvider for AppFlowyServerProvider {
|
||||
/// Returns the [UserAuthService] base on the current [AuthType].
|
||||
/// Creates a new [AppFlowyServer] if it doesn't exist.
|
||||
fn get_auth_service(&self, auth_type: &AuthType) -> Result<Arc<dyn UserAuthService>, FlowyError> {
|
||||
if let Some(provider) = self.providers.read().get(auth_type) {
|
||||
return Ok(provider.user_service());
|
||||
}
|
||||
|
||||
let server = server_from_auth_type(auth_type)?;
|
||||
let user_service = server.user_service();
|
||||
self.providers.write().insert(auth_type.clone(), server);
|
||||
Ok(user_service)
|
||||
}
|
||||
}
|
||||
|
||||
fn server_from_auth_type(auth_type: &AuthType) -> Result<Arc<dyn AppFlowyServer>, FlowyError> {
|
||||
match auth_type {
|
||||
AuthType::Local => {
|
||||
let server = Arc::new(LocalServer::new());
|
||||
Ok(server)
|
||||
},
|
||||
AuthType::SelfHosted => {
|
||||
let config = self_host_server_configuration().map_err(|e| {
|
||||
FlowyError::new(
|
||||
ErrorCode::InvalidAuthConfig,
|
||||
format!("Missing self host config: {:?}. Error: {:?}", auth_type, e),
|
||||
)
|
||||
})?;
|
||||
let server = Arc::new(SelfHostServer::new(config));
|
||||
Ok(server)
|
||||
},
|
||||
AuthType::Supabase => {
|
||||
// init the SupabaseServerConfiguration from the environment variables.
|
||||
let config = SupabaseConfiguration::from_env()?;
|
||||
let server = Arc::new(SupabaseServer::new(config));
|
||||
Ok(server)
|
||||
},
|
||||
}
|
||||
}
|
@ -1,3 +1,5 @@
|
||||
#![allow(unused_doc_comments)]
|
||||
|
||||
use std::str::FromStr;
|
||||
use std::time::Duration;
|
||||
use std::{
|
||||
@ -16,12 +18,10 @@ use flowy_database2::DatabaseManager2;
|
||||
use flowy_document2::manager::DocumentManager as DocumentManager2;
|
||||
use flowy_error::FlowyResult;
|
||||
use flowy_folder2::manager::Folder2Manager;
|
||||
use flowy_net::http_server::self_host::configuration::ClientServerConfiguration;
|
||||
use flowy_net::local_server::LocalServer;
|
||||
use flowy_sqlite::kv::KV;
|
||||
use flowy_task::{TaskDispatcher, TaskRunner};
|
||||
use flowy_user::entities::UserProfile;
|
||||
use flowy_user::event_map::UserStatusCallback;
|
||||
use flowy_user::event_map::{UserCloudServiceProvider, UserStatusCallback};
|
||||
use flowy_user::services::{UserSession, UserSessionConfig};
|
||||
use lib_dispatch::prelude::*;
|
||||
use lib_dispatch::runtime::tokio_default_runtime;
|
||||
@ -30,8 +30,10 @@ use module::make_plugins;
|
||||
pub use module::*;
|
||||
|
||||
use crate::deps_resolve::*;
|
||||
use crate::integrate::server::AppFlowyServerProvider;
|
||||
|
||||
mod deps_resolve;
|
||||
mod integrate;
|
||||
pub mod module;
|
||||
|
||||
static INIT_LOG: AtomicBool = AtomicBool::new(false);
|
||||
@ -47,25 +49,22 @@ pub struct AppFlowyCoreConfig {
|
||||
/// Panics if the `root` path is not existing
|
||||
storage_path: String,
|
||||
log_filter: String,
|
||||
server_config: ClientServerConfiguration,
|
||||
}
|
||||
|
||||
impl fmt::Debug for AppFlowyCoreConfig {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("AppFlowyCoreConfig")
|
||||
.field("storage_path", &self.storage_path)
|
||||
.field("server-config", &self.server_config)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl AppFlowyCoreConfig {
|
||||
pub fn new(root: &str, name: String, server_config: ClientServerConfiguration) -> Self {
|
||||
pub fn new(root: &str, name: String) -> Self {
|
||||
AppFlowyCoreConfig {
|
||||
name,
|
||||
storage_path: root.to_owned(),
|
||||
log_filter: create_log_filter("info".to_owned(), vec![]),
|
||||
server_config,
|
||||
}
|
||||
}
|
||||
|
||||
@ -117,22 +116,33 @@ pub struct AppFlowyCore {
|
||||
pub user_session: Arc<UserSession>,
|
||||
pub document_manager2: Arc<DocumentManager2>,
|
||||
pub folder_manager: Arc<Folder2Manager>,
|
||||
// pub database_manager: Arc<DatabaseManager>,
|
||||
pub database_manager: Arc<DatabaseManager2>,
|
||||
pub event_dispatcher: Arc<AFPluginDispatcher>,
|
||||
pub local_server: Option<Arc<LocalServer>>,
|
||||
pub server_provider: Arc<AppFlowyServerProvider>,
|
||||
pub task_dispatcher: Arc<RwLock<TaskDispatcher>>,
|
||||
}
|
||||
|
||||
impl AppFlowyCore {
|
||||
pub fn new(config: AppFlowyCoreConfig) -> Self {
|
||||
/// The profiling can be used to tracing the performance of the application.
|
||||
/// Check out the [Link](https://appflowy.gitbook.io/docs/essential-documentation/contribute-to-appflowy/architecture/backend/profiling)
|
||||
/// for more information.
|
||||
#[cfg(feature = "profiling")]
|
||||
console_subscriber::init();
|
||||
|
||||
// Init the logger before anything else
|
||||
init_log(&config);
|
||||
|
||||
// Init the key value database
|
||||
init_kv(&config.storage_path);
|
||||
|
||||
// The collab config is used to build the [Collab] instance that used in document,
|
||||
// database, folder, etc.
|
||||
let collab_config = get_collab_config();
|
||||
inject_aws_env(collab_config.aws_config());
|
||||
|
||||
/// The shared collab builder is used to build the [Collab] instance. The plugins will be loaded
|
||||
/// on demand based on the [AppFlowyCollabConfig].
|
||||
let collab_builder = Arc::new(AppFlowyCollabBuilder::new(collab_config));
|
||||
|
||||
tracing::debug!("🔥 {:?}", config);
|
||||
@ -141,11 +151,10 @@ impl AppFlowyCore {
|
||||
let task_dispatcher = Arc::new(RwLock::new(task_scheduler));
|
||||
runtime.spawn(TaskRunner::run(task_dispatcher.clone()));
|
||||
|
||||
let local_server = mk_local_server(&config.server_config);
|
||||
let (user_session, folder_manager, local_server, database_manager, document_manager2) = runtime
|
||||
.block_on(async {
|
||||
let user_session = mk_user_session(&config, &local_server, &config.server_config);
|
||||
|
||||
let server_provider = Arc::new(AppFlowyServerProvider::new());
|
||||
let (user_session, folder_manager, server_provider, database_manager, document_manager2) =
|
||||
runtime.block_on(async {
|
||||
let user_session = mk_user_session(&config, server_provider.clone());
|
||||
let database_manager2 = Database2DepsResolver::resolve(
|
||||
user_session.clone(),
|
||||
task_dispatcher.clone(),
|
||||
@ -170,7 +179,7 @@ impl AppFlowyCore {
|
||||
(
|
||||
user_session,
|
||||
folder_manager,
|
||||
local_server,
|
||||
server_provider,
|
||||
database_manager2,
|
||||
document_manager2,
|
||||
)
|
||||
@ -181,9 +190,11 @@ impl AppFlowyCore {
|
||||
database_manager: database_manager.clone(),
|
||||
config: config.clone(),
|
||||
};
|
||||
|
||||
let user_status_callback = UserStatusCallbackImpl {
|
||||
listener: Arc::new(user_status_listener),
|
||||
};
|
||||
|
||||
let cloned_user_session = user_session.clone();
|
||||
runtime.block_on(async move {
|
||||
cloned_user_session.clone().init(user_status_callback).await;
|
||||
@ -205,7 +216,7 @@ impl AppFlowyCore {
|
||||
folder_manager,
|
||||
database_manager,
|
||||
event_dispatcher,
|
||||
local_server,
|
||||
server_provider,
|
||||
task_dispatcher,
|
||||
}
|
||||
}
|
||||
@ -215,18 +226,6 @@ impl AppFlowyCore {
|
||||
}
|
||||
}
|
||||
|
||||
fn mk_local_server(server_config: &ClientServerConfiguration) -> Option<Arc<LocalServer>> {
|
||||
// let ws_addr = server_config.ws_addr();
|
||||
if cfg!(feature = "http_sync") {
|
||||
// let ws_conn = Arc::new(FlowyWebSocketConnect::new(ws_addr));
|
||||
None
|
||||
} else {
|
||||
let context = flowy_net::local_server::build_server(server_config);
|
||||
// let ws_conn = Arc::new(FlowyWebSocketConnect::from_local(ws_addr, local_ws));
|
||||
Some(Arc::new(context.local_server))
|
||||
}
|
||||
}
|
||||
|
||||
fn init_kv(root: &str) {
|
||||
match KV::init(root) {
|
||||
Ok(_) => {},
|
||||
@ -263,12 +262,10 @@ fn init_log(config: &AppFlowyCoreConfig) {
|
||||
|
||||
fn mk_user_session(
|
||||
config: &AppFlowyCoreConfig,
|
||||
local_server: &Option<Arc<LocalServer>>,
|
||||
server_config: &ClientServerConfiguration,
|
||||
user_cloud_service_provider: Arc<dyn UserCloudServiceProvider>,
|
||||
) -> Arc<UserSession> {
|
||||
let user_config = UserSessionConfig::new(&config.name, &config.storage_path);
|
||||
let cloud_service = UserDepsResolver::resolve(local_server, server_config);
|
||||
Arc::new(UserSession::new(user_config, cloud_service))
|
||||
Arc::new(UserSession::new(user_config, user_cloud_service_provider))
|
||||
}
|
||||
|
||||
struct UserStatusListener {
|
||||
@ -279,20 +276,23 @@ struct UserStatusListener {
|
||||
}
|
||||
|
||||
impl UserStatusListener {
|
||||
async fn did_sign_in(&self, token: &str, user_id: i64) -> FlowyResult<()> {
|
||||
self.folder_manager.initialize(user_id).await?;
|
||||
self.database_manager.initialize(user_id, token).await?;
|
||||
// self
|
||||
// .ws_conn
|
||||
// .start(token.to_owned(), user_id.to_owned())
|
||||
// .await?;
|
||||
async fn did_sign_in(&self, user_id: i64, workspace_id: &str) -> FlowyResult<()> {
|
||||
self
|
||||
.folder_manager
|
||||
.initialize(user_id, workspace_id)
|
||||
.await?;
|
||||
self.database_manager.initialize(user_id).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn did_sign_up(&self, user_profile: &UserProfile) -> FlowyResult<()> {
|
||||
self
|
||||
.folder_manager
|
||||
.initialize_with_new_user(user_profile.id, &user_profile.token)
|
||||
.initialize_with_new_user(
|
||||
user_profile.id,
|
||||
&user_profile.token,
|
||||
&user_profile.workspace_id,
|
||||
)
|
||||
.await?;
|
||||
|
||||
self
|
||||
@ -314,11 +314,11 @@ struct UserStatusCallbackImpl {
|
||||
}
|
||||
|
||||
impl UserStatusCallback for UserStatusCallbackImpl {
|
||||
fn did_sign_in(&self, token: &str, user_id: i64) -> Fut<FlowyResult<()>> {
|
||||
fn did_sign_in(&self, user_id: i64, workspace_id: &str) -> Fut<FlowyResult<()>> {
|
||||
let listener = self.listener.clone();
|
||||
let token = token.to_owned();
|
||||
let user_id = user_id.to_owned();
|
||||
to_fut(async move { listener.did_sign_in(&token, user_id).await })
|
||||
let workspace_id = workspace_id.to_owned();
|
||||
to_fut(async move { listener.did_sign_in(user_id, &workspace_id).await })
|
||||
}
|
||||
|
||||
fn did_sign_up(&self, user_profile: &UserProfile) -> Fut<FlowyResult<()>> {
|
||||
@ -333,9 +333,4 @@ impl UserStatusCallback for UserStatusCallbackImpl {
|
||||
let user_id = user_id.to_owned();
|
||||
to_fut(async move { listener.did_expired(&token, user_id).await })
|
||||
}
|
||||
|
||||
fn will_migrated(&self, _token: &str, _old_user_id: &str, _user_id: i64) -> Fut<FlowyResult<()>> {
|
||||
// Read the folder data
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,12 @@
|
||||
use std::convert::TryInto;
|
||||
use std::sync::Arc;
|
||||
|
||||
use bytes::Bytes;
|
||||
use collab_database::fields::Field;
|
||||
|
||||
use flowy_derive::ProtoBuf;
|
||||
use flowy_error::ErrorCode;
|
||||
|
||||
use crate::entities::parser::NotEmptyStr;
|
||||
use crate::entities::{
|
||||
CheckboxFilterPB, ChecklistFilterPB, DateFilterContentPB, DateFilterPB, FieldType,
|
||||
@ -5,12 +14,6 @@ use crate::entities::{
|
||||
};
|
||||
use crate::services::field::SelectOptionIds;
|
||||
use crate::services::filter::{Filter, FilterType};
|
||||
use bytes::Bytes;
|
||||
use collab_database::fields::Field;
|
||||
use flowy_derive::ProtoBuf;
|
||||
use flowy_error::ErrorCode;
|
||||
use std::convert::TryInto;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
|
||||
pub struct FilterPB {
|
||||
@ -95,7 +98,7 @@ impl TryInto<DeleteFilterParams> for DeleteFilterPayloadPB {
|
||||
.0;
|
||||
|
||||
let filter_id = NotEmptyStr::parse(self.filter_id)
|
||||
.map_err(|_| ErrorCode::UnexpectedEmptyString)?
|
||||
.map_err(|_| ErrorCode::UnexpectedEmpty)?
|
||||
.0;
|
||||
|
||||
let filter_type = FilterType {
|
||||
|
@ -178,7 +178,7 @@ impl TryInto<DeleteSortParams> for DeleteSortPayloadPB {
|
||||
.0;
|
||||
|
||||
let sort_id = NotEmptyStr::parse(self.sort_id)
|
||||
.map_err(|_| ErrorCode::UnexpectedEmptyString)?
|
||||
.map_err(|_| ErrorCode::UnexpectedEmpty)?
|
||||
.0;
|
||||
|
||||
let sort_type = SortType {
|
||||
|
@ -19,7 +19,7 @@ use crate::services::database::{DatabaseEditor, MutexDatabase};
|
||||
|
||||
pub trait DatabaseUser2: Send + Sync {
|
||||
fn user_id(&self) -> Result<i64, FlowyError>;
|
||||
fn token(&self) -> Result<String, FlowyError>;
|
||||
fn token(&self) -> Result<Option<String>, FlowyError>;
|
||||
fn collab_db(&self) -> Result<Arc<RocksCollabDB>, FlowyError>;
|
||||
}
|
||||
|
||||
@ -46,7 +46,7 @@ impl DatabaseManager2 {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn initialize(&self, user_id: i64, _token: &str) -> FlowyResult<()> {
|
||||
pub async fn initialize(&self, user_id: i64) -> FlowyResult<()> {
|
||||
let db = self.user.collab_db()?;
|
||||
*self.user_database.lock() = Some(InnerUserDatabase::new(
|
||||
user_id,
|
||||
@ -58,8 +58,8 @@ impl DatabaseManager2 {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn initialize_with_new_user(&self, user_id: i64, token: &str) -> FlowyResult<()> {
|
||||
self.initialize(user_id, token).await?;
|
||||
pub async fn initialize_with_new_user(&self, user_id: i64, _token: &str) -> FlowyResult<()> {
|
||||
self.initialize(user_id).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,3 @@
|
||||
use std::any::Any;
|
||||
use std::cmp::Ordering;
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
use std::hash::{Hash, Hasher};
|
||||
@ -7,6 +6,7 @@ use collab_database::fields::{Field, TypeOptionData};
|
||||
use collab_database::rows::{Cell, RowId};
|
||||
|
||||
use flowy_error::FlowyResult;
|
||||
use lib_infra::box_any::BoxAny;
|
||||
|
||||
use crate::entities::FieldType;
|
||||
use crate::services::cell::{
|
||||
@ -486,41 +486,7 @@ fn get_type_option_transform_handler(
|
||||
}
|
||||
}
|
||||
|
||||
pub struct BoxCellData(Box<dyn Any + Send + Sync + 'static>);
|
||||
|
||||
impl BoxCellData {
|
||||
fn new<T>(value: T) -> Self
|
||||
where
|
||||
T: Send + Sync + 'static,
|
||||
{
|
||||
Self(Box::new(value))
|
||||
}
|
||||
|
||||
fn unbox_or_default<T>(self) -> T
|
||||
where
|
||||
T: Default + 'static,
|
||||
{
|
||||
match self.0.downcast::<T>() {
|
||||
Ok(value) => *value,
|
||||
Err(_) => T::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn unbox_or_none<T>(self) -> Option<T>
|
||||
where
|
||||
T: Default + 'static,
|
||||
{
|
||||
match self.0.downcast::<T>() {
|
||||
Ok(value) => Some(*value),
|
||||
Err(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn downcast_ref<T: 'static>(&self) -> Option<&T> {
|
||||
self.0.downcast_ref()
|
||||
}
|
||||
}
|
||||
pub type BoxCellData = BoxAny;
|
||||
|
||||
pub struct RowSingleCellData {
|
||||
pub row_id: RowId,
|
||||
|
@ -15,7 +15,7 @@ use crate::{
|
||||
|
||||
pub trait DocumentUser: Send + Sync {
|
||||
fn user_id(&self) -> Result<i64, FlowyError>;
|
||||
fn token(&self) -> Result<String, FlowyError>; // unused now.
|
||||
fn token(&self) -> Result<Option<String>, FlowyError>; // unused now.
|
||||
fn collab_db(&self) -> Result<Arc<RocksCollabDB>, FlowyError>;
|
||||
}
|
||||
|
||||
|
@ -24,8 +24,8 @@ impl DocumentUser for FakeUser {
|
||||
Ok(1)
|
||||
}
|
||||
|
||||
fn token(&self) -> Result<String, flowy_error::FlowyError> {
|
||||
Ok("1".to_string())
|
||||
fn token(&self) -> Result<Option<String>, flowy_error::FlowyError> {
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn collab_db(&self) -> Result<std::sync::Arc<RocksCollabDB>, flowy_error::FlowyError> {
|
||||
|
@ -149,9 +149,6 @@ pub enum ErrorCode {
|
||||
#[error("Invalid date time format")]
|
||||
InvalidDateTimeFormat = 47,
|
||||
|
||||
#[error("The input string is empty or contains invalid characters")]
|
||||
UnexpectedEmptyString = 48,
|
||||
|
||||
#[error("Invalid data")]
|
||||
InvalidData = 49,
|
||||
|
||||
@ -185,14 +182,23 @@ pub enum ErrorCode {
|
||||
#[error("Http request error")]
|
||||
HttpError = 59,
|
||||
|
||||
#[error("Payload should not be empty")]
|
||||
UnexpectedEmptyPayload = 60,
|
||||
#[error("The content should not be empty")]
|
||||
UnexpectedEmpty = 60,
|
||||
|
||||
#[error("Only the date type can be used in calendar")]
|
||||
UnexpectedCalendarFieldType = 61,
|
||||
|
||||
#[error("Document Data Invalid")]
|
||||
DocumentDataInvalid = 62,
|
||||
|
||||
#[error("Unsupported auth type")]
|
||||
UnsupportedAuthType = 63,
|
||||
|
||||
#[error("Invalid auth configuration")]
|
||||
InvalidAuthConfig = 64,
|
||||
|
||||
#[error("Missing auth field")]
|
||||
MissingAuthField = 65,
|
||||
}
|
||||
|
||||
impl ErrorCode {
|
||||
|
@ -1,9 +1,12 @@
|
||||
use crate::code::ErrorCode;
|
||||
use anyhow::Result;
|
||||
use flowy_derive::ProtoBuf;
|
||||
use std::fmt::Debug;
|
||||
|
||||
use anyhow::Result;
|
||||
use thiserror::Error;
|
||||
|
||||
use flowy_derive::ProtoBuf;
|
||||
|
||||
use crate::code::ErrorCode;
|
||||
|
||||
pub type FlowyResult<T> = anyhow::Result<T, FlowyError>;
|
||||
|
||||
#[derive(Debug, Default, Clone, ProtoBuf, Error)]
|
||||
@ -26,10 +29,10 @@ macro_rules! static_flowy_error {
|
||||
}
|
||||
|
||||
impl FlowyError {
|
||||
pub fn new(code: ErrorCode, msg: &str) -> Self {
|
||||
pub fn new<T: ToString>(code: ErrorCode, msg: T) -> Self {
|
||||
Self {
|
||||
code: code.value() as i32,
|
||||
msg: msg.to_owned(),
|
||||
msg: msg.to_string(),
|
||||
}
|
||||
}
|
||||
pub fn context<T: Debug>(mut self, error: T) -> Self {
|
||||
@ -80,7 +83,7 @@ impl FlowyError {
|
||||
static_flowy_error!(out_of_bounds, ErrorCode::OutOfBounds);
|
||||
static_flowy_error!(serde, ErrorCode::Serde);
|
||||
static_flowy_error!(field_record_not_found, ErrorCode::FieldRecordNotFound);
|
||||
static_flowy_error!(payload_none, ErrorCode::UnexpectedEmptyPayload);
|
||||
static_flowy_error!(payload_none, ErrorCode::UnexpectedEmpty);
|
||||
static_flowy_error!(http, ErrorCode::HttpError);
|
||||
static_flowy_error!(
|
||||
unexpect_calendar_field_type,
|
||||
@ -115,3 +118,9 @@ impl std::convert::From<protobuf::ProtobufError> for FlowyError {
|
||||
FlowyError::internal().context(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<anyhow::Error> for FlowyError {
|
||||
fn from(e: anyhow::Error) -> Self {
|
||||
FlowyError::internal().context(e)
|
||||
}
|
||||
}
|
||||
|
@ -26,6 +26,7 @@ chrono = { version = "0.4.24"}
|
||||
strum = "0.21"
|
||||
strum_macros = "0.21"
|
||||
protobuf = {version = "2.28.0"}
|
||||
uuid = { version = "1.3.3", features = ["v4"] }
|
||||
#flowy-document = { path = "../flowy-document" }
|
||||
|
||||
[dev-dependencies]
|
||||
|
@ -4,7 +4,6 @@ use std::sync::Arc;
|
||||
|
||||
use appflowy_integrate::collab_builder::AppFlowyCollabBuilder;
|
||||
use appflowy_integrate::RocksCollabDB;
|
||||
|
||||
use collab_folder::core::{
|
||||
Folder as InnerFolder, FolderContext, TrashChange, TrashChangeReceiver, TrashInfo, TrashRecord,
|
||||
View, ViewChange, ViewChangeReceiver, ViewLayout, Workspace,
|
||||
@ -30,7 +29,7 @@ use crate::view_ext::{
|
||||
|
||||
pub trait FolderUser: Send + Sync {
|
||||
fn user_id(&self) -> Result<i64, FlowyError>;
|
||||
fn token(&self) -> Result<String, FlowyError>;
|
||||
fn token(&self) -> Result<Option<String>, FlowyError>;
|
||||
fn collab_db(&self) -> Result<Arc<RocksCollabDB>, FlowyError>;
|
||||
}
|
||||
|
||||
@ -92,32 +91,37 @@ impl Folder2Manager {
|
||||
|
||||
/// Called immediately after the application launched fi the user already sign in/sign up.
|
||||
#[tracing::instrument(level = "trace", skip(self), err)]
|
||||
pub async fn initialize(&self, user_id: i64) -> FlowyResult<()> {
|
||||
if let Ok(uid) = self.user.user_id() {
|
||||
let folder_id = FolderId::new(uid);
|
||||
|
||||
if let Ok(kv_db) = self.user.collab_db() {
|
||||
let collab = self.collab_builder.build(uid, folder_id.as_ref(), kv_db);
|
||||
let (view_tx, view_rx) = tokio::sync::broadcast::channel(100);
|
||||
let (trash_tx, trash_rx) = tokio::sync::broadcast::channel(100);
|
||||
let folder_context = FolderContext {
|
||||
view_change_tx: Some(view_tx),
|
||||
trash_change_tx: Some(trash_tx),
|
||||
};
|
||||
*self.folder.lock() = Some(InnerFolder::get_or_create(collab, folder_context));
|
||||
listen_on_trash_change(trash_rx, self.folder.clone());
|
||||
listen_on_view_change(view_rx, self.folder.clone());
|
||||
}
|
||||
pub async fn initialize(&self, uid: i64, workspace_id: &str) -> FlowyResult<()> {
|
||||
if let Ok(collab_db) = self.user.collab_db() {
|
||||
let collab = self.collab_builder.build(uid, workspace_id, collab_db);
|
||||
let (view_tx, view_rx) = tokio::sync::broadcast::channel(100);
|
||||
let (trash_tx, trash_rx) = tokio::sync::broadcast::channel(100);
|
||||
let folder_context = FolderContext {
|
||||
view_change_tx: Some(view_tx),
|
||||
trash_change_tx: Some(trash_tx),
|
||||
};
|
||||
*self.folder.lock() = Some(InnerFolder::get_or_create(collab, folder_context));
|
||||
listen_on_trash_change(trash_rx, self.folder.clone());
|
||||
listen_on_view_change(view_rx, self.folder.clone());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Called after the user sign up / sign in
|
||||
pub async fn initialize_with_new_user(&self, user_id: i64, token: &str) -> FlowyResult<()> {
|
||||
self.initialize(user_id).await?;
|
||||
let (folder_data, workspace_pb) =
|
||||
DefaultFolderBuilder::build(self.user.user_id()?, &self.view_processors).await;
|
||||
pub async fn initialize_with_new_user(
|
||||
&self,
|
||||
user_id: i64,
|
||||
token: &str,
|
||||
workspace_id: &str,
|
||||
) -> FlowyResult<()> {
|
||||
self.initialize(user_id, workspace_id).await?;
|
||||
let (folder_data, workspace_pb) = DefaultFolderBuilder::build(
|
||||
self.user.user_id()?,
|
||||
workspace_id.to_string(),
|
||||
&self.view_processors,
|
||||
)
|
||||
.await;
|
||||
self.with_folder((), |folder| {
|
||||
folder.create_with_data(folder_data);
|
||||
});
|
||||
@ -555,7 +559,7 @@ fn notify_parent_view_did_change<T: AsRef<str>>(
|
||||
for parent_view_id in parent_view_ids {
|
||||
let parent_view_id = parent_view_id.as_ref();
|
||||
|
||||
// if the view's bid is equal to workspace id. Then it will fetch the current
|
||||
// if the view's parent id equal to workspace id. Then it will fetch the current
|
||||
// workspace views. Because the the workspace is not a view stored in the views map.
|
||||
if parent_view_id == workspace_id {
|
||||
let repeated_view: RepeatedViewPB = get_workspace_view_pbs(&workspace_id, folder).into();
|
||||
@ -585,19 +589,6 @@ fn folder_not_init_error() -> FlowyError {
|
||||
FlowyError::internal().context("Folder not initialized")
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct FolderId(String);
|
||||
impl FolderId {
|
||||
pub fn new(uid: i64) -> Self {
|
||||
Self(format!("{}:folder", uid))
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<str> for FolderId {
|
||||
fn as_ref(&self) -> &str {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
#[derive(Clone, Default)]
|
||||
pub struct Folder(Arc<Mutex<Option<InnerFolder>>>);
|
||||
|
||||
|
@ -11,10 +11,10 @@ pub struct DefaultFolderBuilder();
|
||||
impl DefaultFolderBuilder {
|
||||
pub async fn build(
|
||||
uid: i64,
|
||||
workspace_id: String,
|
||||
view_processors: &ViewDataProcessorMap,
|
||||
) -> (FolderData, WorkspacePB) {
|
||||
let time = Utc::now().timestamp();
|
||||
let workspace_id = gen_workspace_id();
|
||||
let view_id = gen_view_id();
|
||||
let child_view_id = gen_view_id();
|
||||
|
||||
|
@ -4,7 +4,7 @@ use collab_folder::core::{View, ViewLayout};
|
||||
use flowy_error::FlowyError;
|
||||
use lib_infra::future::FutureResult;
|
||||
use lib_infra::util::timestamp;
|
||||
use nanoid::nanoid;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
@ -68,5 +68,5 @@ pub fn view_from_create_view_params(params: CreateViewParams, layout: ViewLayout
|
||||
}
|
||||
}
|
||||
pub fn gen_view_id() -> String {
|
||||
format!("v:{}", nanoid!(10))
|
||||
uuid::Uuid::new_v4().to_string()
|
||||
}
|
||||
|
@ -9,44 +9,22 @@ edition = "2018"
|
||||
lib-dispatch = { path = "../lib-dispatch" }
|
||||
flowy-error = { path = "../flowy-error", features = ["adaptor_reqwest", "adaptor_server_error"] }
|
||||
flowy-derive = { path = "../../../shared-lib/flowy-derive" }
|
||||
flowy-folder2 = { path = "../flowy-folder2" }
|
||||
flowy-document2 = { path = "../flowy-document2" }
|
||||
flowy-user = { path = "../flowy-user" }
|
||||
#flowy-document = { path = "../flowy-document" }
|
||||
lazy_static = "1.4.0"
|
||||
lib-infra = { path = "../../../shared-lib/lib-infra" }
|
||||
protobuf = {version = "2.28.0"}
|
||||
lib-ws = { path = "../../../shared-lib/lib-ws" }
|
||||
bytes = { version = "1.4" }
|
||||
anyhow = "1.0"
|
||||
tokio = { version = "1.26", features = ["sync"]}
|
||||
parking_lot = "0.12.1"
|
||||
strum = "0.21"
|
||||
strum_macros = "0.21"
|
||||
tracing = { version = "0.1", features = ["log"] }
|
||||
dashmap = "5"
|
||||
async-stream = "0.3.4"
|
||||
futures-util = "0.3.26"
|
||||
reqwest = "0.11.14"
|
||||
hyper = "0.14"
|
||||
config = { version = "0.10.1", default-features = false, features = ["yaml"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
serde-aux = "1.1.0"
|
||||
nanoid = "0.4.0"
|
||||
thiserror = "1.0"
|
||||
bytes = { version = "1.4" }
|
||||
strum_macros = "0.21"
|
||||
tracing = { version = "0.1"}
|
||||
|
||||
[features]
|
||||
http_server = []
|
||||
dart = [
|
||||
"flowy-codegen/dart",
|
||||
"flowy-user/dart",
|
||||
"flowy-error/dart",
|
||||
]
|
||||
|
||||
ts = [
|
||||
"flowy-codegen/ts",
|
||||
"flowy-user/ts",
|
||||
"flowy-error/ts",
|
||||
]
|
||||
|
||||
|
@ -1 +0,0 @@
|
||||
pub mod self_host;
|
@ -1,2 +0,0 @@
|
||||
pub mod configuration;
|
||||
pub mod user;
|
@ -1,134 +0,0 @@
|
||||
use flowy_error::FlowyError;
|
||||
use flowy_user::entities::{
|
||||
SignInParams, SignInResponse, SignUpParams, SignUpResponse, UpdateUserProfileParams,
|
||||
UserProfilePB,
|
||||
};
|
||||
use flowy_user::event_map::UserCloudService;
|
||||
use lib_infra::future::FutureResult;
|
||||
|
||||
use crate::http_server::self_host::configuration::{ClientServerConfiguration, HEADER_TOKEN};
|
||||
use crate::request::HttpRequestBuilder;
|
||||
|
||||
pub struct UserHttpCloudService {
|
||||
config: ClientServerConfiguration,
|
||||
}
|
||||
impl UserHttpCloudService {
|
||||
pub fn new(config: &ClientServerConfiguration) -> Self {
|
||||
Self {
|
||||
config: config.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl UserCloudService for UserHttpCloudService {
|
||||
fn sign_up(&self, params: SignUpParams) -> FutureResult<SignUpResponse, FlowyError> {
|
||||
let url = self.config.sign_up_url();
|
||||
FutureResult::new(async move {
|
||||
let resp = user_sign_up_request(params, &url).await?;
|
||||
Ok(resp)
|
||||
})
|
||||
}
|
||||
|
||||
fn sign_in(&self, params: SignInParams) -> FutureResult<SignInResponse, FlowyError> {
|
||||
let url = self.config.sign_in_url();
|
||||
FutureResult::new(async move {
|
||||
let resp = user_sign_in_request(params, &url).await?;
|
||||
Ok(resp)
|
||||
})
|
||||
}
|
||||
|
||||
fn sign_out(&self, token: &str) -> FutureResult<(), FlowyError> {
|
||||
let token = token.to_owned();
|
||||
let url = self.config.sign_out_url();
|
||||
FutureResult::new(async move {
|
||||
let _ = user_sign_out_request(&token, &url).await;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn update_user(
|
||||
&self,
|
||||
token: &str,
|
||||
params: UpdateUserProfileParams,
|
||||
) -> FutureResult<(), FlowyError> {
|
||||
let token = token.to_owned();
|
||||
let url = self.config.user_profile_url();
|
||||
FutureResult::new(async move {
|
||||
update_user_profile_request(&token, params, &url).await?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn get_user(&self, token: &str) -> FutureResult<UserProfilePB, FlowyError> {
|
||||
let token = token.to_owned();
|
||||
let url = self.config.user_profile_url();
|
||||
FutureResult::new(async move {
|
||||
let profile = get_user_profile_request(&token, &url).await?;
|
||||
Ok(profile)
|
||||
})
|
||||
}
|
||||
|
||||
fn ws_addr(&self) -> String {
|
||||
self.config.ws_addr()
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn user_sign_up_request(
|
||||
params: SignUpParams,
|
||||
url: &str,
|
||||
) -> Result<SignUpResponse, FlowyError> {
|
||||
let response = request_builder()
|
||||
.post(url)
|
||||
.json(params)?
|
||||
.json_response()
|
||||
.await?;
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
pub async fn user_sign_in_request(
|
||||
params: SignInParams,
|
||||
url: &str,
|
||||
) -> Result<SignInResponse, FlowyError> {
|
||||
let response = request_builder()
|
||||
.post(url)
|
||||
.json(params)?
|
||||
.json_response()
|
||||
.await?;
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
pub async fn user_sign_out_request(token: &str, url: &str) -> Result<(), FlowyError> {
|
||||
request_builder()
|
||||
.delete(url)
|
||||
.header(HEADER_TOKEN, token)
|
||||
.send()
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_user_profile_request(token: &str, url: &str) -> Result<UserProfilePB, FlowyError> {
|
||||
let user_profile = request_builder()
|
||||
.get(url)
|
||||
.header(HEADER_TOKEN, token)
|
||||
.response()
|
||||
.await?;
|
||||
Ok(user_profile)
|
||||
}
|
||||
|
||||
pub async fn update_user_profile_request(
|
||||
token: &str,
|
||||
params: UpdateUserProfileParams,
|
||||
url: &str,
|
||||
) -> Result<(), FlowyError> {
|
||||
request_builder()
|
||||
.patch(url)
|
||||
.header(HEADER_TOKEN, token)
|
||||
.json(params)?
|
||||
.send()
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn request_builder() -> HttpRequestBuilder {
|
||||
HttpRequestBuilder::new()
|
||||
}
|
@ -1,8 +1,4 @@
|
||||
pub mod entities;
|
||||
pub mod event_map;
|
||||
mod handlers;
|
||||
pub mod http_server;
|
||||
pub mod local_server;
|
||||
pub mod protobuf;
|
||||
mod request;
|
||||
mod response;
|
||||
|
@ -1,11 +0,0 @@
|
||||
use crate::http_server::self_host::configuration::ClientServerConfiguration;
|
||||
use crate::local_server::LocalServer;
|
||||
|
||||
pub struct LocalServerContext {
|
||||
pub local_server: LocalServer,
|
||||
}
|
||||
|
||||
pub fn build_server(_config: &ClientServerConfiguration) -> LocalServerContext {
|
||||
let local_server = LocalServer::new();
|
||||
LocalServerContext { local_server }
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
pub use context::*;
|
||||
pub use server::*;
|
||||
|
||||
mod context;
|
||||
mod server;
|
@ -1,80 +0,0 @@
|
||||
use lazy_static::lazy_static;
|
||||
use parking_lot::{Mutex, RwLock};
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
use flowy_error::FlowyError;
|
||||
use flowy_user::entities::{
|
||||
SignInParams, SignInResponse, SignUpParams, SignUpResponse, UpdateUserProfileParams,
|
||||
UserProfilePB,
|
||||
};
|
||||
use flowy_user::event_map::UserCloudService;
|
||||
use flowy_user::uid::UserIDGenerator;
|
||||
use lib_infra::future::FutureResult;
|
||||
|
||||
lazy_static! {
|
||||
static ref ID_GEN: Mutex<UserIDGenerator> = Mutex::new(UserIDGenerator::new(1));
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct LocalServer {
|
||||
stop_tx: RwLock<Option<mpsc::Sender<()>>>,
|
||||
}
|
||||
|
||||
impl LocalServer {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub async fn stop(&self) {
|
||||
let sender = self.stop_tx.read().clone();
|
||||
if let Some(stop_tx) = sender {
|
||||
let _ = stop_tx.send(()).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl UserCloudService for LocalServer {
|
||||
fn sign_up(&self, params: SignUpParams) -> FutureResult<SignUpResponse, FlowyError> {
|
||||
let uid = ID_GEN.lock().next_id();
|
||||
FutureResult::new(async move {
|
||||
Ok(SignUpResponse {
|
||||
user_id: uid,
|
||||
name: params.name,
|
||||
email: params.email,
|
||||
token: "".to_string(),
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn sign_in(&self, params: SignInParams) -> FutureResult<SignInResponse, FlowyError> {
|
||||
let uid = ID_GEN.lock().next_id();
|
||||
FutureResult::new(async move {
|
||||
Ok(SignInResponse {
|
||||
user_id: uid,
|
||||
name: params.name,
|
||||
email: params.email,
|
||||
token: "".to_string(),
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn sign_out(&self, _token: &str) -> FutureResult<(), FlowyError> {
|
||||
FutureResult::new(async { Ok(()) })
|
||||
}
|
||||
|
||||
fn update_user(
|
||||
&self,
|
||||
_token: &str,
|
||||
_params: UpdateUserProfileParams,
|
||||
) -> FutureResult<(), FlowyError> {
|
||||
FutureResult::new(async { Ok(()) })
|
||||
}
|
||||
|
||||
fn get_user(&self, _token: &str) -> FutureResult<UserProfilePB, FlowyError> {
|
||||
FutureResult::new(async { Ok(UserProfilePB::default()) })
|
||||
}
|
||||
|
||||
fn ws_addr(&self) -> String {
|
||||
"ws://localhost:8000/ws/".to_owned()
|
||||
}
|
||||
}
|
35
frontend/rust-lib/flowy-server/Cargo.toml
Normal file
35
frontend/rust-lib/flowy-server/Cargo.toml
Normal file
@ -0,0 +1,35 @@
|
||||
[package]
|
||||
name = "flowy-server"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
tracing = { version = "0.1" }
|
||||
futures-util = "0.3.26"
|
||||
reqwest = "0.11.14"
|
||||
hyper = "0.14"
|
||||
config = { version = "0.10.1", default-features = false, features = ["yaml"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
serde-aux = "4.2.0"
|
||||
nanoid = "0.4.0"
|
||||
thiserror = "1.0"
|
||||
tokio = { version = "1.26", features = ["sync"]}
|
||||
parking_lot = "0.12"
|
||||
lazy_static = "1.4.0"
|
||||
bytes = "1.0.1"
|
||||
postgrest = "1.0"
|
||||
tokio-retry = "0.3"
|
||||
anyhow = "1.0"
|
||||
uuid = { version = "1.3.3", features = ["v4"] }
|
||||
|
||||
lib-infra = { path = "../../../shared-lib/lib-infra" }
|
||||
flowy-user = { path = "../flowy-user" }
|
||||
flowy-error = { path = "../flowy-error" }
|
||||
flowy-config = { path = "../flowy-config" }
|
||||
|
||||
[dev-dependencies]
|
||||
uuid = { version = "1.3.3", features = ["v4"] }
|
||||
dotenv = "0.15.0"
|
13
frontend/rust-lib/flowy-server/src/lib.rs
Normal file
13
frontend/rust-lib/flowy-server/src/lib.rs
Normal file
@ -0,0 +1,13 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use flowy_user::event_map::UserAuthService;
|
||||
|
||||
pub mod local_server;
|
||||
mod request;
|
||||
mod response;
|
||||
pub mod self_host;
|
||||
pub mod supabase;
|
||||
|
||||
pub trait AppFlowyServer: Send + Sync + 'static {
|
||||
fn user_service(&self) -> Arc<dyn UserAuthService>;
|
||||
}
|
5
frontend/rust-lib/flowy-server/src/local_server/mod.rs
Normal file
5
frontend/rust-lib/flowy-server/src/local_server/mod.rs
Normal file
@ -0,0 +1,5 @@
|
||||
pub use server::*;
|
||||
|
||||
mod server;
|
||||
pub(crate) mod uid;
|
||||
mod user;
|
34
frontend/rust-lib/flowy-server/src/local_server/server.rs
Normal file
34
frontend/rust-lib/flowy-server/src/local_server/server.rs
Normal file
@ -0,0 +1,34 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use parking_lot::RwLock;
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
use flowy_user::event_map::UserAuthService;
|
||||
|
||||
use crate::local_server::user::LocalServerUserAuthServiceImpl;
|
||||
use crate::AppFlowyServer;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct LocalServer {
|
||||
stop_tx: RwLock<Option<mpsc::Sender<()>>>,
|
||||
}
|
||||
|
||||
impl LocalServer {
|
||||
pub fn new() -> Self {
|
||||
// let _config = self_host_server_configuration().unwrap();
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub async fn stop(&self) {
|
||||
let sender = self.stop_tx.read().clone();
|
||||
if let Some(stop_tx) = sender {
|
||||
let _ = stop_tx.send(()).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AppFlowyServer for LocalServer {
|
||||
fn user_service(&self) -> Arc<dyn UserAuthService> {
|
||||
Arc::new(LocalServerUserAuthServiceImpl())
|
||||
}
|
||||
}
|
71
frontend/rust-lib/flowy-server/src/local_server/user.rs
Normal file
71
frontend/rust-lib/flowy-server/src/local_server/user.rs
Normal file
@ -0,0 +1,71 @@
|
||||
use lazy_static::lazy_static;
|
||||
use parking_lot::Mutex;
|
||||
|
||||
use flowy_error::FlowyError;
|
||||
use flowy_user::entities::{
|
||||
SignInParams, SignInResponse, SignUpParams, SignUpResponse, UpdateUserProfileParams, UserProfile,
|
||||
};
|
||||
use flowy_user::event_map::UserAuthService;
|
||||
use lib_infra::box_any::BoxAny;
|
||||
use lib_infra::future::FutureResult;
|
||||
|
||||
use crate::local_server::uid::UserIDGenerator;
|
||||
|
||||
lazy_static! {
|
||||
static ref ID_GEN: Mutex<UserIDGenerator> = Mutex::new(UserIDGenerator::new(1));
|
||||
}
|
||||
|
||||
pub(crate) struct LocalServerUserAuthServiceImpl();
|
||||
|
||||
impl UserAuthService for LocalServerUserAuthServiceImpl {
|
||||
fn sign_up(&self, params: BoxAny) -> FutureResult<SignUpResponse, FlowyError> {
|
||||
FutureResult::new(async move {
|
||||
let params = params.unbox_or_error::<SignUpParams>()?;
|
||||
let uid = ID_GEN.lock().next_id();
|
||||
let workspace_id = uuid::Uuid::new_v4().to_string();
|
||||
Ok(SignUpResponse {
|
||||
user_id: uid,
|
||||
name: params.name,
|
||||
workspace_id,
|
||||
email: Some(params.email),
|
||||
token: None,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn sign_in(&self, params: BoxAny) -> FutureResult<SignInResponse, FlowyError> {
|
||||
FutureResult::new(async move {
|
||||
let uid = ID_GEN.lock().next_id();
|
||||
let params = params.unbox_or_error::<SignInParams>()?;
|
||||
let workspace_id = uuid::Uuid::new_v4().to_string();
|
||||
Ok(SignInResponse {
|
||||
user_id: uid,
|
||||
name: params.name,
|
||||
workspace_id,
|
||||
email: Some(params.email),
|
||||
token: None,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn sign_out(&self, _token: Option<String>) -> FutureResult<(), FlowyError> {
|
||||
FutureResult::new(async { Ok(()) })
|
||||
}
|
||||
|
||||
fn update_user(
|
||||
&self,
|
||||
_uid: i64,
|
||||
_token: &Option<String>,
|
||||
_params: UpdateUserProfileParams,
|
||||
) -> FutureResult<(), FlowyError> {
|
||||
FutureResult::new(async { Ok(()) })
|
||||
}
|
||||
|
||||
fn get_user_profile(
|
||||
&self,
|
||||
_token: Option<String>,
|
||||
_uid: i64,
|
||||
) -> FutureResult<Option<UserProfile>, FlowyError> {
|
||||
FutureResult::new(async { Ok(None) })
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user