mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +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:
parent
fd4783e19a
commit
568311a855
@ -68,5 +68,6 @@ class KVKeys {
|
||||
/// The key for saving the last opened workspace id
|
||||
///
|
||||
/// The workspace id is a string.
|
||||
@Deprecated('deprecated in version 0.5.5')
|
||||
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/user/application/auth/auth_service.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_backend/log.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
|
@ -1,6 +1,7 @@
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:appflowy/generated/flowy_svgs.g.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/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:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
||||
import 'package:string_validator/string_validator.dart';
|
||||
|
||||
class ResizableImage extends StatefulWidget {
|
||||
@ -95,8 +97,10 @@ class _ResizableImageState extends State<ResizableImage> {
|
||||
url: widget.src,
|
||||
width: imageWidth - moveDistance,
|
||||
userProfilePB: _userProfilePB,
|
||||
errorWidgetBuilder: (context, url, error) =>
|
||||
_buildError(context, error),
|
||||
errorWidgetBuilder: (context, url, error) => _ImageLoadFailedWidget(
|
||||
width: imageWidth,
|
||||
error: error,
|
||||
),
|
||||
progressIndicatorBuilder: (context, url, progress) =>
|
||||
_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(
|
||||
BuildContext context, {
|
||||
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/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/startup/tasks/appflowy_cloud_task.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'
|
||||
show UserProfilePB;
|
||||
import 'package:appflowy_result/appflowy_result.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.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) {
|
||||
// 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) {
|
||||
case ErrorCode.EmailFormatInvalid:
|
||||
return state.copyWith(
|
||||
@ -230,10 +252,19 @@ class SignInBloc extends Bloc<SignInEvent, SignInState> {
|
||||
passwordError: error.msg,
|
||||
emailError: null,
|
||||
);
|
||||
case ErrorCode.UserUnauthorized:
|
||||
return state.copyWith(
|
||||
isSubmitting: false,
|
||||
successOrFail: FlowyResult.failure(
|
||||
FlowyError(msg: LocaleKeys.signIn_limitRateError.tr()),
|
||||
),
|
||||
);
|
||||
default:
|
||||
return state.copyWith(
|
||||
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) =
|
||||
DeepLinkStateChange;
|
||||
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
|
||||
@ -264,6 +303,7 @@ class SignInState with _$SignInState {
|
||||
required String? passwordError,
|
||||
required String? emailError,
|
||||
required FlowyResult<UserProfilePB, FlowyError>? successOrFail,
|
||||
@Default(LoginType.signIn) LoginType loginType,
|
||||
}) = _SignInState;
|
||||
|
||||
factory SignInState.initial() => const SignInState(
|
||||
|
@ -23,12 +23,12 @@ void handleOpenWorkspaceError(BuildContext context, FlowyError error) {
|
||||
case ErrorCode.HttpError:
|
||||
showSnapBar(
|
||||
context,
|
||||
error.toString(),
|
||||
error.msg,
|
||||
);
|
||||
default:
|
||||
showSnapBar(
|
||||
context,
|
||||
error.toString(),
|
||||
error.msg,
|
||||
onClosed: () {
|
||||
getIt<AuthService>().signOut();
|
||||
runAppFlowy();
|
||||
|
@ -1,62 +1,79 @@
|
||||
import 'package:appflowy/core/frameless_window.dart';
|
||||
import 'package:appflowy/env/cloud_env.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/widgets/widgets.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 DesktopSignInScreen extends StatelessWidget {
|
||||
const DesktopSignInScreen({super.key, required this.isLoading});
|
||||
|
||||
final bool isLoading;
|
||||
const DesktopSignInScreen({
|
||||
super.key,
|
||||
});
|
||||
|
||||
@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),
|
||||
return BlocBuilder<SignInBloc, SignInState>(
|
||||
builder: (context, state) {
|
||||
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),
|
||||
|
||||
// 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/locale_keys.g.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:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
class MobileSignInScreen extends StatelessWidget {
|
||||
@ -18,28 +19,43 @@ class MobileSignInScreen extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
const double spacing = 16;
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
return Scaffold(
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 50, horizontal: 40),
|
||||
child: Column(
|
||||
children: [
|
||||
const Spacer(flex: 4),
|
||||
_buildLogo(),
|
||||
const VSpace(spacing * 2),
|
||||
_buildWelcomeText(),
|
||||
_buildAppNameText(colorScheme),
|
||||
const VSpace(spacing * 2),
|
||||
const SignInWithMagicLinkButtons(),
|
||||
const VSpace(spacing),
|
||||
if (isAuthEnabled) _buildThirdPartySignInButtons(colorScheme),
|
||||
const VSpace(spacing),
|
||||
const SignInAnonymousButtonV2(),
|
||||
const VSpace(spacing),
|
||||
_buildSettingsButton(context),
|
||||
if (!isAuthEnabled) const Spacer(flex: 2),
|
||||
],
|
||||
),
|
||||
),
|
||||
return BlocBuilder<SignInBloc, SignInState>(
|
||||
builder: (context, state) {
|
||||
return Scaffold(
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 50, horizontal: 40),
|
||||
child: Column(
|
||||
children: [
|
||||
const Spacer(flex: 4),
|
||||
_buildLogo(),
|
||||
const VSpace(spacing * 2),
|
||||
_buildWelcomeText(),
|
||||
_buildAppNameText(colorScheme),
|
||||
const VSpace(spacing * 2),
|
||||
const SignInWithMagicLinkButtons(),
|
||||
const VSpace(spacing),
|
||||
if (isAuthEnabled) _buildThirdPartySignInButtons(colorScheme),
|
||||
const VSpace(spacing),
|
||||
const SignInAnonymousButtonV2(),
|
||||
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 MobileSignInScreen();
|
||||
}
|
||||
return DesktopSignInScreen(
|
||||
isLoading: isLoading,
|
||||
);
|
||||
return const DesktopSignInScreen();
|
||||
},
|
||||
),
|
||||
);
|
||||
|
@ -37,8 +37,10 @@ class _SignInWithMagicLinkButtonsState
|
||||
SizedBox(
|
||||
height: 48.0,
|
||||
child: FlowyTextField(
|
||||
autoFocus: false,
|
||||
controller: controller,
|
||||
hintText: LocaleKeys.signIn_pleaseInputYourEmail.tr(),
|
||||
keyboardType: TextInputType.emailAddress,
|
||||
onSubmitted: (_) => _sendMagicLink(context, controller.text),
|
||||
),
|
||||
),
|
||||
@ -59,6 +61,9 @@ class _SignInWithMagicLinkButtonsState
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (context.read<SignInBloc>().state.isSubmitting) {
|
||||
return;
|
||||
}
|
||||
context.read<SignInBloc>().add(SignInEvent.signedWithMagicLink(email));
|
||||
showSnackBarMessage(
|
||||
context,
|
||||
@ -77,33 +82,41 @@ class _ConfirmButton extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (PlatformExtension.isMobile) {
|
||||
return ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
minimumSize: const Size(double.infinity, 56),
|
||||
),
|
||||
onPressed: onTap,
|
||||
child: FlowyText(
|
||||
LocaleKeys.signIn_logInWithMagicLink.tr(),
|
||||
fontSize: 14,
|
||||
color: Theme.of(context).colorScheme.onPrimary,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return SizedBox(
|
||||
height: 48,
|
||||
child: FlowyButton(
|
||||
isSelected: true,
|
||||
onTap: onTap,
|
||||
hoverColor: Theme.of(context).colorScheme.primary,
|
||||
text: FlowyText.medium(
|
||||
LocaleKeys.signIn_logInWithMagicLink.tr(),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
radius: Corners.s6Border,
|
||||
),
|
||||
);
|
||||
}
|
||||
return BlocBuilder<SignInBloc, SignInState>(
|
||||
builder: (context, state) {
|
||||
final name = switch (state.loginType) {
|
||||
LoginType.signIn => LocaleKeys.signIn_signInWithMagicLink.tr(),
|
||||
LoginType.signUp => LocaleKeys.signIn_signUpWithMagicLink.tr(),
|
||||
};
|
||||
if (PlatformExtension.isMobile) {
|
||||
return ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
minimumSize: const Size(double.infinity, 56),
|
||||
),
|
||||
onPressed: onTap,
|
||||
child: FlowyText(
|
||||
name,
|
||||
fontSize: 14,
|
||||
color: Theme.of(context).colorScheme.onPrimary,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return SizedBox(
|
||||
height: 48,
|
||||
child: FlowyButton(
|
||||
isSelected: true,
|
||||
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
|
||||
: themeModeFromCubit == ThemeMode.dark;
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
_ThirdPartySignInButton(
|
||||
key: const Key('signInWithGoogleButton'),
|
||||
icon: FlowySvgs.google_mark_xl,
|
||||
labelText: LocaleKeys.signIn_LogInWithGoogle.tr(),
|
||||
onPressed: () {
|
||||
_signInWithGoogle(context);
|
||||
},
|
||||
),
|
||||
const VSpace(8),
|
||||
_ThirdPartySignInButton(
|
||||
icon: isDarkMode
|
||||
? FlowySvgs.github_mark_white_xl
|
||||
: FlowySvgs.github_mark_black_xl,
|
||||
labelText: LocaleKeys.signIn_LogInWithGithub.tr(),
|
||||
onPressed: () {
|
||||
_signInWithGithub(context);
|
||||
},
|
||||
),
|
||||
const VSpace(8),
|
||||
_ThirdPartySignInButton(
|
||||
icon: isDarkMode
|
||||
? FlowySvgs.discord_mark_white_xl
|
||||
: FlowySvgs.discord_mark_blurple_xl,
|
||||
labelText: LocaleKeys.signIn_LogInWithDiscord.tr(),
|
||||
onPressed: () {
|
||||
_signInWithDiscord(context);
|
||||
},
|
||||
),
|
||||
],
|
||||
return BlocBuilder<SignInBloc, SignInState>(
|
||||
builder: (context, state) {
|
||||
final (googleText, githubText, discordText) = switch (state.loginType) {
|
||||
LoginType.signIn => (
|
||||
LocaleKeys.signIn_signInWithGoogle.tr(),
|
||||
LocaleKeys.signIn_signInWithGithub.tr(),
|
||||
LocaleKeys.signIn_signInWithDiscord.tr()
|
||||
),
|
||||
LoginType.signUp => (
|
||||
LocaleKeys.signIn_signUpWithGoogle.tr(),
|
||||
LocaleKeys.signIn_signUpWithGithub.tr(),
|
||||
LocaleKeys.signIn_signUpWithDiscord.tr()
|
||||
),
|
||||
};
|
||||
return Column(
|
||||
children: [
|
||||
_ThirdPartySignInButton(
|
||||
key: const Key('signInWithGoogleButton'),
|
||||
icon: FlowySvgs.google_mark_xl,
|
||||
labelText: googleText,
|
||||
onPressed: () {
|
||||
_signInWithGoogle(context);
|
||||
},
|
||||
),
|
||||
const VSpace(8),
|
||||
_ThirdPartySignInButton(
|
||||
icon: isDarkMode
|
||||
? 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_or_logout_button.dart';
|
||||
export 'switch_sign_in_sign_up_button.dart';
|
||||
export 'third_party_sign_in_buttons.dart';
|
||||
|
@ -11,9 +11,14 @@ class AuthFormContainer extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
width: width,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: children,
|
||||
child: ScrollConfiguration(
|
||||
behavior: ScrollConfiguration.of(context).copyWith(scrollbars: false),
|
||||
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/shared/feature_flags.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/user/application/user_listener.dart';
|
||||
import 'package:appflowy/user/application/user_service.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
@ -43,14 +40,7 @@ class UserWorkspaceBloc extends Bloc<UserWorkspaceEvent, UserWorkspaceState> {
|
||||
userProfile.authenticator == AuthenticatorPB.AppFlowyCloud &&
|
||||
FeatureFlag.collaborativeWorkspace.isOn;
|
||||
if (currentWorkspace != null && result.$3 == true) {
|
||||
final result = await _userService
|
||||
.openWorkspace(currentWorkspace.workspaceId);
|
||||
result.onSuccess((s) async {
|
||||
await getIt<KeyValueStorage>().set(
|
||||
KVKeys.lastOpenedWorkspaceId,
|
||||
currentWorkspace.workspaceId,
|
||||
);
|
||||
});
|
||||
await _userService.openWorkspace(currentWorkspace.workspaceId);
|
||||
}
|
||||
emit(
|
||||
state.copyWith(
|
||||
@ -176,12 +166,6 @@ class UserWorkspaceBloc extends Bloc<UserWorkspaceEvent, UserWorkspaceState> {
|
||||
),
|
||||
(e) => state.currentWorkspace,
|
||||
);
|
||||
result.onSuccess((_) async {
|
||||
await getIt<KeyValueStorage>().set(
|
||||
KVKeys.lastOpenedWorkspaceId,
|
||||
workspaceId,
|
||||
);
|
||||
});
|
||||
emit(
|
||||
state.copyWith(
|
||||
currentWorkspace: currentWorkspace,
|
||||
@ -317,32 +301,22 @@ class UserWorkspaceBloc extends Bloc<UserWorkspaceEvent, UserWorkspaceState> {
|
||||
bool shouldOpenWorkspace,
|
||||
)> _fetchWorkspaces() async {
|
||||
try {
|
||||
final lastOpenedWorkspaceId = await getIt<KeyValueStorage>().get(
|
||||
KVKeys.lastOpenedWorkspaceId,
|
||||
);
|
||||
final currentWorkspace =
|
||||
await _userService.getCurrentWorkspace().getOrThrow();
|
||||
final workspaces = await _userService.getWorkspaces().getOrThrow();
|
||||
if (workspaces.isEmpty) {
|
||||
workspaces.add(convertWorkspacePBToUserWorkspace(currentWorkspace));
|
||||
}
|
||||
UserWorkspacePB? currentWorkspaceInList = workspaces
|
||||
.firstWhereOrNull((e) => e.workspaceId == currentWorkspace.id);
|
||||
if (lastOpenedWorkspaceId != null) {
|
||||
final lastOpenedWorkspace = workspaces
|
||||
.firstWhereOrNull((e) => e.workspaceId == lastOpenedWorkspaceId);
|
||||
if (lastOpenedWorkspace != null) {
|
||||
currentWorkspaceInList = lastOpenedWorkspace;
|
||||
}
|
||||
}
|
||||
currentWorkspaceInList ??= workspaces.firstOrNull;
|
||||
final currentWorkspaceInList = workspaces
|
||||
.firstWhereOrNull((e) => e.workspaceId == currentWorkspace.id) ??
|
||||
workspaces.firstOrNull;
|
||||
return (
|
||||
currentWorkspaceInList,
|
||||
workspaces
|
||||
..sort(
|
||||
(a, b) => a.createdAtTimestamp.compareTo(b.createdAtTimestamp),
|
||||
),
|
||||
lastOpenedWorkspaceId != currentWorkspace.id
|
||||
currentWorkspaceInList?.workspaceId != currentWorkspace.id
|
||||
);
|
||||
} catch (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/startup/plugin/plugin.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/settings/appearance/appearance_cubit.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/presentation/home/errors/workspace_failed_screen.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'
|
||||
show UserProfilePB;
|
||||
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:sized_context/sized_context.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
|
||||
import '../widgets/edit_panel/edit_panel.dart';
|
||||
|
||||
import 'home_layout.dart';
|
||||
import 'home_stack.dart';
|
||||
|
||||
@ -115,9 +114,15 @@ class DesktopHomeScreen extends StatelessWidget {
|
||||
},
|
||||
child: BlocBuilder<HomeSettingBloc, HomeSettingState>(
|
||||
buildWhen: (previous, current) => previous != current,
|
||||
builder: (context, state) => FlowyContainer(
|
||||
Theme.of(context).colorScheme.surface,
|
||||
child: _buildBody(context, userProfile, workspaceSetting),
|
||||
builder: (context, state) => BlocProvider(
|
||||
create: (_) => UserWorkspaceBloc(userProfile: userProfile)
|
||||
..add(
|
||||
const UserWorkspaceEvent.initial(),
|
||||
),
|
||||
child: FlowyContainer(
|
||||
Theme.of(context).colorScheme.surface,
|
||||
child: _buildBody(context, userProfile, workspaceSetting),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -1,6 +1,5 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.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/navigation_action.dart';
|
||||
@ -21,6 +20,7 @@ import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart'
|
||||
show UserProfilePB;
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
/// 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
|
||||
// |
|
||||
// +-- Trash Section
|
||||
return BlocProvider<UserWorkspaceBloc>(
|
||||
create: (_) => UserWorkspaceBloc(userProfile: userProfile)
|
||||
..add(
|
||||
const UserWorkspaceEvent.initial(),
|
||||
),
|
||||
child: BlocBuilder<UserWorkspaceBloc, UserWorkspaceState>(
|
||||
// Rebuild the whole sidebar when the current workspace changes
|
||||
buildWhen: (previous, current) =>
|
||||
previous.currentWorkspace?.workspaceId !=
|
||||
current.currentWorkspace?.workspaceId,
|
||||
builder: (context, state) {
|
||||
if (state.currentWorkspace == null) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
return MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider(create: (_) => getIt<ActionNavigationBloc>()),
|
||||
BlocProvider(
|
||||
create: (_) => SidebarSectionsBloc()
|
||||
..add(
|
||||
SidebarSectionsEvent.initial(
|
||||
userProfile,
|
||||
state.currentWorkspace?.workspaceId ??
|
||||
workspaceSetting.workspaceId,
|
||||
),
|
||||
return BlocBuilder<UserWorkspaceBloc, UserWorkspaceState>(
|
||||
// Rebuild the whole sidebar when the current workspace changes
|
||||
buildWhen: (previous, current) =>
|
||||
previous.currentWorkspace?.workspaceId !=
|
||||
current.currentWorkspace?.workspaceId,
|
||||
builder: (context, state) {
|
||||
if (state.currentWorkspace == null) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
return MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider(create: (_) => getIt<ActionNavigationBloc>()),
|
||||
BlocProvider(
|
||||
create: (_) => SidebarSectionsBloc()
|
||||
..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(
|
||||
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),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
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/locale_keys.g.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/style_widget/hover.dart';
|
||||
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
typedef ViewItemOnSelected = void Function(ViewPB);
|
||||
@ -411,19 +410,6 @@ class _SingleInnerViewItemState extends State<SingleInnerViewItem> {
|
||||
behavior: HitTestBehavior.translucent,
|
||||
onTap: () => widget.onSelected(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(
|
||||
height: widget.height,
|
||||
child: Padding(
|
||||
|
@ -59,6 +59,7 @@ void showSnackBarMessage(
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
backgroundColor: Theme.of(context).colorScheme.surfaceVariant,
|
||||
duration: duration,
|
||||
action: !showCancel
|
||||
? null
|
||||
: SnackBarAction(
|
||||
|
@ -1,6 +1,7 @@
|
||||
import 'package:appflowy/plugins/base/emoji/emoji_text.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/user/user_workspace_bloc.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_service.dart';
|
||||
@ -13,7 +14,7 @@ import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
// workspaces / ... / view_title
|
||||
// workspace name / ... / view_title
|
||||
class ViewTitleBar extends StatefulWidget {
|
||||
const ViewTitleBar({
|
||||
super.key,
|
||||
@ -58,7 +59,7 @@ class _ViewTitleBarState extends State<ViewTitleBar> {
|
||||
final replacement = Row(
|
||||
// refresh the view title bar when the ancestors changed
|
||||
key: ValueKey(ancestors.hashCode),
|
||||
children: _buildViewTitles(ancestors),
|
||||
children: _buildViewTitles(context, ancestors),
|
||||
);
|
||||
return LayoutBuilder(
|
||||
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
|
||||
bool hasAddedEllipsis = false;
|
||||
final children = <Widget>[];
|
||||
|
||||
for (var i = 0; i < views.length; i++) {
|
||||
final view = views[i];
|
||||
|
||||
if (i >= 1 && i < views.length - 2) {
|
||||
if (!hasAddedEllipsis) {
|
||||
hasAddedEllipsis = true;
|
||||
@ -95,8 +97,30 @@ class _ViewTitleBarState extends State<ViewTitleBar> {
|
||||
}
|
||||
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,
|
||||
child: _ViewTitle(
|
||||
view: view,
|
||||
@ -105,8 +129,11 @@ class _ViewTitleBarState extends State<ViewTitleBar> {
|
||||
: _ViewTitleBehavior.uneditable, // others are not editable
|
||||
onUpdated: () => setState(() => _reloadAncestors()),
|
||||
),
|
||||
),
|
||||
);
|
||||
);
|
||||
}
|
||||
|
||||
children.add(child);
|
||||
|
||||
if (i != views.length - 1) {
|
||||
// if not the last one, add a divider
|
||||
children.add(const FlowyText.regular('/'));
|
||||
|
@ -1,3 +1,5 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
@ -7,19 +9,16 @@ void showSnapBar(BuildContext context, String title, {VoidCallback? onClosed}) {
|
||||
ScaffoldMessenger.of(context)
|
||||
.showSnackBar(
|
||||
SnackBar(
|
||||
backgroundColor: Theme.of(context).colorScheme.surfaceVariant,
|
||||
duration: const Duration(milliseconds: 8000),
|
||||
content: PopScope(
|
||||
canPop: () {
|
||||
ScaffoldMessenger.of(context).removeCurrentSnackBar();
|
||||
return true;
|
||||
}(),
|
||||
child: FlowyText.medium(
|
||||
title,
|
||||
fontSize: 12,
|
||||
maxLines: 3,
|
||||
),
|
||||
content: FlowyText(
|
||||
title,
|
||||
maxLines: 2,
|
||||
fontSize:
|
||||
(Platform.isLinux || Platform.isWindows || Platform.isMacOS)
|
||||
? 14
|
||||
: 12,
|
||||
),
|
||||
backgroundColor: Theme.of(context).colorScheme.background,
|
||||
),
|
||||
)
|
||||
.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",
|
||||
"passwordHint": "Password",
|
||||
"dontHaveAnAccount": "Don't have an account?",
|
||||
"createAccount": "Create account",
|
||||
"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",
|
||||
"signInWithGoogle": "Log in with Google",
|
||||
"signInWithGithub": "Log in with Github",
|
||||
"signInWithDiscord": "Log in with Discord",
|
||||
"signUpWithGoogle": "Sign up with Google",
|
||||
"signUpWithGithub": "Sign up with Github",
|
||||
"signUpWithDiscord": "Sign up with Discord",
|
||||
"signInWith": "Sign in with:",
|
||||
"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",
|
||||
"magicLinkSent": "Magic link sent to your email, please check your inbox",
|
||||
"invalidEmail": "Please enter a valid email address"
|
||||
"magicLinkSent": "We emailed a magic link. Click the link to log in.",
|
||||
"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": {
|
||||
"chooseWorkspace": "Choose your workspace",
|
||||
@ -912,7 +921,8 @@
|
||||
"image": {
|
||||
"copiedToPasteBoard": "The image link has been copied to the clipboard",
|
||||
"addAnImage": "Add an image",
|
||||
"imageUploadFailed": "Image upload failed"
|
||||
"imageUploadFailed": "Image upload failed",
|
||||
"errorCode": "Error code"
|
||||
},
|
||||
"urlPreview": {
|
||||
"copiedToPasteBoard": "The link has been copied to the clipboard",
|
||||
@ -1375,7 +1385,7 @@
|
||||
"upload": "Upload",
|
||||
"chooseImage": "Choose an image",
|
||||
"loading": "Loading",
|
||||
"imageLoadFailed": "Could not load the image",
|
||||
"imageLoadFailed": "Image load failed",
|
||||
"divider": "Divider",
|
||||
"table": "Table",
|
||||
"colAddBefore": "Add before",
|
||||
|
Loading…
Reference in New Issue
Block a user