feat: auth pages improvement for mobile and desktop platform (#3217)
Before Width: | Height: | Size: 30 KiB |
@ -1,4 +1,4 @@
|
||||
import 'package:appflowy/user/presentation/sign_in_screen.dart';
|
||||
import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/widgets.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/widgets/sync_setting_view.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
@ -7,15 +7,15 @@ import 'base.dart';
|
||||
|
||||
extension AppFlowyAuthTest on WidgetTester {
|
||||
Future<void> tapGoogleLoginInButton() async {
|
||||
await tapButton(find.byType(GoogleSignUpButton));
|
||||
await tapButton(find.byKey(const Key('signInWithGoogleButton')));
|
||||
}
|
||||
|
||||
Future<void> tapSignInAsGuest() async {
|
||||
await tapButton(find.byType(SignInAsGuestButton));
|
||||
await tapButton(find.byType(SignInAnonymousButton));
|
||||
}
|
||||
|
||||
void expectToSeeGoogleLoginButton() {
|
||||
expect(find.byType(GoogleSignUpButton), findsOneWidget);
|
||||
expect(find.byKey(const Key('signInWithGoogleButton')), findsOneWidget);
|
||||
}
|
||||
|
||||
void assertSwitchValue(Finder finder, bool value) {
|
||||
|
@ -1,7 +1,7 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/share/share_button.dart';
|
||||
import 'package:appflowy/user/presentation/skip_log_in_screen.dart';
|
||||
import 'package:appflowy/user/presentation/screens/screens.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/sidebar_new_page_button.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/view/draggable_view_item.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/view/view_action_type.dart';
|
||||
|
@ -50,6 +50,8 @@ PODS:
|
||||
- Toast
|
||||
- integration_test (0.0.1):
|
||||
- Flutter
|
||||
- irondash_engine_context (0.0.1):
|
||||
- Flutter
|
||||
- package_info_plus (0.4.5):
|
||||
- Flutter
|
||||
- path_provider_foundation (0.0.1):
|
||||
@ -66,6 +68,8 @@ PODS:
|
||||
- FlutterMacOS
|
||||
- sign_in_with_apple (0.0.1):
|
||||
- Flutter
|
||||
- super_native_extensions (0.0.1):
|
||||
- Flutter
|
||||
- SwiftyGif (5.4.3)
|
||||
- Toast (4.0.0)
|
||||
- url_launcher_ios (0.0.1):
|
||||
@ -83,11 +87,13 @@ DEPENDENCIES:
|
||||
- Flutter (from `Flutter`)
|
||||
- fluttertoast (from `.symlinks/plugins/fluttertoast/ios`)
|
||||
- integration_test (from `.symlinks/plugins/integration_test/ios`)
|
||||
- irondash_engine_context (from `.symlinks/plugins/irondash_engine_context/ios`)
|
||||
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
|
||||
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
|
||||
- rich_clipboard_ios (from `.symlinks/plugins/rich_clipboard_ios/ios`)
|
||||
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
|
||||
- sign_in_with_apple (from `.symlinks/plugins/sign_in_with_apple/ios`)
|
||||
- super_native_extensions (from `.symlinks/plugins/super_native_extensions/ios`)
|
||||
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
||||
- webview_flutter_wkwebview (from `.symlinks/plugins/webview_flutter_wkwebview/ios`)
|
||||
|
||||
@ -119,6 +125,8 @@ EXTERNAL SOURCES:
|
||||
:path: ".symlinks/plugins/fluttertoast/ios"
|
||||
integration_test:
|
||||
:path: ".symlinks/plugins/integration_test/ios"
|
||||
irondash_engine_context:
|
||||
:path: ".symlinks/plugins/irondash_engine_context/ios"
|
||||
package_info_plus:
|
||||
:path: ".symlinks/plugins/package_info_plus/ios"
|
||||
path_provider_foundation:
|
||||
@ -129,6 +137,8 @@ EXTERNAL SOURCES:
|
||||
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
|
||||
sign_in_with_apple:
|
||||
:path: ".symlinks/plugins/sign_in_with_apple/ios"
|
||||
super_native_extensions:
|
||||
:path: ".symlinks/plugins/super_native_extensions/ios"
|
||||
url_launcher_ios:
|
||||
:path: ".symlinks/plugins/url_launcher_ios/ios"
|
||||
webview_flutter_wkwebview:
|
||||
@ -146,6 +156,7 @@ SPEC CHECKSUMS:
|
||||
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
|
||||
fluttertoast: fafc4fa4d01a6a9e4f772ecd190ffa525e9e2d9c
|
||||
integration_test: 13825b8a9334a850581300559b8839134b124670
|
||||
irondash_engine_context: 3458bf979b90d616ffb8ae03a150bafe2e860cc9
|
||||
package_info_plus: fd030dabf36271f146f1f3beacd48f564b0f17f7
|
||||
path_provider_foundation: eaf5b3e458fc0e5fbb9940fb09980e853fe058b8
|
||||
ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825
|
||||
@ -153,6 +164,7 @@ SPEC CHECKSUMS:
|
||||
SDWebImage: b9a731e1d6307f44ca703b3976d18c24ca561e84
|
||||
shared_preferences_foundation: e2dae3258e06f44cc55f49d42024fd8dd03c590c
|
||||
sign_in_with_apple: f3bf75217ea4c2c8b91823f225d70230119b8440
|
||||
super_native_extensions: 49e897b6039bb784226e7354e502a3c68e1659b5
|
||||
SwiftyGif: 6c3eafd0ce693cad58bb63d2b2fb9bacb8552780
|
||||
Toast: 91b396c56ee72a5790816f40d3a94dd357abc196
|
||||
url_launcher_ios: 08a3dfac5fb39e8759aeb0abbd5d9480f30fc8b4
|
||||
|
@ -16,6 +16,17 @@
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>$(FLUTTER_BUILD_NAME)</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleURLName</key>
|
||||
<string></string>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>appflowy-flutter</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
|
@ -0,0 +1,47 @@
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/user/application/auth/auth_service.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder2/workspace.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class MobileHomeScreen extends StatelessWidget {
|
||||
const MobileHomeScreen({
|
||||
super.key,
|
||||
required this.userProfile,
|
||||
required this.workspaceSetting,
|
||||
});
|
||||
|
||||
static const routeName = "/MobileHomeScreen";
|
||||
final UserProfilePB userProfile;
|
||||
final WorkspaceSettingPB workspaceSetting;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text("MobileHomeScreen"),
|
||||
),
|
||||
// TODO(yijing): implement home page later
|
||||
body: Center(
|
||||
child: Column(
|
||||
children: [
|
||||
const Text(
|
||||
'User',
|
||||
),
|
||||
Text(
|
||||
userProfile.toString(),
|
||||
),
|
||||
Text('Workspace name: ${workspaceSetting.workspace.name}'),
|
||||
ElevatedButton(
|
||||
onPressed: () async {
|
||||
await getIt<AuthService>().signOut();
|
||||
runAppFlowy();
|
||||
},
|
||||
child: const Text('Logout'),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -107,7 +107,7 @@ void _resolveUserDeps(GetIt getIt, IntegrationMode mode) {
|
||||
() => SignUpBloc(getIt<AuthService>()),
|
||||
);
|
||||
|
||||
getIt.registerFactory<SplashRoute>(() => SplashRoute());
|
||||
getIt.registerFactory<SplashRouter>(() => SplashRouter());
|
||||
getIt.registerFactory<EditPanelBloc>(() => EditPanelBloc());
|
||||
getIt.registerFactory<SplashBloc>(() => SplashBloc());
|
||||
getIt.registerLazySingleton<NetworkListener>(() => NetworkListener());
|
||||
@ -122,8 +122,8 @@ void _resolveHomeDeps(GetIt getIt) {
|
||||
(user, _) => UserListener(userProfile: user),
|
||||
);
|
||||
|
||||
getIt.registerFactoryParam<WelcomeBloc, UserProfilePB, void>(
|
||||
(user, _) => WelcomeBloc(
|
||||
getIt.registerFactoryParam<WorkspaceBloc, UserProfilePB, void>(
|
||||
(user, _) => WorkspaceBloc(
|
||||
userService: UserBackendService(userId: user.id),
|
||||
),
|
||||
);
|
||||
|
@ -1,6 +1,6 @@
|
||||
import 'package:appflowy/startup/launch_configuration.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/user/presentation/splash_screen.dart';
|
||||
import 'package:appflowy/user/presentation/screens/splash_screen.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class FlowyApp implements EntryPoint {
|
||||
|
@ -0,0 +1,33 @@
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/user/application/auth/auth_service.dart';
|
||||
import 'package:appflowy/user/presentation/router.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/protobuf.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder2/workspace.pb.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/snap_bar.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
void handleOpenWorkspaceError(BuildContext context, FlowyError error) {
|
||||
Log.error(error);
|
||||
switch (error.code) {
|
||||
case ErrorCode.WorkspaceDataNotSync:
|
||||
final userFolder = UserFolderPB.fromBuffer(error.payload);
|
||||
getIt<AuthRouter>().pushWorkspaceErrorScreen(context, userFolder, error);
|
||||
break;
|
||||
case ErrorCode.InvalidEncryptSecret:
|
||||
showSnapBar(
|
||||
context,
|
||||
error.msg,
|
||||
);
|
||||
break;
|
||||
default:
|
||||
showSnapBar(
|
||||
context,
|
||||
error.msg,
|
||||
onClosed: () {
|
||||
getIt<AuthService>().signOut();
|
||||
runAppFlowy();
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
import 'package:appflowy/user/presentation/helpers/helpers.dart';
|
||||
import 'package:appflowy/user/presentation/presentation.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/protobuf.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
void handleSuccessOrFail(
|
||||
Either<UserProfilePB, FlowyError> result,
|
||||
BuildContext context,
|
||||
AuthRouter router,
|
||||
) {
|
||||
result.fold(
|
||||
(user) {
|
||||
if (user.encryptionType == EncryptionTypePB.Symmetric) {
|
||||
router.pushEncryptionScreen(context, user);
|
||||
} else {
|
||||
router.pushHomeScreen(context, user);
|
||||
}
|
||||
},
|
||||
(error) {
|
||||
handleOpenWorkspaceError(context, error);
|
||||
},
|
||||
);
|
||||
}
|
@ -0,0 +1,2 @@
|
||||
export 'handle_open_workspace_error.dart';
|
||||
export 'handle_success_or_fail.dart';
|
@ -0,0 +1,4 @@
|
||||
export 'screens/screens.dart';
|
||||
export 'widgets/widgets.dart';
|
||||
export 'historical_user.dart';
|
||||
export 'router.dart';
|
@ -1,9 +1,8 @@
|
||||
import 'package:appflowy/mobile/presentation/mobile_home_page.dart';
|
||||
import 'package:appflowy/startup/startup.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';
|
||||
import 'package:appflowy/user/presentation/welcome_screen.dart';
|
||||
import 'package:appflowy/user/presentation/screens/screens.dart';
|
||||
import 'package:appflowy/user/presentation/screens/workspace_start_screen/workspace_start_screen.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/home_screen.dart';
|
||||
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
@ -13,64 +12,77 @@ import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart'
|
||||
show UserProfilePB;
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder2/protobuf.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'encrypt_secret_screen.dart';
|
||||
import 'workspace_error_screen.dart';
|
||||
|
||||
const routerNameRoot = '/';
|
||||
const routerNameSignUp = '/signUp';
|
||||
const routerNameSignIn = '/signIn';
|
||||
const routerNameSkipLogIn = '/skipLogIn';
|
||||
const routerNameWelcome = '/welcome';
|
||||
const routerNameHome = '/home';
|
||||
import 'package:appflowy/util/platform_extension.dart';
|
||||
|
||||
class AuthRouter {
|
||||
void pushForgetPasswordScreen(BuildContext context) {}
|
||||
|
||||
void pushWelcomeScreen(BuildContext context, UserProfilePB userProfile) {
|
||||
getIt<SplashRoute>().pushWelcomeScreen(context, userProfile);
|
||||
void pushWorkspaceStartScreen(
|
||||
BuildContext context,
|
||||
UserProfilePB userProfile,
|
||||
) {
|
||||
getIt<SplashRouter>().pushWorkspaceStartScreen(context, userProfile);
|
||||
}
|
||||
|
||||
void pushSignUpScreen(BuildContext context) {
|
||||
Navigator.of(context).push(
|
||||
PageRoutes.fade(
|
||||
() => SignUpScreen(router: getIt<AuthRouter>()),
|
||||
const RouteSettings(name: routerNameSignUp),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void pushHomeScreenWithWorkSpace(
|
||||
BuildContext context,
|
||||
UserProfilePB profile,
|
||||
WorkspaceSettingPB workspaceSetting,
|
||||
) {
|
||||
Navigator.push(
|
||||
context,
|
||||
PageRoutes.fade(
|
||||
() => HomeScreen(
|
||||
profile,
|
||||
workspaceSetting,
|
||||
key: ValueKey(profile.id),
|
||||
),
|
||||
const RouteSettings(name: routerNameHome),
|
||||
RouteDurations.slow.inMilliseconds * .001,
|
||||
const RouteSettings(name: SignUpScreen.routeName),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// Navigates to the home screen based on the current workspace and platform.
|
||||
///
|
||||
/// This function takes in a [BuildContext] and a [UserProfilePB] object to
|
||||
/// determine the user's settings and then navigate to the appropriate home screen
|
||||
/// (`MobileHomeScreen` for mobile platforms, `DesktopHomeScreen` for others).
|
||||
///
|
||||
/// It first fetches the current workspace settings using [FolderEventGetCurrentWorkspace].
|
||||
/// If the workspace settings are successfully fetched, it navigates to the home screen.
|
||||
/// If there's an error, it defaults to the workspace start screen.
|
||||
///
|
||||
/// @param [context] BuildContext for navigating to the appropriate screen.
|
||||
/// @param [userProfile] UserProfilePB object containing the details of the current user.
|
||||
///
|
||||
Future<void> pushHomeScreen(
|
||||
BuildContext context,
|
||||
UserProfilePB userProfile,
|
||||
) async {
|
||||
final result = await FolderEventGetCurrentWorkspace().send();
|
||||
result.fold(
|
||||
(workspaceSettingPB) => pushHomeScreenWithWorkSpace(
|
||||
context,
|
||||
userProfile,
|
||||
workspaceSettingPB,
|
||||
),
|
||||
(r) => pushWelcomeScreen(context, userProfile),
|
||||
(workspaceSetting) {
|
||||
if (PlatformExtension.isMobile) {
|
||||
Navigator.of(context).pushAndRemoveUntil(
|
||||
MaterialPageRoute<void>(
|
||||
builder: (BuildContext context) => MobileHomeScreen(
|
||||
key: ValueKey(userProfile.id),
|
||||
userProfile: userProfile,
|
||||
workspaceSetting: workspaceSetting,
|
||||
),
|
||||
),
|
||||
// pop up all the pages until [SplashScreen]
|
||||
(route) => route.settings.name == SplashScreen.routeName,
|
||||
);
|
||||
} else {
|
||||
Navigator.push(
|
||||
context,
|
||||
PageRoutes.fade(
|
||||
() => DesktopHomeScreen(
|
||||
key: ValueKey(userProfile.id),
|
||||
userProfile: userProfile,
|
||||
workspaceSetting: workspaceSetting,
|
||||
),
|
||||
const RouteSettings(
|
||||
name: DesktopHomeScreen.routeName,
|
||||
),
|
||||
RouteDurations.slow.inMilliseconds * .001,
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
(error) => pushWorkspaceStartScreen(context, userProfile),
|
||||
);
|
||||
}
|
||||
|
||||
@ -85,7 +97,7 @@ class AuthRouter {
|
||||
user: userProfile,
|
||||
key: ValueKey(userProfile.id),
|
||||
),
|
||||
const RouteSettings(name: routerNameWelcome),
|
||||
const RouteSettings(name: EncryptSecretScreen.routeName),
|
||||
RouteDurations.slow.inMilliseconds * .001,
|
||||
),
|
||||
);
|
||||
@ -103,23 +115,23 @@ class AuthRouter {
|
||||
await Navigator.of(context).push(
|
||||
PageRoutes.fade(
|
||||
() => screen,
|
||||
const RouteSettings(name: routerNameWelcome),
|
||||
const RouteSettings(name: WorkspaceErrorScreen.routeName),
|
||||
RouteDurations.slow.inMilliseconds * .001,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SplashRoute {
|
||||
Future<void> pushWelcomeScreen(
|
||||
class SplashRouter {
|
||||
Future<void> pushWorkspaceStartScreen(
|
||||
BuildContext context,
|
||||
UserProfilePB userProfile,
|
||||
) async {
|
||||
final screen = WelcomeScreen(userProfile: userProfile);
|
||||
final screen = WorkspaceStartScreen(userProfile: userProfile);
|
||||
await Navigator.of(context).push(
|
||||
PageRoutes.fade(
|
||||
() => screen,
|
||||
const RouteSettings(name: routerNameWelcome),
|
||||
const RouteSettings(name: WorkspaceStartScreen.routeName),
|
||||
RouteDurations.slow.inMilliseconds * .001,
|
||||
),
|
||||
);
|
||||
@ -138,18 +150,35 @@ class SplashRoute {
|
||||
UserProfilePB userProfile,
|
||||
WorkspaceSettingPB workspaceSetting,
|
||||
) {
|
||||
Navigator.push(
|
||||
context,
|
||||
PageRoutes.fade(
|
||||
() => HomeScreen(
|
||||
userProfile,
|
||||
workspaceSetting,
|
||||
key: ValueKey(userProfile.id),
|
||||
if (PlatformExtension.isMobile) {
|
||||
Navigator.pushAndRemoveUntil<void>(
|
||||
context,
|
||||
MaterialPageRoute<void>(
|
||||
builder: (BuildContext context) => MobileHomeScreen(
|
||||
key: ValueKey(userProfile.id),
|
||||
userProfile: userProfile,
|
||||
workspaceSetting: workspaceSetting,
|
||||
),
|
||||
),
|
||||
const RouteSettings(name: routerNameWelcome),
|
||||
RouteDurations.slow.inMilliseconds * .001,
|
||||
),
|
||||
);
|
||||
// pop up all the pages until [SplashScreen]
|
||||
(route) => route.settings.name == SplashScreen.routeName,
|
||||
);
|
||||
} else {
|
||||
Navigator.push(
|
||||
context,
|
||||
PageRoutes.fade(
|
||||
() => DesktopHomeScreen(
|
||||
userProfile: userProfile,
|
||||
workspaceSetting: workspaceSetting,
|
||||
key: ValueKey(userProfile.id),
|
||||
),
|
||||
const RouteSettings(
|
||||
name: DesktopHomeScreen.routeName,
|
||||
),
|
||||
RouteDurations.slow.inMilliseconds * .001,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void pushSignInScreen(BuildContext context) {
|
||||
@ -157,7 +186,7 @@ class SplashRoute {
|
||||
context,
|
||||
PageRoutes.fade(
|
||||
() => SignInScreen(router: getIt<AuthRouter>()),
|
||||
const RouteSettings(name: routerNameSignIn),
|
||||
const RouteSettings(name: SignInScreen.routeName),
|
||||
RouteDurations.slow.inMilliseconds * .001,
|
||||
),
|
||||
);
|
||||
@ -171,7 +200,7 @@ class SplashRoute {
|
||||
router: getIt<AuthRouter>(),
|
||||
authService: getIt<AuthService>(),
|
||||
),
|
||||
const RouteSettings(name: routerNameSkipLogIn),
|
||||
const RouteSettings(name: SkipLogInScreen.routeName),
|
||||
RouteDurations.slow.inMilliseconds * .001,
|
||||
),
|
||||
);
|
||||
|
@ -1,6 +1,6 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/user/presentation/sign_in_screen.dart';
|
||||
import 'package:appflowy/user/presentation/helpers/helpers.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
@ -9,9 +9,10 @@ import 'package:flowy_infra_ui/widget/buttons/secondary_button.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../application/encrypt_secret_bloc.dart';
|
||||
import '../../application/encrypt_secret_bloc.dart';
|
||||
|
||||
class EncryptSecretScreen extends StatefulWidget {
|
||||
static const routeName = "/EncryptSecretScreen";
|
||||
final UserProfilePB user;
|
||||
const EncryptSecretScreen({required this.user, super.key});
|
||||
|
@ -0,0 +1,6 @@
|
||||
export 'sign_in_screen/sign_in_screen.dart';
|
||||
export 'skip_log_in_screen.dart';
|
||||
export 'splash_screen.dart';
|
||||
export 'sign_up_screen.dart';
|
||||
export 'encrypt_secret_screen.dart';
|
||||
export 'workspace_error_screen.dart';
|
@ -0,0 +1,238 @@
|
||||
import 'package:appflowy/core/frameless_window.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/widgets.dart';
|
||||
import 'package:appflowy/user/presentation/widgets/widgets.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class DesktopSignInScreen extends StatelessWidget {
|
||||
const DesktopSignInScreen({super.key, required this.isLoading});
|
||||
|
||||
final bool isLoading;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
const indicatorMinHeight = 4.0;
|
||||
return Scaffold(
|
||||
appBar: const PreferredSize(
|
||||
preferredSize: Size(double.infinity, 60),
|
||||
child: MoveWindowDetector(),
|
||||
),
|
||||
body: Center(
|
||||
child: AuthFormContainer(
|
||||
children: [
|
||||
FlowyLogoTitle(
|
||||
title: LocaleKeys.welcomeText.tr(),
|
||||
logoSize: const Size(60, 60),
|
||||
),
|
||||
const VSpace(30),
|
||||
// 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 SignInAnonymousButton(),
|
||||
|
||||
// third-party sign in.
|
||||
const VSpace(20),
|
||||
const _OrDivider(),
|
||||
const VSpace(10),
|
||||
const ThirdPartySignInButtons(),
|
||||
const VSpace(20),
|
||||
// loading status
|
||||
const VSpace(indicatorMinHeight),
|
||||
isLoading
|
||||
? const LinearProgressIndicator(
|
||||
value: null,
|
||||
minHeight: indicatorMinHeight,
|
||||
)
|
||||
: const VSpace(indicatorMinHeight),
|
||||
// add the same space when there's no loading status.
|
||||
// ConstrainedBox(
|
||||
// constraints: const BoxConstraints(maxHeight: 140),
|
||||
// child: HistoricalUserList(
|
||||
// didOpenUser: () async {
|
||||
// await FlowyRunner.run(
|
||||
// FlowyApp(),
|
||||
// integrationEnv(),
|
||||
// );
|
||||
// },
|
||||
// ),
|
||||
// ),
|
||||
const VSpace(20),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _OrDivider extends StatelessWidget {
|
||||
const _OrDivider();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
children: [
|
||||
const Flexible(
|
||||
child: Divider(
|
||||
thickness: 1,
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10),
|
||||
child: FlowyText.regular(LocaleKeys.signIn_or.tr()),
|
||||
),
|
||||
const Flexible(
|
||||
child: Divider(
|
||||
thickness: 1,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// The following code is migrated from previous signInScreen.dart(for desktop)
|
||||
// We may need this later when sign up&in feature is ready
|
||||
// class SignUpPrompt extends StatelessWidget {
|
||||
// const SignUpPrompt({
|
||||
// super.key,
|
||||
// required this.router,
|
||||
// }) ;
|
||||
|
||||
// final AuthRouter router;
|
||||
|
||||
// @override
|
||||
// Widget build(BuildContext context) {
|
||||
// return Row(
|
||||
// mainAxisAlignment: MainAxisAlignment.center,
|
||||
// children: [
|
||||
// FlowyText.medium(
|
||||
// LocaleKeys.signIn_dontHaveAnAccount.tr(),
|
||||
// color: Theme.of(context).hintColor,
|
||||
// ),
|
||||
// TextButton(
|
||||
// style: TextButton.styleFrom(
|
||||
// textStyle: Theme.of(context).textTheme.bodyMedium,
|
||||
// ),
|
||||
// onPressed: () => router.pushSignUpScreen(context),
|
||||
// child: Text(
|
||||
// LocaleKeys.signUp_buttonText.tr(),
|
||||
// style: TextStyle(color: Theme.of(context).colorScheme.primary),
|
||||
// ),
|
||||
// ),
|
||||
// ForgetPasswordButton(router: router),
|
||||
// ],
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
||||
// class LoginButton extends StatelessWidget {
|
||||
// const LoginButton({
|
||||
// super.key
|
||||
// }) ;
|
||||
|
||||
// @override
|
||||
// Widget build(BuildContext context) {
|
||||
// return RoundedTextButton(
|
||||
// title: LocaleKeys.signIn_loginButtonText.tr(),
|
||||
// height: 48,
|
||||
// borderRadius: Corners.s10Border,
|
||||
// onPressed: () => context
|
||||
// .read<SignInBloc>()
|
||||
// .add(const SignInEvent.signedInWithUserEmailAndPassword()),
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
// class ForgetPasswordButton extends StatelessWidget {
|
||||
// const ForgetPasswordButton({
|
||||
// super.key
|
||||
// required this.router,
|
||||
// }) ;
|
||||
|
||||
// final AuthRouter router;
|
||||
|
||||
// @override
|
||||
// Widget build(BuildContext context) {
|
||||
// return TextButton(
|
||||
// style: TextButton.styleFrom(
|
||||
// textStyle: Theme.of(context).textTheme.bodyMedium,
|
||||
// ),
|
||||
// onPressed: () {
|
||||
// throw UnimplementedError();
|
||||
// },
|
||||
// child: Text(
|
||||
// LocaleKeys.signIn_forgotPassword.tr(),
|
||||
// style: TextStyle(color: Theme.of(context).colorScheme.primary),
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
||||
// class PasswordTextField extends StatelessWidget {
|
||||
// const PasswordTextField({
|
||||
// super.key
|
||||
// }) ;
|
||||
|
||||
// @override
|
||||
// Widget build(BuildContext context) {
|
||||
// return BlocBuilder<SignInBloc, SignInState>(
|
||||
// buildWhen: (previous, current) =>
|
||||
// previous.passwordError != current.passwordError,
|
||||
// builder: (context, state) {
|
||||
// return RoundedInputField(
|
||||
// obscureText: true,
|
||||
// obscureIcon: const FlowySvg(FlowySvgs.hide_m),
|
||||
// obscureHideIcon: const FlowySvg(FlowySvgs.show_m),
|
||||
// hintText: LocaleKeys.signIn_passwordHint.tr(),
|
||||
// errorText: context
|
||||
// .read<SignInBloc>()
|
||||
// .state
|
||||
// .passwordError
|
||||
// .fold(() => "", (error) => error),
|
||||
// onChanged: (value) => context
|
||||
// .read<SignInBloc>()
|
||||
// .add(SignInEvent.passwordChanged(value)),
|
||||
// );
|
||||
// },
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
||||
// class EmailTextField extends StatelessWidget {
|
||||
// const EmailTextField({
|
||||
// super.key
|
||||
// }) ;
|
||||
|
||||
// @override
|
||||
// Widget build(BuildContext context) {
|
||||
// return BlocBuilder<SignInBloc, SignInState>(
|
||||
// buildWhen: (previous, current) =>
|
||||
// previous.emailError != current.emailError,
|
||||
// builder: (context, state) {
|
||||
// return RoundedInputField(
|
||||
// hintText: LocaleKeys.signIn_emailHint.tr(),
|
||||
// errorText: context
|
||||
// .read<SignInBloc>()
|
||||
// .state
|
||||
// .emailError
|
||||
// .fold(() => "", (error) => error),
|
||||
// onChanged: (value) =>
|
||||
// context.read<SignInBloc>().add(SignInEvent.emailChanged(value)),
|
||||
// );
|
||||
// },
|
||||
// );
|
||||
// }
|
||||
// }
|
@ -0,0 +1,91 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/widgets.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class MobileSignInScreen extends StatelessWidget {
|
||||
const MobileSignInScreen({super.key, required this.isLoading});
|
||||
final bool isLoading;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
const spacing = 16;
|
||||
// Welcome to Appflowy
|
||||
final welcomeString = LocaleKeys.welcomeText.tr();
|
||||
final style = Theme.of(context);
|
||||
return Scaffold(
|
||||
resizeToAvoidBottomInset: false,
|
||||
body: isLoading
|
||||
? // TODO(yijing): improve loading effect in the future
|
||||
const Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text('Signing in...'),
|
||||
VSpace(8),
|
||||
CircularProgressIndicator(),
|
||||
],
|
||||
),
|
||||
)
|
||||
: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(50, 0, 50, 30),
|
||||
child: Column(
|
||||
children: [
|
||||
const Spacer(
|
||||
flex: 4,
|
||||
),
|
||||
const FlowySvg(
|
||||
FlowySvgs.flowy_logo_xl,
|
||||
size: Size.square(64),
|
||||
blendMode: null,
|
||||
),
|
||||
const VSpace(spacing * 2),
|
||||
// Welcome to
|
||||
Text(
|
||||
welcomeString.substring(0, welcomeString.length - 8),
|
||||
style: style.textTheme.displayMedium,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
// Appflowy
|
||||
Text(
|
||||
welcomeString.substring(welcomeString.length - 8),
|
||||
style: style.textTheme.displayLarge,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const VSpace(16),
|
||||
// TODO(yijing): confirm the subtitle before release app
|
||||
Text(
|
||||
'You are in charge of your data and customizations.',
|
||||
style: style.textTheme.bodyMedium,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const Spacer(
|
||||
flex: 2,
|
||||
),
|
||||
const SignInAnonymousButton(),
|
||||
const VSpace(16),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Expanded(child: Divider()),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
child: Text(
|
||||
LocaleKeys.signIn_or.tr(),
|
||||
style: style.textTheme.bodyMedium,
|
||||
),
|
||||
),
|
||||
const Expanded(child: Divider()),
|
||||
],
|
||||
),
|
||||
const VSpace(16),
|
||||
const ThirdPartySignInButtons(),
|
||||
const VSpace(16),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
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/util/platform_extension.dart';
|
||||
import 'package:appflowy/user/presentation/screens/sign_in_screen/desktop_sign_in_screen.dart';
|
||||
import 'package:appflowy/user/presentation/screens/sign_in_screen/mobile_sign_in_screen.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../../helpers/helpers.dart';
|
||||
|
||||
class SignInScreen extends StatelessWidget {
|
||||
const SignInScreen({
|
||||
super.key,
|
||||
required this.router,
|
||||
});
|
||||
|
||||
static const routeName = '/SignInScreen';
|
||||
final AuthRouter router;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => getIt<SignInBloc>(),
|
||||
child: BlocConsumer<SignInBloc, SignInState>(
|
||||
listener: (context, state) {
|
||||
state.successOrFail.fold(
|
||||
() => null,
|
||||
(result) => handleSuccessOrFail(result, context, router),
|
||||
);
|
||||
},
|
||||
builder: (context, state) {
|
||||
// When user is logining through 3rd party, a loading widget will appear on the screen. [isLoading] is used to control it is on or not.
|
||||
final isLoading = context.read<SignInBloc>().state.isSubmitting;
|
||||
if (PlatformExtension.isMobile) {
|
||||
return MobileSignInScreen(
|
||||
isLoading: isLoading,
|
||||
);
|
||||
}
|
||||
return DesktopSignInScreen(
|
||||
isLoading: isLoading,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,85 @@
|
||||
import 'package:appflowy/core/config/kv.dart';
|
||||
import 'package:appflowy/core/config/kv_keys.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/user/application/historical_user_bloc.dart';
|
||||
import 'package:appflowy/user/application/sign_in_bloc.dart';
|
||||
import 'package:appflowy/util/platform_extension.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/size.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class SignInAnonymousButton extends StatelessWidget {
|
||||
/// Used in DesktopSignInScreen and MobileSignInScreen
|
||||
const SignInAnonymousButton({
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isMobile = PlatformExtension.isMobile;
|
||||
|
||||
return BlocBuilder<SignInBloc, SignInState>(
|
||||
builder: (context, signInState) {
|
||||
return BlocProvider(
|
||||
create: (context) => HistoricalUserBloc()
|
||||
..add(
|
||||
const HistoricalUserEvent.initial(),
|
||||
),
|
||||
child: BlocListener<HistoricalUserBloc, HistoricalUserState>(
|
||||
listenWhen: (previous, current) =>
|
||||
previous.openedHistoricalUser != current.openedHistoricalUser,
|
||||
listener: (context, state) async {
|
||||
await runAppFlowy();
|
||||
},
|
||||
child: BlocBuilder<HistoricalUserBloc, HistoricalUserState>(
|
||||
builder: (context, state) {
|
||||
final text = state.historicalUsers.isEmpty
|
||||
? LocaleKeys.signIn_loginStartWithAnonymous.tr()
|
||||
: LocaleKeys.signIn_continueAnonymousUser.tr();
|
||||
final onTap = state.historicalUsers.isEmpty
|
||||
? () {
|
||||
getIt<KeyValueStorage>().set(KVKeys.loginType, 'local');
|
||||
context
|
||||
.read<SignInBloc>()
|
||||
.add(const SignInEvent.signedInAsGuest());
|
||||
}
|
||||
: () {
|
||||
final bloc = context.read<HistoricalUserBloc>();
|
||||
final user = bloc.state.historicalUsers.first;
|
||||
bloc.add(HistoricalUserEvent.openHistoricalUser(user));
|
||||
};
|
||||
// SignInAnonymousButton in mobile
|
||||
if (isMobile) {
|
||||
return ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
minimumSize: const Size(double.infinity, 56),
|
||||
),
|
||||
onPressed: onTap,
|
||||
child: Text(LocaleKeys.signIn_loginStartWithAnonymous.tr()),
|
||||
);
|
||||
}
|
||||
// SignInAnonymousButton in desktop
|
||||
return SizedBox(
|
||||
height: 48,
|
||||
child: FlowyButton(
|
||||
isSelected: true,
|
||||
disable: signInState.isSubmitting,
|
||||
text: FlowyText.medium(
|
||||
text,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
radius: Corners.s6Border,
|
||||
onTap: onTap,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,219 @@
|
||||
import 'package:appflowy/core/config/kv.dart';
|
||||
import 'package:appflowy/core/config/kv_keys.dart';
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/user/application/sign_in_bloc.dart';
|
||||
import 'package:appflowy/util/platform_extension.dart';
|
||||
import 'package:appflowy/workspace/application/appearance.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/size.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class ThirdPartySignInButtons extends StatelessWidget {
|
||||
/// Used in DesktopSignInScreen and MobileSignInScreen
|
||||
const ThirdPartySignInButtons({
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isMobile = PlatformExtension.isMobile;
|
||||
|
||||
// Get themeMode from AppearanceSettingsCubit
|
||||
// When user changes themeMode, it changes the state in AppearanceSettingsCubit, but the themeMode for the MaterialApp won't change, it only got updated(get value from AppearanceSettingsCubit) when user open the app again. Thus, we should get themeMode from AppearanceSettingsCubit rather than MediaQuery.
|
||||
final isDarkMode =
|
||||
context.read<AppearanceSettingsCubit>().state.themeMode ==
|
||||
ThemeMode.dark;
|
||||
|
||||
if (isMobile) {
|
||||
// ThirdPartySignInButtons in mobile
|
||||
return Column(
|
||||
children: [
|
||||
_ThirdPartySignInButton(
|
||||
isMobile: true,
|
||||
icon: FlowySvgs.google_mark_xl,
|
||||
labelText: LocaleKeys.signIn_LogInWithGoogle.tr(),
|
||||
onPressed: () {
|
||||
_signInWithGoogle(context);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
_ThirdPartySignInButton(
|
||||
isMobile: true,
|
||||
icon: isDarkMode
|
||||
? FlowySvgs.github_mark_white_xl
|
||||
: FlowySvgs.github_mark_black_xl,
|
||||
labelText: LocaleKeys.signIn_LogInWithGithub.tr(),
|
||||
onPressed: () {
|
||||
_signInWithGithub(context);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
_ThirdPartySignInButton(
|
||||
isMobile: true,
|
||||
icon: isDarkMode
|
||||
? FlowySvgs.discord_mark_white_xl
|
||||
: FlowySvgs.discord_mark_blurple_xl,
|
||||
labelText: LocaleKeys.signIn_LogInWithDiscord.tr(),
|
||||
onPressed: () {
|
||||
_signInWithDiscord(context);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
// ThirdPartySignInButtons in desktop
|
||||
return Column(
|
||||
children: [
|
||||
_ThirdPartySignInButton(
|
||||
key: const Key('signInWithGoogleButton'),
|
||||
isMobile: false,
|
||||
icon: FlowySvgs.google_mark_xl,
|
||||
labelText: LocaleKeys.signIn_LogInWithGoogle.tr(),
|
||||
onPressed: () {
|
||||
_signInWithGoogle(context);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
_ThirdPartySignInButton(
|
||||
isMobile: false,
|
||||
icon: isDarkMode
|
||||
? FlowySvgs.github_mark_white_xl
|
||||
: FlowySvgs.github_mark_black_xl,
|
||||
labelText: LocaleKeys.signIn_LogInWithGithub.tr(),
|
||||
onPressed: () {
|
||||
_signInWithGithub(context);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
_ThirdPartySignInButton(
|
||||
isMobile: false,
|
||||
icon: isDarkMode
|
||||
? FlowySvgs.discord_mark_white_xl
|
||||
: FlowySvgs.discord_mark_blurple_xl,
|
||||
labelText: LocaleKeys.signIn_LogInWithDiscord.tr(),
|
||||
onPressed: () {
|
||||
_signInWithDiscord(context);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ThirdPartySignInButton extends StatelessWidget {
|
||||
const _ThirdPartySignInButton({
|
||||
super.key,
|
||||
required this.isMobile,
|
||||
required this.icon,
|
||||
required this.labelText,
|
||||
required this.onPressed,
|
||||
});
|
||||
|
||||
final bool isMobile;
|
||||
final FlowySvgData icon;
|
||||
final String labelText;
|
||||
|
||||
final VoidCallback onPressed;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final style = Theme.of(context);
|
||||
final buttonSize = MediaQuery.of(context).size;
|
||||
if (isMobile) {
|
||||
return SizedBox(
|
||||
width: double.infinity,
|
||||
height: 48,
|
||||
child: OutlinedButton.icon(
|
||||
icon: Container(
|
||||
width: buttonSize.width / 5.5,
|
||||
alignment: Alignment.centerRight,
|
||||
child: SizedBox(
|
||||
width: 24,
|
||||
child: FlowySvg(
|
||||
icon,
|
||||
blendMode: null,
|
||||
),
|
||||
),
|
||||
),
|
||||
label: Container(
|
||||
padding: const EdgeInsets.only(left: 4),
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(labelText),
|
||||
),
|
||||
onPressed: onPressed,
|
||||
),
|
||||
);
|
||||
}
|
||||
return SizedBox(
|
||||
height: 48,
|
||||
width: double.infinity,
|
||||
child: OutlinedButton.icon(
|
||||
// In order to align all the labels vertically in a relatively centered position to the button, we use a fixed width container to wrap the icon(align to the right), then use another container to align the label to left.
|
||||
icon: Container(
|
||||
width: buttonSize.width / 8,
|
||||
alignment: Alignment.centerRight,
|
||||
child: SizedBox(
|
||||
// Some icons are not square, so we just use a fixed width here.
|
||||
width: 24,
|
||||
child: FlowySvg(
|
||||
icon,
|
||||
blendMode: null,
|
||||
),
|
||||
),
|
||||
),
|
||||
label: Container(
|
||||
padding: const EdgeInsets.only(left: 8),
|
||||
alignment: Alignment.centerLeft,
|
||||
child: FlowyText(
|
||||
labelText,
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
style: ButtonStyle(
|
||||
overlayColor: MaterialStateProperty.resolveWith<Color?>(
|
||||
(states) {
|
||||
if (states.contains(MaterialState.hovered)) {
|
||||
return style.colorScheme.onSecondaryContainer;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
shape: MaterialStateProperty.all(
|
||||
const RoundedRectangleBorder(
|
||||
borderRadius: Corners.s6Border,
|
||||
),
|
||||
),
|
||||
side: MaterialStateProperty.all(
|
||||
BorderSide(
|
||||
color: style.dividerColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
onPressed: onPressed,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _signInWithGoogle(BuildContext context) {
|
||||
getIt<KeyValueStorage>().set(KVKeys.loginType, 'supabase');
|
||||
context.read<SignInBloc>().add(
|
||||
const SignInEvent.signedInWithOAuth('google'),
|
||||
);
|
||||
}
|
||||
|
||||
void _signInWithGithub(BuildContext context) {
|
||||
getIt<KeyValueStorage>().set(KVKeys.loginType, 'supabase');
|
||||
context.read<SignInBloc>().add(const SignInEvent.signedInWithOAuth('github'));
|
||||
}
|
||||
|
||||
void _signInWithDiscord(BuildContext context) {
|
||||
getIt<KeyValueStorage>().set(KVKeys.loginType, 'supabase');
|
||||
context
|
||||
.read<SignInBloc>()
|
||||
.add(const SignInEvent.signedInWithOAuth('discord'));
|
||||
}
|
@ -0,0 +1,2 @@
|
||||
export 'sign_in_anonymous_button.dart';
|
||||
export 'third_party_sign_in_buttons.dart';
|
@ -2,7 +2,7 @@ import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/user/application/sign_up_bloc.dart';
|
||||
import 'package:appflowy/user/presentation/router.dart';
|
||||
import 'package:appflowy/user/presentation/widgets/background.dart';
|
||||
import 'package:appflowy/user/presentation/widgets/widgets.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
import 'package:flowy_infra_ui/widget/rounded_button.dart';
|
||||
@ -23,6 +23,7 @@ class SignUpScreen extends StatelessWidget {
|
||||
required this.router,
|
||||
});
|
||||
|
||||
static const routeName = '/SignUpScreen';
|
||||
final AuthRouter router;
|
||||
|
||||
@override
|
||||
@ -46,7 +47,7 @@ class SignUpScreen extends StatelessWidget {
|
||||
Either<UserProfilePB, FlowyError> result,
|
||||
) {
|
||||
result.fold(
|
||||
(user) => router.pushWelcomeScreen(context, user),
|
||||
(user) => router.pushWorkspaceStartScreen(context, user),
|
||||
(error) => showSnapBar(context, error.msg),
|
||||
);
|
||||
}
|
||||
@ -54,8 +55,8 @@ class SignUpScreen extends StatelessWidget {
|
||||
|
||||
class SignUpForm extends StatelessWidget {
|
||||
const SignUpForm({
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -89,8 +90,8 @@ class SignUpForm extends StatelessWidget {
|
||||
|
||||
class SignUpPrompt extends StatelessWidget {
|
||||
const SignUpPrompt({
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -118,8 +119,8 @@ class SignUpPrompt extends StatelessWidget {
|
||||
|
||||
class SignUpButton extends StatelessWidget {
|
||||
const SignUpButton({
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -137,8 +138,8 @@ class SignUpButton extends StatelessWidget {
|
||||
|
||||
class PasswordTextField extends StatelessWidget {
|
||||
const PasswordTextField({
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -170,8 +171,8 @@ class PasswordTextField extends StatelessWidget {
|
||||
|
||||
class RepeatPasswordTextField extends StatelessWidget {
|
||||
const RepeatPasswordTextField({
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -203,8 +204,8 @@ class RepeatPasswordTextField extends StatelessWidget {
|
||||
|
||||
class EmailTextField extends StatelessWidget {
|
||||
const EmailTextField({
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
@ -1,41 +1,35 @@
|
||||
import 'package:appflowy/core/frameless_window.dart';
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/startup/entry_point.dart';
|
||||
import 'package:appflowy/startup/launch_configuration.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/user/application/auth/auth_service.dart';
|
||||
import 'package:appflowy/user/application/historical_user_bloc.dart';
|
||||
import 'package:appflowy/user/presentation/router.dart';
|
||||
import 'package:appflowy/user/presentation/widgets/widgets.dart';
|
||||
import 'package:appflowy/workspace/application/appearance.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/widgets/settings_language_view.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:dartz/dartz.dart' as dartz;
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/language.dart';
|
||||
import 'package:flowy_infra/size.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.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-folder2/protobuf.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
import '../../generated/locale_keys.g.dart';
|
||||
import 'folder/folder_widget.dart';
|
||||
import 'router.dart';
|
||||
import 'widgets/background.dart';
|
||||
|
||||
class SkipLogInScreen extends StatefulWidget {
|
||||
final AuthRouter router;
|
||||
final AuthService authService;
|
||||
static const routeName = '/SkipLogInScreen';
|
||||
|
||||
const SkipLogInScreen({
|
||||
Key? key,
|
||||
super.key,
|
||||
required this.router,
|
||||
required this.authService,
|
||||
}) : super(key: key);
|
||||
});
|
||||
|
||||
@override
|
||||
State<SkipLogInScreen> createState() => _SkipLogInScreenState();
|
||||
@ -98,9 +92,7 @@ class _SkipLogInScreenState extends State<SkipLogInScreen> {
|
||||
Log.error(error);
|
||||
},
|
||||
(user) {
|
||||
FolderEventGetCurrentWorkspace().send().then((result) {
|
||||
_openCurrentWorkspace(context, user, result);
|
||||
});
|
||||
widget.router.pushHomeScreen(context, user);
|
||||
},
|
||||
);
|
||||
}
|
||||
@ -114,22 +106,6 @@ class _SkipLogInScreenState extends State<SkipLogInScreen> {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _openCurrentWorkspace(
|
||||
BuildContext context,
|
||||
UserProfilePB user,
|
||||
dartz.Either<WorkspaceSettingPB, FlowyError> workspacesOrError,
|
||||
) {
|
||||
workspacesOrError.fold(
|
||||
(workspaceSetting) {
|
||||
widget.router
|
||||
.pushHomeScreenWithWorkSpace(context, user, workspaceSetting);
|
||||
},
|
||||
(error) {
|
||||
Log.error(error);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SkipLoginPageFooter extends StatelessWidget {
|
@ -1,22 +1,34 @@
|
||||
import 'package:appflowy/env/env.dart';
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/user/application/auth/auth_service.dart';
|
||||
import 'package:appflowy/user/presentation/sign_in_screen.dart';
|
||||
import 'package:appflowy/user/application/splash_bloc.dart';
|
||||
import 'package:appflowy/user/domain/auth_state.dart';
|
||||
import 'package:appflowy/user/presentation/helpers/helpers.dart';
|
||||
import 'package:appflowy/user/presentation/router.dart';
|
||||
import 'package:appflowy/util/platform_extension.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/splash_bloc.dart';
|
||||
import '../domain/auth_state.dart';
|
||||
import 'router.dart';
|
||||
|
||||
// [[diagram: splash screen]]
|
||||
// ┌────────────────┐1.get user ┌──────────┐ ┌────────────┐ 2.send UserEventCheckUser
|
||||
// │ SplashScreen │──────────▶│SplashBloc│────▶│ISplashUser │─────┐
|
||||
// └────────────────┘ └──────────┘ └────────────┘ │
|
||||
// │
|
||||
// ▼
|
||||
// ┌───────────┐ ┌─────────────┐ ┌────────┐
|
||||
// │HomeScreen │◀───────────│BlocListener │◀────────────────│RustSDK │
|
||||
// └───────────┘ └─────────────┘ └────────┘
|
||||
// 4. Show HomeScreen or SignIn 3.return AuthState
|
||||
class SplashScreen extends StatelessWidget {
|
||||
const SplashScreen({
|
||||
Key? key,
|
||||
super.key,
|
||||
required this.autoRegister,
|
||||
}) : super(key: key);
|
||||
});
|
||||
|
||||
static const routeName = '/SplashScreen';
|
||||
final bool autoRegister;
|
||||
|
||||
@override
|
||||
@ -75,15 +87,13 @@ class SplashScreen extends StatelessWidget {
|
||||
final result = await FolderEventGetCurrentWorkspace().send();
|
||||
result.fold(
|
||||
(workspaceSetting) {
|
||||
getIt<SplashRoute>().pushHomeScreen(
|
||||
getIt<SplashRouter>().pushHomeScreen(
|
||||
context,
|
||||
userProfile,
|
||||
workspaceSetting,
|
||||
);
|
||||
},
|
||||
(error) {
|
||||
handleOpenWorkspaceError(context, error);
|
||||
},
|
||||
(error) => handleOpenWorkspaceError(context, error),
|
||||
);
|
||||
}
|
||||
},
|
||||
@ -94,11 +104,14 @@ class SplashScreen extends StatelessWidget {
|
||||
}
|
||||
|
||||
void _handleUnauthenticated(BuildContext context, Unauthenticated result) {
|
||||
Log.debug(
|
||||
'_handleUnauthenticated -> Supabase is enabled: $isSupabaseEnabled',
|
||||
);
|
||||
// if the env is not configured, we will skip to the 'skip login screen'.
|
||||
if (isSupabaseEnabled) {
|
||||
getIt<SplashRoute>().pushSignInScreen(context);
|
||||
getIt<SplashRouter>().pushSignInScreen(context);
|
||||
} else {
|
||||
getIt<SplashRoute>().pushSkipLoginScreen(context);
|
||||
getIt<SplashRouter>().pushSkipLoginScreen(context);
|
||||
}
|
||||
}
|
||||
|
||||
@ -111,27 +124,41 @@ class SplashScreen extends StatelessWidget {
|
||||
}
|
||||
|
||||
class Body extends StatelessWidget {
|
||||
const Body({Key? key}) : super(key: key);
|
||||
const Body({super.key});
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
alignment: Alignment.center,
|
||||
child: PlatformExtension.isMobile
|
||||
? const FlowySvg(
|
||||
FlowySvgs.flowy_logo_xl,
|
||||
blendMode: null,
|
||||
)
|
||||
: const _DesktopSplashBody(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _DesktopSplashBody extends StatelessWidget {
|
||||
const _DesktopSplashBody();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final size = MediaQuery.of(context).size;
|
||||
|
||||
return Container(
|
||||
alignment: Alignment.center,
|
||||
child: SingleChildScrollView(
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
Image(
|
||||
fit: BoxFit.cover,
|
||||
width: size.width,
|
||||
height: size.height,
|
||||
image:
|
||||
const AssetImage('assets/images/appflowy_launch_splash.jpg'),
|
||||
return SingleChildScrollView(
|
||||
child: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
Image(
|
||||
fit: BoxFit.cover,
|
||||
width: size.width,
|
||||
height: size.height,
|
||||
image: const AssetImage(
|
||||
'assets/images/appflowy_launch_splash.jpg',
|
||||
),
|
||||
const CircularProgressIndicator.adaptive(),
|
||||
],
|
||||
),
|
||||
),
|
||||
const CircularProgressIndicator.adaptive(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
@ -10,9 +10,10 @@ import 'package:flowy_infra_ui/style_widget/snap_bar.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../application/workspace_error_bloc.dart';
|
||||
import '../../application/workspace_error_bloc.dart';
|
||||
|
||||
class WorkspaceErrorScreen extends StatelessWidget {
|
||||
static const routeName = "/WorkspaceErrorScreen";
|
||||
final FlowyError error;
|
||||
final UserFolderPB userFolder;
|
||||
const WorkspaceErrorScreen({
|
@ -0,0 +1,106 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/workspace/application/workspace/prelude.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder2/workspace.pb.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/theme_extension.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flowy_infra_ui/widget/error_page.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class DesktopWorkspaceStartScreen extends StatelessWidget {
|
||||
const DesktopWorkspaceStartScreen({super.key, required this.workspaceState});
|
||||
|
||||
final WorkspaceState workspaceState;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(60.0),
|
||||
child: Column(
|
||||
children: [
|
||||
_renderBody(workspaceState),
|
||||
_renderCreateButton(context),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Widget _renderBody(WorkspaceState state) {
|
||||
final body = state.successOrFailure.fold(
|
||||
(_) => _renderList(state.workspaces),
|
||||
(error) => FlowyErrorPage.message(
|
||||
error.toString(),
|
||||
howToFix: LocaleKeys.errorDialog_howToFixFallback.tr(),
|
||||
),
|
||||
);
|
||||
return body;
|
||||
}
|
||||
|
||||
Widget _renderList(List<WorkspacePB> workspaces) {
|
||||
return Expanded(
|
||||
child: StyledListView(
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
final workspace = workspaces[index];
|
||||
return _WorkspaceItem(
|
||||
workspace: workspace,
|
||||
onPressed: (workspace) => _popToWorkspace(context, workspace),
|
||||
);
|
||||
},
|
||||
itemCount: workspaces.length,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class _WorkspaceItem extends StatelessWidget {
|
||||
final WorkspacePB workspace;
|
||||
final void Function(WorkspacePB workspace) onPressed;
|
||||
const _WorkspaceItem({
|
||||
required this.workspace,
|
||||
required this.onPressed,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
height: 46,
|
||||
child: FlowyTextButton(
|
||||
workspace.name,
|
||||
hoverColor: AFThemeExtension.of(context).lightGreyHover,
|
||||
fontSize: 14,
|
||||
onPressed: () => onPressed(workspace),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Widget _renderCreateButton(BuildContext context) {
|
||||
return SizedBox(
|
||||
width: 200,
|
||||
height: 40,
|
||||
child: FlowyTextButton(
|
||||
LocaleKeys.workspace_create.tr(),
|
||||
fontSize: 14,
|
||||
hoverColor: AFThemeExtension.of(context).lightGreyHover,
|
||||
onPressed: () {
|
||||
// same method as in mobile
|
||||
context.read<WorkspaceBloc>().add(
|
||||
WorkspaceEvent.createWorkspace(
|
||||
LocaleKeys.workspace_hint.tr(),
|
||||
"",
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// same method as in mobile
|
||||
void _popToWorkspace(BuildContext context, WorkspacePB workspace) {
|
||||
context.read<WorkspaceBloc>().add(WorkspaceEvent.openWorkspace(workspace));
|
||||
|
||||
Navigator.of(context).pop(workspace.id);
|
||||
}
|
@ -0,0 +1,144 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/workspace/application/workspace/prelude.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder2/workspace.pb.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flowy_infra_ui/widget/error_page.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
// TODO(yijing): needs refactor when multiple workspaces are supported
|
||||
class MobileWorkspaceStartScreen extends StatefulWidget {
|
||||
const MobileWorkspaceStartScreen({
|
||||
super.key,
|
||||
required this.workspaceState,
|
||||
});
|
||||
|
||||
@override
|
||||
State<MobileWorkspaceStartScreen> createState() =>
|
||||
_MobileWorkspaceStartScreenState();
|
||||
final WorkspaceState workspaceState;
|
||||
}
|
||||
|
||||
class _MobileWorkspaceStartScreenState
|
||||
extends State<MobileWorkspaceStartScreen> {
|
||||
WorkspacePB? selectedWorkspace;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final style = Theme.of(context);
|
||||
final size = MediaQuery.of(context).size;
|
||||
const double spacing = 16.0;
|
||||
final TextEditingController controller = TextEditingController();
|
||||
final List<DropdownMenuEntry<WorkspacePB>> workspaceEntries =
|
||||
<DropdownMenuEntry<WorkspacePB>>[];
|
||||
for (final WorkspacePB workspace in widget.workspaceState.workspaces) {
|
||||
workspaceEntries.add(
|
||||
DropdownMenuEntry<WorkspacePB>(
|
||||
value: workspace,
|
||||
label: workspace.name,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// render the workspace dropdown menu if success, otherwise render error page
|
||||
final body = widget.workspaceState.successOrFailure.fold(
|
||||
(_) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.fromLTRB(50, 0, 50, 30),
|
||||
child: Column(
|
||||
children: [
|
||||
const Spacer(),
|
||||
const FlowySvg(
|
||||
FlowySvgs.flowy_logo_xl,
|
||||
size: Size.square(64),
|
||||
blendMode: null,
|
||||
),
|
||||
const VSpace(spacing * 2),
|
||||
Text(
|
||||
LocaleKeys.workspace_chooseWorkspace.tr(),
|
||||
style: style.textTheme.displaySmall,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const VSpace(spacing * 4),
|
||||
DropdownMenu<WorkspacePB>(
|
||||
width: size.width - 100,
|
||||
// TODO(yijing): The following code cause the bad state error, need to fix it
|
||||
// initialSelection: widget.workspaceState.workspaces.first,
|
||||
label: const Text('Workspace'),
|
||||
controller: controller,
|
||||
dropdownMenuEntries: workspaceEntries,
|
||||
onSelected: (WorkspacePB? workspace) {
|
||||
setState(() {
|
||||
selectedWorkspace = workspace;
|
||||
});
|
||||
},
|
||||
),
|
||||
const Spacer(),
|
||||
// TODO(yijing): needs to implement create workspace in the future
|
||||
// TextButton(
|
||||
// child: Text(
|
||||
// LocaleKeys.workspace_create.tr(),
|
||||
// style: style.textTheme.labelMedium,
|
||||
// textAlign: TextAlign.center,
|
||||
// ),
|
||||
// onPressed: () {
|
||||
// setState(() {
|
||||
// // same method as in desktop
|
||||
// context.read<WorkspaceBloc>().add(
|
||||
// WorkspaceEvent.createWorkspace(
|
||||
// LocaleKeys.workspace_hint.tr(),
|
||||
// "",
|
||||
// ),
|
||||
// );
|
||||
// });
|
||||
// },
|
||||
// ),
|
||||
const VSpace(spacing / 2),
|
||||
ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
minimumSize: const Size(double.infinity, 56),
|
||||
),
|
||||
onPressed: () {
|
||||
if (selectedWorkspace == null) {
|
||||
// If user didn't choose any workspace, pop to the initial workspace(first workspace)
|
||||
_popToWorkspace(
|
||||
context,
|
||||
widget.workspaceState.workspaces.first,
|
||||
);
|
||||
return;
|
||||
}
|
||||
// pop to the selected workspace
|
||||
_popToWorkspace(
|
||||
context,
|
||||
selectedWorkspace!,
|
||||
);
|
||||
},
|
||||
child: Text(LocaleKeys.signUp_getStartedText.tr()),
|
||||
),
|
||||
const VSpace(spacing),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
(error) {
|
||||
return FlowyErrorPage.message(
|
||||
error.toString(),
|
||||
howToFix: LocaleKeys.errorDialog_howToFixFallback.tr(),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
return Scaffold(
|
||||
body: body,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// same method as in desktop
|
||||
void _popToWorkspace(BuildContext context, WorkspacePB workspace) {
|
||||
context.read<WorkspaceBloc>().add(WorkspaceEvent.openWorkspace(workspace));
|
||||
|
||||
Navigator.of(context).pop(workspace.id);
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/user/presentation/screens/workspace_start_screen/desktop_workspace_start_screen.dart';
|
||||
import 'package:appflowy/user/presentation/screens/workspace_start_screen/mobile_workspace_start_screen.dart';
|
||||
import 'package:appflowy/util/platform_extension.dart';
|
||||
import 'package:appflowy/workspace/application/workspace/workspace_bloc.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
// For future use
|
||||
class WorkspaceStartScreen extends StatelessWidget {
|
||||
final UserProfilePB userProfile;
|
||||
static const routeName = "/WorkspaceStartScreen";
|
||||
const WorkspaceStartScreen({
|
||||
super.key,
|
||||
required this.userProfile,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (_) => getIt<WorkspaceBloc>(param1: userProfile)
|
||||
..add(const WorkspaceEvent.initial()),
|
||||
child: BlocBuilder<WorkspaceBloc, WorkspaceState>(
|
||||
builder: (context, state) {
|
||||
if (PlatformExtension.isMobile) {
|
||||
return MobileWorkspaceStartScreen(
|
||||
workspaceState: state,
|
||||
);
|
||||
}
|
||||
return DesktopWorkspaceStartScreen(
|
||||
workspaceState: state,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,491 +0,0 @@
|
||||
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/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/user/application/auth/auth_service.dart';
|
||||
import 'package:appflowy/user/application/historical_user_bloc.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:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/protobuf.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder2/workspace.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/size.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/snap_bar.dart';
|
||||
import 'package:flowy_infra_ui/widget/rounded_button.dart';
|
||||
import 'package:flowy_infra_ui/widget/rounded_input_field.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class SignInScreen extends StatelessWidget {
|
||||
const SignInScreen({
|
||||
super.key,
|
||||
required this.router,
|
||||
});
|
||||
|
||||
final AuthRouter router;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => getIt<SignInBloc>(),
|
||||
child: BlocConsumer<SignInBloc, SignInState>(
|
||||
listener: (context, state) {
|
||||
state.successOrFail.fold(
|
||||
() => null,
|
||||
(result) => _handleSuccessOrFail(result, context),
|
||||
);
|
||||
},
|
||||
builder: (_, __) => Scaffold(
|
||||
appBar: const PreferredSize(
|
||||
preferredSize: Size(double.infinity, 60),
|
||||
child: MoveWindowDetector(),
|
||||
),
|
||||
body: SignInForm(router: router),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _handleSuccessOrFail(
|
||||
Either<UserProfilePB, FlowyError> result,
|
||||
BuildContext context,
|
||||
) {
|
||||
result.fold(
|
||||
(user) {
|
||||
if (user.encryptionType == EncryptionTypePB.Symmetric) {
|
||||
router.pushEncryptionScreen(context, user);
|
||||
} else {
|
||||
router.pushHomeScreen(context, user);
|
||||
}
|
||||
},
|
||||
(error) {
|
||||
handleOpenWorkspaceError(context, error);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void handleOpenWorkspaceError(BuildContext context, FlowyError error) {
|
||||
Log.error(error);
|
||||
switch (error.code) {
|
||||
case ErrorCode.WorkspaceDataNotSync:
|
||||
final userFolder = UserFolderPB.fromBuffer(error.payload);
|
||||
getIt<AuthRouter>().pushWorkspaceErrorScreen(context, userFolder, error);
|
||||
break;
|
||||
case ErrorCode.InvalidEncryptSecret:
|
||||
showSnapBar(
|
||||
context,
|
||||
error.msg,
|
||||
);
|
||||
break;
|
||||
default:
|
||||
showSnapBar(
|
||||
context,
|
||||
error.msg,
|
||||
onClosed: () {
|
||||
getIt<AuthService>().signOut();
|
||||
runAppFlowy();
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SignInForm extends StatelessWidget {
|
||||
const SignInForm({
|
||||
super.key,
|
||||
required this.router,
|
||||
});
|
||||
|
||||
final AuthRouter router;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isSubmitting = context.read<SignInBloc>().state.isSubmitting;
|
||||
const indicatorMinHeight = 4.0;
|
||||
return Align(
|
||||
alignment: Alignment.center,
|
||||
child: AuthFormContainer(
|
||||
children: [
|
||||
// Email.
|
||||
FlowyLogoTitle(
|
||||
title: LocaleKeys.signIn_loginTitle.tr(),
|
||||
logoSize: const Size(60, 60),
|
||||
),
|
||||
const VSpace(30),
|
||||
// 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),
|
||||
const ThirdPartySignInButtons(),
|
||||
const VSpace(20),
|
||||
// loading status
|
||||
...isSubmitting
|
||||
? [
|
||||
const VSpace(indicatorMinHeight),
|
||||
const LinearProgressIndicator(
|
||||
value: null,
|
||||
minHeight: indicatorMinHeight,
|
||||
),
|
||||
]
|
||||
: [
|
||||
const VSpace(indicatorMinHeight * 2.0)
|
||||
], // add the same space when there's no loading status.
|
||||
// ConstrainedBox(
|
||||
// constraints: const BoxConstraints(maxHeight: 140),
|
||||
// child: HistoricalUserList(
|
||||
// didOpenUser: () async {
|
||||
// await FlowyRunner.run(
|
||||
// FlowyApp(),
|
||||
// integrationEnv(),
|
||||
// );
|
||||
// },
|
||||
// ),
|
||||
// ),
|
||||
const VSpace(20),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SignUpPrompt extends StatelessWidget {
|
||||
const SignUpPrompt({
|
||||
Key? key,
|
||||
required this.router,
|
||||
}) : super(key: key);
|
||||
|
||||
final AuthRouter router;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
FlowyText.medium(
|
||||
LocaleKeys.signIn_dontHaveAnAccount.tr(),
|
||||
color: Theme.of(context).hintColor,
|
||||
),
|
||||
TextButton(
|
||||
style: TextButton.styleFrom(
|
||||
textStyle: Theme.of(context).textTheme.bodyMedium,
|
||||
),
|
||||
onPressed: () => router.pushSignUpScreen(context),
|
||||
child: Text(
|
||||
LocaleKeys.signUp_buttonText.tr(),
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.primary),
|
||||
),
|
||||
),
|
||||
ForgetPasswordButton(router: router),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class LoginButton extends StatelessWidget {
|
||||
const LoginButton({
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return RoundedTextButton(
|
||||
title: LocaleKeys.signIn_loginButtonText.tr(),
|
||||
height: 48,
|
||||
borderRadius: Corners.s10Border,
|
||||
onPressed: () => context
|
||||
.read<SignInBloc>()
|
||||
.add(const SignInEvent.signedInWithUserEmailAndPassword()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class SignInAsGuestButton extends StatelessWidget {
|
||||
const SignInAsGuestButton({
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<SignInBloc, SignInState>(
|
||||
builder: (context, signInState) {
|
||||
return BlocProvider(
|
||||
create: (context) => HistoricalUserBloc()
|
||||
..add(
|
||||
const HistoricalUserEvent.initial(),
|
||||
),
|
||||
child: BlocListener<HistoricalUserBloc, HistoricalUserState>(
|
||||
listenWhen: (previous, current) =>
|
||||
previous.openedHistoricalUser != current.openedHistoricalUser,
|
||||
listener: (context, state) async {
|
||||
await runAppFlowy();
|
||||
},
|
||||
child: BlocBuilder<HistoricalUserBloc, HistoricalUserState>(
|
||||
builder: (context, state) {
|
||||
final text = state.historicalUsers.isEmpty
|
||||
? LocaleKeys.signIn_loginAsGuestButtonText.tr()
|
||||
: LocaleKeys.signIn_continueAnonymousUser.tr();
|
||||
|
||||
final onTap = state.historicalUsers.isEmpty
|
||||
? () {
|
||||
getIt<KeyValueStorage>().set(KVKeys.loginType, 'local');
|
||||
context
|
||||
.read<SignInBloc>()
|
||||
.add(const SignInEvent.signedInAsGuest());
|
||||
}
|
||||
: () {
|
||||
final bloc = context.read<HistoricalUserBloc>();
|
||||
final user = bloc.state.historicalUsers.first;
|
||||
bloc.add(HistoricalUserEvent.openHistoricalUser(user));
|
||||
};
|
||||
|
||||
return SizedBox(
|
||||
height: 48,
|
||||
child: FlowyButton(
|
||||
isSelected: true,
|
||||
disable: signInState.isSubmitting,
|
||||
text: FlowyText.medium(
|
||||
text,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
radius: Corners.s6Border,
|
||||
onTap: onTap,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ForgetPasswordButton extends StatelessWidget {
|
||||
const ForgetPasswordButton({
|
||||
Key? key,
|
||||
required this.router,
|
||||
}) : super(key: key);
|
||||
|
||||
final AuthRouter router;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return TextButton(
|
||||
style: TextButton.styleFrom(
|
||||
textStyle: Theme.of(context).textTheme.bodyMedium,
|
||||
),
|
||||
onPressed: () {
|
||||
throw UnimplementedError();
|
||||
},
|
||||
child: Text(
|
||||
LocaleKeys.signIn_forgotPassword.tr(),
|
||||
style: TextStyle(color: Theme.of(context).colorScheme.primary),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class PasswordTextField extends StatelessWidget {
|
||||
const PasswordTextField({
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<SignInBloc, SignInState>(
|
||||
buildWhen: (previous, current) =>
|
||||
previous.passwordError != current.passwordError,
|
||||
builder: (context, state) {
|
||||
return RoundedInputField(
|
||||
obscureText: true,
|
||||
obscureIcon: const FlowySvg(FlowySvgs.hide_m),
|
||||
obscureHideIcon: const FlowySvg(FlowySvgs.show_m),
|
||||
hintText: LocaleKeys.signIn_passwordHint.tr(),
|
||||
errorText: context
|
||||
.read<SignInBloc>()
|
||||
.state
|
||||
.passwordError
|
||||
.fold(() => "", (error) => error),
|
||||
onChanged: (value) => context
|
||||
.read<SignInBloc>()
|
||||
.add(SignInEvent.passwordChanged(value)),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class EmailTextField extends StatelessWidget {
|
||||
const EmailTextField({
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<SignInBloc, SignInState>(
|
||||
buildWhen: (previous, current) =>
|
||||
previous.emailError != current.emailError,
|
||||
builder: (context, state) {
|
||||
return RoundedInputField(
|
||||
hintText: LocaleKeys.signIn_emailHint.tr(),
|
||||
errorText: context
|
||||
.read<SignInBloc>()
|
||||
.state
|
||||
.emailError
|
||||
.fold(() => "", (error) => error),
|
||||
onChanged: (value) =>
|
||||
context.read<SignInBloc>().add(SignInEvent.emailChanged(value)),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class OrContinueWith extends StatelessWidget {
|
||||
const OrContinueWith({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
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 FlowySvgData 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: FlowySvg(
|
||||
icon,
|
||||
blendMode: null,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ThirdPartySignInButtons extends StatelessWidget {
|
||||
final MainAxisAlignment mainAxisAlignment;
|
||||
const ThirdPartySignInButtons({
|
||||
this.mainAxisAlignment = MainAxisAlignment.center,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
mainAxisAlignment: mainAxisAlignment,
|
||||
children: const [
|
||||
GoogleSignUpButton(),
|
||||
SizedBox(width: 20),
|
||||
GitHubSignUpButton(),
|
||||
SizedBox(width: 20),
|
||||
DiscordSignUpButton(),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class GoogleSignUpButton extends StatelessWidget {
|
||||
const GoogleSignUpButton({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ThirdPartySignInButton(
|
||||
icon: FlowySvgs.google_mark_xl,
|
||||
onPressed: () {
|
||||
getIt<KeyValueStorage>().set(KVKeys.loginType, 'supabase');
|
||||
context.read<SignInBloc>().add(
|
||||
const SignInEvent.signedInWithOAuth('google'),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class GitHubSignUpButton extends StatelessWidget {
|
||||
const GitHubSignUpButton({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ThirdPartySignInButton(
|
||||
icon: FlowySvgs.github_mark_s,
|
||||
onPressed: () {
|
||||
getIt<KeyValueStorage>().set(KVKeys.loginType, 'supabase');
|
||||
context
|
||||
.read<SignInBloc>()
|
||||
.add(const SignInEvent.signedInWithOAuth('github'));
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class DiscordSignUpButton extends StatelessWidget {
|
||||
const DiscordSignUpButton({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ThirdPartySignInButton(
|
||||
icon: FlowySvgs.discord_mark_s,
|
||||
onPressed: () {
|
||||
getIt<KeyValueStorage>().set(KVKeys.loginType, 'supabase');
|
||||
context
|
||||
.read<SignInBloc>()
|
||||
.add(const SignInEvent.signedInWithOAuth('discord'));
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
@ -1,115 +0,0 @@
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/workspace/application/workspace/welcome_bloc.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/theme_extension.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/button.dart';
|
||||
import 'package:flowy_infra_ui/widget/error_page.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder2/workspace.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
|
||||
class WelcomeScreen extends StatelessWidget {
|
||||
final UserProfilePB userProfile;
|
||||
const WelcomeScreen({
|
||||
Key? key,
|
||||
required this.userProfile,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (_) => getIt<WelcomeBloc>(param1: userProfile)
|
||||
..add(const WelcomeEvent.initial()),
|
||||
child: BlocBuilder<WelcomeBloc, WelcomeState>(
|
||||
builder: (context, state) {
|
||||
return Scaffold(
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(60.0),
|
||||
child: Column(
|
||||
children: [
|
||||
_renderBody(state),
|
||||
_renderCreateButton(context),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _renderBody(WelcomeState state) {
|
||||
final body = state.successOrFailure.fold(
|
||||
(_) => _renderList(state.workspaces),
|
||||
(error) => FlowyErrorPage.message(error.toString(), howToFix: LocaleKeys.errorDialog_howToFixFallback.tr(),),
|
||||
);
|
||||
return body;
|
||||
}
|
||||
|
||||
Widget _renderCreateButton(BuildContext context) {
|
||||
return SizedBox(
|
||||
width: 200,
|
||||
height: 40,
|
||||
child: FlowyTextButton(
|
||||
LocaleKeys.workspace_create.tr(),
|
||||
fontSize: 14,
|
||||
hoverColor: AFThemeExtension.of(context).lightGreyHover,
|
||||
onPressed: () {
|
||||
context.read<WelcomeBloc>().add(
|
||||
WelcomeEvent.createWorkspace(
|
||||
LocaleKeys.workspace_hint.tr(),
|
||||
"",
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _renderList(List<WorkspacePB> workspaces) {
|
||||
return Expanded(
|
||||
child: StyledListView(
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
final workspace = workspaces[index];
|
||||
return WorkspaceItem(
|
||||
workspace: workspace,
|
||||
onPressed: (workspace) => _handleOnPress(context, workspace),
|
||||
);
|
||||
},
|
||||
itemCount: workspaces.length,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _handleOnPress(BuildContext context, WorkspacePB workspace) {
|
||||
context.read<WelcomeBloc>().add(WelcomeEvent.openWorkspace(workspace));
|
||||
|
||||
Navigator.of(context).pop(workspace.id);
|
||||
}
|
||||
}
|
||||
|
||||
class WorkspaceItem extends StatelessWidget {
|
||||
final WorkspacePB workspace;
|
||||
final void Function(WorkspacePB workspace) onPressed;
|
||||
const WorkspaceItem({
|
||||
Key? key,
|
||||
required this.workspace,
|
||||
required this.onPressed,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
height: 46,
|
||||
child: FlowyTextButton(
|
||||
workspace.name,
|
||||
hoverColor: AFThemeExtension.of(context).lightGreyHover,
|
||||
fontSize: 14,
|
||||
onPressed: () => onPressed(workspace),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class AuthFormContainer extends StatelessWidget {
|
||||
final List<Widget> children;
|
||||
const AuthFormContainer({
|
||||
super.key,
|
||||
required this.children,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final size = MediaQuery.of(context).size;
|
||||
return SizedBox(
|
||||
width: min(size.width, 340),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: children,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,40 +1,17 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:flowy_infra/size.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
|
||||
class AuthFormContainer extends StatelessWidget {
|
||||
final List<Widget> children;
|
||||
const AuthFormContainer({
|
||||
Key? key,
|
||||
required this.children,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final size = MediaQuery.of(context).size;
|
||||
return SizedBox(
|
||||
width: min(size.width, 340),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: children,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class FlowyLogoTitle extends StatelessWidget {
|
||||
final String title;
|
||||
final Size logoSize;
|
||||
const FlowyLogoTitle({
|
||||
Key? key,
|
||||
super.key,
|
||||
required this.title,
|
||||
this.logoSize = const Size.square(40),
|
||||
}) : super(key: key);
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
@ -102,10 +102,10 @@ class FolderOptionsWidget extends StatelessWidget {
|
||||
|
||||
class CreateFolderWidget extends StatefulWidget {
|
||||
const CreateFolderWidget({
|
||||
Key? key,
|
||||
super.key,
|
||||
required this.onPressedBack,
|
||||
required this.onPressedCreate,
|
||||
}) : super(key: key);
|
||||
});
|
||||
|
||||
final VoidCallback onPressedBack;
|
||||
final Future<void> Function() onPressedCreate;
|
@ -0,0 +1,3 @@
|
||||
export 'folder_widget.dart';
|
||||
export 'flowy_logo_title.dart';
|
||||
export 'auth_form_container.dart';
|
11
frontend/appflowy_flutter/lib/util/platform_extension.dart
Normal file
@ -0,0 +1,11 @@
|
||||
import 'dart:io';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
extension PlatformExtension on Platform {
|
||||
static bool get isMobile {
|
||||
if (kIsWeb) {
|
||||
return false;
|
||||
}
|
||||
return Platform.isAndroid || Platform.isIOS;
|
||||
}
|
||||
}
|
@ -1,7 +1,9 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:appflowy/user/application/user_settings_service.dart';
|
||||
import 'package:appflowy/util/platform_extension.dart';
|
||||
import 'package:appflowy/workspace/application/appearance_defaults.dart';
|
||||
import 'package:appflowy/workspace/application/mobile_theme_data.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/user_setting.pb.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
@ -281,7 +283,15 @@ class AppearanceSettingsState with _$AppearanceSettingsState {
|
||||
MaterialState.dragged,
|
||||
};
|
||||
|
||||
return ThemeData(
|
||||
if (PlatformExtension.isMobile) {
|
||||
// Mobile version has only one theme(light mode) for now.
|
||||
// The desktop theme and the mobile theme are independent.
|
||||
final mobileThemeData = getMobileThemeData();
|
||||
return mobileThemeData;
|
||||
}
|
||||
|
||||
// Due to Desktop version has multiple themes, it relies on the current theme to build the ThemeData
|
||||
final desktopThemeData = ThemeData(
|
||||
brightness: brightness,
|
||||
dialogBackgroundColor: theme.surface,
|
||||
textTheme: _getTextTheme(fontFamily: fontFamily, fontColor: theme.text),
|
||||
@ -372,6 +382,7 @@ class AppearanceSettingsState with _$AppearanceSettingsState {
|
||||
)
|
||||
],
|
||||
);
|
||||
return desktopThemeData;
|
||||
}
|
||||
|
||||
TextStyle _getFontStyle({
|
||||
|
@ -13,9 +13,9 @@ class HomeBloc extends Bloc<HomeEvent, HomeState> {
|
||||
final UserWorkspaceListener _workspaceListener;
|
||||
|
||||
HomeBloc(
|
||||
UserProfilePB user,
|
||||
UserProfilePB userProfile,
|
||||
WorkspaceSettingPB workspaceSetting,
|
||||
) : _workspaceListener = UserWorkspaceListener(userProfile: user),
|
||||
) : _workspaceListener = UserWorkspaceListener(userProfile: userProfile),
|
||||
super(HomeState.initial(workspaceSetting)) {
|
||||
on<HomeEvent>(
|
||||
(event, emit) async {
|
||||
|
@ -0,0 +1,136 @@
|
||||
// ThemeData in mobile
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
ThemeData getMobileThemeData() {
|
||||
const mobileColorTheme = ColorScheme(
|
||||
brightness: Brightness.light,
|
||||
primary: Color(0xFF2DA2F6), //primary 100
|
||||
onPrimary: Colors.white,
|
||||
// TODO(yijing): add color later
|
||||
secondary: Colors.white,
|
||||
onSecondary: Colors.white,
|
||||
error: Color(0xffFB006D),
|
||||
onError: Color(0xffFB006D),
|
||||
background: Colors.white,
|
||||
onBackground: Color(0xff2F3030), // title text
|
||||
outline: Color(0xffBDC0C5), //caption
|
||||
//Snack bar
|
||||
surface: Colors.white,
|
||||
onSurface: Color(0xff2F3030), // title text
|
||||
);
|
||||
return ThemeData(
|
||||
// color
|
||||
primaryColor: mobileColorTheme.primary, //primary 100
|
||||
primaryColorLight: const Color(0xFF57B5F8), //primary 80
|
||||
dividerColor: mobileColorTheme.outline, //caption
|
||||
scaffoldBackgroundColor: Colors.white,
|
||||
// button
|
||||
elevatedButtonTheme: ElevatedButtonThemeData(
|
||||
style: ButtonStyle(
|
||||
elevation: MaterialStateProperty.all(0),
|
||||
shadowColor: MaterialStateProperty.all(null),
|
||||
backgroundColor: MaterialStateProperty.resolveWith<Color>(
|
||||
(Set<MaterialState> states) {
|
||||
if (states.contains(MaterialState.disabled)) {
|
||||
return const Color(0xFF57B5F8);
|
||||
}
|
||||
return mobileColorTheme.primary;
|
||||
},
|
||||
),
|
||||
foregroundColor: MaterialStateProperty.all(Colors.white),
|
||||
),
|
||||
),
|
||||
outlinedButtonTheme: OutlinedButtonThemeData(
|
||||
style: ButtonStyle(
|
||||
foregroundColor: MaterialStateProperty.all(
|
||||
mobileColorTheme.onBackground,
|
||||
),
|
||||
backgroundColor: MaterialStateProperty.all(Colors.white),
|
||||
shape: MaterialStateProperty.all(
|
||||
RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
),
|
||||
side: MaterialStateProperty.all(
|
||||
BorderSide(
|
||||
color: mobileColorTheme.outline,
|
||||
width: 0.5,
|
||||
),
|
||||
),
|
||||
padding: MaterialStateProperty.all(
|
||||
const EdgeInsets.symmetric(horizontal: 16),
|
||||
),
|
||||
// splash color
|
||||
overlayColor: MaterialStateProperty.all(
|
||||
Colors.grey[100],
|
||||
),
|
||||
),
|
||||
),
|
||||
// text
|
||||
fontFamily: 'Poppins',
|
||||
textTheme: const TextTheme(
|
||||
displayLarge: TextStyle(
|
||||
color: Color(0xFF57B5F8),
|
||||
fontSize: 32,
|
||||
fontFamily: 'Poppins',
|
||||
fontWeight: FontWeight.w700,
|
||||
height: 1.20,
|
||||
letterSpacing: 0.16,
|
||||
),
|
||||
displayMedium: TextStyle(
|
||||
color: Color(0xff2F3030),
|
||||
fontSize: 32,
|
||||
fontWeight: FontWeight.w600,
|
||||
height: 1.20,
|
||||
letterSpacing: 0.16,
|
||||
),
|
||||
// H1 Semi 26
|
||||
displaySmall: TextStyle(
|
||||
color: Color(0xFF2F3030),
|
||||
fontSize: 26,
|
||||
fontWeight: FontWeight.w600,
|
||||
height: 1.10,
|
||||
letterSpacing: 0.13,
|
||||
),
|
||||
// body2 14 Regular
|
||||
bodyMedium: TextStyle(
|
||||
color: Color(0xFFC5C7CB),
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w400,
|
||||
height: 1.20,
|
||||
letterSpacing: 0.07,
|
||||
),
|
||||
// blue text button
|
||||
labelMedium: TextStyle(
|
||||
color: Color(0xFF2DA2F6),
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
height: 1.20,
|
||||
),
|
||||
),
|
||||
inputDecorationTheme: InputDecorationTheme(
|
||||
focusedBorder: const OutlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
width: 2,
|
||||
color: Color(0xFF2DA2F6), //primary 100
|
||||
),
|
||||
borderRadius: BorderRadius.all(Radius.circular(6)),
|
||||
),
|
||||
focusedErrorBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(color: mobileColorTheme.error),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(6)),
|
||||
),
|
||||
errorBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(color: mobileColorTheme.error),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(6)),
|
||||
),
|
||||
enabledBorder: const OutlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: Color(0xffBDC0C5), //caption
|
||||
),
|
||||
borderRadius: BorderRadius.all(Radius.circular(6)),
|
||||
),
|
||||
),
|
||||
colorScheme: mobileColorTheme,
|
||||
);
|
||||
}
|
@ -1,3 +1,3 @@
|
||||
export 'welcome_bloc.dart';
|
||||
export 'workspace_bloc.dart';
|
||||
export 'workspace_listener.dart';
|
||||
export 'workspace_service.dart';
|
||||
|
@ -6,12 +6,14 @@ import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
|
||||
part 'welcome_bloc.freezed.dart';
|
||||
part 'workspace_bloc.freezed.dart';
|
||||
|
||||
class WelcomeBloc extends Bloc<WelcomeEvent, WelcomeState> {
|
||||
class WorkspaceBloc extends Bloc<WorkspaceEvent, WorkspaceState> {
|
||||
final UserBackendService userService;
|
||||
WelcomeBloc({required this.userService}) : super(WelcomeState.initial()) {
|
||||
on<WelcomeEvent>(
|
||||
WorkspaceBloc({
|
||||
required this.userService,
|
||||
}) : super(WorkspaceState.initial()) {
|
||||
on<WorkspaceEvent>(
|
||||
(event, emit) async {
|
||||
await event.map(
|
||||
initial: (e) async {
|
||||
@ -39,7 +41,7 @@ class WelcomeBloc extends Bloc<WelcomeEvent, WelcomeState> {
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _fetchWorkspaces(Emitter<WelcomeState> emit) async {
|
||||
Future<void> _fetchWorkspaces(Emitter<WorkspaceState> emit) async {
|
||||
final workspacesOrFailed = await userService.getWorkspaces();
|
||||
emit(
|
||||
workspacesOrFailed.fold(
|
||||
@ -57,7 +59,7 @@ class WelcomeBloc extends Bloc<WelcomeEvent, WelcomeState> {
|
||||
|
||||
Future<void> _openWorkspace(
|
||||
WorkspacePB workspace,
|
||||
Emitter<WelcomeState> emit,
|
||||
Emitter<WorkspaceState> emit,
|
||||
) async {
|
||||
final result = await userService.openWorkspace(workspace.id);
|
||||
emit(
|
||||
@ -74,7 +76,7 @@ class WelcomeBloc extends Bloc<WelcomeEvent, WelcomeState> {
|
||||
Future<void> _createWorkspace(
|
||||
String name,
|
||||
String desc,
|
||||
Emitter<WelcomeState> emit,
|
||||
Emitter<WorkspaceState> emit,
|
||||
) async {
|
||||
final result = await userService.createWorkspace(name, desc);
|
||||
emit(
|
||||
@ -92,27 +94,26 @@ class WelcomeBloc extends Bloc<WelcomeEvent, WelcomeState> {
|
||||
}
|
||||
|
||||
@freezed
|
||||
class WelcomeEvent with _$WelcomeEvent {
|
||||
const factory WelcomeEvent.initial() = Initial;
|
||||
// const factory WelcomeEvent.fetchWorkspaces() = FetchWorkspace;
|
||||
const factory WelcomeEvent.createWorkspace(String name, String desc) =
|
||||
class WorkspaceEvent with _$WorkspaceEvent {
|
||||
const factory WorkspaceEvent.initial() = Initial;
|
||||
const factory WorkspaceEvent.createWorkspace(String name, String desc) =
|
||||
CreateWorkspace;
|
||||
const factory WelcomeEvent.openWorkspace(WorkspacePB workspace) =
|
||||
const factory WorkspaceEvent.openWorkspace(WorkspacePB workspace) =
|
||||
OpenWorkspace;
|
||||
const factory WelcomeEvent.workspacesReveived(
|
||||
const factory WorkspaceEvent.workspacesReveived(
|
||||
Either<List<WorkspacePB>, FlowyError> workspacesOrFail,
|
||||
) = WorkspacesReceived;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class WelcomeState with _$WelcomeState {
|
||||
const factory WelcomeState({
|
||||
class WorkspaceState with _$WorkspaceState {
|
||||
const factory WorkspaceState({
|
||||
required bool isLoading,
|
||||
required List<WorkspacePB> workspaces,
|
||||
required Either<Unit, FlowyError> successOrFailure,
|
||||
}) = _WelcomeState;
|
||||
}) = _WorkspaceState;
|
||||
|
||||
factory WelcomeState.initial() => WelcomeState(
|
||||
factory WorkspaceState.initial() => WorkspaceState(
|
||||
isLoading: false,
|
||||
workspaces: List.empty(),
|
||||
successOrFailure: left(unit),
|
@ -25,17 +25,21 @@ import '../widgets/edit_panel/edit_panel.dart';
|
||||
import 'home_layout.dart';
|
||||
import 'home_stack.dart';
|
||||
|
||||
class HomeScreen extends StatefulWidget {
|
||||
final UserProfilePB user;
|
||||
class DesktopHomeScreen extends StatefulWidget {
|
||||
static const routeName = '/DesktopHomeScreen';
|
||||
final UserProfilePB userProfile;
|
||||
final WorkspaceSettingPB workspaceSetting;
|
||||
const HomeScreen(this.user, this.workspaceSetting, {Key? key})
|
||||
: super(key: key);
|
||||
const DesktopHomeScreen({
|
||||
super.key,
|
||||
required this.userProfile,
|
||||
required this.workspaceSetting,
|
||||
});
|
||||
|
||||
@override
|
||||
State<HomeScreen> createState() => _HomeScreenState();
|
||||
State<DesktopHomeScreen> createState() => _DesktopHomeScreenState();
|
||||
}
|
||||
|
||||
class _HomeScreenState extends State<HomeScreen> {
|
||||
class _DesktopHomeScreenState extends State<DesktopHomeScreen> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MultiBlocProvider(
|
||||
@ -43,14 +47,14 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||
BlocProvider<TabsBloc>.value(value: getIt<TabsBloc>()),
|
||||
BlocProvider<HomeBloc>(
|
||||
create: (context) {
|
||||
return HomeBloc(widget.user, widget.workspaceSetting)
|
||||
return HomeBloc(widget.userProfile, widget.workspaceSetting)
|
||||
..add(const HomeEvent.initial());
|
||||
},
|
||||
),
|
||||
BlocProvider<HomeSettingBloc>(
|
||||
create: (context) {
|
||||
return HomeSettingBloc(
|
||||
widget.user,
|
||||
widget.userProfile,
|
||||
widget.workspaceSetting,
|
||||
context.read<AppearanceSettingsCubit>(),
|
||||
)..add(const HomeSettingEvent.initial());
|
||||
@ -104,7 +108,7 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||
final layout = HomeLayout(context, constraints);
|
||||
final homeStack = HomeStack(
|
||||
layout: layout,
|
||||
delegate: HomeScreenStackAdaptor(
|
||||
delegate: DesktopHomeScreenStackAdaptor(
|
||||
buildContext: context,
|
||||
),
|
||||
);
|
||||
@ -136,7 +140,7 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||
}) {
|
||||
final workspaceSetting = widget.workspaceSetting;
|
||||
final homeMenu = HomeSideBar(
|
||||
user: widget.user,
|
||||
user: widget.userProfile,
|
||||
workspaceSetting: workspaceSetting,
|
||||
);
|
||||
return FocusTraversalGroup(child: RepaintBoundary(child: homeMenu));
|
||||
@ -255,10 +259,10 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||
}
|
||||
}
|
||||
|
||||
class HomeScreenStackAdaptor extends HomeStackDelegate {
|
||||
class DesktopHomeScreenStackAdaptor extends HomeStackDelegate {
|
||||
final BuildContext buildContext;
|
||||
|
||||
HomeScreenStackAdaptor({
|
||||
DesktopHomeScreenStackAdaptor({
|
||||
required this.buildContext,
|
||||
});
|
||||
|
||||
|
@ -30,8 +30,8 @@ class HomeStack extends StatelessWidget {
|
||||
const HomeStack({
|
||||
required this.delegate,
|
||||
required this.layout,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -108,13 +108,13 @@ class FadingIndexedStack extends StatefulWidget {
|
||||
final Duration duration;
|
||||
|
||||
const FadingIndexedStack({
|
||||
Key? key,
|
||||
super.key,
|
||||
required this.index,
|
||||
required this.children,
|
||||
this.duration = const Duration(
|
||||
milliseconds: 250,
|
||||
),
|
||||
}) : super(key: key);
|
||||
});
|
||||
|
||||
@override
|
||||
FadingIndexedStackState createState() => FadingIndexedStackState();
|
||||
@ -253,7 +253,7 @@ class PageManager {
|
||||
}
|
||||
|
||||
class HomeTopBar extends StatelessWidget {
|
||||
const HomeTopBar({Key? key, required this.layout}) : super(key: key);
|
||||
const HomeTopBar({super.key, required this.layout});
|
||||
|
||||
final HomeLayout layout;
|
||||
|
||||
|
@ -145,7 +145,7 @@ class NaviItemWidget extends StatelessWidget {
|
||||
|
||||
class NaviItemDivider extends StatelessWidget {
|
||||
final Widget child;
|
||||
const NaviItemDivider({Key? key, required this.child}) : super(key: key);
|
||||
const NaviItemDivider({super.key, required this.child});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
@ -2,7 +2,7 @@ import 'package:appflowy/generated/locale_keys.g.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/sign_in_screen.dart';
|
||||
import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/widgets.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
@ -55,9 +55,7 @@ class SettingThirdPartyLogin extends StatelessWidget {
|
||||
const VSpace(6),
|
||||
promptMessage,
|
||||
const VSpace(6),
|
||||
const ThirdPartySignInButtons(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
),
|
||||
const ThirdPartySignInButtons(),
|
||||
const VSpace(6),
|
||||
],
|
||||
);
|
||||
|
1
frontend/resources/flowy_icons/40x/discord_mark_blurple.svg
Executable file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 127.14 96.36"><path class="cls-1" d="M107.7,8.07A105.15,105.15,0,0,0,81.47,0a72.06,72.06,0,0,0-3.36,6.83A97.68,97.68,0,0,0,49,6.83,72.37,72.37,0,0,0,45.64,0,105.89,105.89,0,0,0,19.39,8.09C2.79,32.65-1.71,56.6.54,80.21h0A105.73,105.73,0,0,0,32.71,96.36,77.7,77.7,0,0,0,39.6,85.25a68.42,68.42,0,0,1-10.85-5.18c.91-.66,1.8-1.34,2.66-2a75.57,75.57,0,0,0,64.32,0c.87.71,1.76,1.39,2.66,2a68.68,68.68,0,0,1-10.87,5.19,77,77,0,0,0,6.89,11.1A105.25,105.25,0,0,0,126.6,80.22h0C129.24,52.84,122.09,29.11,107.7,8.07ZM42.45,65.69C36.18,65.69,31,60,31,53s5-12.74,11.43-12.74S54,46,53.89,53,48.84,65.69,42.45,65.69Zm42.24,0C78.41,65.69,73.25,60,73.25,53s5-12.74,11.44-12.74S96.23,46,96.12,53,91.08,65.69,84.69,65.69Z" fill="#5865f2"/></svg>
|
After Width: | Height: | Size: 778 B |
1
frontend/resources/flowy_icons/40x/discord_mark_white.svg
Executable file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 127.14 96.36"><path class="cls-1" d="M107.7,8.07A105.15,105.15,0,0,0,81.47,0a72.06,72.06,0,0,0-3.36,6.83A97.68,97.68,0,0,0,49,6.83,72.37,72.37,0,0,0,45.64,0,105.89,105.89,0,0,0,19.39,8.09C2.79,32.65-1.71,56.6.54,80.21h0A105.73,105.73,0,0,0,32.71,96.36,77.7,77.7,0,0,0,39.6,85.25a68.42,68.42,0,0,1-10.85-5.18c.91-.66,1.8-1.34,2.66-2a75.57,75.57,0,0,0,64.32,0c.87.71,1.76,1.39,2.66,2a68.68,68.68,0,0,1-10.87,5.19,77,77,0,0,0,6.89,11.1A105.25,105.25,0,0,0,126.6,80.22h0C129.24,52.84,122.09,29.11,107.7,8.07ZM42.45,65.69C36.18,65.69,31,60,31,53s5-12.74,11.43-12.74S54,46,53.89,53,48.84,65.69,42.45,65.69Zm42.24,0C78.41,65.69,73.25,60,73.25,53s5-12.74,11.44-12.74S96.23,46,96.12,53,91.08,65.69,84.69,65.69Z" fill="#fff"/></svg>
|
After Width: | Height: | Size: 775 B |
1
frontend/resources/flowy_icons/40x/github-mark-black.svg
Normal file
@ -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="#24292f"/></svg>
|
After Width: | Height: | Size: 963 B |
1
frontend/resources/flowy_icons/40x/github-mark-white.svg
Normal file
@ -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 |
@ -1,15 +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>
|
||||
<svg xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="5 5 38 38"> <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>
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.3 KiB |
@ -32,8 +32,8 @@
|
||||
"signIn": {
|
||||
"loginTitle": "Login to @:appName",
|
||||
"loginButtonText": "Login",
|
||||
"loginAsGuestButtonText": "Get Started",
|
||||
"continueAnonymousUser": "Continue in an anonymous session",
|
||||
"loginStartWithAnonymous": "Start with an anonymous session",
|
||||
"continueAnonymousUser": "Continue with an anonymous session",
|
||||
"buttonText": "Sign In",
|
||||
"forgotPassword": "Forgot Password?",
|
||||
"emailHint": "Email",
|
||||
@ -42,9 +42,14 @@
|
||||
"repeatPasswordEmptyError": "Repeat password can't be empty",
|
||||
"unmatchedPasswordError": "Repeat password is not the same as password",
|
||||
"syncPromptMessage": "Syncing the data might take a while. Please don't close this page",
|
||||
"or": "OR",
|
||||
"LogInWithGoogle": "Log in with Google",
|
||||
"LogInWithGithub": "Log in with Github",
|
||||
"LogInWithDiscord": "Log in with Discord",
|
||||
"signInWith": "Sign in with:"
|
||||
},
|
||||
"workspace": {
|
||||
"chooseWorkspace": "Choose your workspace",
|
||||
"create": "Create workspace",
|
||||
"reset": "Reset workspace",
|
||||
"resetWorkspacePrompt": "Resetting the workspace will delete all pages and data within it. Are you sure you want to reset the workspace? Alternatively, you can contact the support team to restore the workspace",
|
||||
|