chore: merge with main

This commit is contained in:
Zack Fu Zi Xiang 2024-05-16 13:58:44 +08:00
commit 38c7bfe721
No known key found for this signature in database
57 changed files with 1466 additions and 919 deletions

View File

@ -1,4 +1,22 @@
# Release Notes
## Version 0.5.7 - 05/10/2024
### Bug Fixes
- Resolved page opening issue on Android.
- Fixed text input inconsistency on Kanban board cards.
## Version 0.5.6 - 05/07/2024
### New Features
- Team collaboration is live! Add members to your workspace to edit and collaborate on pages together.
- Collaborate in real time on the same page with other members. Edits made by others will appear instantly.
- Create multiple workspaces for different kinds of content.
- Customize your entire page on mobile through the Page Style menu with options for layout, font, font size, emoji, and cover image.
- Open a row record as a full page.
### Bug Fixes
- Resolved issue with setting background color for the Simple Table block.
- Adjusted toolbar for various screen sizes.
- Added a request for photo permission before uploading images on mobile.
- Exported creation and last modification timestamps to CSV.
## Version 0.5.5 - 04/24/2024
### New Features
- Improved the display of code blocks with line numbers

View File

@ -26,7 +26,7 @@ CARGO_MAKE_EXTEND_WORKSPACE_MAKEFILE = true
CARGO_MAKE_CRATE_FS_NAME = "dart_ffi"
CARGO_MAKE_CRATE_NAME = "dart-ffi"
LIB_NAME = "dart_ffi"
APPFLOWY_VERSION = "0.5.6"
APPFLOWY_VERSION = "0.5.7"
FLUTTER_DESKTOP_FEATURES = "dart"
PRODUCT_NAME = "AppFlowy"
MACOSX_DEPLOYMENT_TARGET = "11.0"

View File

@ -101,7 +101,7 @@ void main() {
// open settings and restore the location
await tester.openSettings();
await tester.openSettingsPage(SettingsPage.files);
await tester.openSettingsPage(SettingsPage.manageData);
await tester.restoreLocation();
expect(

View File

@ -39,10 +39,14 @@ extension AppFlowySettings on WidgetTester {
/// Restore the AppFlowy data storage location
Future<void> restoreLocation() async {
final button =
find.byTooltip(LocaleKeys.settings_files_recoverLocationTooltips.tr());
final button = find.text(LocaleKeys.settings_common_reset.tr());
expect(button, findsOneWidget);
await tapButton(button);
await pumpAndSettle();
final confirmButton = find.text(LocaleKeys.button_confirm.tr());
expect(confirmButton, findsOneWidget);
await tapButton(confirmButton);
return;
}

View File

@ -1,6 +1,15 @@
import 'dart:io';
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/shared/window_title_bar.dart';
import 'package:appflowy/workspace/application/home/home_setting_bloc.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/style_widget/icon_button.dart';
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
import 'package:flutter/services.dart';
import 'package:flutter/material.dart';
import 'dart:io' show Platform;
import 'package:flutter_bloc/flutter_bloc.dart';
class CocoaWindowChannel {
CocoaWindowChannel._();
@ -26,9 +35,14 @@ class CocoaWindowChannel {
}
class MoveWindowDetector extends StatefulWidget {
const MoveWindowDetector({super.key, this.child});
const MoveWindowDetector({
super.key,
this.child,
this.showTitleBar = false,
});
final Widget? child;
final bool showTitleBar;
@override
MoveWindowDetectorState createState() => MoveWindowDetectorState();
@ -40,15 +54,32 @@ class MoveWindowDetectorState extends State<MoveWindowDetector> {
@override
Widget build(BuildContext context) {
if (!Platform.isMacOS) {
if (!Platform.isMacOS && !Platform.isWindows) {
return widget.child ?? const SizedBox.shrink();
}
if (Platform.isWindows) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
if (widget.showTitleBar) ...[
WindowTitleBar(
leftChildren: [
_buildToggleMenuButton(context),
],
),
] else ...[
const SizedBox(height: 5),
],
widget.child ?? const SizedBox.shrink(),
],
);
}
return GestureDetector(
// https://stackoverflow.com/questions/52965799/flutter-gesturedetector-not-working-with-containers-in-stack
behavior: HitTestBehavior.translucent,
onDoubleTap: () async {
await CocoaWindowChannel.instance.zoom();
},
onDoubleTap: () async => CocoaWindowChannel.instance.zoom(),
onPanStart: (DragStartDetails details) {
winX = details.globalPosition.dx;
winY = details.globalPosition.dy;
@ -65,4 +96,29 @@ class MoveWindowDetectorState extends State<MoveWindowDetector> {
child: widget.child,
);
}
Widget _buildToggleMenuButton(BuildContext context) {
if (!context.read<HomeSettingBloc>().state.isMenuCollapsed) {
return const SizedBox.shrink();
}
return FlowyTooltip(
richMessage: TextSpan(
children: [
TextSpan(text: '${LocaleKeys.sideBar_closeSidebar.tr()}\n'),
const TextSpan(text: 'Ctrl+\\'),
],
),
child: FlowyIconButton(
hoverColor: Colors.transparent,
onPressed: () => context
.read<HomeSettingBloc>()
.add(const HomeSettingEvent.collapseMenu()),
iconPadding: const EdgeInsets.fromLTRB(4, 4, 4, 4),
icon: context.read<HomeSettingBloc>().state.isMenuCollapsed
? const FlowySvg(FlowySvgs.show_menu_s)
: const FlowySvg(FlowySvgs.hide_menu_m),
),
);
}
}

View File

@ -1,8 +1,10 @@
import 'package:appflowy/mobile/application/page_style/document_page_style_bloc.dart';
import 'package:appflowy/user/application/user_service.dart';
import 'package:appflowy/workspace/application/view/prelude.dart';
import 'package:appflowy/workspace/application/view/view_ext.dart';
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
import 'package:appflowy_result/appflowy_result.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
@ -21,6 +23,9 @@ class MobileViewPageBloc
initial: () async {
_registerListeners();
final userProfilePB =
await UserBackendService.getCurrentUserProfile()
.fold((s) => s, (f) => null);
final result = await ViewBackendService.getView(viewId);
final isImmersiveMode =
_isImmersiveMode(result.fold((s) => s, (f) => null));
@ -29,6 +34,7 @@ class MobileViewPageBloc
isLoading: false,
result: result,
isImmersiveMode: isImmersiveMode,
userProfilePB: userProfilePB,
),
);
},
@ -71,7 +77,7 @@ class MobileViewPageBloc
final cover = view.cover;
if (cover == null || cover.type == PageStyleCoverImageType.none) {
return false;
} else if (view.layout == ViewLayoutPB.Document) {
} else if (view.layout == ViewLayoutPB.Document && !cover.isPresets) {
// only support immersive mode for document layout
return true;
}
@ -93,6 +99,7 @@ class MobileViewPageState with _$MobileViewPageState {
@Default(true) bool isLoading,
@Default(null) FlowyResult<ViewPB, FlowyError>? result,
@Default(false) bool isImmersiveMode,
@Default(null) UserProfilePB? userProfilePB,
}) = _MobileViewPageState;
factory MobileViewPageState.initial() => const MobileViewPageState();

View File

@ -37,6 +37,7 @@ class FlowyAppBar extends AppBar {
Widget? title,
String? titleText,
FlowyAppBarLeadingType leadingType = FlowyAppBarLeadingType.back,
double? leadingWidth,
Widget? leading,
super.centerTitle,
VoidCallback? onTapLeading,
@ -52,7 +53,7 @@ class FlowyAppBar extends AppBar {
titleSpacing: 0,
elevation: 0,
leading: leading ?? leadingType.getWidget(onTapLeading),
leadingWidth: leadingType.width,
leadingWidth: leadingWidth ?? leadingType.width,
toolbarHeight: 44.0,
bottom: showDivider
? const PreferredSize(

View File

@ -43,8 +43,9 @@ class MobileViewPageImmersiveAppBar extends StatelessWidget
AppBarTheme.of(context).backgroundColor?.withOpacity(opacity),
showDivider: false,
title: Opacity(opacity: opacity >= 0.99 ? 1.0 : 0, child: title),
leadingWidth: 44,
leading: Padding(
padding: const EdgeInsets.symmetric(horizontal: 2.0, vertical: 4.0),
padding: const EdgeInsets.only(top: 4.0, bottom: 4.0, left: 12.0),
child: _buildAppBarBackButton(context),
),
actions: actions,
@ -59,7 +60,7 @@ class MobileViewPageImmersiveAppBar extends StatelessWidget
child: _ImmersiveAppBarButton(
icon: FlowySvgs.m_app_bar_back_s,
dimension: 30.0,
iconPadding: 6.0,
iconPadding: 3.0,
isImmersiveMode:
context.read<MobileViewPageBloc>().state.isImmersiveMode,
appBarOpacity: appBarOpacity,
@ -104,7 +105,7 @@ class MobileViewPageMoreButton extends StatelessWidget {
child: _ImmersiveAppBarButton(
icon: FlowySvgs.m_app_bar_more_s,
dimension: 30.0,
iconPadding: 5.0,
iconPadding: 3.0,
isImmersiveMode: isImmersiveMode,
appBarOpacity: appBarOpacity,
),
@ -144,8 +145,11 @@ class MobileViewPageLayoutButton extends StatelessWidget {
showHeader: true,
title: LocaleKeys.pageStyle_title.tr(),
backgroundColor: Theme.of(context).colorScheme.background,
builder: (_) => BlocProvider.value(
value: context.read<DocumentPageStyleBloc>(),
builder: (_) => MultiBlocProvider(
providers: [
BlocProvider.value(value: context.read<DocumentPageStyleBloc>()),
BlocProvider.value(value: context.read<MobileViewPageBloc>()),
],
child: PageStyleBottomSheet(
view: context.read<ViewBloc>().state.view,
),
@ -155,7 +159,7 @@ class MobileViewPageLayoutButton extends StatelessWidget {
child: _ImmersiveAppBarButton(
icon: FlowySvgs.m_layout_s,
dimension: 30.0,
iconPadding: 5.0,
iconPadding: 3.0,
isImmersiveMode: isImmersiveMode,
appBarOpacity: appBarOpacity,
),

View File

@ -163,19 +163,18 @@ class _TextCellState extends State<TextCardCell> {
return BlocBuilder<TextCellBloc, TextCellState>(
builder: (context, state) {
final content = state.content;
final text = content.isEmpty
? LocaleKeys.grid_row_textPlaceholder.tr()
: content;
final color = content.isEmpty ? Theme.of(context).hintColor : null;
return Padding(
padding: widget.style.padding,
child: Text(
text,
style: widget.style.textStyle.copyWith(color: color),
maxLines: widget.style.maxLines,
),
);
return content.isEmpty
? const SizedBox.shrink()
: Container(
padding: widget.style.padding,
alignment: AlignmentDirectional.centerStart,
child: Text(
content,
style: widget.style.textStyle,
maxLines: widget.style.maxLines,
),
);
},
);
}

View File

@ -79,8 +79,11 @@ class _NumberCellState extends GridEditableTextCell<EditableNumberCell> {
return BlocProvider.value(
value: cellBloc,
child: BlocListener<NumberCellBloc, NumberCellState>(
listener: (context, state) =>
_textEditingController.text = state.content,
listener: (context, state) {
if (!focusNode.hasFocus) {
_textEditingController.text = state.content;
}
},
child: Builder(
builder: (context) {
return widget.skin.build(

View File

@ -112,8 +112,10 @@ class _GridURLCellState extends GridEditableTextCell<EditableURLCell> {
child: BlocListener<URLCellBloc, URLCellState>(
listenWhen: (previous, current) => previous.content != current.content,
listener: (context, state) {
_textEditingController.value =
_textEditingController.value.copyWith(text: state.content);
if (!focusNode.hasFocus) {
_textEditingController.value =
_textEditingController.value.copyWith(text: state.content);
}
widget._cellDataNotifier.value = state.content;
},
child: widget.skin.build(

View File

@ -1,8 +1,5 @@
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/document/application/document_bloc.dart';
import 'package:appflowy/plugins/document/presentation/editor_configuration.dart';
@ -10,6 +7,7 @@ import 'package:appflowy/plugins/document/presentation/editor_plugins/align_tool
import 'package:appflowy/plugins/document/presentation/editor_plugins/background_color/theme_background_color.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/base/format_arrow_character.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/base/page_reference_commands.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/callout/callout_block_shortcuts.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/i18n/editor_i18n.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/slash_menu_items.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
@ -29,6 +27,8 @@ import 'package:collection/collection.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/theme_extension.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
final codeBlockLocalization = CodeBlockLocalizations(
@ -148,6 +148,9 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
// code block
...codeBlockCharacterEvents,
// callout block
insertNewLineInCalloutBlock,
// toggle list
formatGreaterToToggleList,
insertChildNodeInsideToggleList,

View File

@ -0,0 +1,66 @@
import 'package:flutter/material.dart';
class EditorFontColors {
static final lightColors = [
const Color(0x00FFFFFF),
const Color(0xFFE8E0FF),
const Color(0xFFFFE6FD),
const Color(0xFFFFDAE6),
const Color(0xFFFFEFE3),
const Color(0xFFF5FFDC),
const Color(0xFFDDFFD6),
const Color(0xFFDEFFF1),
const Color(0xFFE1FBFF),
const Color(0xFFFFADAD),
const Color(0xFFFFE088),
const Color(0xFFA7DF4A),
const Color(0xFFD4C0FF),
const Color(0xFFFDB2FE),
const Color(0xFFFFD18B),
const Color(0xFF65E7F0),
const Color(0xFF71E6B4),
const Color(0xFF80F1FF),
];
static final darkColors = [
const Color(0x00FFFFFF),
const Color(0xFF8B80AD),
const Color(0xFF987195),
const Color(0xFF906D78),
const Color(0xFFA68B77),
const Color(0xFF88936D),
const Color(0xFF72936B),
const Color(0xFF6B9483),
const Color(0xFF658B90),
const Color(0xFF95405A),
const Color(0xFFA6784D),
const Color(0xFF6E9234),
const Color(0xFF6455A2),
const Color(0xFF924F83),
const Color(0xFFA48F34),
const Color(0xFF29A3AC),
const Color(0xFF2E9F84),
const Color(0xFF405EA6),
];
// if the input color doesn't exist in the list, return the input color itself.
static Color? fromBuiltInColors(BuildContext context, Color? color) {
if (color == null) {
return null;
}
final brightness = Theme.of(context).brightness;
// if the dark mode color using light mode, return it's corresponding light color. Same for light mode.
if (brightness == Brightness.light) {
if (darkColors.contains(color)) {
return lightColors[darkColors.indexOf(color)];
}
} else {
if (lightColors.contains(color)) {
return darkColors[lightColors.indexOf(color)];
}
}
return color;
}
}

View File

@ -56,7 +56,7 @@ SelectionMenuItem calloutItem = SelectionMenuItem.node(
iconData: Icons.note,
keywords: [CalloutBlockKeys.type],
nodeBuilder: (editorState, context) =>
calloutNode(defaultColor: AFThemeExtension.of(context).calloutBGColor),
calloutNode(defaultColor: Colors.transparent),
replace: (_, node) => node.delta?.isEmpty ?? false,
updateSelection: (_, path, __, ___) {
return Selection.single(path: path, startOffset: 0);

View File

@ -0,0 +1,41 @@
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:flutter/services.dart';
/// Pressing Enter in a callout block will insert a newline (\n) within the callout,
/// while pressing Shift+Enter in a callout will insert a new paragraph next to the callout.
///
/// - support
/// - desktop
/// - mobile
/// - web
///
final CharacterShortcutEvent insertNewLineInCalloutBlock =
CharacterShortcutEvent(
key: 'insert a new line in callout block',
character: '\n',
handler: _insertNewLineHandler,
);
CharacterShortcutEventHandler _insertNewLineHandler = (editorState) async {
final selection = editorState.selection?.normalized;
if (selection == null) {
return false;
}
final node = editorState.getNodeAtPath(selection.start.path);
if (node == null || node.type != CalloutBlockKeys.type) {
return false;
}
// delete the selection
await editorState.deleteSelection(selection);
if (HardwareKeyboard.instance.isShiftPressed) {
await editorState.insertNewLine();
} else {
await editorState.insertTextAtCurrentSelection('\n');
}
return true;
};

View File

@ -149,14 +149,11 @@ class _DocumentImmersiveCoverState extends State<DocumentImmersiveCover> {
fontSize: 28.0,
fontWeight: FontWeight.w700,
fontFamily: fontFamily,
color: state.cover.type == PageStyleCoverImageType.none
? null
: Colors.white,
color:
state.cover.isNone || state.cover.isPresets ? null : Colors.white,
),
onSubmitted: (value) {
scrollController.position.jumpTo(0);
context.read<ViewBloc>().add(ViewEvent.rename(value));
},
onChanged: _rename,
onSubmitted: _rename,
);
}
@ -250,4 +247,9 @@ class _DocumentImmersiveCoverState extends State<DocumentImmersiveCover> {
focusNode.unfocus(disposition: UnfocusDisposition.previouslyFocusedChild);
}
}
void _rename(String name) {
scrollController.position.jumpTo(0);
context.read<ViewBloc>().add(ViewEvent.rename(name));
}
}

View File

@ -131,12 +131,16 @@ class _UnsplashImages extends StatelessWidget {
};
final mainAxisSpacing = switch (type) {
UnsplashImageType.halfScreen => 16.0,
UnsplashImageType.fullScreen => 8.0,
UnsplashImageType.fullScreen => 16.0,
};
final crossAxisSpacing = switch (type) {
UnsplashImageType.halfScreen => 10.0,
UnsplashImageType.fullScreen => 16.0,
};
return GridView.count(
crossAxisCount: crossAxisCount,
mainAxisSpacing: mainAxisSpacing,
crossAxisSpacing: 10.0,
crossAxisSpacing: crossAxisSpacing,
childAspectRatio: 4 / 3,
children: photos
.map(
@ -197,28 +201,31 @@ class _UnsplashImage extends StatelessWidget {
}
Widget _buildFullScreenImage(BuildContext context) {
return Stack(
children: [
LayoutBuilder(
builder: (context, constraints) {
return Image.network(
photo.urls.thumb.toString(),
fit: BoxFit.cover,
width: constraints.maxWidth,
height: constraints.maxHeight,
);
},
),
Positioned(
bottom: 6,
left: 6,
child: FlowyText.medium(
photo.name,
fontSize: 10.0,
color: Colors.white,
return ClipRRect(
borderRadius: BorderRadius.circular(8.0),
child: Stack(
children: [
LayoutBuilder(
builder: (context, constraints) {
return Image.network(
photo.urls.thumb.toString(),
fit: BoxFit.cover,
width: constraints.maxWidth,
height: constraints.maxHeight,
);
},
),
),
],
Positioned(
bottom: 9,
left: 10,
child: FlowyText.medium(
photo.name,
fontSize: 13.0,
color: Colors.white,
),
),
],
),
);
}
}

View File

@ -1,3 +1,5 @@
import 'dart:io';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:easy_localization/easy_localization.dart';
@ -100,14 +102,7 @@ class CustomMobileFloatingToolbar extends StatelessWidget {
Widget build(BuildContext context) {
return Animate(
autoPlay: true,
effects: [
const FadeEffect(duration: SelectionOverlay.fadeDuration),
MoveEffect(
curve: Curves.easeOutCubic,
begin: const Offset(0, 16),
duration: 100.milliseconds,
),
],
effects: _getEffects(context),
child: AdaptiveTextSelectionToolbar.buttonItems(
buttonItems: buildMobileFloatingToolbarItems(
editorState,
@ -120,4 +115,32 @@ class CustomMobileFloatingToolbar extends StatelessWidget {
),
);
}
List<Effect> _getEffects(BuildContext context) {
if (Platform.isIOS) {
final Size(:width, :height) = MediaQuery.of(context).size;
final alignmentX = (anchor.dx - width / 2) / (width / 2);
final alignmentY = (anchor.dy - height / 2) / (height / 2);
return [
ScaleEffect(
curve: Curves.easeInOut,
alignment: Alignment(alignmentX, alignmentY),
duration: 250.milliseconds,
),
];
} else if (Platform.isAndroid) {
return [
const FadeEffect(
duration: SelectionOverlay.fadeDuration,
),
MoveEffect(
curve: Curves.easeOutCubic,
begin: const Offset(0, 16),
duration: 100.milliseconds,
),
];
} else {
return [];
}
}
}

View File

@ -1,6 +1,7 @@
import 'dart:async';
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/base/font_colors.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/_get_selection_color.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/aa_menu/_color_list.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/aa_menu/_toolbar_theme.dart';
@ -25,7 +26,10 @@ class ColorItem extends StatelessWidget {
editorState.getSelectionColor(AppFlowyRichTextKeys.textColor);
final String? selectedBackgroundColor =
editorState.getSelectionColor(AppFlowyRichTextKeys.backgroundColor);
final backgroundColor = EditorFontColors.fromBuiltInColors(
context,
selectedBackgroundColor?.tryToColor(),
);
return MobileToolbarMenuItemWrapper(
size: const Size(82, 52),
onTap: () async {
@ -48,10 +52,12 @@ class ColorItem extends StatelessWidget {
);
},
icon: FlowySvgs.m_aa_font_color_m,
iconColor: selectedTextColor?.tryToColor(),
backgroundColor: selectedBackgroundColor?.tryToColor() ??
theme.toolbarMenuItemBackgroundColor,
selectedBackgroundColor: selectedBackgroundColor?.tryToColor(),
iconColor: EditorFontColors.fromBuiltInColors(
context,
selectedTextColor?.tryToColor(),
),
backgroundColor: backgroundColor ?? theme.toolbarMenuItemBackgroundColor,
selectedBackgroundColor: backgroundColor,
isSelected: selectedBackgroundColor != null,
showRightArrow: true,
iconPadding: const EdgeInsets.only(

View File

@ -1,6 +1,7 @@
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/base/font_colors.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/_get_selection_color.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/aa_menu/_toolbar_theme.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
@ -154,7 +155,7 @@ class _TextColorAndBackgroundColorState
}
class _BackgroundColors extends StatelessWidget {
_BackgroundColors({
const _BackgroundColors({
this.selectedColor,
required this.onSelectedColor,
});
@ -162,29 +163,11 @@ class _BackgroundColors extends StatelessWidget {
final Color? selectedColor;
final void Function(Color color) onSelectedColor;
final colors = [
const Color(0x00FFFFFF),
const Color(0xFFE8E0FF),
const Color(0xFFFFE6FD),
const Color(0xFFFFDAE6),
const Color(0xFFFFEFE3),
const Color(0xFFF5FFDC),
const Color(0xFFDDFFD6),
const Color(0xFFDEFFF1),
const Color(0xFFE1FBFF),
const Color(0xFFFFADAD),
const Color(0xFFFFE088),
const Color(0xFFA7DF4A),
const Color(0xFFD4C0FF),
const Color(0xFFFDB2FE),
const Color(0xFFFFD18B),
const Color(0xFFFFF176),
const Color(0xFF71E6B4),
const Color(0xFF80F1FF),
];
@override
Widget build(BuildContext context) {
final colors = Theme.of(context).brightness == Brightness.light
? EditorFontColors.lightColors
: EditorFontColors.darkColors;
return GridView.count(
crossAxisCount: _count,
shrinkWrap: true,

View File

@ -1,4 +1,5 @@
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/base/font_colors.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/aa_menu/_color_list.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
@ -122,11 +123,17 @@ final colorToolbarItem = AppFlowyMobileToolbarItem(
padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 8),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(9),
color: backgroundColor?.tryToColor(),
color: EditorFontColors.fromBuiltInColors(
context,
backgroundColor?.tryToColor(),
),
),
child: FlowySvg(
FlowySvgs.m_aa_font_color_m,
color: textColor?.tryToColor(),
color: EditorFontColors.fromBuiltInColors(
context,
textColor?.tryToColor(),
),
),
);
},

View File

@ -1,16 +1,21 @@
import 'dart:async';
import 'dart:io';
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/application/base/mobile_view_page_bloc.dart';
import 'package:appflowy/mobile/application/page_style/document_page_style_bloc.dart';
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/image_util.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/unsplash_image_widget.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/page_style/_page_cover_bottom_sheet.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/page_style/_page_style_util.dart';
import 'package:appflowy/shared/appflowy_network_image.dart';
import 'package:appflowy/shared/feedback_gesture_detector.dart';
import 'package:appflowy/shared/flowy_gradient_colors.dart';
import 'package:appflowy/shared/permission/permission_checker.dart';
import 'package:appflowy/user/application/user_service.dart';
import 'package:appflowy/util/string_extension.dart';
import 'package:appflowy_backend/log.dart';
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
import 'package:appflowy_result/appflowy_result.dart';
@ -19,6 +24,7 @@ import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/style_widget/snap_bar.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import 'package:image_picker/image_picker.dart';
class PageStyleCoverImage extends StatelessWidget {
@ -33,13 +39,11 @@ class PageStyleCoverImage extends StatelessWidget {
final backgroundColor = context.pageStyleBackgroundColor;
return BlocBuilder<DocumentPageStyleBloc, DocumentPageStyleState>(
builder: (context, state) {
return Row(
return Column(
children: [
_buildOptionGroup(
context,
backgroundColor,
state,
),
_buildOptionGroup(context, backgroundColor, state),
const VSpace(16.0),
_buildPreview(context, state),
],
);
},
@ -51,46 +55,124 @@ class PageStyleCoverImage extends StatelessWidget {
Color backgroundColor,
DocumentPageStyleState state,
) {
return Expanded(
child: Container(
decoration: BoxDecoration(
color: backgroundColor,
borderRadius: const BorderRadius.horizontal(
left: Radius.circular(12),
right: Radius.circular(12),
return Container(
decoration: BoxDecoration(
color: backgroundColor,
borderRadius: const BorderRadius.horizontal(
left: Radius.circular(12),
right: Radius.circular(12),
),
),
padding: const EdgeInsets.all(4.0),
child: Row(
children: [
_CoverOptionButton(
showLeftCorner: true,
showRightCorner: false,
selected: state.coverImage.isPresets,
onTap: () => _showPresets(context),
child: const _PresetCover(),
),
),
padding: const EdgeInsets.all(4.0),
child: Row(
children: [
_CoverOptionButton(
showLeftCorner: true,
showRightCorner: false,
selected: state.coverImage.isPresets,
onTap: () => _showPresets(context),
child: const _PresetCover(),
),
_CoverOptionButton(
showLeftCorner: false,
showRightCorner: false,
selected: state.coverImage.isPhoto,
onTap: () => _pickImage(context),
child: const _PhotoCover(),
),
_CoverOptionButton(
showLeftCorner: false,
showRightCorner: true,
selected: state.coverImage.isUnsplashImage,
onTap: () => _showUnsplash(context),
child: const _UnsplashCover(),
),
],
),
_CoverOptionButton(
showLeftCorner: false,
showRightCorner: false,
selected: state.coverImage.isPhoto,
onTap: () => _pickImage(context),
child: const _PhotoCover(),
),
_CoverOptionButton(
showLeftCorner: false,
showRightCorner: true,
selected: state.coverImage.isUnsplashImage,
onTap: () => _showUnsplash(context),
child: const _UnsplashCover(),
),
],
),
);
}
Widget _buildPreview(
BuildContext context,
DocumentPageStyleState state,
) {
final cover = state.coverImage;
if (cover.isNone) {
return const SizedBox.shrink();
}
final value = cover.value;
final type = cover.type;
Widget preview = const SizedBox.shrink();
if (type == PageStyleCoverImageType.customImage ||
type == PageStyleCoverImageType.unsplashImage) {
final userProfilePB =
context.read<MobileViewPageBloc>().state.userProfilePB;
preview = FlowyNetworkImage(
url: value,
userProfilePB: userProfilePB,
);
}
if (type == PageStyleCoverImageType.builtInImage) {
preview = Image.asset(
PageStyleCoverImageType.builtInImagePath(value),
fit: BoxFit.cover,
);
}
if (type == PageStyleCoverImageType.pureColor) {
final color = value.coverColor(context);
if (color != null) {
preview = ColoredBox(
color: color,
);
}
}
if (type == PageStyleCoverImageType.gradientColor) {
preview = Container(
decoration: BoxDecoration(
gradient: FlowyGradientColor.fromId(value).linear,
),
);
}
if (type == PageStyleCoverImageType.localImage) {
preview = Image.file(
File(value),
fit: BoxFit.cover,
);
}
return Row(
children: [
FlowyText(LocaleKeys.pageStyle_image.tr()),
const Spacer(),
Container(
width: 40,
height: 28,
clipBehavior: Clip.hardEdge,
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(Radius.circular(6.0)),
border: Border.all(color: const Color(0x1F222533)),
),
child: ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(5.0)),
child: preview,
),
),
],
);
}
void _showPresets(BuildContext context) {
final pageStyleBloc = context.read<DocumentPageStyleBloc>();
context.pop();
showMobileBottomSheet(
context,
showDragHandle: true,
@ -99,18 +181,18 @@ class PageStyleCoverImage extends StatelessWidget {
showHeader: true,
showRemoveButton: true,
onRemove: () {
context.read<DocumentPageStyleBloc>().add(
DocumentPageStyleEvent.updateCoverImage(
PageStyleCover.none(),
),
);
pageStyleBloc.add(
DocumentPageStyleEvent.updateCoverImage(
PageStyleCover.none(),
),
);
},
title: LocaleKeys.pageStyle_pageCover.tr(),
title: LocaleKeys.pageStyle_presets.tr(),
barrierColor: Colors.transparent,
backgroundColor: Theme.of(context).colorScheme.background,
builder: (_) {
return BlocProvider.value(
value: context.read<DocumentPageStyleBloc>(),
value: pageStyleBloc,
child: const PageCoverBottomSheet(),
);
},
@ -174,6 +256,9 @@ class PageStyleCoverImage extends StatelessWidget {
}
void _showUnsplash(BuildContext context) {
final pageStyleBloc = context.read<DocumentPageStyleBloc>();
context.pop();
showMobileBottomSheet(
context,
showDragHandle: true,
@ -181,15 +266,15 @@ class PageStyleCoverImage extends StatelessWidget {
showDoneButton: true,
showHeader: true,
showRemoveButton: true,
title: LocaleKeys.pageStyle_coverImage.tr(),
title: LocaleKeys.pageStyle_unsplash.tr(),
barrierColor: Colors.transparent,
backgroundColor: Theme.of(context).colorScheme.background,
onRemove: () {
context.read<DocumentPageStyleBloc>().add(
DocumentPageStyleEvent.updateCoverImage(
PageStyleCover.none(),
),
);
pageStyleBloc.add(
DocumentPageStyleEvent.updateCoverImage(
PageStyleCover.none(),
),
);
},
builder: (_) {
return ConstrainedBox(
@ -204,14 +289,14 @@ class PageStyleCoverImage extends StatelessWidget {
child: UnsplashImageWidget(
type: UnsplashImageType.fullScreen,
onSelectUnsplashImage: (url) {
context.read<DocumentPageStyleBloc>().add(
DocumentPageStyleEvent.updateCoverImage(
PageStyleCover(
type: PageStyleCoverImageType.unsplashImage,
value: url,
),
),
);
pageStyleBloc.add(
DocumentPageStyleEvent.updateCoverImage(
PageStyleCover(
type: PageStyleCoverImageType.unsplashImage,
value: url,
),
),
);
},
),
),
@ -308,6 +393,7 @@ class _CoverOptionButton extends StatelessWidget {
duration: Durations.medium1,
decoration: selected
? ShapeDecoration(
color: const Color(0x141AC3F2),
shape: RoundedRectangleBorder(
side: const BorderSide(
width: 1.50,

View File

@ -11,8 +11,9 @@ import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_emoji_mart/flutter_emoji_mart.dart';
import 'package:go_router/go_router.dart';
class PageStyleIcon extends StatelessWidget {
class PageStyleIcon extends StatefulWidget {
const PageStyleIcon({
super.key,
required this.view,
@ -20,10 +21,15 @@ class PageStyleIcon extends StatelessWidget {
final ViewPB view;
@override
State<PageStyleIcon> createState() => _PageStyleIconState();
}
class _PageStyleIconState extends State<PageStyleIcon> {
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => PageStyleIconBloc(view: view)
create: (_) => PageStyleIconBloc(view: widget.view)
..add(const PageStyleIconEvent.initial()),
child: BlocBuilder<PageStyleIconBloc, PageStyleIconState>(
builder: (context, state) {
@ -60,6 +66,10 @@ class PageStyleIcon extends StatelessWidget {
}
void _showIconSelector(BuildContext context, String selectedIcon) {
context.pop();
final pageStyleIconBloc = PageStyleIconBloc(view: widget.view)
..add(const PageStyleIconEvent.initial());
showMobileBottomSheet(
context,
showDragHandle: true,
@ -75,13 +85,13 @@ class PageStyleIcon extends StatelessWidget {
initialChildSize: 0.61,
showRemoveButton: true,
onRemove: () {
context.read<PageStyleIconBloc>().add(
const PageStyleIconEvent.updateIcon('', true),
);
pageStyleIconBloc.add(
const PageStyleIconEvent.updateIcon('', true),
);
},
scrollableWidgetBuilder: (_, controller) {
return BlocProvider.value(
value: context.read<PageStyleIconBloc>(),
value: pageStyleIconBloc,
child: Expanded(
child: Scrollbar(
controller: controller,
@ -112,6 +122,8 @@ class _IconSelectorState extends State<_IconSelector> {
EmojiData? emojiData;
List<String> availableEmojis = [];
PageStyleIconBloc? pageStyleIconBloc;
@override
void initState() {
super.initState();
@ -131,6 +143,14 @@ class _IconSelectorState extends State<_IconSelector> {
},
);
}
pageStyleIconBloc = context.read<PageStyleIconBloc>();
}
@override
void dispose() {
pageStyleIconBloc?.close();
super.dispose();
}
@override
@ -146,7 +166,7 @@ class _IconSelectorState extends State<_IconSelector> {
_buildSearchBar(context),
Expanded(
child: GridView.count(
crossAxisCount: _getEmojiPerLine(context),
crossAxisCount: 7,
controller: widget.scrollController,
children: [
for (final emoji in availableEmojis)
@ -165,27 +185,33 @@ class _IconSelectorState extends State<_IconSelector> {
String emoji,
String? selectedEmoji,
) {
Widget child = Center(
child: FlowyText.emoji(
emoji,
fontSize: 24,
Widget child = SizedBox.square(
dimension: 24.0,
child: Center(
child: FlowyText.emoji(
emoji,
fontSize: 24,
),
),
);
if (emoji == selectedEmoji) {
child = Container(
margin: const EdgeInsets.all(8.0),
decoration: ShapeDecoration(
shape: RoundedRectangleBorder(
side: const BorderSide(
width: 1.50,
strokeAlign: BorderSide.strokeAlignOutside,
color: Color(0xFF00BCF0),
child = Center(
child: Container(
width: 40,
height: 40,
decoration: ShapeDecoration(
shape: RoundedRectangleBorder(
side: const BorderSide(
width: 1.40,
strokeAlign: BorderSide.strokeAlignOutside,
color: Color(0xFF00BCF0),
),
borderRadius: BorderRadius.circular(10),
),
borderRadius: BorderRadius.circular(9),
),
child: child,
),
child: child,
);
}
@ -208,11 +234,6 @@ class _IconSelectorState extends State<_IconSelector> {
return availableEmojis;
}
int _getEmojiPerLine(BuildContext context) {
final width = MediaQuery.of(context).size.width;
return width ~/ 48.0; // the size of the emoji
}
Widget _buildSearchBar(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(

View File

@ -11,6 +11,7 @@ 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';
const kPageStyleLayoutHeight = 52.0;
@ -117,6 +118,7 @@ class _OptionGroup<T> extends StatelessWidget {
duration: Durations.medium1,
decoration: selected
? ShapeDecoration(
color: const Color(0x141AC3F2),
shape: RoundedRectangleBorder(
side: const BorderSide(
width: 1.50,
@ -180,7 +182,10 @@ class _FontButton extends StatelessWidget {
const HSpace(16.0),
FlowyText(LocaleKeys.titleBar_font.tr()),
const Spacer(),
FlowyText(fontFamilyDisplayName),
FlowyText(
fontFamilyDisplayName,
color: context.pageStyleTextColor,
),
const HSpace(6.0),
const FlowySvg(FlowySvgs.m_page_style_arrow_right_s),
const HSpace(12.0),
@ -193,6 +198,9 @@ class _FontButton extends StatelessWidget {
}
void _showFontSelector(BuildContext context) {
final pageStyleBloc = context.read<DocumentPageStyleBloc>();
context.pop();
showMobileBottomSheet(
context,
showDragHandle: true,
@ -208,7 +216,7 @@ class _FontButton extends StatelessWidget {
initialChildSize: 0.61,
scrollableWidgetBuilder: (_, controller) {
return BlocProvider.value(
value: context.read<DocumentPageStyleBloc>(),
value: pageStyleBloc,
child: BlocBuilder<DocumentPageStyleBloc, DocumentPageStyleState>(
builder: (context, state) {
return Expanded(
@ -219,11 +227,11 @@ class _FontButton extends StatelessWidget {
selectedFontFamilyName:
state.fontFamily ?? defaultFontFamily,
onFontFamilySelected: (fontFamilyName) {
context.read<DocumentPageStyleBloc>().add(
DocumentPageStyleEvent.updateFontFamily(
fontFamilyName,
),
);
pageStyleBloc.add(
DocumentPageStyleEvent.updateFontFamily(
fontFamilyName,
),
);
},
),
),

View File

@ -25,7 +25,7 @@ class PageStyleBottomSheet extends StatelessWidget {
children: [
// cover image
FlowyText(
LocaleKeys.pageStyle_backgroundImage.tr(),
LocaleKeys.pageStyle_coverImage.tr(),
color: context.pageStyleTextColor,
fontSize: 14.0,
),

View File

@ -1,11 +1,9 @@
import 'dart:math';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:appflowy/core/helpers/url_launcher.dart';
import 'package:appflowy/mobile/application/page_style/document_page_style_bloc.dart';
import 'package:appflowy/plugins/document/application/document_appearance_cubit.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/base/font_colors.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mention_block.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/mobile_toolbar_item/utils.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
@ -17,6 +15,8 @@ import 'package:appflowy/workspace/application/settings/appearance/appearance_cu
import 'package:appflowy/workspace/application/settings/appearance/base_appearance.dart';
import 'package:appflowy_editor/appflowy_editor.dart' hide Log;
import 'package:collection/collection.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import 'package:google_fonts/google_fonts.dart';
@ -243,6 +243,21 @@ class EditorStyleCustomizer {
return before;
}
if (attributes.backgroundColor != null) {
final color = EditorFontColors.fromBuiltInColors(
context,
attributes.backgroundColor!,
);
if (color != null) {
return TextSpan(
text: before.text,
style: after.style?.merge(
TextStyle(backgroundColor: color),
),
);
}
}
// try to refresh font here.
if (attributes.fontFamily != null) {
try {

View File

@ -1,7 +1,14 @@
import 'package:appflowy/workspace/application/settings/appearance/base_appearance.dart';
import 'package:appflowy_backend/log.dart';
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
const _defaultFontFamilies = [
defaultFontFamily,
builtInCodeFontFamily,
fallbackFontFamily,
];
// if the font family is not available, google fonts packages will throw an exception
// this method will return the system font family if the font family is not available
TextStyle getGoogleFontSafely(
@ -12,19 +19,31 @@ TextStyle getGoogleFontSafely(
double? letterSpacing,
double? lineHeight,
}) {
try {
return GoogleFonts.getFont(
fontFamily,
// if the font family is the built-in font family, we can use it directly
if (_defaultFontFamilies.contains(fontFamily)) {
return TextStyle(
fontFamily: fontFamily.isEmpty ? null : fontFamily,
fontWeight: fontWeight,
fontSize: fontSize,
color: fontColor,
letterSpacing: letterSpacing,
height: lineHeight,
);
} catch (e) {
Log.error(
'Font family $fontFamily is not available, using default font family instead',
);
} else {
try {
return GoogleFonts.getFont(
fontFamily,
fontWeight: fontWeight,
fontSize: fontSize,
color: fontColor,
letterSpacing: letterSpacing,
height: lineHeight,
);
} catch (e) {
Log.error(
'Font family $fontFamily is not available, using default font family instead',
);
}
}
return TextStyle(

View File

@ -0,0 +1,108 @@
import 'package:flutter/material.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:flowy_infra_ui/widget/spacing.dart';
import 'package:window_manager/window_manager.dart';
class WindowsButtonListener extends WindowListener {
WindowsButtonListener();
final ValueNotifier<bool> isMaximized = ValueNotifier(false);
@override
void onWindowMaximize() => isMaximized.value = true;
@override
void onWindowUnmaximize() => isMaximized.value = false;
void dispose() => isMaximized.dispose();
}
class WindowTitleBar extends StatefulWidget {
const WindowTitleBar({
super.key,
this.leftChildren = const [],
});
final List<Widget> leftChildren;
@override
State<WindowTitleBar> createState() => _WindowTitleBarState();
}
class _WindowTitleBarState extends State<WindowTitleBar> {
late final WindowsButtonListener? windowsButtonListener;
bool isMaximized = false;
@override
void initState() {
super.initState();
if (PlatformExtension.isWindows || PlatformExtension.isLinux) {
windowsButtonListener = WindowsButtonListener();
windowManager.addListener(windowsButtonListener!);
windowsButtonListener!.isMaximized.addListener(() {
if (mounted) {
setState(
() => isMaximized = windowsButtonListener!.isMaximized.value,
);
}
});
} else {
windowsButtonListener = null;
}
windowManager.isMaximized().then(
(v) => mounted ? setState(() => isMaximized = v) : null,
);
}
@override
void dispose() {
if (windowsButtonListener != null) {
windowManager.removeListener(windowsButtonListener!);
windowsButtonListener?.dispose();
}
super.dispose();
}
@override
Widget build(BuildContext context) {
final brightness = Theme.of(context).brightness;
return Container(
height: 40,
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surfaceVariant,
),
child: DragToMoveArea(
child: Row(
children: [
const HSpace(4),
...widget.leftChildren,
const Spacer(),
WindowCaptionButton.minimize(
brightness: brightness,
onPressed: () => windowManager.minimize(),
),
if (isMaximized) ...[
WindowCaptionButton.unmaximize(
brightness: brightness,
onPressed: () => windowManager.unmaximize(),
),
] else ...[
WindowCaptionButton.maximize(
brightness: brightness,
onPressed: () => windowManager.maximize(),
),
],
WindowCaptionButton.close(
brightness: brightness,
onPressed: () => windowManager.close(),
),
],
),
),
);
}
}

View File

@ -6,8 +6,6 @@ import 'package:flutter/material.dart';
class AppFlowyApplication implements EntryPoint {
@override
Widget create(LaunchConfiguration config) {
return SplashScreen(
isAnon: config.isAnon,
);
return SplashScreen(isAnon: config.isAnon);
}
}

View File

@ -4,6 +4,7 @@ import 'dart:ui';
import 'package:appflowy/core/helpers/helpers.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/startup/tasks/app_window_size_manager.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:scaled_app/scaled_app.dart';
@ -46,6 +47,11 @@ class InitAppWindowTask extends LaunchTask with WindowListener {
await windowManager.show();
await windowManager.focus();
if (PlatformExtension.isWindows) {
// Hide title bar on Windows, we implement a custom solution elsewhere
await windowManager.setTitleBarStyle(TitleBarStyle.hidden);
}
final position = await windowsManager.getPosition();
if (position != null) {
await windowManager.setPosition(position);
@ -54,8 +60,7 @@ class InitAppWindowTask extends LaunchTask with WindowListener {
unawaited(
windowsManager.getScaleFactor().then(
(value) =>
ScaledWidgetsFlutterBinding.instance.scaleFactor = (_) => value,
(v) => ScaledWidgetsFlutterBinding.instance.scaleFactor = (_) => v,
),
);
}

View File

@ -1,3 +1,5 @@
import 'package:flutter/material.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/user/presentation/helpers/helpers.dart';
@ -6,7 +8,6 @@ import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/widget/buttons/secondary_button.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../application/encrypt_secret_bloc.dart';
@ -98,23 +99,20 @@ class _EncryptSecretScreenState extends State<EncryptSecretScreen> {
controller: _textEditingController,
hintText:
LocaleKeys.settings_menu_inputTextFieldHint.tr(),
onChanged: (p0) {},
onChanged: (_) {},
),
),
OkCancelButton(
alignment: MainAxisAlignment.end,
onOkPressed: () {
context.read<EncryptSecretBloc>().add(
EncryptSecretEvent.setEncryptSecret(
_textEditingController.text,
onOkPressed: () =>
context.read<EncryptSecretBloc>().add(
EncryptSecretEvent.setEncryptSecret(
_textEditingController.text,
),
),
);
},
onCancelPressed: () {
context.read<EncryptSecretBloc>().add(
const EncryptSecretEvent.cancelInputSecret(),
);
},
onCancelPressed: () => context
.read<EncryptSecretBloc>()
.add(const EncryptSecretEvent.cancelInputSecret()),
mode: TextButtonMode.normal,
),
const VSpace(6),

View File

@ -1,18 +1,18 @@
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/shared/window_title_bar.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:appflowy_editor/appflowy_editor.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,
});
const DesktopSignInScreen({super.key});
@override
Widget build(BuildContext context) {
@ -20,18 +20,22 @@ class DesktopSignInScreen extends StatelessWidget {
return BlocBuilder<SignInBloc, SignInState>(
builder: (context, state) {
return Scaffold(
appBar: const PreferredSize(
preferredSize: Size(double.infinity, 60),
child: MoveWindowDetector(),
appBar: PreferredSize(
preferredSize:
Size.fromHeight(PlatformExtension.isWindows ? 40 : 60),
child: PlatformExtension.isWindows
? const WindowTitleBar()
: const MoveWindowDetector(),
),
body: Center(
child: AuthFormContainer(
children: [
const VSpace(20),
FlowyLogoTitle(
title: LocaleKeys.welcomeText.tr(),
logoSize: const Size(60, 60),
),
const VSpace(30),
const VSpace(20),
// const SignInAnonymousButton(),
const SignInWithMagicLinkButtons(),
@ -55,9 +59,9 @@ class DesktopSignInScreen extends StatelessWidget {
final type = state.loginType == LoginType.signIn
? LoginType.signUp
: LoginType.signIn;
context.read<SignInBloc>().add(
SignInEvent.switchLoginType(type),
);
context
.read<SignInBloc>()
.add(SignInEvent.switchLoginType(type));
},
),
@ -85,20 +89,12 @@ class _OrDivider extends StatelessWidget {
Widget build(BuildContext context) {
return Row(
children: [
const Flexible(
child: Divider(
thickness: 1,
),
),
const Flexible(child: Divider(thickness: 1)),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 10),
child: FlowyText.regular(LocaleKeys.signIn_or.tr()),
),
const Flexible(
child: Divider(
thickness: 1,
),
),
const Flexible(child: Divider(thickness: 1)),
],
);
}

View File

@ -16,10 +16,7 @@ import 'package:go_router/go_router.dart';
class SplashScreen extends StatelessWidget {
/// Root Page of the app.
const SplashScreen({
super.key,
required this.isAnon,
});
const SplashScreen({super.key, required this.isAnon});
final bool isAnon;

View File

@ -27,7 +27,7 @@ class FlowyLogoTitle extends StatelessWidget {
blendMode: null,
),
),
const VSpace(40),
const VSpace(20),
FlowyText.regular(
title,
fontSize: FontSizes.s24,

View File

@ -12,8 +12,8 @@ enum SettingsPage {
// NEW
account,
workspace,
manageData,
// OLD
files,
notifications,
cloud,
shortcuts,

View File

@ -220,9 +220,10 @@ class PageManager {
],
child: Selector<PageNotifier, Widget>(
selector: (context, notifier) => notifier.titleWidget,
builder: (context, widget, child) {
return MoveWindowDetector(child: HomeTopBar(layout: layout));
},
builder: (_, __, child) => MoveWindowDetector(
showTitleBar: true,
child: HomeTopBar(layout: layout),
),
),
);
}

View File

@ -6,6 +6,7 @@ import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/workspace/application/home/home_setting_bloc.dart';
import 'package:appflowy/workspace/application/menu/sidebar_sections_bloc.dart';
import 'package:appflowy/workspace/presentation/home/home_sizes.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/style_widget/icon_button.dart';
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
@ -25,20 +26,18 @@ class SidebarTopMenu extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocBuilder<SidebarSectionsBloc, SidebarSectionsState>(
builder: (context, state) {
return SizedBox(
height: HomeSizes.topBarHeight,
child: MoveWindowDetector(
child: Row(
children: [
_buildLogoIcon(context),
const Spacer(),
_buildCollapseMenuButton(context),
],
),
builder: (context, _) => SizedBox(
height: !PlatformExtension.isWindows ? HomeSizes.topBarHeight : 45,
child: MoveWindowDetector(
child: Row(
children: [
_buildLogoIcon(context),
const Spacer(),
_buildCollapseMenuButton(context),
],
),
);
},
),
),
);
}
@ -72,15 +71,13 @@ class SidebarTopMenu extends StatelessWidget {
return FlowyTooltip(
richMessage: textSpan,
child: FlowyIconButton(
width: 28,
width: PlatformExtension.isWindows ? 30 : 28,
hoverColor: Colors.transparent,
onPressed: () => context
.read<HomeSettingBloc>()
.add(const HomeSettingEvent.collapseMenu()),
iconPadding: const EdgeInsets.fromLTRB(4, 4, 4, 4),
icon: const FlowySvg(
FlowySvgs.hide_menu_m,
),
icon: const FlowySvg(FlowySvgs.hide_menu_m),
),
);
}

View File

@ -1,5 +1,6 @@
import 'dart:io';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:flutter/material.dart';
import 'package:appflowy/generated/flowy_svgs.g.dart';
@ -63,7 +64,7 @@ class FlowyNavigation extends StatelessWidget {
return BlocBuilder<HomeSettingBloc, HomeSettingState>(
buildWhen: (p, c) => p.isMenuCollapsed != c.isMenuCollapsed,
builder: (context, state) {
if (state.isMenuCollapsed) {
if (!PlatformExtension.isWindows && state.isMenuCollapsed) {
return RotationTransition(
turns: const AlwaysStoppedAnimation(180 / 360),
child: FlowyTooltip(

View File

@ -0,0 +1,493 @@
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:appflowy/core/helpers/url_launcher.dart';
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/shared/appflowy_cache_manager.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/startup/tasks/rust_sdk.dart';
import 'package:appflowy/workspace/application/settings/setting_file_importer_bloc.dart';
import 'package:appflowy/workspace/application/settings/settings_location_cubit.dart';
import 'package:appflowy/workspace/presentation/home/toast.dart';
import 'package:appflowy/workspace/presentation/settings/shared/setting_action.dart';
import 'package:appflowy/workspace/presentation/settings/shared/settings_alert_dialog.dart';
import 'package:appflowy/workspace/presentation/settings/shared/settings_body.dart';
import 'package:appflowy/workspace/presentation/settings/shared/settings_category.dart';
import 'package:appflowy/workspace/presentation/settings/shared/single_setting_action.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/files/settings_export_file_widget.dart';
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:dotted_border/dotted_border.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/file_picker/file_picker_service.dart';
import 'package:flowy_infra/theme_extension.dart';
import 'package:flowy_infra_ui/style_widget/button.dart';
import 'package:flowy_infra_ui/style_widget/hover.dart';
import 'package:flowy_infra_ui/style_widget/text.dart';
import 'package:flowy_infra_ui/widget/spacing.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:fluttertoast/fluttertoast.dart';
class SettingsManageDataView extends StatelessWidget {
const SettingsManageDataView({super.key, required this.userProfile});
final UserProfilePB userProfile;
@override
Widget build(BuildContext context) {
return BlocProvider<SettingsLocationCubit>(
create: (_) => SettingsLocationCubit(),
child: BlocBuilder<SettingsLocationCubit, SettingsLocationState>(
builder: (context, state) {
return SettingsBody(
title: LocaleKeys.settings_manageDataPage_title.tr(),
description: LocaleKeys.settings_manageDataPage_description.tr(),
children: [
SettingsCategory(
title:
LocaleKeys.settings_manageDataPage_dataStorage_title.tr(),
tooltip:
LocaleKeys.settings_manageDataPage_dataStorage_tooltip.tr(),
actions: [
if (state.mapOrNull(didReceivedPath: (_) => true) == true)
SettingAction(
icon: const FlowySvg(FlowySvgs.restore_s),
label: LocaleKeys.settings_common_reset.tr(),
onPressed: () => SettingsAlertDialog(
title: LocaleKeys
.settings_manageDataPage_dataStorage_resetDialog_title
.tr(),
subtitle: LocaleKeys
.settings_manageDataPage_dataStorage_resetDialog_description
.tr(),
implyLeading: true,
confirm: () async {
final directory =
await appFlowyApplicationDataDirectory();
final path = directory.path;
if (!context.mounted ||
state.mapOrNull(didReceivedPath: (e) => e.path) ==
path) {
return;
}
await context
.read<SettingsLocationCubit>()
.resetDataStoragePathToApplicationDefault();
await runAppFlowy(isAnon: true);
if (context.mounted) Navigator.of(context).pop();
},
).show(context),
),
],
children: state
.map(
initial: (_) => [const CircularProgressIndicator()],
didReceivedPath: (event) => [
_CurrentPath(path: event.path),
_DataPathActions(currentPath: event.path),
],
)
.toList(),
),
SettingsCategory(
title: LocaleKeys.settings_manageDataPage_importData_title.tr(),
tooltip:
LocaleKeys.settings_manageDataPage_importData_tooltip.tr(),
children: const [_ImportDataField()],
),
if (kDebugMode) ...[
SettingsCategory(
title: LocaleKeys.settings_files_exportData.tr(),
children: const [SettingsExportFileWidget()],
),
],
SettingsCategory(
title: LocaleKeys.settings_manageDataPage_cache_title.tr(),
children: [
SingleSettingAction(
labelMaxLines: 4,
label: LocaleKeys.settings_manageDataPage_cache_description
.tr(),
buttonLabel:
LocaleKeys.settings_manageDataPage_cache_title.tr(),
onPressed: () {
SettingsAlertDialog(
title: LocaleKeys
.settings_manageDataPage_cache_dialog_title
.tr(),
subtitle: LocaleKeys
.settings_manageDataPage_cache_dialog_description
.tr(),
confirm: () async {
await getIt<FlowyCacheManager>().clearAllCache();
if (context.mounted) {
showSnackBarMessage(
context,
LocaleKeys
.settings_manageDataPage_cache_dialog_successHint
.tr(),
);
Navigator.of(context).pop();
}
},
).show(context);
},
),
],
),
// Uncomment if we need to enable encryption
// if (userProfile.authenticator == AuthenticatorPB.Supabase) ...[
// const SettingsCategorySpacer(),
// BlocProvider(
// create: (_) => EncryptSecretBloc(user: userProfile),
// child: SettingsCategory(
// title: LocaleKeys.settings_manageDataPage_encryption_title
// .tr(),
// tooltip: LocaleKeys
// .settings_manageDataPage_encryption_tooltip
// .tr(),
// description: userProfile.encryptionType ==
// EncryptionTypePB.NoEncryption
// ? LocaleKeys
// .settings_manageDataPage_encryption_descriptionNoEncryption
// .tr()
// : LocaleKeys
// .settings_manageDataPage_encryption_descriptionEncrypted
// .tr(),
// children: [_EncryptDataSetting(userProfile: userProfile)],
// ),
// ),
// ],
],
);
},
),
);
}
}
// class _EncryptDataSetting extends StatelessWidget {
// const _EncryptDataSetting({required this.userProfile});
// final UserProfilePB userProfile;
// @override
// Widget build(BuildContext context) {
// return BlocProvider<EncryptSecretBloc>.value(
// value: context.read<EncryptSecretBloc>(),
// child: BlocBuilder<EncryptSecretBloc, EncryptSecretState>(
// builder: (context, state) {
// if (state.loadingState?.isLoading() == true) {
// return const Row(
// children: [
// SizedBox(
// width: 20,
// height: 20,
// child: CircularProgressIndicator(
// strokeWidth: 3,
// ),
// ),
// HSpace(16),
// FlowyText.medium(
// 'Encrypting data...',
// fontSize: 14,
// ),
// ],
// );
// }
// if (userProfile.encryptionType == EncryptionTypePB.NoEncryption) {
// return Row(
// children: [
// SizedBox(
// height: 42,
// child: FlowyTextButton(
// LocaleKeys.settings_manageDataPage_encryption_action.tr(),
// padding: const EdgeInsets.symmetric(
// horizontal: 24,
// vertical: 12,
// ),
// fontWeight: FontWeight.w600,
// radius: BorderRadius.circular(12),
// fillColor: Theme.of(context).colorScheme.primary,
// hoverColor: const Color(0xFF005483),
// fontHoverColor: Colors.white,
// onPressed: () => SettingsAlertDialog(
// title: LocaleKeys
// .settings_manageDataPage_encryption_dialog_title
// .tr(),
// subtitle: LocaleKeys
// .settings_manageDataPage_encryption_dialog_description
// .tr(),
// confirmLabel: LocaleKeys
// .settings_manageDataPage_encryption_dialog_title
// .tr(),
// implyLeading: true,
// // Generate a secret one time for the user
// confirm: () => context
// .read<EncryptSecretBloc>()
// .add(const EncryptSecretEvent.setEncryptSecret('')),
// ).show(context),
// ),
// ),
// ],
// );
// }
// // Show encryption secret for copy/save
// return const SizedBox.shrink();
// },
// ),
// );
// }
// }
class _ImportDataField extends StatefulWidget {
const _ImportDataField();
@override
State<_ImportDataField> createState() => _ImportDataFieldState();
}
class _ImportDataFieldState extends State<_ImportDataField> {
final _fToast = FToast();
@override
void initState() {
super.initState();
_fToast.init(context);
}
@override
void dispose() {
_fToast.removeQueuedCustomToasts();
super.dispose();
}
@override
Widget build(BuildContext context) {
return BlocProvider<SettingFileImportBloc>(
create: (context) => SettingFileImportBloc(),
child: BlocConsumer<SettingFileImportBloc, SettingFileImportState>(
listenWhen: (previous, current) =>
previous.successOrFail != current.successOrFail,
listener: (_, state) => state.successOrFail?.fold(
(_) => _showToast(LocaleKeys.settings_menu_importSuccess.tr()),
(_) => _showToast(LocaleKeys.settings_menu_importFailed.tr()),
),
builder: (context, state) {
return DottedBorder(
radius: const Radius.circular(8),
dashPattern: const [2, 2],
borderType: BorderType.RRect,
color: Theme.of(context).colorScheme.primary,
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
// When dragging files are enabled
// FlowyText.regular('Drag file here or'),
// const VSpace(8),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
height: 42,
child: FlowyTextButton(
LocaleKeys.settings_manageDataPage_importData_action
.tr(),
padding: const EdgeInsets.symmetric(
horizontal: 24,
vertical: 12,
),
fontWeight: FontWeight.w600,
radius: BorderRadius.circular(12),
fillColor: Theme.of(context).colorScheme.primary,
hoverColor: const Color(0xFF005483),
fontHoverColor: Colors.white,
onPressed: () async {
final path = await getIt<FilePickerService>()
.getDirectoryPath();
if (path == null || !context.mounted) {
return;
}
context.read<SettingFileImportBloc>().add(
SettingFileImportEvent
.importAppFlowyDataFolder(
path,
),
);
},
),
),
],
),
const VSpace(8),
FlowyText.regular(
LocaleKeys.settings_manageDataPage_importData_description
.tr(),
// 'Supported filetypes:\nCSV, Notion, Text, and Markdown',
maxLines: 3,
lineHeight: 1.5,
textAlign: TextAlign.center,
),
],
),
),
);
},
),
);
}
void _showToast(String message) {
_fToast.showToast(
child: FlowyMessageToast(message: message),
gravity: ToastGravity.CENTER,
);
}
}
class _CurrentPath extends StatefulWidget {
const _CurrentPath({required this.path});
final String path;
@override
State<_CurrentPath> createState() => _CurrentPathState();
}
class _CurrentPathState extends State<_CurrentPath> {
Timer? linkCopiedTimer;
bool showCopyMessage = false;
@override
void dispose() {
linkCopiedTimer?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Column(
children: [
Row(
children: [
Expanded(
child: Listener(
behavior: HitTestBehavior.opaque,
onPointerDown: (_) => _copyLink(widget.path),
child: FlowyHover(
style: const HoverStyle.transparent(),
resetHoverOnRebuild: false,
builder: (_, isHovering) => FlowyText.regular(
widget.path,
lineHeight: 1.5,
maxLines: 2,
overflow: TextOverflow.ellipsis,
decoration: isHovering ? TextDecoration.underline : null,
color: const Color(0xFF005483),
),
),
),
),
const HSpace(8),
showCopyMessage
? SizedBox(
height: 36,
child: FlowyTextButton(
LocaleKeys
.settings_manageDataPage_dataStorage_actions_copiedHint
.tr(),
padding: const EdgeInsets.symmetric(
horizontal: 24,
vertical: 12,
),
fontWeight: FontWeight.w500,
radius: BorderRadius.circular(12),
fillColor: AFThemeExtension.of(context).tint7,
hoverColor: AFThemeExtension.of(context).tint7,
),
)
: Padding(
padding: const EdgeInsets.only(left: 100),
child: SettingAction(
tooltip: LocaleKeys
.settings_manageDataPage_dataStorage_actions_copy
.tr(),
icon: const FlowySvg(
FlowySvgs.copy_s,
size: Size.square(24),
),
onPressed: () => _copyLink(widget.path),
),
),
],
),
],
);
}
void _copyLink(String? path) {
AppFlowyClipboard.setData(text: path);
setState(() => showCopyMessage = true);
linkCopiedTimer?.cancel();
linkCopiedTimer = Timer(
const Duration(milliseconds: 300),
() => mounted ? setState(() => showCopyMessage = false) : null,
);
}
}
class _DataPathActions extends StatelessWidget {
const _DataPathActions({required this.currentPath});
final String currentPath;
@override
Widget build(BuildContext context) {
return Row(
children: [
SizedBox(
height: 42,
child: FlowyTextButton(
LocaleKeys.settings_manageDataPage_dataStorage_actions_change.tr(),
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
fontWeight: FontWeight.w600,
radius: BorderRadius.circular(12),
fillColor: Theme.of(context).colorScheme.primary,
hoverColor: const Color(0xFF005483),
fontHoverColor: Colors.white,
onPressed: () async {
final path = await getIt<FilePickerService>().getDirectoryPath();
if (!context.mounted || path == null || currentPath == path) {
return;
}
await context.read<SettingsLocationCubit>().setCustomPath(path);
await runAppFlowy(isAnon: true);
if (context.mounted) Navigator.of(context).pop();
},
),
),
const HSpace(16),
SettingAction(
tooltip: LocaleKeys
.settings_manageDataPage_dataStorage_actions_openTooltip
.tr(),
label:
LocaleKeys.settings_manageDataPage_dataStorage_actions_open.tr(),
icon: const FlowySvg(FlowySvgs.folder_m, size: Size.square(16)),
onPressed: () => afLaunchUrlString('file://$currentPath'),
),
],
);
}
}

View File

@ -3,11 +3,11 @@ import 'package:flutter/material.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/workspace/application/settings/settings_dialog_bloc.dart';
import 'package:appflowy/workspace/presentation/settings/pages/settings_account_view.dart';
import 'package:appflowy/workspace/presentation/settings/pages/settings_manage_data_view.dart';
import 'package:appflowy/workspace/presentation/settings/pages/settings_workspace_view.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/feature_flags/feature_flag_page.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/members/workspace_member_page.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/settings_customize_shortcuts_view.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/settings_file_system_view.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/settings_menu.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/settings_notifications_view.dart';
import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart';
@ -79,8 +79,8 @@ class SettingsDialog extends StatelessWidget {
);
case SettingsPage.workspace:
return SettingsWorkspaceView(userProfile: user);
case SettingsPage.files:
return const SettingsFileSystemView();
case SettingsPage.manageData:
return SettingsManageDataView(userProfile: user);
case SettingsPage.notifications:
return const SettingsNotificationsView();
case SettingsPage.cloud:

View File

@ -21,11 +21,7 @@ class SettingAction extends StatelessWidget {
@override
Widget build(BuildContext context) {
final iconWidget = tooltip != null && tooltip!.isNotEmpty
? FlowyTooltip(message: tooltip, child: icon)
: icon;
return GestureDetector(
final child = GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: onPressed,
child: SizedBox(
@ -36,7 +32,7 @@ class SettingAction extends StatelessWidget {
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 2),
child: Row(
children: [
iconWidget,
icon,
if (label != null) ...[
const HSpace(4),
FlowyText.regular(label!),
@ -47,5 +43,14 @@ class SettingAction extends StatelessWidget {
),
),
);
if (tooltip != null) {
return FlowyTooltip(
message: tooltip!,
child: child,
);
}
return child;
}
}

View File

@ -1,167 +0,0 @@
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:appflowy/core/helpers/url_launcher.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/workspace/application/settings/setting_file_importer_bloc.dart';
import 'package:appflowy/workspace/presentation/home/toast.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/file_picker/file_picker_service.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:fluttertoast/fluttertoast.dart';
class ImportAppFlowyData extends StatefulWidget {
const ImportAppFlowyData({super.key});
@override
State<ImportAppFlowyData> createState() => _ImportAppFlowyDataState();
}
class _ImportAppFlowyDataState extends State<ImportAppFlowyData> {
final _fToast = FToast();
@override
void initState() {
super.initState();
_fToast.init(context);
}
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => SettingFileImportBloc(),
child: BlocListener<SettingFileImportBloc, SettingFileImportState>(
listener: (context, state) {
state.successOrFail?.fold(
(_) {
_showToast(LocaleKeys.settings_menu_importSuccess.tr());
},
(_) {
_showToast(LocaleKeys.settings_menu_importFailed.tr());
},
);
},
child: BlocBuilder<SettingFileImportBloc, SettingFileImportState>(
builder: (context, state) {
final List<Widget> children = [
const ImportAppFlowyDataButton(),
const VSpace(6),
];
if (state.loadingState.isLoading()) {
children.add(const AppFlowyDataImportingTip());
} else {
children.add(const AppFlowyDataImportTip());
}
return Column(children: children);
},
),
),
);
}
void _showToast(String message) {
_fToast.showToast(
child: FlowyMessageToast(message: message),
gravity: ToastGravity.CENTER,
);
}
}
class AppFlowyDataImportTip extends StatelessWidget {
const AppFlowyDataImportTip({super.key});
final url = "https://docs.appflowy.io/docs/appflowy/product/data-storage";
@override
Widget build(BuildContext context) {
return Opacity(
opacity: 0.6,
child: RichText(
text: TextSpan(
children: <TextSpan>[
TextSpan(
text: LocaleKeys.settings_menu_importAppFlowyDataDescription.tr(),
style: Theme.of(context).textTheme.bodySmall!,
),
TextSpan(
text: " ${LocaleKeys.settings_menu_importGuide.tr()} ",
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
color: Theme.of(context).colorScheme.primary,
decoration: TextDecoration.underline,
),
recognizer: TapGestureRecognizer()
..onTap = () => afLaunchUrlString(url),
),
],
),
),
);
}
}
class ImportAppFlowyDataButton extends StatefulWidget {
const ImportAppFlowyDataButton({super.key});
@override
State<ImportAppFlowyDataButton> createState() =>
_ImportAppFlowyDataButtonState();
}
class _ImportAppFlowyDataButtonState extends State<ImportAppFlowyDataButton> {
@override
Widget build(BuildContext context) {
return BlocBuilder<SettingFileImportBloc, SettingFileImportState>(
builder: (context, state) {
return Column(
children: [
SizedBox(
height: 40,
child: FlowyButton(
disable: state.loadingState.isLoading(),
text:
FlowyText(LocaleKeys.settings_menu_importAppFlowyData.tr()),
onTap: () async {
final path =
await getIt<FilePickerService>().getDirectoryPath();
if (path == null || !context.mounted) {
return;
}
context.read<SettingFileImportBloc>().add(
SettingFileImportEvent.importAppFlowyDataFolder(path),
);
},
),
),
if (state.loadingState.isLoading())
const LinearProgressIndicator(minHeight: 1),
],
);
},
);
}
}
class AppFlowyDataImportingTip extends StatelessWidget {
const AppFlowyDataImportingTip({super.key});
@override
Widget build(BuildContext context) {
return Opacity(
opacity: 0.6,
child: RichText(
text: TextSpan(
children: <TextSpan>[
TextSpan(
text: LocaleKeys.settings_menu_importingAppFlowyDataTip.tr(),
style: Theme.of(context).textTheme.bodySmall!,
),
],
),
),
);
}
}

View File

@ -1,16 +1,15 @@
import 'package:flutter/material.dart';
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/files/settings_file_exporter_widget.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:styled_widget/styled_widget.dart';
import '../../../../../generated/locale_keys.g.dart';
class SettingsExportFileWidget extends StatefulWidget {
const SettingsExportFileWidget({
super.key,
});
const SettingsExportFileWidget({super.key});
@override
State<SettingsExportFileWidget> createState() =>

View File

@ -1,80 +0,0 @@
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/shared/appflowy_cache_manager.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/workspace/presentation/home/toast.dart';
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
class SettingsFileCacheWidget extends StatelessWidget {
const SettingsFileCacheWidget({
super.key,
});
@override
Widget build(BuildContext context) {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 5.0),
child: FlowyText.medium(
LocaleKeys.settings_files_clearCache.tr(),
fontSize: 13,
overflow: TextOverflow.ellipsis,
),
),
const VSpace(8),
Opacity(
opacity: 0.6,
child: FlowyText(
LocaleKeys.settings_files_clearCacheDesc.tr(),
fontSize: 10,
maxLines: 3,
),
),
],
),
),
const _ClearCacheButton(),
],
);
}
}
class _ClearCacheButton extends StatelessWidget {
const _ClearCacheButton();
@override
Widget build(BuildContext context) {
return FlowyIconButton(
hoverColor: Theme.of(context).colorScheme.secondaryContainer,
tooltipText: LocaleKeys.settings_files_clearCache.tr(),
icon: FlowySvg(
FlowySvgs.delete_s,
size: const Size.square(18),
color: Theme.of(context).iconTheme.color,
),
onPressed: () {
NavigatorAlertDialog(
title: LocaleKeys.settings_files_areYouSureToClearCache.tr(),
confirm: () async {
await getIt<FlowyCacheManager>().clearAllCache();
if (context.mounted) {
showSnackBarMessage(
context,
LocaleKeys.settings_files_clearCacheSuccess.tr(),
);
}
},
).show(context);
},
);
}
}

View File

@ -1,285 +0,0 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:appflowy/core/helpers/url_launcher.dart';
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/workspace/application/settings/settings_location_cubit.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/file_picker/file_picker_service.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/buttons/secondary_button.dart';
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:styled_widget/styled_widget.dart';
import '../../../../../generated/locale_keys.g.dart';
import '../../../../../startup/startup.dart';
import '../../../../../startup/tasks/prelude.dart';
class SettingsFileLocationCustomizer extends StatefulWidget {
const SettingsFileLocationCustomizer({super.key});
@override
State<SettingsFileLocationCustomizer> createState() =>
SettingsFileLocationCustomizerState();
}
@visibleForTesting
class SettingsFileLocationCustomizerState
extends State<SettingsFileLocationCustomizer> {
@override
Widget build(BuildContext context) {
return BlocProvider<SettingsLocationCubit>(
create: (_) => SettingsLocationCubit(),
child: BlocBuilder<SettingsLocationCubit, SettingsLocationState>(
builder: (context, state) {
return state.when(
initial: () => const Center(
child: CircularProgressIndicator(),
),
didReceivedPath: (path) {
return Column(
children: [
Row(
mainAxisSize: MainAxisSize.min,
children: [
// display file paths.
_path(path),
// display the icons
_buttons(path),
],
),
const VSpace(10),
IntrinsicHeight(
child: Opacity(
opacity: 0.6,
child: FlowyText.medium(
LocaleKeys.settings_menu_customPathPrompt.tr(),
maxLines: 13,
),
),
),
],
);
},
);
},
),
);
}
Widget _path(String path) {
return Flexible(
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
FlowyText.medium(
LocaleKeys.settings_files_defaultLocation.tr(),
fontSize: 13,
overflow: TextOverflow.visible,
).padding(horizontal: 5),
const VSpace(5),
_CopyableText(
usingPath: path,
),
],
),
);
}
Widget _buttons(String path) {
final List<Widget> children = [];
children.addAll([
Flexible(
child: _ChangeStoragePathButton(
usingPath: path,
),
),
const HSpace(10),
]);
children.add(
_OpenStorageButton(
usingPath: path,
),
);
children.add(
_RecoverDefaultStorageButton(
usingPath: path,
),
);
return Flexible(
child: Row(mainAxisAlignment: MainAxisAlignment.end, children: children),
);
}
}
class _CopyableText extends StatelessWidget {
const _CopyableText({
required this.usingPath,
});
final String usingPath;
@override
Widget build(BuildContext context) {
return FlowyHover(
builder: (_, onHover) {
return GestureDetector(
onTap: () {
Clipboard.setData(ClipboardData(text: usingPath));
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: FlowyText(
LocaleKeys.settings_files_pathCopiedSnackbar.tr(),
color: Theme.of(context).colorScheme.onSurface,
),
),
);
},
child: Container(
height: 20,
padding: const EdgeInsets.symmetric(horizontal: 5),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Flexible(
child: FlowyText.regular(
usingPath,
fontSize: 12,
overflow: TextOverflow.ellipsis,
),
),
if (onHover) ...[
const HSpace(5),
FlowyText.regular(
LocaleKeys.settings_files_copy.tr(),
fontSize: 12,
color: Theme.of(context).colorScheme.primary,
),
],
],
),
),
);
},
);
}
}
class _ChangeStoragePathButton extends StatefulWidget {
const _ChangeStoragePathButton({
required this.usingPath,
});
final String usingPath;
@override
State<_ChangeStoragePathButton> createState() =>
_ChangeStoragePathButtonState();
}
class _ChangeStoragePathButtonState extends State<_ChangeStoragePathButton> {
@override
Widget build(BuildContext context) {
return FlowyTooltip(
message: LocaleKeys.settings_files_changeLocationTooltips.tr(),
child: SecondaryTextButton(
LocaleKeys.settings_files_change.tr(),
mode: TextButtonMode.small,
onPressed: () async {
// pick the new directory and reload app
final path = await getIt<FilePickerService>().getDirectoryPath();
if (path == null || widget.usingPath == path) {
return;
}
if (!context.mounted) {
return;
}
await context.read<SettingsLocationCubit>().setCustomPath(path);
await runAppFlowy(isAnon: true);
if (context.mounted) {
Navigator.of(context).pop();
}
},
),
);
}
}
class _OpenStorageButton extends StatelessWidget {
const _OpenStorageButton({
required this.usingPath,
});
final String usingPath;
@override
Widget build(BuildContext context) {
return FlowyIconButton(
hoverColor: Theme.of(context).colorScheme.secondaryContainer,
tooltipText: LocaleKeys.settings_files_openCurrentDataFolder.tr(),
icon: FlowySvg(
FlowySvgs.open_folder_lg,
color: Theme.of(context).iconTheme.color,
),
onPressed: () async {
final uri = Directory(usingPath).uri;
await afLaunchUrl(uri, context: context);
},
);
}
}
class _RecoverDefaultStorageButton extends StatefulWidget {
const _RecoverDefaultStorageButton({
required this.usingPath,
});
final String usingPath;
@override
State<_RecoverDefaultStorageButton> createState() =>
_RecoverDefaultStorageButtonState();
}
class _RecoverDefaultStorageButtonState
extends State<_RecoverDefaultStorageButton> {
@override
Widget build(BuildContext context) {
return FlowyIconButton(
hoverColor: Theme.of(context).colorScheme.secondaryContainer,
tooltipText: LocaleKeys.settings_files_recoverLocationTooltips.tr(),
icon: const FlowySvg(
FlowySvgs.restore_s,
size: Size.square(20),
),
onPressed: () async {
// reset to the default directory and reload app
final directory = await appFlowyApplicationDataDirectory();
final path = directory.path;
if (widget.usingPath == path) {
return;
}
if (!context.mounted) {
return;
}
await context
.read<SettingsLocationCubit>()
.resetDataStoragePathToApplicationDefault();
await runAppFlowy(isAnon: true);
if (context.mounted) {
Navigator.of(context).pop();
}
},
);
}
}

View File

@ -1,32 +0,0 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/workspace/presentation/settings/shared/settings_body.dart';
import 'package:appflowy/workspace/presentation/settings/shared/settings_category_spacer.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/files/setting_file_import_appflowy_data_view.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/files/settings_export_file_widget.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/files/settings_file_cache_widget.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/files/settings_file_customize_location_view.dart';
import 'package:easy_localization/easy_localization.dart';
class SettingsFileSystemView extends StatelessWidget {
const SettingsFileSystemView({super.key});
@override
Widget build(BuildContext context) {
return SettingsBody(
title: LocaleKeys.settings_menu_files.tr(),
children: const [
SettingsFileLocationCustomizer(),
SettingsCategorySpacer(),
if (kDebugMode) ...[
SettingsExportFileWidget(),
],
ImportAppFlowyData(),
SettingsCategorySpacer(),
SettingsFileCacheWidget(),
],
);
}
}

View File

@ -72,10 +72,10 @@ class SettingsMenu extends StatelessWidget {
changeSelectedPage: changeSelectedPage,
),
SettingsMenuElement(
page: SettingsPage.files,
page: SettingsPage.manageData,
selectedPage: currentPage,
label: LocaleKeys.settings_menu_files.tr(),
icon: const Icon(Icons.file_present_outlined),
label: LocaleKeys.settings_manageDataPage_menuLabel.tr(),
icon: const FlowySvg(FlowySvgs.settings_data_m),
changeSelectedPage: changeSelectedPage,
),
SettingsMenuElement(

View File

@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart';
import 'package:appflowy/workspace/application/view/view_bloc.dart';
import 'package:appflowy/workspace/application/view_info/view_info_bloc.dart';
import 'package:appflowy/workspace/presentation/widgets/more_view_actions/widgets/common_view_action.dart';
import 'package:appflowy/workspace/presentation/widgets/more_view_actions/widgets/font_size_action.dart';
@ -33,23 +34,8 @@ class MoreViewActions extends StatefulWidget {
}
class _MoreViewActionsState extends State<MoreViewActions> {
late final List<Widget> viewActions;
final popoverMutex = PopoverMutex();
@override
void initState() {
super.initState();
viewActions = ViewActionType.values
.map(
(type) => ViewAction(
type: type,
view: widget.view,
mutex: popoverMutex,
),
)
.toList();
}
@override
void dispose() {
popoverMutex.dispose();
@ -74,7 +60,13 @@ class _MoreViewActionsState extends State<MoreViewActions> {
const FontSizeAction(),
const Divider(height: 4),
],
...viewActions,
...ViewActionType.values.map(
(type) => ViewAction(
type: type,
view: widget.view,
mutex: popoverMutex,
),
),
if (state.documentCounters != null ||
state.createdAt != null) ...[
const Divider(height: 4),
@ -87,13 +79,17 @@ class _MoreViewActionsState extends State<MoreViewActions> {
],
];
return ListView.separated(
shrinkWrap: true,
padding: EdgeInsets.zero,
itemCount: actions.length,
separatorBuilder: (_, __) => const VSpace(4),
physics: StyledScrollPhysics(),
itemBuilder: (_, index) => actions[index],
return BlocProvider(
create: (_) =>
ViewBloc(view: widget.view)..add(const ViewEvent.initial()),
child: ListView.separated(
shrinkWrap: true,
padding: EdgeInsets.zero,
itemCount: actions.length,
separatorBuilder: (_, __) => const VSpace(4),
physics: StyledScrollPhysics(),
itemBuilder: (_, index) => actions[index],
),
);
},
child: FlowyTooltip(

View File

@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/workspace/application/view/view_bloc.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
@ -10,6 +9,7 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/theme_extension.dart';
import 'package:flowy_infra_ui/style_widget/button.dart';
import 'package:flowy_infra_ui/style_widget/text.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
enum ViewActionType {
delete,
@ -47,7 +47,7 @@ class ViewAction extends StatelessWidget {
Widget build(BuildContext context) {
return FlowyButton(
onTap: () {
getIt<ViewBloc>(param1: view).add(type.actionEvent);
context.read<ViewBloc>().add(type.actionEvent);
mutex?.close();
},
text: FlowyText.regular(

View File

@ -15,7 +15,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 0.5.6
version: 0.5.7
environment:
flutter: ">=3.19.0"

View File

@ -29,7 +29,7 @@ tokio = "1.34.0"
tokio-stream = "0.1.14"
async-trait = "0.1.74"
chrono = { version = "0.4.31", default-features = false, features = ["clock"] }
yrs = "0.18.7"
yrs = "0.18.8"
# Please use the following script to update collab.
# Working directory: frontend
#
@ -104,10 +104,10 @@ default = ["custom-protocol"]
custom-protocol = ["tauri/custom-protocol"]
[patch.crates-io]
collab = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "870cd70" }
collab-entity = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "870cd70" }
collab-folder = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "870cd70" }
collab-document = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "870cd70" }
collab-database = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "870cd70" }
collab-plugins = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "870cd70" }
collab-user = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "870cd70" }
collab = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2c430e0" }
collab-entity = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2c430e0" }
collab-folder = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2c430e0" }
collab-document = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2c430e0" }
collab-database = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2c430e0" }
collab-plugins = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2c430e0" }
collab-user = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2c430e0" }

View File

@ -48,7 +48,7 @@ collab-document = { version = "0.2" }
collab-database = { version = "0.2" }
collab-plugins = { version = "0.2" }
collab-user = { version = "0.2" }
yrs = "0.18.7"
yrs = "0.18.8"
# Please using the following command to update the revision id
# Current directory: frontend
@ -70,10 +70,10 @@ opt-level = 3
codegen-units = 1
[patch.crates-io]
collab = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "870cd70" }
collab-entity = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "870cd70" }
collab-folder = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "870cd70" }
collab-document = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "870cd70" }
collab-database = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "870cd70" }
collab-plugins = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "870cd70" }
collab-user = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "870cd70" }
collab = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2c430e0" }
collab-entity = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2c430e0" }
collab-folder = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2c430e0" }
collab-document = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2c430e0" }
collab-database = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2c430e0" }
collab-plugins = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2c430e0" }
collab-user = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2c430e0" }

View File

@ -29,7 +29,7 @@ tokio = "1.34.0"
tokio-stream = "0.1.14"
async-trait = "0.1.74"
chrono = { version = "0.4.31", default-features = false, features = ["clock"] }
yrs = "0.18.7"
yrs = "0.18.8"
# Please use the following script to update collab.
# Working directory: frontend
#
@ -103,10 +103,10 @@ default = ["custom-protocol"]
custom-protocol = ["tauri/custom-protocol"]
[patch.crates-io]
collab = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "870cd70" }
collab-entity = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "870cd70" }
collab-folder = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "870cd70" }
collab-document = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "870cd70" }
collab-database = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "870cd70" }
collab-plugins = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "870cd70" }
collab-user = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "870cd70" }
collab = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2c430e0" }
collab-entity = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2c430e0" }
collab-folder = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2c430e0" }
collab-document = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2c430e0" }
collab-database = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2c430e0" }
collab-plugins = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2c430e0" }
collab-user = { version = "0.2", git = "https://github.com/AppFlowy-IO/AppFlowy-Collab", rev = "2c430e0" }

View File

@ -1,5 +1,5 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="icon_left_outlined">
<path id="Union" d="M13.5774 1.9107C13.9028 2.23614 13.9028 2.76378 13.5774 3.08921L6.66667 9.99996L13.5774 16.9107C13.9028 17.2361 13.9028 17.7638 13.5774 18.0892C13.252 18.4147 12.7243 18.4147 12.3989 18.0892L5.48816 11.1785C4.83728 10.5276 4.83728 9.47232 5.48816 8.82145L12.3989 1.9107C12.7243 1.58527 13.252 1.58527 13.5774 1.9107Z" fill="#2B2F36"/>
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<g opacity="0.8">
<path d="M15.0796 19.9001L8.57604 13.3966C7.80799 12.6285 7.80799 11.3717 8.57604 10.6036L15.0796 4.1001" stroke="black" stroke-width="1.49621" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 491 B

After

Width:  |  Height:  |  Size: 342 B

View File

@ -226,6 +226,7 @@
"viewDataBase": "Datenbank ansehen",
"referencePage": "Auf diesen {Name} wird verwiesen",
"addBlockBelow": "Einen Block hinzufügen",
"genSummary": "Zusammenfassung generieren",
"urlLaunchAccessory": "Im Browser öffnen",
"urlCopyAccessory": "Webadresse kopieren."
},
@ -354,6 +355,79 @@
"logoutLabel": "Ausloggen"
}
},
"workspacePage": {
"menuLabel": "Arbeitsbereich",
"title": "Arbeitsbereich",
"description": "Passe das Erscheinungsbild, das Design, die Schriftart, das Textlayout, das Datums-/Zeitformat und die Sprache deines Arbeitsbereichs an.",
"workspaceName": {
"title": "Name des Arbeitsbereichs",
"savedMessage": "Name des Arbeitsbereichs gespeichert"
},
"workspaceIcon": {
"title": "Arbeitsbereich-Symbol",
"description": "Passe das Erscheinungsbild, das Design, die Schriftart, das Textlayout, das Datum, die Uhrzeit und die Sprache deines Arbeitsbereichs an."
},
"appearance": {
"title": "Aussehen",
"description": "Passe das Erscheinungsbild, das Design, die Schriftart, das Textlayout, das Datums-/Zeitformat und die Sprache deines Arbeitsbereichs an.",
"options": {
"system": "Auto",
"light": "Hell",
"dark": "Dunkel"
}
},
"theme": {
"title": "Design",
"description": "Wähle ein voreingestelltes Design aus oder lade dein eigenes benutzerdefiniertes Design hoch."
},
"workspaceFont": {
"title": "Schriftart Arbeitsbereich"
},
"textDirection": {
"title": "Textrichtung",
"leftToRight": "Links nach rechts",
"rightToLeft": "Rechts nach links",
"auto": "Auto",
"enableRTLItems": "RTL-Symbolleistenelemente aktivieren"
},
"layoutDirection": {
"title": "Layoutrichtung",
"leftToRight": "Links nach rechts",
"rightToLeft": "Rechts nach links"
},
"dateTime": {
"title": "Datum & Zeit",
"example": "{} um {} ({})",
"24HourTime": "24-Stunden-Zeit",
"dateFormat": {
"label": "Datumsformat",
"local": "Lokal",
"us": "US",
"iso": "ISO",
"friendly": "leserlich",
"dmy": "T/M/J"
}
},
"language": {
"title": "Sprache"
},
"deleteWorkspacePrompt": {
"title": "Arbeitsbereich löschen",
"content": "Möchtest du diesen Arbeitsbereich wirklich löschen? Diese Aktion kann nicht rückgängig gemacht werden."
},
"leaveWorkspacePrompt": {
"title": "Arbeitsbereich verlassen",
"content": "Möchtest du diesen Arbeitsbereich wirklich verlassen? Du verlierst den Zugriff auf alle darin enthaltenen Seiten und Daten."
},
"manageWorkspace": {
"title": "Arbeitsbereich verwalten",
"leaveWorkspace": "Arbeitsbereich verlassen",
"deleteWorkspace": "Arbeitsbereich löschen"
}
},
"common": {
"reset": "Zurücksetzen"
},
"menu": {
"appearance": "Oberfläche",
"language": "Sprache",
@ -622,14 +696,14 @@
"typeAValue": "Einen Wert eingeben...",
"layout": "Layout",
"databaseLayout": "Layout",
"viewList": "Datenbank-Ansichten",
"editView": "Ansicht editieren",
"boardSettings": "Board-Einstellungen",
"calendarSettings": "Kalender-Einstellungen",
"createView": "New Ansicht",
"duplicateView": "Ansicht duplizieren",
"deleteView": "Anslicht löschen",
"numberOfVisibleFields": "{} angezeigt",
"viewList": "Datenbank-Ansichten"
"numberOfVisibleFields": "{} angezeigt"
},
"textFilter": {
"contains": "Enthält",
@ -715,6 +789,7 @@
"urlFieldName": "URL",
"checklistFieldName": "Checkliste",
"relationFieldName": "Beziehung",
"summaryFieldName": "KI-Zusammenfassung",
"numberFormat": "Zahlenformat",
"dateFormat": "Datumsformat",
"includeTime": "Zeitangabe",
@ -762,7 +837,9 @@
"one": "Blende {count} verstecktes Feld aus",
"many": "Blende {count} versteckte Felder aus",
"other": "Blende {count} versteckte Felder aus"
}
},
"openAsFullPage": "Als ganze Seite öffnen",
"moreRowActions": "Weitere Zeilenaktionen"
},
"sort": {
"ascending": "Aufsteigend",
@ -788,7 +865,8 @@
"drag": "Ziehen, um zu verschieben",
"dragAndClick": "Ziehen, um zu verschieben. Klicke, um das Menü zu öffnen",
"insertRecordAbove": "Füge Datensatz oben ein",
"insertRecordBelow": "Füge Datensatz unten ein"
"insertRecordBelow": "Füge Datensatz unten ein",
"noContent": "Kein Inhalt"
},
"selectOption": {
"create": "Erstellen",
@ -1087,6 +1165,11 @@
"errorBlock": {
"theBlockIsNotSupported": "Die aktuelle Version unterstützt diesen Block nicht.",
"blockContentHasBeenCopied": "Der Blockinhalt wurde kopiert."
},
"mobilePageSelector": {
"title": "Seite auswählen",
"failedToLoad": "Seitenliste konnte nicht geladen werden",
"noPagesFound": "Keine Seiten gefunden"
}
},
"board": {
@ -1334,6 +1417,7 @@
"color": "Farbe",
"image": "Bild",
"date": "Datum",
"page": "Seite",
"italic": "Kursiv",
"link": "Link",
"numberedList": "Nummerierte Liste",
@ -1529,7 +1613,12 @@
"photo": "Foto",
"unsplash": "Unsplash",
"pageCover": "Deckblatt",
"none": "Keines"
"none": "Keines",
"photoPermissionDescription": "Erlaube den Zugriff auf die Fotobibliothek zum Hochladen von Bildern.",
"openSettings": "Einstellungen öffnen",
"photoPermissionTitle": "AppFlowy möchte auf deine Fotobibliothek zugreifen",
"doNotAllow": "Nicht zulassen",
"image": "Bild"
},
"commandPalette": {
"placeholder": "Tippe, um nach Ansichten zu suchen...",

View File

@ -417,6 +417,52 @@
"deleteWorkspace": "Delete workspace"
}
},
"manageDataPage": {
"menuLabel": "Manage data",
"title": "Manage data",
"description": "Manage data local storage or Import your existing data into Appflowy. You can secure your data with end to end encryption.",
"dataStorage": {
"title": "File storage location",
"tooltip": "The location where your files are stored",
"actions": {
"change": "Change path",
"open": "Open folder",
"openTooltip": "Open current data folder location",
"copy": "Copy path",
"copiedHint": "Link copied!"
},
"resetDialog": {
"title": "Are you sure?",
"description": "Resetting the path to the default data location will not delete your data. If you want to re-import your current data, you should copy the path of your current location first."
}
},
"importData": {
"title": "Import data",
"tooltip": "Import data from AppFlowy backups/data folders",
"description": "Copy data from an external AppFlowy data folder and import it into the current AppFlowy data folder",
"action": "Browse folder"
},
"encryption": {
"title": "Encryption",
"tooltip": "Manage how your data is stored and encrypted",
"descriptionNoEncryption": "Turning on encryption will encrypt all data. This can not be undone.",
"descriptionEncrypted": "Your data is encrypted.",
"action": "Encrypt data",
"dialog": {
"title": "Encrypt all your data?",
"description": "Encrypting all your data will keep your data safe and secure. This action can NOT be undone. Are you sure you want to continue?"
}
},
"cache": {
"title": "Clear cache",
"description": "If you encounter issues with images not loading or fonts not displaying correctly, try clearing your cache. This action will not remove your user data.",
"dialog": {
"title": "Are you sure?",
"description": "Clearing the cache will cause images and fonts to be re-downloaded on load. This action will not remove or modify your data.",
"successHint": "Cache cleared!"
}
}
},
"common": {
"reset": "Reset"
},
@ -1611,7 +1657,8 @@
"photoPermissionDescription": "Allow access to the photo library for uploading images.",
"openSettings": "Open Settings",
"photoPermissionTitle": "AppFlowy Would Like to Access Your Photo Library",
"doNotAllow": "Don't Allow"
"doNotAllow": "Don't Allow",
"image": "Image"
},
"commandPalette": {
"placeholder": "Type to search for views...",
@ -1623,4 +1670,4 @@
"betaTooltip": "We currently only support searching for pages",
"fromTrashHint": "From trash"
}
}
}

View File

@ -620,7 +620,6 @@
"typeAValue": "Escriba un valor...",
"layout": "Disposición",
"databaseLayout": "Disposición",
"viewList": "Vistas de base de datos",
"editView": "Editar vista",
"boardSettings": "Configuración del tablero",
"calendarSettings": "Configuración del calendario",
@ -628,7 +627,8 @@
"duplicateView": "Duplicar vista",
"deleteView": "Eliminar vista",
"numberOfVisibleFields": "{} mostrado",
"Properties": "Propiedades"
"Properties": "Propiedades",
"viewList": "Vistas de base de datos"
},
"textFilter": {
"contains": "Contiene",
@ -1156,10 +1156,10 @@
"layoutDateField": "Diseño de calendario por",
"changeLayoutDateField": "Cambiar campo de diseño",
"noDateTitle": "Sin cita",
"noDateHint": "Los eventos no programados se mostrarán aquí",
"unscheduledEventsTitle": "Eventos no programados",
"clickToAdd": "Haga clic para agregar al calendario",
"name": "Diseño de calendario"
"name": "Diseño de calendario",
"noDateHint": "Los eventos no programados se mostrarán aquí"
},
"referencedCalendarPrefix": "Vista de",
"quickJumpYear": "Ir a",