mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2025-07-25 11:02:32 +00:00
fix: launch review issues 0.5.5 (#5162)
* fix: remove doubel tap to rename * fix: keep showing the magic link toast * feat: display workspace name instead of workspace * feat: set the keyboard type of magic link textfield to email_address * feat: support switching sign in and sign up * fix: magic link ui design * fix: improve sign in error toast * fix: improve image load failed
This commit is contained in:
@ -68,5 +68,6 @@ class KVKeys {
|
|||||||
/// The key for saving the last opened workspace id
|
/// The key for saving the last opened workspace id
|
||||||
///
|
///
|
||||||
/// The workspace id is a string.
|
/// The workspace id is a string.
|
||||||
|
@Deprecated('deprecated in version 0.5.5')
|
||||||
static const String lastOpenedWorkspaceId = 'lastOpenedWorkspaceId';
|
static const String lastOpenedWorkspaceId = 'lastOpenedWorkspaceId';
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,6 @@ import 'package:appflowy/mobile/presentation/widgets/show_flowy_mobile_confirm_d
|
|||||||
import 'package:appflowy/startup/startup.dart';
|
import 'package:appflowy/startup/startup.dart';
|
||||||
import 'package:appflowy/user/application/auth/auth_service.dart';
|
import 'package:appflowy/user/application/auth/auth_service.dart';
|
||||||
import 'package:appflowy/user/application/sign_in_bloc.dart';
|
import 'package:appflowy/user/application/sign_in_bloc.dart';
|
||||||
import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/sign_in_or_logout_button.dart';
|
|
||||||
import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/widgets.dart';
|
import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/widgets.dart';
|
||||||
import 'package:appflowy_backend/log.dart';
|
import 'package:appflowy_backend/log.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||||
import 'package:appflowy/plugins/document/application/prelude.dart';
|
import 'package:appflowy/plugins/document/application/prelude.dart';
|
||||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/custom_image_block_component.dart';
|
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/custom_image_block_component.dart';
|
||||||
import 'package:appflowy/shared/appflowy_network_image.dart';
|
import 'package:appflowy/shared/appflowy_network_image.dart';
|
||||||
@ -9,6 +10,7 @@ import 'package:appflowy_editor/appflowy_editor.dart';
|
|||||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
||||||
import 'package:string_validator/string_validator.dart';
|
import 'package:string_validator/string_validator.dart';
|
||||||
|
|
||||||
class ResizableImage extends StatefulWidget {
|
class ResizableImage extends StatefulWidget {
|
||||||
@ -95,8 +97,10 @@ class _ResizableImageState extends State<ResizableImage> {
|
|||||||
url: widget.src,
|
url: widget.src,
|
||||||
width: imageWidth - moveDistance,
|
width: imageWidth - moveDistance,
|
||||||
userProfilePB: _userProfilePB,
|
userProfilePB: _userProfilePB,
|
||||||
errorWidgetBuilder: (context, url, error) =>
|
errorWidgetBuilder: (context, url, error) => _ImageLoadFailedWidget(
|
||||||
_buildError(context, error),
|
width: imageWidth,
|
||||||
|
error: error,
|
||||||
|
),
|
||||||
progressIndicatorBuilder: (context, url, progress) =>
|
progressIndicatorBuilder: (context, url, progress) =>
|
||||||
_buildLoading(context),
|
_buildLoading(context),
|
||||||
);
|
);
|
||||||
@ -159,31 +163,6 @@ class _ResizableImageState extends State<ResizableImage> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildError(BuildContext context, Object error) {
|
|
||||||
return Container(
|
|
||||||
height: 100,
|
|
||||||
width: imageWidth,
|
|
||||||
alignment: Alignment.center,
|
|
||||||
padding: const EdgeInsets.only(top: 8.0, bottom: 8.0),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(4.0)),
|
|
||||||
border: Border.all(),
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
FlowyText(AppFlowyEditorL10n.current.imageLoadFailed),
|
|
||||||
const VSpace(4),
|
|
||||||
FlowyText.small(
|
|
||||||
error.toString(),
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
maxLines: 2,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildEdgeGesture(
|
Widget _buildEdgeGesture(
|
||||||
BuildContext context, {
|
BuildContext context, {
|
||||||
double? top,
|
double? top,
|
||||||
@ -241,3 +220,59 @@ class _ResizableImageState extends State<ResizableImage> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _ImageLoadFailedWidget extends StatelessWidget {
|
||||||
|
const _ImageLoadFailedWidget({
|
||||||
|
required this.width,
|
||||||
|
required this.error,
|
||||||
|
});
|
||||||
|
|
||||||
|
final double width;
|
||||||
|
final Object error;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final error = _getErrorMessage();
|
||||||
|
return Container(
|
||||||
|
height: 140,
|
||||||
|
width: width,
|
||||||
|
alignment: Alignment.center,
|
||||||
|
padding: const EdgeInsets.only(top: 8.0, bottom: 8.0),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(4.0)),
|
||||||
|
border: Border.all(
|
||||||
|
color: Colors.grey.withOpacity(0.6),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
const FlowySvg(
|
||||||
|
FlowySvgs.broken_image_xl,
|
||||||
|
size: Size.square(48),
|
||||||
|
),
|
||||||
|
FlowyText(
|
||||||
|
AppFlowyEditorL10n.current.imageLoadFailed,
|
||||||
|
),
|
||||||
|
const VSpace(6),
|
||||||
|
if (error != null)
|
||||||
|
FlowyText(
|
||||||
|
error,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
color: Theme.of(context).hintColor.withOpacity(0.6),
|
||||||
|
fontSize: 10,
|
||||||
|
maxLines: 2,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
String? _getErrorMessage() {
|
||||||
|
if (error is HttpExceptionWithStatus) {
|
||||||
|
return 'Error ${(error as HttpExceptionWithStatus).statusCode}';
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import 'package:appflowy/env/cloud_env.dart';
|
import 'package:appflowy/env/cloud_env.dart';
|
||||||
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
import 'package:appflowy/startup/startup.dart';
|
import 'package:appflowy/startup/startup.dart';
|
||||||
import 'package:appflowy/startup/tasks/appflowy_cloud_task.dart';
|
import 'package:appflowy/startup/tasks/appflowy_cloud_task.dart';
|
||||||
import 'package:appflowy/user/application/auth/auth_service.dart';
|
import 'package:appflowy/user/application/auth/auth_service.dart';
|
||||||
@ -7,6 +8,7 @@ import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
|||||||
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart'
|
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart'
|
||||||
show UserProfilePB;
|
show UserProfilePB;
|
||||||
import 'package:appflowy_result/appflowy_result.dart';
|
import 'package:appflowy_result/appflowy_result.dart';
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
@ -69,6 +71,11 @@ class SignInBloc extends Bloc<SignInEvent, SignInState> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
switchLoginType: (type) {
|
||||||
|
emit(
|
||||||
|
state.copyWith(loginType: type),
|
||||||
|
);
|
||||||
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -217,6 +224,21 @@ class SignInBloc extends Bloc<SignInEvent, SignInState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SignInState _stateFromCode(FlowyError error) {
|
SignInState _stateFromCode(FlowyError error) {
|
||||||
|
// edge case: 429 is the rate limit error code
|
||||||
|
// since the error code and error msg are saved in the msg field,
|
||||||
|
// we need to check if the msg contains 429
|
||||||
|
final msg = error.msg;
|
||||||
|
if (msg.isNotEmpty) {
|
||||||
|
if (msg.contains('429')) {
|
||||||
|
return state.copyWith(
|
||||||
|
isSubmitting: false,
|
||||||
|
successOrFail: FlowyResult.failure(
|
||||||
|
FlowyError(msg: LocaleKeys.signIn_limitRateError.tr()),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
switch (error.code) {
|
switch (error.code) {
|
||||||
case ErrorCode.EmailFormatInvalid:
|
case ErrorCode.EmailFormatInvalid:
|
||||||
return state.copyWith(
|
return state.copyWith(
|
||||||
@ -230,10 +252,19 @@ class SignInBloc extends Bloc<SignInEvent, SignInState> {
|
|||||||
passwordError: error.msg,
|
passwordError: error.msg,
|
||||||
emailError: null,
|
emailError: null,
|
||||||
);
|
);
|
||||||
|
case ErrorCode.UserUnauthorized:
|
||||||
|
return state.copyWith(
|
||||||
|
isSubmitting: false,
|
||||||
|
successOrFail: FlowyResult.failure(
|
||||||
|
FlowyError(msg: LocaleKeys.signIn_limitRateError.tr()),
|
||||||
|
),
|
||||||
|
);
|
||||||
default:
|
default:
|
||||||
return state.copyWith(
|
return state.copyWith(
|
||||||
isSubmitting: false,
|
isSubmitting: false,
|
||||||
successOrFail: FlowyResult.failure(error),
|
successOrFail: FlowyResult.failure(
|
||||||
|
FlowyError(msg: LocaleKeys.signIn_generalError.tr()),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -253,6 +284,14 @@ class SignInEvent with _$SignInEvent {
|
|||||||
const factory SignInEvent.deepLinkStateChange(DeepLinkResult result) =
|
const factory SignInEvent.deepLinkStateChange(DeepLinkResult result) =
|
||||||
DeepLinkStateChange;
|
DeepLinkStateChange;
|
||||||
const factory SignInEvent.cancel() = _Cancel;
|
const factory SignInEvent.cancel() = _Cancel;
|
||||||
|
const factory SignInEvent.switchLoginType(LoginType type) = _SwitchLoginType;
|
||||||
|
}
|
||||||
|
|
||||||
|
// we support sign in directly without sign up, but we want to allow the users to sign up if they want to
|
||||||
|
// this type is only for the UI to know which form to show
|
||||||
|
enum LoginType {
|
||||||
|
signIn,
|
||||||
|
signUp,
|
||||||
}
|
}
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
@ -264,6 +303,7 @@ class SignInState with _$SignInState {
|
|||||||
required String? passwordError,
|
required String? passwordError,
|
||||||
required String? emailError,
|
required String? emailError,
|
||||||
required FlowyResult<UserProfilePB, FlowyError>? successOrFail,
|
required FlowyResult<UserProfilePB, FlowyError>? successOrFail,
|
||||||
|
@Default(LoginType.signIn) LoginType loginType,
|
||||||
}) = _SignInState;
|
}) = _SignInState;
|
||||||
|
|
||||||
factory SignInState.initial() => const SignInState(
|
factory SignInState.initial() => const SignInState(
|
||||||
|
@ -23,12 +23,12 @@ void handleOpenWorkspaceError(BuildContext context, FlowyError error) {
|
|||||||
case ErrorCode.HttpError:
|
case ErrorCode.HttpError:
|
||||||
showSnapBar(
|
showSnapBar(
|
||||||
context,
|
context,
|
||||||
error.toString(),
|
error.msg,
|
||||||
);
|
);
|
||||||
default:
|
default:
|
||||||
showSnapBar(
|
showSnapBar(
|
||||||
context,
|
context,
|
||||||
error.toString(),
|
error.msg,
|
||||||
onClosed: () {
|
onClosed: () {
|
||||||
getIt<AuthService>().signOut();
|
getIt<AuthService>().signOut();
|
||||||
runAppFlowy();
|
runAppFlowy();
|
||||||
|
@ -1,62 +1,79 @@
|
|||||||
import 'package:appflowy/core/frameless_window.dart';
|
import 'package:appflowy/core/frameless_window.dart';
|
||||||
import 'package:appflowy/env/cloud_env.dart';
|
import 'package:appflowy/env/cloud_env.dart';
|
||||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/magic_link_sign_in_buttons.dart';
|
import 'package:appflowy/user/application/sign_in_bloc.dart';
|
||||||
import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/widgets.dart';
|
import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/widgets.dart';
|
||||||
import 'package:appflowy/user/presentation/widgets/widgets.dart';
|
import 'package:appflowy/user/presentation/widgets/widgets.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
class DesktopSignInScreen extends StatelessWidget {
|
class DesktopSignInScreen extends StatelessWidget {
|
||||||
const DesktopSignInScreen({super.key, required this.isLoading});
|
const DesktopSignInScreen({
|
||||||
|
super.key,
|
||||||
final bool isLoading;
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
const indicatorMinHeight = 4.0;
|
const indicatorMinHeight = 4.0;
|
||||||
return Scaffold(
|
return BlocBuilder<SignInBloc, SignInState>(
|
||||||
appBar: const PreferredSize(
|
builder: (context, state) {
|
||||||
preferredSize: Size(double.infinity, 60),
|
return Scaffold(
|
||||||
child: MoveWindowDetector(),
|
appBar: const PreferredSize(
|
||||||
),
|
preferredSize: Size(double.infinity, 60),
|
||||||
body: Center(
|
child: MoveWindowDetector(),
|
||||||
child: AuthFormContainer(
|
),
|
||||||
children: [
|
body: Center(
|
||||||
FlowyLogoTitle(
|
child: AuthFormContainer(
|
||||||
title: LocaleKeys.welcomeText.tr(),
|
children: [
|
||||||
logoSize: const Size(60, 60),
|
FlowyLogoTitle(
|
||||||
|
title: LocaleKeys.welcomeText.tr(),
|
||||||
|
logoSize: const Size(60, 60),
|
||||||
|
),
|
||||||
|
const VSpace(30),
|
||||||
|
|
||||||
|
// const SignInAnonymousButton(),
|
||||||
|
const SignInWithMagicLinkButtons(),
|
||||||
|
|
||||||
|
// third-party sign in.
|
||||||
|
const VSpace(20),
|
||||||
|
|
||||||
|
if (isAuthEnabled) ...[
|
||||||
|
const _OrDivider(),
|
||||||
|
const VSpace(10),
|
||||||
|
const ThirdPartySignInButtons(),
|
||||||
|
],
|
||||||
|
const VSpace(20),
|
||||||
|
|
||||||
|
// anonymous sign in
|
||||||
|
const SignInAnonymousButtonV2(),
|
||||||
|
const VSpace(10),
|
||||||
|
|
||||||
|
SwitchSignInSignUpButton(
|
||||||
|
onTap: () {
|
||||||
|
final type = state.loginType == LoginType.signIn
|
||||||
|
? LoginType.signUp
|
||||||
|
: LoginType.signIn;
|
||||||
|
context.read<SignInBloc>().add(
|
||||||
|
SignInEvent.switchLoginType(type),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
|
||||||
|
// loading status
|
||||||
|
const VSpace(indicatorMinHeight),
|
||||||
|
state.isSubmitting
|
||||||
|
? const LinearProgressIndicator(
|
||||||
|
minHeight: indicatorMinHeight,
|
||||||
|
)
|
||||||
|
: const VSpace(indicatorMinHeight),
|
||||||
|
const VSpace(20),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
const VSpace(30),
|
),
|
||||||
|
);
|
||||||
// const SignInAnonymousButton(),
|
},
|
||||||
const SignInWithMagicLinkButtons(),
|
|
||||||
|
|
||||||
// third-party sign in.
|
|
||||||
const VSpace(20),
|
|
||||||
|
|
||||||
if (isAuthEnabled) ...[
|
|
||||||
const _OrDivider(),
|
|
||||||
const VSpace(10),
|
|
||||||
const ThirdPartySignInButtons(),
|
|
||||||
],
|
|
||||||
const VSpace(20),
|
|
||||||
|
|
||||||
// anonymous sign in
|
|
||||||
const SignInAnonymousButtonV2(),
|
|
||||||
|
|
||||||
// loading status
|
|
||||||
const VSpace(indicatorMinHeight),
|
|
||||||
isLoading
|
|
||||||
? const LinearProgressIndicator(
|
|
||||||
minHeight: indicatorMinHeight,
|
|
||||||
)
|
|
||||||
: const VSpace(indicatorMinHeight),
|
|
||||||
const VSpace(20),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,11 +2,12 @@ import 'package:appflowy/env/cloud_env.dart';
|
|||||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
import 'package:appflowy/mobile/presentation/setting/launch_settings_page.dart';
|
import 'package:appflowy/mobile/presentation/setting/launch_settings_page.dart';
|
||||||
import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/magic_link_sign_in_buttons.dart';
|
import 'package:appflowy/user/application/sign_in_bloc.dart';
|
||||||
import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/widgets.dart';
|
import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/widgets.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
|
|
||||||
class MobileSignInScreen extends StatelessWidget {
|
class MobileSignInScreen extends StatelessWidget {
|
||||||
@ -18,28 +19,43 @@ class MobileSignInScreen extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
const double spacing = 16;
|
const double spacing = 16;
|
||||||
final colorScheme = Theme.of(context).colorScheme;
|
final colorScheme = Theme.of(context).colorScheme;
|
||||||
return Scaffold(
|
return BlocBuilder<SignInBloc, SignInState>(
|
||||||
body: Padding(
|
builder: (context, state) {
|
||||||
padding: const EdgeInsets.symmetric(vertical: 50, horizontal: 40),
|
return Scaffold(
|
||||||
child: Column(
|
body: Padding(
|
||||||
children: [
|
padding: const EdgeInsets.symmetric(vertical: 50, horizontal: 40),
|
||||||
const Spacer(flex: 4),
|
child: Column(
|
||||||
_buildLogo(),
|
children: [
|
||||||
const VSpace(spacing * 2),
|
const Spacer(flex: 4),
|
||||||
_buildWelcomeText(),
|
_buildLogo(),
|
||||||
_buildAppNameText(colorScheme),
|
const VSpace(spacing * 2),
|
||||||
const VSpace(spacing * 2),
|
_buildWelcomeText(),
|
||||||
const SignInWithMagicLinkButtons(),
|
_buildAppNameText(colorScheme),
|
||||||
const VSpace(spacing),
|
const VSpace(spacing * 2),
|
||||||
if (isAuthEnabled) _buildThirdPartySignInButtons(colorScheme),
|
const SignInWithMagicLinkButtons(),
|
||||||
const VSpace(spacing),
|
const VSpace(spacing),
|
||||||
const SignInAnonymousButtonV2(),
|
if (isAuthEnabled) _buildThirdPartySignInButtons(colorScheme),
|
||||||
const VSpace(spacing),
|
const VSpace(spacing),
|
||||||
_buildSettingsButton(context),
|
const SignInAnonymousButtonV2(),
|
||||||
if (!isAuthEnabled) const Spacer(flex: 2),
|
const VSpace(spacing),
|
||||||
],
|
SwitchSignInSignUpButton(
|
||||||
),
|
onTap: () {
|
||||||
),
|
final type = state.loginType == LoginType.signIn
|
||||||
|
? LoginType.signUp
|
||||||
|
: LoginType.signIn;
|
||||||
|
context.read<SignInBloc>().add(
|
||||||
|
SignInEvent.switchLoginType(type),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const VSpace(spacing),
|
||||||
|
_buildSettingsButton(context),
|
||||||
|
if (!isAuthEnabled) const Spacer(flex: 2),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,9 +39,7 @@ class SignInScreen extends StatelessWidget {
|
|||||||
? const MobileLoadingScreen()
|
? const MobileLoadingScreen()
|
||||||
: const MobileSignInScreen();
|
: const MobileSignInScreen();
|
||||||
}
|
}
|
||||||
return DesktopSignInScreen(
|
return const DesktopSignInScreen();
|
||||||
isLoading: isLoading,
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -37,8 +37,10 @@ class _SignInWithMagicLinkButtonsState
|
|||||||
SizedBox(
|
SizedBox(
|
||||||
height: 48.0,
|
height: 48.0,
|
||||||
child: FlowyTextField(
|
child: FlowyTextField(
|
||||||
|
autoFocus: false,
|
||||||
controller: controller,
|
controller: controller,
|
||||||
hintText: LocaleKeys.signIn_pleaseInputYourEmail.tr(),
|
hintText: LocaleKeys.signIn_pleaseInputYourEmail.tr(),
|
||||||
|
keyboardType: TextInputType.emailAddress,
|
||||||
onSubmitted: (_) => _sendMagicLink(context, controller.text),
|
onSubmitted: (_) => _sendMagicLink(context, controller.text),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -59,6 +61,9 @@ class _SignInWithMagicLinkButtonsState
|
|||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (context.read<SignInBloc>().state.isSubmitting) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
context.read<SignInBloc>().add(SignInEvent.signedWithMagicLink(email));
|
context.read<SignInBloc>().add(SignInEvent.signedWithMagicLink(email));
|
||||||
showSnackBarMessage(
|
showSnackBarMessage(
|
||||||
context,
|
context,
|
||||||
@ -77,33 +82,41 @@ class _ConfirmButton extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
if (PlatformExtension.isMobile) {
|
return BlocBuilder<SignInBloc, SignInState>(
|
||||||
return ElevatedButton(
|
builder: (context, state) {
|
||||||
style: ElevatedButton.styleFrom(
|
final name = switch (state.loginType) {
|
||||||
minimumSize: const Size(double.infinity, 56),
|
LoginType.signIn => LocaleKeys.signIn_signInWithMagicLink.tr(),
|
||||||
),
|
LoginType.signUp => LocaleKeys.signIn_signUpWithMagicLink.tr(),
|
||||||
onPressed: onTap,
|
};
|
||||||
child: FlowyText(
|
if (PlatformExtension.isMobile) {
|
||||||
LocaleKeys.signIn_logInWithMagicLink.tr(),
|
return ElevatedButton(
|
||||||
fontSize: 14,
|
style: ElevatedButton.styleFrom(
|
||||||
color: Theme.of(context).colorScheme.onPrimary,
|
minimumSize: const Size(double.infinity, 56),
|
||||||
fontWeight: FontWeight.w500,
|
),
|
||||||
),
|
onPressed: onTap,
|
||||||
);
|
child: FlowyText(
|
||||||
} else {
|
name,
|
||||||
return SizedBox(
|
fontSize: 14,
|
||||||
height: 48,
|
color: Theme.of(context).colorScheme.onPrimary,
|
||||||
child: FlowyButton(
|
fontWeight: FontWeight.w500,
|
||||||
isSelected: true,
|
),
|
||||||
onTap: onTap,
|
);
|
||||||
hoverColor: Theme.of(context).colorScheme.primary,
|
} else {
|
||||||
text: FlowyText.medium(
|
return SizedBox(
|
||||||
LocaleKeys.signIn_logInWithMagicLink.tr(),
|
height: 48,
|
||||||
textAlign: TextAlign.center,
|
child: FlowyButton(
|
||||||
),
|
isSelected: true,
|
||||||
radius: Corners.s6Border,
|
onTap: onTap,
|
||||||
),
|
hoverColor: Theme.of(context).colorScheme.primary,
|
||||||
);
|
text: FlowyText.medium(
|
||||||
}
|
name,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
radius: Corners.s6Border,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,52 @@
|
|||||||
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
|
import 'package:appflowy/user/application/sign_in_bloc.dart';
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
|
class SwitchSignInSignUpButton extends StatelessWidget {
|
||||||
|
const SwitchSignInSignUpButton({
|
||||||
|
super.key,
|
||||||
|
required this.onTap,
|
||||||
|
});
|
||||||
|
|
||||||
|
final VoidCallback onTap;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return BlocBuilder<SignInBloc, SignInState>(
|
||||||
|
builder: (context, state) {
|
||||||
|
return MouseRegion(
|
||||||
|
cursor: SystemMouseCursors.click,
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: onTap,
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
FlowyText(
|
||||||
|
switch (state.loginType) {
|
||||||
|
LoginType.signIn =>
|
||||||
|
LocaleKeys.signIn_dontHaveAnAccount.tr(),
|
||||||
|
LoginType.signUp =>
|
||||||
|
LocaleKeys.signIn_alreadyHaveAnAccount.tr(),
|
||||||
|
},
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
const HSpace(4),
|
||||||
|
FlowyText(
|
||||||
|
switch (state.loginType) {
|
||||||
|
LoginType.signIn => LocaleKeys.signIn_createAccount.tr(),
|
||||||
|
LoginType.signUp => LocaleKeys.signIn_logIn.tr(),
|
||||||
|
},
|
||||||
|
color: Colors.blue,
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -29,37 +29,53 @@ class ThirdPartySignInButtons extends StatelessWidget {
|
|||||||
? MediaQuery.of(context).platformBrightness == Brightness.dark
|
? MediaQuery.of(context).platformBrightness == Brightness.dark
|
||||||
: themeModeFromCubit == ThemeMode.dark;
|
: themeModeFromCubit == ThemeMode.dark;
|
||||||
|
|
||||||
return Column(
|
return BlocBuilder<SignInBloc, SignInState>(
|
||||||
children: [
|
builder: (context, state) {
|
||||||
_ThirdPartySignInButton(
|
final (googleText, githubText, discordText) = switch (state.loginType) {
|
||||||
key: const Key('signInWithGoogleButton'),
|
LoginType.signIn => (
|
||||||
icon: FlowySvgs.google_mark_xl,
|
LocaleKeys.signIn_signInWithGoogle.tr(),
|
||||||
labelText: LocaleKeys.signIn_LogInWithGoogle.tr(),
|
LocaleKeys.signIn_signInWithGithub.tr(),
|
||||||
onPressed: () {
|
LocaleKeys.signIn_signInWithDiscord.tr()
|
||||||
_signInWithGoogle(context);
|
),
|
||||||
},
|
LoginType.signUp => (
|
||||||
),
|
LocaleKeys.signIn_signUpWithGoogle.tr(),
|
||||||
const VSpace(8),
|
LocaleKeys.signIn_signUpWithGithub.tr(),
|
||||||
_ThirdPartySignInButton(
|
LocaleKeys.signIn_signUpWithDiscord.tr()
|
||||||
icon: isDarkMode
|
),
|
||||||
? FlowySvgs.github_mark_white_xl
|
};
|
||||||
: FlowySvgs.github_mark_black_xl,
|
return Column(
|
||||||
labelText: LocaleKeys.signIn_LogInWithGithub.tr(),
|
children: [
|
||||||
onPressed: () {
|
_ThirdPartySignInButton(
|
||||||
_signInWithGithub(context);
|
key: const Key('signInWithGoogleButton'),
|
||||||
},
|
icon: FlowySvgs.google_mark_xl,
|
||||||
),
|
labelText: googleText,
|
||||||
const VSpace(8),
|
onPressed: () {
|
||||||
_ThirdPartySignInButton(
|
_signInWithGoogle(context);
|
||||||
icon: isDarkMode
|
},
|
||||||
? FlowySvgs.discord_mark_white_xl
|
),
|
||||||
: FlowySvgs.discord_mark_blurple_xl,
|
const VSpace(8),
|
||||||
labelText: LocaleKeys.signIn_LogInWithDiscord.tr(),
|
_ThirdPartySignInButton(
|
||||||
onPressed: () {
|
icon: isDarkMode
|
||||||
_signInWithDiscord(context);
|
? FlowySvgs.github_mark_white_xl
|
||||||
},
|
: FlowySvgs.github_mark_black_xl,
|
||||||
),
|
labelText: githubText,
|
||||||
],
|
onPressed: () {
|
||||||
|
_signInWithGithub(context);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const VSpace(8),
|
||||||
|
_ThirdPartySignInButton(
|
||||||
|
icon: isDarkMode
|
||||||
|
? FlowySvgs.discord_mark_white_xl
|
||||||
|
: FlowySvgs.discord_mark_blurple_xl,
|
||||||
|
labelText: discordText,
|
||||||
|
onPressed: () {
|
||||||
|
_signInWithDiscord(context);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,2 +1,5 @@
|
|||||||
|
export 'magic_link_sign_in_buttons.dart';
|
||||||
export 'sign_in_anonymous_button.dart';
|
export 'sign_in_anonymous_button.dart';
|
||||||
|
export 'sign_in_or_logout_button.dart';
|
||||||
|
export 'switch_sign_in_sign_up_button.dart';
|
||||||
export 'third_party_sign_in_buttons.dart';
|
export 'third_party_sign_in_buttons.dart';
|
||||||
|
@ -11,9 +11,14 @@ class AuthFormContainer extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
width: width,
|
width: width,
|
||||||
child: Column(
|
child: ScrollConfiguration(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
behavior: ScrollConfiguration.of(context).copyWith(scrollbars: false),
|
||||||
children: children,
|
child: SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: children,
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,5 @@
|
|||||||
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/generated/locale_keys.g.dart';
|
||||||
import 'package:appflowy/shared/feature_flags.dart';
|
import 'package:appflowy/shared/feature_flags.dart';
|
||||||
import 'package:appflowy/startup/startup.dart';
|
|
||||||
import 'package:appflowy/user/application/user_listener.dart';
|
import 'package:appflowy/user/application/user_listener.dart';
|
||||||
import 'package:appflowy/user/application/user_service.dart';
|
import 'package:appflowy/user/application/user_service.dart';
|
||||||
import 'package:appflowy_backend/log.dart';
|
import 'package:appflowy_backend/log.dart';
|
||||||
@ -43,14 +40,7 @@ class UserWorkspaceBloc extends Bloc<UserWorkspaceEvent, UserWorkspaceState> {
|
|||||||
userProfile.authenticator == AuthenticatorPB.AppFlowyCloud &&
|
userProfile.authenticator == AuthenticatorPB.AppFlowyCloud &&
|
||||||
FeatureFlag.collaborativeWorkspace.isOn;
|
FeatureFlag.collaborativeWorkspace.isOn;
|
||||||
if (currentWorkspace != null && result.$3 == true) {
|
if (currentWorkspace != null && result.$3 == true) {
|
||||||
final result = await _userService
|
await _userService.openWorkspace(currentWorkspace.workspaceId);
|
||||||
.openWorkspace(currentWorkspace.workspaceId);
|
|
||||||
result.onSuccess((s) async {
|
|
||||||
await getIt<KeyValueStorage>().set(
|
|
||||||
KVKeys.lastOpenedWorkspaceId,
|
|
||||||
currentWorkspace.workspaceId,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
@ -176,12 +166,6 @@ class UserWorkspaceBloc extends Bloc<UserWorkspaceEvent, UserWorkspaceState> {
|
|||||||
),
|
),
|
||||||
(e) => state.currentWorkspace,
|
(e) => state.currentWorkspace,
|
||||||
);
|
);
|
||||||
result.onSuccess((_) async {
|
|
||||||
await getIt<KeyValueStorage>().set(
|
|
||||||
KVKeys.lastOpenedWorkspaceId,
|
|
||||||
workspaceId,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
emit(
|
emit(
|
||||||
state.copyWith(
|
state.copyWith(
|
||||||
currentWorkspace: currentWorkspace,
|
currentWorkspace: currentWorkspace,
|
||||||
@ -317,32 +301,22 @@ class UserWorkspaceBloc extends Bloc<UserWorkspaceEvent, UserWorkspaceState> {
|
|||||||
bool shouldOpenWorkspace,
|
bool shouldOpenWorkspace,
|
||||||
)> _fetchWorkspaces() async {
|
)> _fetchWorkspaces() async {
|
||||||
try {
|
try {
|
||||||
final lastOpenedWorkspaceId = await getIt<KeyValueStorage>().get(
|
|
||||||
KVKeys.lastOpenedWorkspaceId,
|
|
||||||
);
|
|
||||||
final currentWorkspace =
|
final currentWorkspace =
|
||||||
await _userService.getCurrentWorkspace().getOrThrow();
|
await _userService.getCurrentWorkspace().getOrThrow();
|
||||||
final workspaces = await _userService.getWorkspaces().getOrThrow();
|
final workspaces = await _userService.getWorkspaces().getOrThrow();
|
||||||
if (workspaces.isEmpty) {
|
if (workspaces.isEmpty) {
|
||||||
workspaces.add(convertWorkspacePBToUserWorkspace(currentWorkspace));
|
workspaces.add(convertWorkspacePBToUserWorkspace(currentWorkspace));
|
||||||
}
|
}
|
||||||
UserWorkspacePB? currentWorkspaceInList = workspaces
|
final currentWorkspaceInList = workspaces
|
||||||
.firstWhereOrNull((e) => e.workspaceId == currentWorkspace.id);
|
.firstWhereOrNull((e) => e.workspaceId == currentWorkspace.id) ??
|
||||||
if (lastOpenedWorkspaceId != null) {
|
workspaces.firstOrNull;
|
||||||
final lastOpenedWorkspace = workspaces
|
|
||||||
.firstWhereOrNull((e) => e.workspaceId == lastOpenedWorkspaceId);
|
|
||||||
if (lastOpenedWorkspace != null) {
|
|
||||||
currentWorkspaceInList = lastOpenedWorkspace;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
currentWorkspaceInList ??= workspaces.firstOrNull;
|
|
||||||
return (
|
return (
|
||||||
currentWorkspaceInList,
|
currentWorkspaceInList,
|
||||||
workspaces
|
workspaces
|
||||||
..sort(
|
..sort(
|
||||||
(a, b) => a.createdAtTimestamp.compareTo(b.createdAtTimestamp),
|
(a, b) => a.createdAtTimestamp.compareTo(b.createdAtTimestamp),
|
||||||
),
|
),
|
||||||
lastOpenedWorkspaceId != currentWorkspace.id
|
currentWorkspaceInList?.workspaceId != currentWorkspace.id
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Log.error('fetch workspace error: $e');
|
Log.error('fetch workspace error: $e');
|
||||||
|
@ -1,6 +1,3 @@
|
|||||||
import 'package:flutter/gestures.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
import 'package:appflowy/plugins/blank/blank.dart';
|
import 'package:appflowy/plugins/blank/blank.dart';
|
||||||
import 'package:appflowy/startup/plugin/plugin.dart';
|
import 'package:appflowy/startup/plugin/plugin.dart';
|
||||||
import 'package:appflowy/startup/startup.dart';
|
import 'package:appflowy/startup/startup.dart';
|
||||||
@ -13,6 +10,7 @@ import 'package:appflowy/workspace/application/home/home_service.dart';
|
|||||||
import 'package:appflowy/workspace/application/home/home_setting_bloc.dart';
|
import 'package:appflowy/workspace/application/home/home_setting_bloc.dart';
|
||||||
import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart';
|
import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart';
|
||||||
import 'package:appflowy/workspace/application/tabs/tabs_bloc.dart';
|
import 'package:appflowy/workspace/application/tabs/tabs_bloc.dart';
|
||||||
|
import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart';
|
||||||
import 'package:appflowy/workspace/application/view/view_ext.dart';
|
import 'package:appflowy/workspace/application/view/view_ext.dart';
|
||||||
import 'package:appflowy/workspace/presentation/home/errors/workspace_failed_screen.dart';
|
import 'package:appflowy/workspace/presentation/home/errors/workspace_failed_screen.dart';
|
||||||
import 'package:appflowy/workspace/presentation/home/hotkeys.dart';
|
import 'package:appflowy/workspace/presentation/home/hotkeys.dart';
|
||||||
@ -25,12 +23,13 @@ import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart';
|
|||||||
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart'
|
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart'
|
||||||
show UserProfilePB;
|
show UserProfilePB;
|
||||||
import 'package:flowy_infra_ui/style_widget/container.dart';
|
import 'package:flowy_infra_ui/style_widget/container.dart';
|
||||||
|
import 'package:flutter/gestures.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:sized_context/sized_context.dart';
|
import 'package:sized_context/sized_context.dart';
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
|
|
||||||
import '../widgets/edit_panel/edit_panel.dart';
|
import '../widgets/edit_panel/edit_panel.dart';
|
||||||
|
|
||||||
import 'home_layout.dart';
|
import 'home_layout.dart';
|
||||||
import 'home_stack.dart';
|
import 'home_stack.dart';
|
||||||
|
|
||||||
@ -115,9 +114,15 @@ class DesktopHomeScreen extends StatelessWidget {
|
|||||||
},
|
},
|
||||||
child: BlocBuilder<HomeSettingBloc, HomeSettingState>(
|
child: BlocBuilder<HomeSettingBloc, HomeSettingState>(
|
||||||
buildWhen: (previous, current) => previous != current,
|
buildWhen: (previous, current) => previous != current,
|
||||||
builder: (context, state) => FlowyContainer(
|
builder: (context, state) => BlocProvider(
|
||||||
Theme.of(context).colorScheme.surface,
|
create: (_) => UserWorkspaceBloc(userProfile: userProfile)
|
||||||
child: _buildBody(context, userProfile, workspaceSetting),
|
..add(
|
||||||
|
const UserWorkspaceEvent.initial(),
|
||||||
|
),
|
||||||
|
child: FlowyContainer(
|
||||||
|
Theme.of(context).colorScheme.surface,
|
||||||
|
child: _buildBody(context, userProfile, workspaceSetting),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:appflowy/startup/startup.dart';
|
import 'package:appflowy/startup/startup.dart';
|
||||||
import 'package:appflowy/workspace/application/action_navigation/action_navigation_bloc.dart';
|
import 'package:appflowy/workspace/application/action_navigation/action_navigation_bloc.dart';
|
||||||
import 'package:appflowy/workspace/application/action_navigation/navigation_action.dart';
|
import 'package:appflowy/workspace/application/action_navigation/navigation_action.dart';
|
||||||
@ -21,6 +20,7 @@ import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart'
|
|||||||
show UserProfilePB;
|
show UserProfilePB;
|
||||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
/// Home Sidebar is the left side bar of the home page.
|
/// Home Sidebar is the left side bar of the home page.
|
||||||
@ -58,75 +58,69 @@ class HomeSideBar extends StatelessWidget {
|
|||||||
// +-- Public Or Private Section: control the sections of the workspace
|
// +-- Public Or Private Section: control the sections of the workspace
|
||||||
// |
|
// |
|
||||||
// +-- Trash Section
|
// +-- Trash Section
|
||||||
return BlocProvider<UserWorkspaceBloc>(
|
return BlocBuilder<UserWorkspaceBloc, UserWorkspaceState>(
|
||||||
create: (_) => UserWorkspaceBloc(userProfile: userProfile)
|
// Rebuild the whole sidebar when the current workspace changes
|
||||||
..add(
|
buildWhen: (previous, current) =>
|
||||||
const UserWorkspaceEvent.initial(),
|
previous.currentWorkspace?.workspaceId !=
|
||||||
),
|
current.currentWorkspace?.workspaceId,
|
||||||
child: BlocBuilder<UserWorkspaceBloc, UserWorkspaceState>(
|
builder: (context, state) {
|
||||||
// Rebuild the whole sidebar when the current workspace changes
|
if (state.currentWorkspace == null) {
|
||||||
buildWhen: (previous, current) =>
|
return const SizedBox.shrink();
|
||||||
previous.currentWorkspace?.workspaceId !=
|
}
|
||||||
current.currentWorkspace?.workspaceId,
|
return MultiBlocProvider(
|
||||||
builder: (context, state) {
|
providers: [
|
||||||
if (state.currentWorkspace == null) {
|
BlocProvider(create: (_) => getIt<ActionNavigationBloc>()),
|
||||||
return const SizedBox.shrink();
|
BlocProvider(
|
||||||
}
|
create: (_) => SidebarSectionsBloc()
|
||||||
return MultiBlocProvider(
|
..add(
|
||||||
providers: [
|
SidebarSectionsEvent.initial(
|
||||||
BlocProvider(create: (_) => getIt<ActionNavigationBloc>()),
|
userProfile,
|
||||||
BlocProvider(
|
state.currentWorkspace?.workspaceId ??
|
||||||
create: (_) => SidebarSectionsBloc()
|
workspaceSetting.workspaceId,
|
||||||
..add(
|
|
||||||
SidebarSectionsEvent.initial(
|
|
||||||
userProfile,
|
|
||||||
state.currentWorkspace?.workspaceId ??
|
|
||||||
workspaceSetting.workspaceId,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
child: MultiBlocListener(
|
||||||
|
listeners: [
|
||||||
|
BlocListener<SidebarSectionsBloc, SidebarSectionsState>(
|
||||||
|
listenWhen: (p, c) =>
|
||||||
|
p.lastCreatedRootView?.id != c.lastCreatedRootView?.id,
|
||||||
|
listener: (context, state) => context.read<TabsBloc>().add(
|
||||||
|
TabsEvent.openPlugin(
|
||||||
|
plugin: state.lastCreatedRootView!.plugin(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
BlocListener<ActionNavigationBloc, ActionNavigationState>(
|
||||||
|
listenWhen: (_, curr) => curr.action != null,
|
||||||
|
listener: _onNotificationAction,
|
||||||
|
),
|
||||||
|
BlocListener<UserWorkspaceBloc, UserWorkspaceState>(
|
||||||
|
listener: (context, state) {
|
||||||
|
final actionType = state.actionResult?.actionType;
|
||||||
|
|
||||||
|
if (actionType == UserWorkspaceActionType.create ||
|
||||||
|
actionType == UserWorkspaceActionType.delete ||
|
||||||
|
actionType == UserWorkspaceActionType.open) {
|
||||||
|
context.read<SidebarSectionsBloc>().add(
|
||||||
|
SidebarSectionsEvent.reload(
|
||||||
|
userProfile,
|
||||||
|
state.currentWorkspace?.workspaceId ??
|
||||||
|
workspaceSetting.workspaceId,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
context.read<FavoriteBloc>().add(
|
||||||
|
const FavoriteEvent.fetchFavorites(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
child: MultiBlocListener(
|
child: _Sidebar(userProfile: userProfile),
|
||||||
listeners: [
|
),
|
||||||
BlocListener<SidebarSectionsBloc, SidebarSectionsState>(
|
);
|
||||||
listenWhen: (p, c) =>
|
},
|
||||||
p.lastCreatedRootView?.id != c.lastCreatedRootView?.id,
|
|
||||||
listener: (context, state) => context.read<TabsBloc>().add(
|
|
||||||
TabsEvent.openPlugin(
|
|
||||||
plugin: state.lastCreatedRootView!.plugin(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
BlocListener<ActionNavigationBloc, ActionNavigationState>(
|
|
||||||
listenWhen: (_, curr) => curr.action != null,
|
|
||||||
listener: _onNotificationAction,
|
|
||||||
),
|
|
||||||
BlocListener<UserWorkspaceBloc, UserWorkspaceState>(
|
|
||||||
listener: (context, state) {
|
|
||||||
final actionType = state.actionResult?.actionType;
|
|
||||||
|
|
||||||
if (actionType == UserWorkspaceActionType.create ||
|
|
||||||
actionType == UserWorkspaceActionType.delete ||
|
|
||||||
actionType == UserWorkspaceActionType.open) {
|
|
||||||
context.read<SidebarSectionsBloc>().add(
|
|
||||||
SidebarSectionsEvent.reload(
|
|
||||||
userProfile,
|
|
||||||
state.currentWorkspace?.workspaceId ??
|
|
||||||
workspaceSetting.workspaceId,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
context.read<FavoriteBloc>().add(
|
|
||||||
const FavoriteEvent.fetchFavorites(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
child: _Sidebar(userProfile: userProfile),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
import 'package:appflowy/plugins/base/emoji/emoji_text.dart';
|
import 'package:appflowy/plugins/base/emoji/emoji_text.dart';
|
||||||
@ -26,6 +24,7 @@ import 'package:easy_localization/easy_localization.dart';
|
|||||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||||
import 'package:flowy_infra_ui/style_widget/hover.dart';
|
import 'package:flowy_infra_ui/style_widget/hover.dart';
|
||||||
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
|
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
typedef ViewItemOnSelected = void Function(ViewPB);
|
typedef ViewItemOnSelected = void Function(ViewPB);
|
||||||
@ -411,19 +410,6 @@ class _SingleInnerViewItemState extends State<SingleInnerViewItem> {
|
|||||||
behavior: HitTestBehavior.translucent,
|
behavior: HitTestBehavior.translucent,
|
||||||
onTap: () => widget.onSelected(widget.view),
|
onTap: () => widget.onSelected(widget.view),
|
||||||
onTertiaryTapDown: (_) => widget.onTertiarySelected?.call(widget.view),
|
onTertiaryTapDown: (_) => widget.onTertiarySelected?.call(widget.view),
|
||||||
onDoubleTap: isSelected
|
|
||||||
? () {
|
|
||||||
NavigatorTextFieldDialog(
|
|
||||||
title: LocaleKeys.disclosureAction_rename.tr(),
|
|
||||||
autoSelectAllText: true,
|
|
||||||
value: widget.view.name,
|
|
||||||
maxLength: 256,
|
|
||||||
onConfirm: (newValue, _) {
|
|
||||||
context.read<ViewBloc>().add(ViewEvent.rename(newValue));
|
|
||||||
},
|
|
||||||
).show(context);
|
|
||||||
}
|
|
||||||
: null,
|
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
height: widget.height,
|
height: widget.height,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
|
@ -59,6 +59,7 @@ void showSnackBarMessage(
|
|||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(
|
SnackBar(
|
||||||
backgroundColor: Theme.of(context).colorScheme.surfaceVariant,
|
backgroundColor: Theme.of(context).colorScheme.surfaceVariant,
|
||||||
|
duration: duration,
|
||||||
action: !showCancel
|
action: !showCancel
|
||||||
? null
|
? null
|
||||||
: SnackBarAction(
|
: SnackBarAction(
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import 'package:appflowy/plugins/base/emoji/emoji_text.dart';
|
import 'package:appflowy/plugins/base/emoji/emoji_text.dart';
|
||||||
import 'package:appflowy/startup/tasks/app_window_size_manager.dart';
|
import 'package:appflowy/startup/tasks/app_window_size_manager.dart';
|
||||||
import 'package:appflowy/workspace/application/tabs/tabs_bloc.dart';
|
import 'package:appflowy/workspace/application/tabs/tabs_bloc.dart';
|
||||||
|
import 'package:appflowy/workspace/application/user/user_workspace_bloc.dart';
|
||||||
import 'package:appflowy/workspace/application/view/view_ext.dart';
|
import 'package:appflowy/workspace/application/view/view_ext.dart';
|
||||||
import 'package:appflowy/workspace/application/view/view_listener.dart';
|
import 'package:appflowy/workspace/application/view/view_listener.dart';
|
||||||
import 'package:appflowy/workspace/application/view/view_service.dart';
|
import 'package:appflowy/workspace/application/view/view_service.dart';
|
||||||
@ -13,7 +14,7 @@ import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
// workspaces / ... / view_title
|
// workspace name / ... / view_title
|
||||||
class ViewTitleBar extends StatefulWidget {
|
class ViewTitleBar extends StatefulWidget {
|
||||||
const ViewTitleBar({
|
const ViewTitleBar({
|
||||||
super.key,
|
super.key,
|
||||||
@ -58,7 +59,7 @@ class _ViewTitleBarState extends State<ViewTitleBar> {
|
|||||||
final replacement = Row(
|
final replacement = Row(
|
||||||
// refresh the view title bar when the ancestors changed
|
// refresh the view title bar when the ancestors changed
|
||||||
key: ValueKey(ancestors.hashCode),
|
key: ValueKey(ancestors.hashCode),
|
||||||
children: _buildViewTitles(ancestors),
|
children: _buildViewTitles(context, ancestors),
|
||||||
);
|
);
|
||||||
return LayoutBuilder(
|
return LayoutBuilder(
|
||||||
builder: (context, constraints) {
|
builder: (context, constraints) {
|
||||||
@ -79,13 +80,14 @@ class _ViewTitleBarState extends State<ViewTitleBar> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Widget> _buildViewTitles(List<ViewPB> views) {
|
List<Widget> _buildViewTitles(BuildContext context, List<ViewPB> views) {
|
||||||
// if the level is too deep, only show the last two view, the first one view and the root view
|
// if the level is too deep, only show the last two view, the first one view and the root view
|
||||||
bool hasAddedEllipsis = false;
|
bool hasAddedEllipsis = false;
|
||||||
final children = <Widget>[];
|
final children = <Widget>[];
|
||||||
|
|
||||||
for (var i = 0; i < views.length; i++) {
|
for (var i = 0; i < views.length; i++) {
|
||||||
final view = views[i];
|
final view = views[i];
|
||||||
|
|
||||||
if (i >= 1 && i < views.length - 2) {
|
if (i >= 1 && i < views.length - 2) {
|
||||||
if (!hasAddedEllipsis) {
|
if (!hasAddedEllipsis) {
|
||||||
hasAddedEllipsis = true;
|
hasAddedEllipsis = true;
|
||||||
@ -95,8 +97,30 @@ class _ViewTitleBarState extends State<ViewTitleBar> {
|
|||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
children.add(
|
|
||||||
FlowyTooltip(
|
Widget child;
|
||||||
|
if (i == 0) {
|
||||||
|
final currentWorkspace =
|
||||||
|
context.read<UserWorkspaceBloc>().state.currentWorkspace;
|
||||||
|
final icon = currentWorkspace?.icon ?? '';
|
||||||
|
final name = currentWorkspace?.name ?? view.name;
|
||||||
|
// the first one is the workspace name
|
||||||
|
child = FlowyTooltip(
|
||||||
|
message: name,
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
EmojiText(
|
||||||
|
emoji: icon,
|
||||||
|
fontSize: 18.0,
|
||||||
|
),
|
||||||
|
const HSpace(2.0),
|
||||||
|
FlowyText.regular(name),
|
||||||
|
const HSpace(4.0),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
child = FlowyTooltip(
|
||||||
message: view.name,
|
message: view.name,
|
||||||
child: _ViewTitle(
|
child: _ViewTitle(
|
||||||
view: view,
|
view: view,
|
||||||
@ -105,8 +129,11 @@ class _ViewTitleBarState extends State<ViewTitleBar> {
|
|||||||
: _ViewTitleBehavior.uneditable, // others are not editable
|
: _ViewTitleBehavior.uneditable, // others are not editable
|
||||||
onUpdated: () => setState(() => _reloadAncestors()),
|
onUpdated: () => setState(() => _reloadAncestors()),
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
);
|
}
|
||||||
|
|
||||||
|
children.add(child);
|
||||||
|
|
||||||
if (i != views.length - 1) {
|
if (i != views.length - 1) {
|
||||||
// if not the last one, add a divider
|
// if not the last one, add a divider
|
||||||
children.add(const FlowyText.regular('/'));
|
children.add(const FlowyText.regular('/'));
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
@ -7,19 +9,16 @@ void showSnapBar(BuildContext context, String title, {VoidCallback? onClosed}) {
|
|||||||
ScaffoldMessenger.of(context)
|
ScaffoldMessenger.of(context)
|
||||||
.showSnackBar(
|
.showSnackBar(
|
||||||
SnackBar(
|
SnackBar(
|
||||||
|
backgroundColor: Theme.of(context).colorScheme.surfaceVariant,
|
||||||
duration: const Duration(milliseconds: 8000),
|
duration: const Duration(milliseconds: 8000),
|
||||||
content: PopScope(
|
content: FlowyText(
|
||||||
canPop: () {
|
title,
|
||||||
ScaffoldMessenger.of(context).removeCurrentSnackBar();
|
maxLines: 2,
|
||||||
return true;
|
fontSize:
|
||||||
}(),
|
(Platform.isLinux || Platform.isWindows || Platform.isMacOS)
|
||||||
child: FlowyText.medium(
|
? 14
|
||||||
title,
|
: 12,
|
||||||
fontSize: 12,
|
|
||||||
maxLines: 3,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
backgroundColor: Theme.of(context).colorScheme.background,
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.closed
|
.closed
|
||||||
|
1
frontend/resources/flowy_icons/40x/broken_image.svg
Normal file
1
frontend/resources/flowy_icons/40x/broken_image.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128" width="40" height="40"><path d="M70.936 57.193c4.38 0 7.943-3.564 7.943-7.945s-3.563-7.945-7.943-7.945c-4.381 0-7.945 3.564-7.945 7.945s3.564 7.945 7.945 7.945zm0-11.89c2.175 0 3.943 1.77 3.943 3.945s-1.769 3.945-3.943 3.945c-2.176 0-3.945-1.77-3.945-3.945s1.769-3.945 3.945-3.945zm40.646 15.56a1.997 1.997 0 0 0-2.216.591l-8.671 10.305-.056.063-18.456 21.935a2 2 0 0 0 1.53 3.288h27.184a2 2 0 0 0 2-2V62.742a2.003 2.003 0 0 0-1.315-1.879zm-2.686 32.182h-18.72l-1.171-1.172 13.293-15.807 6.599 6.598v10.381zm0-16.037-4.014-4.014 4.014-4.77v8.784z" fill="#000000" class="color000 svgShape"></path><path d="M110.896 30.955H17.104a2 2 0 0 0-2 2v62.09a2 2 0 0 0 2 2H67.94c.59 0 1.15-.261 1.53-.712l42.957-51.045c.304-.36.47-.816.47-1.288V32.955a2.002 2.002 0 0 0-2.001-2zm-43.887 62.09H19.104V76.523l24.447-24.447 25.338 25.338 6.122 6.122-8.002 9.509zm10.587-12.58L73.131 76l14.551-14.552 3.213 3.214-13.299 15.803zm31.3-37.194L93.479 61.59l-4.384-4.385a2 2 0 0 0-2.828 0L70.303 73.172 44.965 47.834c-.391-.391-.902-.586-1.414-.586s-1.023.195-1.414.586L19.104 70.867V34.955h89.793v8.316z" fill="#000000" class="color000 svgShape"></path></svg>
|
After Width: | Height: | Size: 1.2 KiB |
@ -42,19 +42,28 @@
|
|||||||
"emailHint": "Email",
|
"emailHint": "Email",
|
||||||
"passwordHint": "Password",
|
"passwordHint": "Password",
|
||||||
"dontHaveAnAccount": "Don't have an account?",
|
"dontHaveAnAccount": "Don't have an account?",
|
||||||
|
"createAccount": "Create account",
|
||||||
"repeatPasswordEmptyError": "Repeat password can't be empty",
|
"repeatPasswordEmptyError": "Repeat password can't be empty",
|
||||||
"unmatchedPasswordError": "Repeat password is not the same as password",
|
"unmatchedPasswordError": "Repeat password is not the same as password",
|
||||||
"syncPromptMessage": "Syncing the data might take a while. Please don't close this page",
|
"syncPromptMessage": "Syncing the data might take a while. Please don't close this page",
|
||||||
"or": "OR",
|
"or": "OR",
|
||||||
"LogInWithGoogle": "Log in with Google",
|
"signInWithGoogle": "Log in with Google",
|
||||||
"LogInWithGithub": "Log in with Github",
|
"signInWithGithub": "Log in with Github",
|
||||||
"LogInWithDiscord": "Log in with Discord",
|
"signInWithDiscord": "Log in with Discord",
|
||||||
|
"signUpWithGoogle": "Sign up with Google",
|
||||||
|
"signUpWithGithub": "Sign up with Github",
|
||||||
|
"signUpWithDiscord": "Sign up with Discord",
|
||||||
"signInWith": "Sign in with:",
|
"signInWith": "Sign in with:",
|
||||||
"signInWithEmail": "Sign in with Email",
|
"signInWithEmail": "Sign in with Email",
|
||||||
"logInWithMagicLink": "Log in with Magic Link",
|
"signInWithMagicLink": "Log in with Magic Link",
|
||||||
|
"signUpWithMagicLink": "Sign up with Magic Link",
|
||||||
"pleaseInputYourEmail": "Please enter your email address",
|
"pleaseInputYourEmail": "Please enter your email address",
|
||||||
"magicLinkSent": "Magic link sent to your email, please check your inbox",
|
"magicLinkSent": "We emailed a magic link. Click the link to log in.",
|
||||||
"invalidEmail": "Please enter a valid email address"
|
"invalidEmail": "Please enter a valid email address",
|
||||||
|
"alreadyHaveAnAccount": "Already have an account?",
|
||||||
|
"logIn": "Log in",
|
||||||
|
"generalError": "Something went wrong. Please try again later",
|
||||||
|
"limitRateError": "For security reasons, you can only request a magic link every 60 seconds"
|
||||||
},
|
},
|
||||||
"workspace": {
|
"workspace": {
|
||||||
"chooseWorkspace": "Choose your workspace",
|
"chooseWorkspace": "Choose your workspace",
|
||||||
@ -912,7 +921,8 @@
|
|||||||
"image": {
|
"image": {
|
||||||
"copiedToPasteBoard": "The image link has been copied to the clipboard",
|
"copiedToPasteBoard": "The image link has been copied to the clipboard",
|
||||||
"addAnImage": "Add an image",
|
"addAnImage": "Add an image",
|
||||||
"imageUploadFailed": "Image upload failed"
|
"imageUploadFailed": "Image upload failed",
|
||||||
|
"errorCode": "Error code"
|
||||||
},
|
},
|
||||||
"urlPreview": {
|
"urlPreview": {
|
||||||
"copiedToPasteBoard": "The link has been copied to the clipboard",
|
"copiedToPasteBoard": "The link has been copied to the clipboard",
|
||||||
@ -1375,7 +1385,7 @@
|
|||||||
"upload": "Upload",
|
"upload": "Upload",
|
||||||
"chooseImage": "Choose an image",
|
"chooseImage": "Choose an image",
|
||||||
"loading": "Loading",
|
"loading": "Loading",
|
||||||
"imageLoadFailed": "Could not load the image",
|
"imageLoadFailed": "Image load failed",
|
||||||
"divider": "Divider",
|
"divider": "Divider",
|
||||||
"table": "Table",
|
"table": "Table",
|
||||||
"colAddBefore": "Add before",
|
"colAddBefore": "Add before",
|
||||||
|
Reference in New Issue
Block a user