fix: some UI issues were present in version 0.3.1. (#3401)

This commit is contained in:
Lucas.Xu 2023-09-14 19:22:32 +08:00 committed by GitHub
parent fe55452c79
commit 26a2bffcd1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 292 additions and 83 deletions

View File

@ -1,3 +1,4 @@
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/database_view/board/presentation/board_page.dart';
import 'package:appflowy/plugins/database_view/calendar/presentation/calendar_page.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/grid_page.dart';
@ -7,7 +8,7 @@ import 'package:appflowy/workspace/presentation/home/menu/view/view_item.dart';
import 'package:appflowy/workspace/presentation/home/menu/view/view_more_action_button.dart';
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:flutter/material.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
@ -22,14 +23,11 @@ void main() {
await tester.tapGoButton();
// create a new page
const name = 'Hello AppFlowy';
await tester.tapNewPageButton();
await tester.enterText(find.byType(TextFormField), name);
await tester.tapOKButton();
// expect to see a new document
tester.expectToSeePageName(
name,
LocaleKeys.menuAppHeader_defaultNewPageName.tr(),
);
// and with one paragraph block
expect(find.byType(TextBlockComponentWidget), findsOneWidget);

View File

@ -1,7 +1,9 @@
import 'dart:async';
import 'dart:io';
import 'package:appflowy/startup/entry_point.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/user/presentation/presentation.dart';
import 'package:appflowy/workspace/application/settings/prelude.dart';
import 'package:flowy_infra/uuid.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
@ -9,8 +11,8 @@ import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:path_provider/path_provider.dart';
import 'package:path/path.dart' as p;
import 'package:path_provider/path_provider.dart';
class FlowyTestContext {
FlowyTestContext({
@ -39,8 +41,8 @@ extension AppFlowyTestBase on WidgetTester {
IntegrationMode.integrationTest,
);
await wait(3000);
await pumpAndSettle(const Duration(seconds: 2));
await waitUntilSignInPageShow();
return FlowyTestContext(
applicationDataDirectory: directory,
);
@ -78,6 +80,27 @@ extension AppFlowyTestBase on WidgetTester {
return directory.path;
}
Future<void> waitUntilSignInPageShow() async {
final finder = find.byType(GoButton);
await pumpUntilFound(finder);
expect(finder, findsOneWidget);
}
Future<void> pumpUntilFound(
Finder finder, {
Duration timeout = const Duration(seconds: 10),
}) async {
bool timerDone = false;
final timer = Timer(timeout, () => timerDone = true);
while (timerDone != true) {
await pump();
if (any(finder)) {
timerDone = true;
}
}
timer.cancel();
}
Future<void> tapButton(
Finder finder, {
int? pointer,

View File

@ -1,6 +1,9 @@
import 'package:appflowy/core/config/kv.dart';
import 'package:appflowy/core/config/kv_keys.dart';
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/document/presentation/share/share_button.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/user/presentation/screens/screens.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/sidebar_new_page_button.dart';
import 'package:appflowy/workspace/presentation/home/menu/view/draggable_view_item.dart';
@ -279,7 +282,14 @@ extension CommonOperations on WidgetTester {
// create a new page
await tapAddViewButton(name: parentName ?? gettingStarted);
await tapButtonWithName(layout.menuName);
await tapOKButton();
final settingsOrFailure = await getIt<KeyValueStorage>().getWithFormat(
KVKeys.showRenameDialogWhenCreatingNewFile,
(value) => bool.parse(value),
);
final showRenameDialog = settingsOrFailure.fold((l) => false, (r) => r);
if (showRenameDialog) {
await tapOKButton();
}
await pumpAndSettle();
// hover on it and change it's name

View File

@ -7,6 +7,10 @@ import 'package:shared_preferences/shared_preferences.dart';
abstract class KeyValueStorage {
Future<void> set(String key, String value);
Future<Either<FlowyError, String>> get(String key);
Future<Either<FlowyError, T>> getWithFormat<T>(
String key,
T Function(String value) formatter,
);
Future<void> remove(String key);
Future<void> clear();
}
@ -26,6 +30,18 @@ class DartKeyValue implements KeyValueStorage {
return Left(FlowyError());
}
@override
Future<Either<FlowyError, T>> getWithFormat<T>(
String key,
T Function(String value) formatter,
) async {
final value = await get(key);
return value.fold(
(l) => left(l),
(r) => right(formatter(r)),
);
}
@override
Future<void> remove(String key) async {
await _initSharedPreferencesIfNeeded();
@ -71,6 +87,18 @@ class RustKeyValue implements KeyValueStorage {
return response.swap().map((r) => r.value);
}
@override
Future<Either<FlowyError, T>> getWithFormat<T>(
String key,
T Function(String value) formatter,
) async {
final value = await get(key);
return value.fold(
(l) => left(l),
(r) => right(formatter(r)),
);
}
@override
Future<void> remove(String key) async {
await ConfigEventRemoveKeyValue(

View File

@ -43,4 +43,10 @@ class KVKeys {
/// The value is a json string with the following format:
/// {'SidebarFolderCategoryType.value': true}
static const String expandedFolders = 'expandedFolders';
/// The key for saving if showing the rename dialog when creating a new file
///
/// The value is a boolean string.
static const String showRenameDialogWhenCreatingNewFile =
'showRenameDialogWhenCreatingNewFile';
}

View File

@ -34,11 +34,13 @@ class TransactionAdapter {
final DocumentService documentService;
final String documentId;
final bool _enableDebug = false;
final bool _enableDebug = true;
Future<void> apply(Transaction transaction, EditorState editorState) async {
final stopwatch = Stopwatch()..start();
Log.debug('transaction => ${transaction.toJson()}');
if (_enableDebug) {
Log.debug('transaction => ${transaction.toJson()}');
}
final actions = transaction.operations
.map((op) => op.toBlockAction(editorState, documentId))
.whereNotNull()
@ -58,14 +60,18 @@ class TransactionAdapter {
textId: payload.textId,
delta: payload.delta,
);
Log.debug('create external text: ${payload.delta}');
if (_enableDebug) {
Log.debug('create external text: ${payload.delta}');
}
} else if (type == TextDeltaType.update) {
await documentService.updateExternalText(
documentId: payload.documentId,
textId: payload.textId,
delta: payload.delta,
);
Log.debug('update external text: ${payload.delta}');
if (_enableDebug) {
Log.debug('update external text: ${payload.delta}');
}
}
}
final blockActions =

View File

@ -40,12 +40,16 @@ class BlockOptionButton extends StatelessWidget {
return PopoverActionList<PopoverAction>(
direction: PopoverDirection.leftWithCenterAligned,
actions: popoverActions,
onPopupBuilder: () => blockComponentState.alwaysShowActions = true,
onPopupBuilder: () {
keepEditorFocusNotifier.value += 1;
blockComponentState.alwaysShowActions = true;
},
onClosed: () {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
editorState.selectionType = null;
editorState.selection = null;
blockComponentState.alwaysShowActions = false;
keepEditorFocusNotifier.value -= 1;
});
},
onSelected: (action, controller) {

View File

@ -1,6 +1,8 @@
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
@ -31,24 +33,31 @@ final alignToolbarItem = ToolbarItem(
data = FlowySvgs.toolbar_align_right_s;
}
final child = FlowySvg(
data,
size: const Size.square(16),
color: isHighlight ? highlightColor : Colors.white,
final child = MouseRegion(
cursor: SystemMouseCursors.click,
child: FlowySvg(
data,
size: const Size.square(20),
color: isHighlight ? highlightColor : Colors.white,
),
);
return _AlignmentButtons(
child: child,
onAlignChanged: (align) async {
await editorState.updateNode(
selection,
(node) => node.copyWith(
attributes: {
...node.attributes,
blockComponentAlign: align,
},
),
);
},
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 4.0),
child: _AlignmentButtons(
child: child,
onAlignChanged: (align) async {
await editorState.updateNode(
selection,
(node) => node.copyWith(
attributes: {
...node.attributes,
blockComponentAlign: align,
},
),
);
},
),
);
},
);
@ -71,11 +80,15 @@ class _AlignmentButtonsState extends State<_AlignmentButtons> {
Widget build(BuildContext context) {
return AppFlowyPopover(
windowPadding: const EdgeInsets.all(0),
margin: const EdgeInsets.all(0),
margin: const EdgeInsets.all(4),
direction: PopoverDirection.bottomWithCenterAligned,
offset: const Offset(0, 10),
child: widget.child,
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.onTertiary,
borderRadius: const BorderRadius.all(Radius.circular(4)),
),
popupBuilder: (_) => _AlignButtons(onAlignChanged: widget.onAlignChanged),
child: widget.child,
);
}
}
@ -97,16 +110,19 @@ class _AlignButtons extends StatelessWidget {
const HSpace(4),
_AlignButton(
icon: FlowySvgs.toolbar_align_left_s,
tooltips: LocaleKeys.document_plugins_optionAction_left.tr(),
onTap: () => onAlignChanged('left'),
),
const _Divider(),
_AlignButton(
icon: FlowySvgs.toolbar_align_center_s,
tooltips: LocaleKeys.document_plugins_optionAction_center.tr(),
onTap: () => onAlignChanged('center'),
),
const _Divider(),
_AlignButton(
icon: FlowySvgs.toolbar_align_right_s,
tooltips: LocaleKeys.document_plugins_optionAction_right.tr(),
onTap: () => onAlignChanged('right'),
),
const HSpace(4),
@ -119,19 +135,25 @@ class _AlignButtons extends StatelessWidget {
class _AlignButton extends StatelessWidget {
const _AlignButton({
required this.icon,
required this.tooltips,
required this.onTap,
});
final FlowySvgData icon;
final String tooltips;
final VoidCallback onTap;
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onTap,
child: FlowySvg(
icon,
size: const Size.square(16),
child: Tooltip(
message: tooltips,
child: FlowySvg(
icon,
size: const Size.square(16),
color: Colors.white,
),
),
);
}

View File

@ -168,7 +168,7 @@ class OutlineItemWidget extends StatelessWidget {
hoverColor: Theme.of(context).hoverColor,
),
child: GestureDetector(
onTap: () => updateBlockSelection(context),
onTap: () => scrollToBlock(context),
child: Container(
padding: EdgeInsets.only(left: node.leftIndent),
child: Text(
@ -180,14 +180,14 @@ class OutlineItemWidget extends StatelessWidget {
);
}
void updateBlockSelection(BuildContext context) async {
void scrollToBlock(BuildContext context) {
final editorState = context.read<EditorState>();
editorState.selectionType = SelectionType.block;
editorState.selection = Selection.collapsed(
Position(path: node.path, offset: node.delta?.length ?? 0),
);
final editorScrollController = context.read<EditorScrollController>();
editorScrollController.itemScrollController.jumpTo(index: node.path.first);
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
editorState.selectionType = null;
editorState.selection = Selection.collapsed(
Position(path: node.path, offset: node.delta?.length ?? 0),
);
});
}
}

View File

@ -146,7 +146,10 @@ class _ToggleListBlockComponentWidgetState
}
@override
Widget buildComponent(BuildContext context) {
Widget buildComponent(
BuildContext context, {
bool withBackgroundColor = false,
}) {
final textDirection = calculateTextDirection(
layoutDirection: Directionality.maybeOf(context),
);

View File

@ -0,0 +1,29 @@
import 'package:appflowy/core/config/kv.dart';
import 'package:appflowy/core/config/kv_keys.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class CreateFileSettingsCubit extends Cubit<bool> {
CreateFileSettingsCubit(super.initialState) {
getInitialSettings();
}
Future<void> toggle({bool? value}) async {
await getIt<KeyValueStorage>().set(
KVKeys.showRenameDialogWhenCreatingNewFile,
(value ?? !state).toString(),
);
emit(value ?? !state);
}
Future<void> getInitialSettings() async {
final settingsOrFailure = await getIt<KeyValueStorage>().getWithFormat(
KVKeys.showRenameDialogWhenCreatingNewFile,
(value) => bool.parse(value),
);
settingsOrFailure.fold(
(_) => emit(false),
(settings) => emit(settings),
);
}
}

View File

@ -1,2 +1,3 @@
export 'settings_dialog_bloc.dart';
export 'application_data_storage.dart';
export 'create_file_settings_cubit.dart';
export 'settings_dialog_bloc.dart';

View File

@ -4,8 +4,8 @@ import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/workspace/application/menu/menu_bloc.dart';
import 'package:appflowy/workspace/application/sidebar/folder/folder_bloc.dart';
import 'package:appflowy/workspace/application/tabs/tabs_bloc.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/rename_view_dialog.dart';
import 'package:appflowy/workspace/presentation/home/menu/view/view_item.dart';
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
@ -118,14 +118,14 @@ class _PersonalFolderHeaderState extends State<PersonalFolderHeader> {
width: iconSize,
icon: const FlowySvg(FlowySvgs.add_s),
onPressed: () {
NavigatorTextFieldDialog(
title: LocaleKeys.newPageText.tr(),
value: '',
confirm: (value) {
if (value.isNotEmpty) {
createViewAndShowRenameDialogIfNeeded(
context,
LocaleKeys.newPageText.tr(),
(viewName) {
if (viewName.isNotEmpty) {
context.read<MenuBloc>().add(
MenuEvent.createApp(
value,
viewName,
index: 0,
),
);
@ -133,7 +133,7 @@ class _PersonalFolderHeaderState extends State<PersonalFolderHeader> {
widget.onAdded();
}
},
).show(context);
);
},
),
]

View File

@ -0,0 +1,35 @@
import 'package:appflowy/core/config/kv.dart';
import 'package:appflowy/core/config/kv_keys.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
/// Creates a new view and shows the rename dialog if needed.
///
/// If the user has enabled the setting to show the rename dialog when creating a new view,
/// this function will show the rename dialog.
///
/// Otherwise, it will just create the view with default name.
Future<void> createViewAndShowRenameDialogIfNeeded(
BuildContext context,
String dialogTitle,
void Function(String viewName) createView,
) async {
final value = await getIt<KeyValueStorage>().getWithFormat(
KVKeys.showRenameDialogWhenCreatingNewFile,
(value) => bool.parse(value),
);
final showRenameDialog = value.fold((l) => false, (r) => r);
if (context.mounted && showRenameDialog) {
NavigatorTextFieldDialog(
title: dialogTitle,
value: LocaleKeys.menuAppHeader_defaultNewPageName.tr(),
autoSelectAllText: true,
confirm: createView,
).show(context);
} else {
createView(LocaleKeys.menuAppHeader_defaultNewPageName.tr());
}
}

View File

@ -1,11 +1,11 @@
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/workspace/application/menu/menu_bloc.dart';
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/rename_view_dialog.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/style_widget/button.dart';
import 'package:flutter/material.dart';
import 'package:flowy_infra_ui/style_widget/extension.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class SidebarNewPageButton extends StatelessWidget {
@ -20,7 +20,15 @@ class SidebarNewPageButton extends StatelessWidget {
fillColor: Colors.transparent,
hoverColor: Colors.transparent,
fontColor: Theme.of(context).colorScheme.tertiary,
onPressed: () async => await _showCreatePageDialog(context),
onPressed: () async => await createViewAndShowRenameDialogIfNeeded(
context,
LocaleKeys.newPageText.tr(),
(viewName) {
if (viewName.isNotEmpty) {
context.read<MenuBloc>().add(MenuEvent.createApp(viewName));
}
},
),
heading: Container(
width: 16,
height: 16,
@ -44,16 +52,4 @@ class SidebarNewPageButton extends StatelessWidget {
),
);
}
Future<void> _showCreatePageDialog(BuildContext context) async {
return NavigatorTextFieldDialog(
title: LocaleKeys.newPageText.tr(),
value: '',
confirm: (value) {
if (value.isNotEmpty) {
context.read<MenuBloc>().add(MenuEvent.createApp(value));
}
},
).show(context);
}
}

View File

@ -7,6 +7,7 @@ import 'package:appflowy/workspace/application/tabs/tabs_bloc.dart';
import 'package:appflowy/workspace/application/view/view_bloc.dart';
import 'package:appflowy/workspace/application/view/view_ext.dart';
import 'package:appflowy/workspace/presentation/home/menu/menu_shared_state.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/rename_view_dialog.dart';
import 'package:appflowy/workspace/presentation/home/menu/view/draggable_view_item.dart';
import 'package:appflowy/workspace/presentation/home/menu/view/view_action_type.dart';
import 'package:appflowy/workspace/presentation/home/menu/view/view_add_button.dart';
@ -350,22 +351,21 @@ class _SingleInnerViewItemState extends State<SingleInnerViewItem> {
createNewView,
) {
if (createNewView) {
NavigatorTextFieldDialog(
title: _convertLayoutToHintText(pluginBuilder.layoutType!),
value: LocaleKeys.menuAppHeader_defaultNewPageName.tr(),
autoSelectAllText: true,
confirm: (value) {
if (value.isNotEmpty) {
createViewAndShowRenameDialogIfNeeded(
context,
_convertLayoutToHintText(pluginBuilder.layoutType!),
(viewName) {
if (viewName.isNotEmpty) {
context.read<ViewBloc>().add(
ViewEvent.createView(
value,
viewName,
pluginBuilder.layoutType!,
openAfterCreated: openAfterCreated,
),
);
}
},
).show(context);
);
}
context.read<ViewBloc>().add(
const ViewEvent.setIsExpanded(true),

View File

@ -0,0 +1,37 @@
import 'package:appflowy/workspace/application/settings/prelude.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/settings_appearance/theme_setting_entry_template.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
bool _prevSetting = false;
class CreateFileSettings extends StatelessWidget {
CreateFileSettings({
super.key,
});
final cubit = CreateFileSettingsCubit(_prevSetting);
@override
Widget build(BuildContext context) {
return ThemeSettingEntryTemplateWidget(
label: 'Show rename dialog when creating a new file',
trailing: [
BlocProvider.value(
value: cubit,
child: BlocBuilder<CreateFileSettingsCubit, bool>(
builder: (context, state) {
return Switch(
value: state,
onChanged: (value) {
cubit.toggle(value: value);
_prevSetting = value;
},
);
},
),
),
],
);
}
}

View File

@ -1,4 +1,5 @@
import 'package:appflowy/workspace/application/appearance.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/settings_appearance/create_file_setting.dart';
import 'package:flowy_infra/plugins/bloc/dynamic_plugin_bloc.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
@ -34,6 +35,7 @@ class SettingsAppearanceView extends StatelessWidget {
TextDirectionSetting(
currentTextDirection: state.textDirection,
),
CreateFileSettings(),
],
);
},

View File

@ -1,9 +1,10 @@
import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart';
import 'dart:ui';
import 'package:flowy_infra/size.dart';
import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart';
import 'package:flowy_infra_ui/widget/dialog/dialog_size.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'dart:ui';
extension IntoDialog on Widget {
Future<dynamic> show(BuildContext context) async {
@ -71,7 +72,7 @@ class StyledDialog extends StatelessWidget {
maxWidth: maxWidth ?? double.infinity,
),
child: ClipRRect(
borderRadius: borderRadius,
borderRadius: borderRadius ?? BorderRadius.zero,
child: SingleChildScrollView(
physics: StyledScrollPhysics(),
//https://medium.com/saugo360/https-medium-com-saugo360-flutter-using-overlay-to-display-floating-widgets-2e6d0e8decb9

View File

@ -54,8 +54,8 @@ packages:
dependency: "direct main"
description:
path: "."
ref: "6d68f90"
resolved-ref: "6d68f9003fa023d215dc5f20e8900f985c2cdaa1"
ref: a97c816
resolved-ref: a97c816c1d8cfbc5644a8be49deae334c47261e3
url: "https://github.com/AppFlowy-IO/appflowy-editor.git"
source: git
version: "1.3.0"
@ -1430,6 +1430,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.2.0"
string_validator:
dependency: transitive
description:
name: string_validator
sha256: b419cf5d21d608522e6e7cafed4deb34b6f268c43df866e63c320bab98a08cf6
url: "https://pub.dev"
source: hosted
version: "1.0.0"
styled_widget:
dependency: "direct main"
description:

View File

@ -47,7 +47,7 @@ dependencies:
appflowy_editor:
git:
url: https://github.com/AppFlowy-IO/appflowy-editor.git
ref: 6d68f90
ref: a97c816
appflowy_popover:
path: packages/appflowy_popover