mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
chore: merge with main
This commit is contained in:
commit
38c7bfe721
18
CHANGELOG.md
18
CHANGELOG.md
@ -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
|
||||
|
@ -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"
|
||||
|
@ -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(
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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(
|
||||
|
@ -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,
|
||||
),
|
||||
|
@ -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,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
@ -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(
|
||||
|
@ -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(
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
@ -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;
|
||||
};
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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 [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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(
|
||||
|
@ -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,
|
||||
|
@ -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(),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
|
@ -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,
|
||||
|
@ -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(
|
||||
|
@ -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,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
|
@ -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,
|
||||
),
|
||||
|
@ -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 {
|
||||
|
@ -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(
|
||||
|
108
frontend/appflowy_flutter/lib/shared/window_title_bar.dart
Normal file
108
frontend/appflowy_flutter/lib/shared/window_title_bar.dart
Normal 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(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -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),
|
||||
|
@ -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)),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -27,7 +27,7 @@ class FlowyLogoTitle extends StatelessWidget {
|
||||
blendMode: null,
|
||||
),
|
||||
),
|
||||
const VSpace(40),
|
||||
const VSpace(20),
|
||||
FlowyText.regular(
|
||||
title,
|
||||
fontSize: FontSizes.s24,
|
||||
|
@ -12,8 +12,8 @@ enum SettingsPage {
|
||||
// NEW
|
||||
account,
|
||||
workspace,
|
||||
manageData,
|
||||
// OLD
|
||||
files,
|
||||
notifications,
|
||||
cloud,
|
||||
shortcuts,
|
||||
|
@ -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),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -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),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -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(
|
||||
|
@ -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'),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -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:
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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!,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -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() =>
|
||||
|
@ -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);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
@ -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(),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -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(
|
||||
|
@ -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(
|
||||
|
@ -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(
|
||||
|
@ -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"
|
||||
|
@ -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" }
|
@ -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" }
|
@ -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" }
|
@ -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 |
@ -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...",
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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",
|
||||
|
Loading…
Reference in New Issue
Block a user