feat: word and char count (#4705)

* feat: word and char count

* chore: lints after merge

* feat: add create time of view in more action

* fix: missing comma lint

* feat: add duplicate/delete + database view more

* fix: dispose logic

* feat: code cleanup

* fix: add false for isDocument in databases

* feat: register view info bloc on plugin basis

* fix: accidental removal

* chore: clean up

* fix: add ViewInfoBloc above Row Document Editor

* chore: final clean up
This commit is contained in:
Mathias Mogensen 2024-02-25 16:46:13 +01:00 committed by GitHub
parent 93af6e69a1
commit 609557c357
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
30 changed files with 566 additions and 173 deletions

View File

@ -1,10 +1,11 @@
import 'package:flutter/material.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/more/font_size_slider.dart';
import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart';
import 'package:appflowy/workspace/presentation/widgets/more_view_actions/widgets/font_size_stepper.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../setting.dart';

View File

@ -1,12 +1,13 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/presentation/setting/font/font_picker_screen.dart';
import 'package:appflowy/plugins/document/presentation/more/cubit/document_appearance_cubit.dart';
import 'package:appflowy/plugins/document/application/document_appearance_cubit.dart';
import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import 'package:google_fonts/google_fonts.dart';

View File

@ -1,16 +1,20 @@
import 'package:flutter/material.dart';
import 'package:appflowy/plugins/database/application/database_controller.dart';
import 'package:appflowy/plugins/database/application/tab_bar_bloc.dart';
import 'package:appflowy/plugins/database/grid/presentation/layout/sizes.dart';
import 'package:appflowy/plugins/database/widgets/share_button.dart';
import 'package:appflowy/plugins/util.dart';
import 'package:appflowy/startup/plugin/plugin.dart';
import 'package:appflowy/workspace/application/view_info/view_info_bloc.dart';
import 'package:appflowy/workspace/presentation/home/home_stack.dart';
import 'package:appflowy/workspace/presentation/widgets/more_view_actions/more_view_actions.dart';
import 'package:appflowy/workspace/presentation/widgets/tab_bar_item.dart';
import 'package:appflowy/workspace/presentation/widgets/view_title_bar.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/widget/spacing.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'desktop/tab_bar_header.dart';
@ -183,7 +187,9 @@ class DatabaseTabBarViewPlugin extends Plugin {
@override
final ViewPluginNotifier notifier;
final PluginType _pluginType;
late final ViewInfoBloc _viewInfoBloc;
/// Used to open a Row on plugin load
///
@ -191,6 +197,7 @@ class DatabaseTabBarViewPlugin extends Plugin {
@override
PluginWidgetBuilder get widgetBuilder => DatabasePluginWidgetBuilder(
bloc: _viewInfoBloc,
notifier: notifier,
initialRowId: initialRowId,
);
@ -200,11 +207,28 @@ class DatabaseTabBarViewPlugin extends Plugin {
@override
PluginType get pluginType => _pluginType;
@override
void init() {
_viewInfoBloc = ViewInfoBloc(view: notifier.view)
..add(const ViewInfoEvent.started());
}
@override
void dispose() {
_viewInfoBloc.close();
notifier.dispose();
}
}
class DatabasePluginWidgetBuilder extends PluginWidgetBuilder {
DatabasePluginWidgetBuilder({required this.notifier, this.initialRowId});
DatabasePluginWidgetBuilder({
required this.bloc,
required this.notifier,
this.initialRowId,
});
final ViewInfoBloc bloc;
final ViewPluginNotifier notifier;
/// Used to open a Row on plugin load
@ -225,6 +249,7 @@ class DatabasePluginWidgetBuilder extends PluginWidgetBuilder {
context?.onDeleted(notifier.view, deletedView.index);
}
});
return DatabaseTabBarView(
key: ValueKey(notifier.view.id),
view: notifier.view,
@ -238,9 +263,16 @@ class DatabasePluginWidgetBuilder extends PluginWidgetBuilder {
@override
Widget? get rightBarItem {
return DatabaseShareButton(
key: ValueKey(notifier.view.id),
view: notifier.view,
final view = notifier.view;
return BlocProvider<ViewInfoBloc>.value(
value: bloc,
child: Row(
children: [
DatabaseShareButton(key: ValueKey(view.id), view: view),
const HSpace(4),
MoreViewActions(view: view, isDocument: false),
],
),
);
}
}

View File

@ -1,7 +1,9 @@
import 'package:flutter/material.dart';
import 'package:appflowy/startup/plugin/plugin.dart';
import 'package:appflowy/workspace/application/view/view_ext.dart';
import 'package:appflowy/workspace/application/view/view_listener.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:flutter/material.dart';
class DatabaseViewWidget extends StatefulWidget {
const DatabaseViewWidget({
@ -28,11 +30,13 @@ class _DatabaseViewWidgetState extends State<DatabaseViewWidget> {
/// The view will be updated by the [ViewListener].
late ViewPB view;
late Plugin viewPlugin;
@override
void initState() {
super.initState();
view = widget.view;
viewPlugin = view.plugin()..init();
_listenOnViewUpdated();
}
@ -40,6 +44,7 @@ class _DatabaseViewWidgetState extends State<DatabaseViewWidget> {
void dispose() {
_layoutTypeChangeNotifier.dispose();
_listener.stop();
viewPlugin.dispose();
super.dispose();
}
@ -47,12 +52,9 @@ class _DatabaseViewWidgetState extends State<DatabaseViewWidget> {
Widget build(BuildContext context) {
return ValueListenableBuilder<ViewLayoutPB>(
valueListenable: _layoutTypeChangeNotifier,
builder: (_, __, ___) {
return view
.plugin()
.widgetBuilder
.buildWidget(shrinkWrap: widget.shrinkWrap);
},
builder: (_, __, ___) => viewPlugin.widgetBuilder.buildWidget(
shrinkWrap: widget.shrinkWrap,
),
);
}

View File

@ -1,13 +1,15 @@
import 'package:flutter/material.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/database/grid/application/row/row_document_bloc.dart';
import 'package:appflowy/plugins/document/application/doc_bloc.dart';
import 'package:appflowy/plugins/document/presentation/editor_page.dart';
import 'package:appflowy/plugins/document/presentation/editor_style.dart';
import 'package:appflowy/workspace/application/view_info/view_info_bloc.dart';
import 'package:appflowy_backend/log.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/widget/error_page.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class RowDocument extends StatelessWidget {
@ -107,22 +109,25 @@ class _RowEditorState extends State<RowEditor> {
howToFix: LocaleKeys.errorDialog_howToFixFallback.tr(),
);
}
return IntrinsicHeight(
child: Container(
constraints: const BoxConstraints(minHeight: 300),
child: AppFlowyEditorPage(
shrinkWrap: true,
autoFocus: false,
editorState: editorState,
// scrollController: widget.scrollController,
styleCustomizer: EditorStyleCustomizer(
context: context,
padding: const EdgeInsets.only(left: 16, right: 54),
child: BlocProvider<ViewInfoBloc>(
create: (context) => ViewInfoBloc(view: widget.viewPB),
child: AppFlowyEditorPage(
shrinkWrap: true,
autoFocus: false,
editorState: editorState,
styleCustomizer: EditorStyleCustomizer(
context: context,
padding: const EdgeInsets.only(left: 16, right: 54),
),
showParagraphPlaceholder: (editorState, node) =>
editorState.document.isEmpty,
placeholderText: (node) =>
LocaleKeys.cardDetails_notesPlaceholder.tr(),
),
showParagraphPlaceholder: (editorState, node) =>
editorState.document.isEmpty,
placeholderText: (node) =>
LocaleKeys.cardDetails_notesPlaceholder.tr(),
),
),
);

View File

@ -1,10 +1,11 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:appflowy/core/config/kv_keys.dart';
import 'package:appflowy/util/color_to_hex_string.dart';
import 'package:appflowy/workspace/application/settings/appearance/base_appearance.dart';
import 'package:bloc/bloc.dart';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
class DocumentAppearance {

View File

@ -1,22 +1,24 @@
library document_plugin;
import 'package:flutter/material.dart';
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/document/application/document_appearance_cubit.dart';
import 'package:appflowy/plugins/document/document_page.dart';
import 'package:appflowy/plugins/document/presentation/favorite/favorite_button.dart';
import 'package:appflowy/plugins/document/presentation/more/cubit/document_appearance_cubit.dart';
import 'package:appflowy/plugins/document/presentation/more/more_button.dart';
import 'package:appflowy/plugins/document/presentation/share/share_button.dart';
import 'package:appflowy/plugins/util.dart';
import 'package:appflowy/startup/plugin/plugin.dart';
import 'package:appflowy/workspace/application/view_info/view_info_bloc.dart';
import 'package:appflowy/workspace/presentation/home/home_stack.dart';
import 'package:appflowy/workspace/presentation/widgets/more_view_actions/more_view_actions.dart';
import 'package:appflowy/workspace/presentation/widgets/tab_bar_item.dart';
import 'package:appflowy/workspace/presentation/widgets/view_title_bar.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.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 DocumentPluginBuilder extends PluginBuilder {
@ -52,6 +54,7 @@ class DocumentPlugin extends Plugin<int> {
}
late PluginType _pluginType;
late final ViewInfoBloc _viewInfoBloc;
@override
final ViewPluginNotifier notifier;
@ -60,6 +63,7 @@ class DocumentPlugin extends Plugin<int> {
@override
PluginWidgetBuilder get widgetBuilder => DocumentPluginWidgetBuilder(
bloc: _viewInfoBloc,
notifier: notifier,
initialSelection: initialSelection,
);
@ -69,15 +73,29 @@ class DocumentPlugin extends Plugin<int> {
@override
PluginId get id => notifier.view.id;
@override
void init() {
_viewInfoBloc = ViewInfoBloc(view: notifier.view)
..add(const ViewInfoEvent.started());
}
@override
void dispose() {
_viewInfoBloc.close();
notifier.dispose();
}
}
class DocumentPluginWidgetBuilder extends PluginWidgetBuilder
with NavigationItem {
DocumentPluginWidgetBuilder({
required this.bloc,
required this.notifier,
this.initialSelection,
});
final ViewInfoBloc bloc;
final ViewPluginNotifier notifier;
ViewPB get view => notifier.view;
int? deletedViewIndex;
@ -95,12 +113,15 @@ class DocumentPluginWidgetBuilder extends PluginWidgetBuilder
}
});
return BlocBuilder<DocumentAppearanceCubit, DocumentAppearance>(
builder: (_, state) => DocumentPage(
key: ValueKey(view.id),
view: view,
onDeleted: () => context?.onDeleted(view, deletedViewIndex),
initialSelection: initialSelection,
return BlocProvider<ViewInfoBloc>.value(
value: bloc,
child: BlocBuilder<DocumentAppearanceCubit, DocumentAppearance>(
builder: (_, state) => DocumentPage(
key: ValueKey(view.id),
view: view,
onDeleted: () => context?.onDeleted(view, deletedViewIndex),
initialSelection: initialSelection,
),
),
);
}
@ -113,17 +134,20 @@ class DocumentPluginWidgetBuilder extends PluginWidgetBuilder
@override
Widget? get rightBarItem {
return Row(
children: [
DocumentShareButton(key: ValueKey(view.id), view: view),
const HSpace(4),
DocumentFavoriteButton(
key: ValueKey('favorite_button_${view.id}'),
view: view,
),
const HSpace(4),
const DocumentMoreButton(),
],
return BlocProvider<ViewInfoBloc>.value(
value: bloc,
child: Row(
children: [
DocumentShareButton(key: ValueKey(view.id), view: view),
const HSpace(4),
DocumentFavoriteButton(
key: ValueKey('favorite_button_${view.id}'),
view: view,
),
const HSpace(4),
MoreViewActions(view: view),
],
),
);
}

View File

@ -1,3 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:appflowy/plugins/document/application/doc_bloc.dart';
import 'package:appflowy/plugins/document/presentation/editor_configuration.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/align_toolbar_item/custom_text_align_command.dart';
@ -14,13 +17,12 @@ import 'package:appflowy/plugins/inline_actions/inline_actions_command.dart';
import 'package:appflowy/plugins/inline_actions/inline_actions_service.dart';
import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart';
import 'package:appflowy/workspace/application/settings/shortcuts/settings_shortcuts_service.dart';
import 'package:appflowy/workspace/application/view_info/view_info_bloc.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/emoji_picker/emoji_picker.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:collection/collection.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 List<CommandShortcutEvent> commandShortcutEvents = [
@ -176,6 +178,8 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
late final EditorScrollController editorScrollController;
late final ViewInfoBloc viewInfoBloc = context.read<ViewInfoBloc>();
Future<bool> showSlashMenu(editorState) async => customSlashCommand(
slashMenuItems,
shouldInsertSlash: false,
@ -186,6 +190,12 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
void initState() {
super.initState();
viewInfoBloc.add(
ViewInfoEvent.registerEditorState(
editorState: widget.editorState,
),
);
_initEditorL10n();
_initializeShortcuts();
indentableBlockTypes.add(ToggleListBlockKeys.type);
@ -224,6 +234,10 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
@override
void dispose() {
if (!viewInfoBloc.isClosed) {
viewInfoBloc.add(const ViewInfoEvent.unregisterEditorState());
}
SystemChannels.textInput.invokeMethod('TextInput.hide');
if (widget.scrollController == null) {

View File

@ -5,8 +5,8 @@ import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/presentation/bottom_sheet/show_mobile_bottom_sheet.dart';
import 'package:appflowy/plugins/base/drag_handler.dart';
import 'package:appflowy/plugins/document/application/doc_bloc.dart';
import 'package:appflowy/plugins/document/application/document_appearance_cubit.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mention_block.dart';
import 'package:appflowy/plugins/document/presentation/more/cubit/document_appearance_cubit.dart';
import 'package:appflowy/user/application/reminder/reminder_bloc.dart';
import 'package:appflowy/user/application/reminder/reminder_extension.dart';
import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart';

View File

@ -1,12 +1,13 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:appflowy/mobile/presentation/setting/font/font_picker_screen.dart';
import 'package:appflowy/plugins/document/application/document_appearance_cubit.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/_toolbar_theme.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
import 'package:appflowy/plugins/document/presentation/more/cubit/document_appearance_cubit.dart';
import 'package:appflowy/util/google_font_family_extension.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:provider/provider.dart';

View File

@ -1,9 +1,12 @@
import 'dart:math';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:appflowy/plugins/document/application/document_appearance_cubit.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';
import 'package:appflowy/plugins/document/presentation/more/cubit/document_appearance_cubit.dart';
import 'package:appflowy/plugins/inline_actions/inline_actions_menu.dart';
import 'package:appflowy/util/google_font_family_extension.dart';
import 'package:appflowy/workspace/application/appearance_defaults.dart';
@ -11,8 +14,6 @@ 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';

View File

@ -1,85 +0,0 @@
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/document/presentation/more/cubit/document_appearance_cubit.dart';
import 'package:appflowy/plugins/document/presentation/more/font_size_slider.dart';
import 'package:appflowy_popover/appflowy_popover.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:flowy_infra_ui/style_widget/hover.dart';
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class DocumentMoreButton extends StatelessWidget {
const DocumentMoreButton({super.key});
@override
Widget build(BuildContext context) {
return AppFlowyPopover(
constraints: BoxConstraints.loose(const Size(200, 400)),
offset: const Offset(0, 30),
popupBuilder: (_) {
final actions = [
AppFlowyPopover(
direction: PopoverDirection.leftWithCenterAligned,
constraints: const BoxConstraints(maxHeight: 40, maxWidth: 240),
offset: const Offset(-10, 0),
popupBuilder: (context) {
return BlocBuilder<DocumentAppearanceCubit, DocumentAppearance>(
builder: (context, state) {
return FontSizeStepper(
minimumValue: 10,
maximumValue: 24,
value: state.fontSize,
divisions: 8,
onChanged: (newFontSize) {
context
.read<DocumentAppearanceCubit>()
.syncFontSize(newFontSize);
},
);
},
);
},
child: FlowyButton(
text: FlowyText.regular(
LocaleKeys.moreAction_fontSize.tr(),
color: AFThemeExtension.of(context).textColor,
),
leftIcon: Icon(
Icons.format_size_sharp,
color: Theme.of(context).iconTheme.color,
size: 18,
),
leftIconSize: const Size(18, 18),
hoverColor: AFThemeExtension.of(context).lightGreyHover,
),
),
];
return ListView.separated(
shrinkWrap: true,
padding: EdgeInsets.zero,
itemCount: actions.length,
separatorBuilder: (_, __) => const VSpace(4),
physics: StyledScrollPhysics(),
itemBuilder: (_, index) => actions[index],
);
},
child: FlowyTooltip(
message: LocaleKeys.moreAction_moreOptions.tr(),
child: FlowyHover(
child: Padding(
padding: const EdgeInsets.all(6),
child: FlowySvg(
FlowySvgs.details_s,
size: const Size(18, 18),
color: Theme.of(context).iconTheme.color,
),
),
),
),
);
}
}

View File

@ -1,11 +1,12 @@
library flowy_plugin;
import 'package:flutter/widgets.dart';
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/startup/plugin/plugin.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/workspace/presentation/home/home_stack.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:flutter/widgets.dart';
export "./src/sandbox.dart";
@ -29,6 +30,8 @@ abstract class Plugin<T> {
PluginType get pluginType;
void init() {}
void dispose() {
notifier?.dispose();
}

View File

@ -1,7 +1,10 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:appflowy/mobile/application/mobile_router.dart';
import 'package:appflowy/plugins/document/presentation/more/cubit/document_appearance_cubit.dart';
import 'package:appflowy/plugins/document/application/document_appearance_cubit.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/user/application/reminder/reminder_bloc.dart';
import 'package:appflowy/user/application/user_settings_service.dart';
@ -18,8 +21,6 @@ import 'package:appflowy_editor/appflowy_editor.dart' hide Log;
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/theme.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';
import 'package:go_router/go_router.dart';

View File

@ -0,0 +1,87 @@
import 'package:appflowy/util/int64_extension.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:bloc/bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'view_info_bloc.freezed.dart';
class ViewInfoBloc extends Bloc<ViewInfoEvent, ViewInfoState> {
ViewInfoBloc({required this.view}) : super(ViewInfoState.initial()) {
on<ViewInfoEvent>((event, emit) {
event.when(
started: () {
emit(state.copyWith(createdAt: view.createTime.toDateTime()));
},
unregisterEditorState: () {
_clearWordCountService();
emit(state.copyWith(documentCounters: null));
},
registerEditorState: (editorState) {
_clearWordCountService();
_wordCountService = WordCountService(editorState: editorState)
..addListener(_onWordCountChanged)
..register();
emit(
state.copyWith(
documentCounters: _wordCountService!.documentCounters,
),
);
},
wordCountChanged: () {
emit(
state.copyWith(
documentCounters: _wordCountService?.documentCounters,
),
);
},
);
});
}
final ViewPB view;
WordCountService? _wordCountService;
@override
Future<void> close() async {
_clearWordCountService();
await super.close();
}
void _onWordCountChanged() => add(const ViewInfoEvent.wordCountChanged());
void _clearWordCountService() {
_wordCountService
?..removeListener(_onWordCountChanged)
..dispose();
_wordCountService = null;
}
}
@freezed
class ViewInfoEvent with _$ViewInfoEvent {
const factory ViewInfoEvent.started() = _Started;
const factory ViewInfoEvent.unregisterEditorState() = _UnregisterEditorState;
const factory ViewInfoEvent.registerEditorState({
required EditorState editorState,
}) = _RegisterEditorState;
const factory ViewInfoEvent.wordCountChanged() = _WordCountChanged;
}
@freezed
class ViewInfoState with _$ViewInfoState {
const factory ViewInfoState({
required Counters? documentCounters,
required DateTime? createdAt,
}) = _ViewInfoState;
factory ViewInfoState.initial() => const ViewInfoState(
documentCounters: null,
createdAt: null,
);
}

View File

@ -1,3 +1,5 @@
import 'package:flutter/material.dart';
import 'package:appflowy/core/frameless_window.dart';
import 'package:appflowy/plugins/blank/blank.dart';
import 'package:appflowy/startup/plugin/plugin.dart';
@ -10,7 +12,6 @@ import 'package:appflowy/workspace/presentation/home/toast.dart';
import 'package:appflowy_backend/dispatch/dispatch.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:provider/provider.dart';
import 'package:time/time.dart';
@ -175,6 +176,7 @@ class PageNotifier extends ChangeNotifier {
/// No need compare the old plugin with the new plugin. Just set it.
set plugin(Plugin newPlugin) {
_plugin.dispose();
newPlugin.init();
/// Set the plugin view as the latest view.
FolderEventSetLatestView(ViewIdPB(value: newPlugin.id)).send();

View File

@ -1,6 +1,6 @@
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/document/presentation/more/cubit/document_appearance_cubit.dart';
import 'package:appflowy/plugins/document/application/document_appearance_cubit.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/user/application/auth/auth_service.dart';
import 'package:appflowy/workspace/application/menu/menu_user_bloc.dart';

View File

@ -1,11 +1,12 @@
import 'package:flutter/material.dart';
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/document/presentation/more/cubit/document_appearance_cubit.dart';
import 'package:appflowy/plugins/document/application/document_appearance_cubit.dart';
import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.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';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'theme_setting_entry_template.dart';

View File

@ -1,12 +1,13 @@
import 'package:flutter/material.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/document/presentation/more/cubit/document_appearance_cubit.dart';
import 'package:appflowy/plugins/document/application/document_appearance_cubit.dart';
import 'package:appflowy/workspace/application/appearance_defaults.dart';
import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/settings_appearance/document_color_setting_button.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/settings_appearance/theme_setting_entry_template.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 DocumentCursorColorSetting extends StatelessWidget {

View File

@ -1,12 +1,13 @@
import 'package:flutter/material.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/document/presentation/more/cubit/document_appearance_cubit.dart';
import 'package:appflowy/plugins/document/application/document_appearance_cubit.dart';
import 'package:appflowy/workspace/application/appearance_defaults.dart';
import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/settings_appearance/document_color_setting_button.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/settings_appearance/theme_setting_entry_template.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 DocumentSelectionColorSetting extends StatelessWidget {

View File

@ -1,6 +1,8 @@
import 'package:flutter/material.dart';
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/document/presentation/more/cubit/document_appearance_cubit.dart';
import 'package:appflowy/plugins/document/application/document_appearance_cubit.dart';
import 'package:appflowy/util/google_font_family_extension.dart';
import 'package:appflowy/workspace/application/appearance_defaults.dart';
import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart';
@ -8,7 +10,6 @@ import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:collection/collection.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:google_fonts/google_fonts.dart';

View File

@ -0,0 +1,119 @@
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_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';
import 'package:appflowy/workspace/presentation/widgets/more_view_actions/widgets/view_meta_info.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-user/date_time.pbenum.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:flowy_infra_ui/style_widget/hover.dart';
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class MoreViewActions extends StatefulWidget {
const MoreViewActions({
super.key,
required this.view,
this.isDocument = true,
});
/// The view to show the actions for.
final ViewPB view;
/// If false the view is a Database, otherwise it is a Document.
final bool isDocument;
@override
State<MoreViewActions> createState() => _MoreViewActionsState();
}
class _MoreViewActionsState extends State<MoreViewActions> {
late final List<Widget> viewActions;
late final UserDateFormatPB dateFormat;
final popoverMutex = PopoverMutex();
@override
void initState() {
super.initState();
dateFormat = context.read<AppearanceSettingsCubit>().state.dateFormat;
viewActions = ViewActionType.values
.map(
(type) => ViewAction(
type: type,
view: widget.view,
mutex: popoverMutex,
),
)
.toList();
}
@override
void dispose() {
popoverMutex.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return BlocBuilder<ViewInfoBloc, ViewInfoState>(
builder: (context, state) {
return AppFlowyPopover(
mutex: popoverMutex,
constraints: BoxConstraints.loose(const Size(200, 400)),
offset: const Offset(0, 30),
popupBuilder: (_) {
final actions = [
if (widget.isDocument) ...[
const FontSizeAction(),
const Divider(height: 4),
],
...viewActions,
if (state.documentCounters != null ||
state.createdAt != null) ...[
const Divider(height: 4),
ViewMetaInfo(
dateFormat: dateFormat,
documentCounters: state.documentCounters,
createdAt: state.createdAt,
),
],
];
return ListView.separated(
shrinkWrap: true,
padding: EdgeInsets.zero,
itemCount: actions.length,
separatorBuilder: (_, __) => const VSpace(4),
physics: StyledScrollPhysics(),
itemBuilder: (_, index) => actions[index],
);
},
child: FlowyTooltip(
message: LocaleKeys.moreAction_moreOptions.tr(),
child: FlowyHover(
style: HoverStyle(
foregroundColorOnHover: Theme.of(context).colorScheme.onPrimary,
),
builder: (context, isHovering) => Padding(
padding: const EdgeInsets.all(6),
child: FlowySvg(
FlowySvgs.details_s,
size: const Size(18, 18),
color: isHovering
? Theme.of(context).colorScheme.onPrimary
: Theme.of(context).iconTheme.color,
),
),
),
),
);
},
);
}
}

View File

@ -0,0 +1,66 @@
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';
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';
enum ViewActionType {
delete,
duplicate;
String get label => switch (this) {
ViewActionType.delete => LocaleKeys.moreAction_deleteView.tr(),
ViewActionType.duplicate => LocaleKeys.moreAction_duplicateView.tr(),
};
FlowySvgData get icon => switch (this) {
ViewActionType.delete => FlowySvgs.delete_s,
ViewActionType.duplicate => FlowySvgs.m_duplicate_s,
};
ViewEvent get actionEvent => switch (this) {
ViewActionType.delete => const ViewEvent.delete(),
ViewActionType.duplicate => const ViewEvent.duplicate(),
};
}
class ViewAction extends StatelessWidget {
const ViewAction({
super.key,
required this.type,
required this.view,
this.mutex,
});
final ViewActionType type;
final ViewPB view;
final PopoverMutex? mutex;
@override
Widget build(BuildContext context) {
return FlowyButton(
onTap: () {
getIt<ViewBloc>(param1: view).add(type.actionEvent);
mutex?.close();
},
text: FlowyText.regular(
type.label,
color: AFThemeExtension.of(context).textColor,
),
leftIcon: FlowySvg(
type.icon,
color: Theme.of(context).iconTheme.color,
size: const Size.square(18),
),
leftIconSize: const Size(18, 18),
hoverColor: AFThemeExtension.of(context).lightGreyHover,
);
}
}

View File

@ -0,0 +1,49 @@
import 'package:flutter/material.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/document/application/document_appearance_cubit.dart';
import 'package:appflowy/workspace/presentation/widgets/more_view_actions/widgets/font_size_stepper.dart';
import 'package:appflowy_popover/appflowy_popover.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_bloc/flutter_bloc.dart';
class FontSizeAction extends StatelessWidget {
const FontSizeAction({super.key});
@override
Widget build(BuildContext context) {
return AppFlowyPopover(
direction: PopoverDirection.leftWithCenterAligned,
constraints: const BoxConstraints(maxHeight: 40, maxWidth: 240),
offset: const Offset(-10, 0),
popupBuilder: (context) {
return BlocBuilder<DocumentAppearanceCubit, DocumentAppearance>(
builder: (_, state) => FontSizeStepper(
minimumValue: 10,
maximumValue: 24,
value: state.fontSize,
divisions: 8,
onChanged: (newFontSize) => context
.read<DocumentAppearanceCubit>()
.syncFontSize(newFontSize),
),
);
},
child: FlowyButton(
text: FlowyText.regular(
LocaleKeys.moreAction_fontSize.tr(),
color: AFThemeExtension.of(context).textColor,
),
leftIcon: Icon(
Icons.format_size_sharp,
color: Theme.of(context).iconTheme.color,
size: 18,
),
leftIconSize: const Size(18, 18),
hoverColor: AFThemeExtension.of(context).lightGreyHover,
),
);
}
}

View File

@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flowy_infra_ui/style_widget/text.dart';
import 'package:flowy_infra_ui/widget/spacing.dart';
import 'package:flutter/material.dart';
class FontSizeStepper extends StatefulWidget {
const FontSizeStepper({
@ -53,10 +54,7 @@ class _FontSizeStepperState extends State<FontSizeStepper> {
max: widget.maximumValue,
divisions: widget.divisions,
onChanged: (value) {
setState(() {
_value = value;
});
setState(() => _value = value);
widget.onChanged(value);
},
),

View File

@ -0,0 +1,59 @@
import 'package:flutter/material.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/workspace/application/settings/date_time/date_format_ext.dart';
import 'package:appflowy_backend/protobuf/flowy-user/date_time.pbenum.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/style_widget/text.dart';
import 'package:flowy_infra_ui/widget/spacing.dart';
class ViewMetaInfo extends StatelessWidget {
const ViewMetaInfo({
super.key,
required this.dateFormat,
this.documentCounters,
this.createdAt,
});
final UserDateFormatPB dateFormat;
final Counters? documentCounters;
final DateTime? createdAt;
@override
Widget build(BuildContext context) {
// If more info is added to this Widget, use a separated ListView
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 6),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (documentCounters != null) ...[
FlowyText.regular(
LocaleKeys.moreAction_wordCount.tr(
args: [documentCounters!.wordCount.toString()],
),
color: Theme.of(context).hintColor,
),
const VSpace(2),
FlowyText.regular(
LocaleKeys.moreAction_charCount.tr(
args: [documentCounters!.charCount.toString()],
),
color: Theme.of(context).hintColor,
),
],
if (createdAt != null) ...[
if (documentCounters != null) const VSpace(2),
FlowyText.regular(
LocaleKeys.moreAction_createdAt.tr(
args: [dateFormat.formatDate(createdAt!, false)],
),
color: Theme.of(context).hintColor,
),
],
],
),
);
}
}

View File

@ -1,7 +1,8 @@
import 'package:appflowy/core/config/kv_keys.dart';
import 'package:appflowy/plugins/document/presentation/more/cubit/document_appearance_cubit.dart';
import 'package:appflowy/workspace/application/settings/appearance/base_appearance.dart';
import 'package:flutter/widgets.dart';
import 'package:appflowy/core/config/kv_keys.dart';
import 'package:appflowy/plugins/document/application/document_appearance_cubit.dart';
import 'package:appflowy/workspace/application/settings/appearance/base_appearance.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:shared_preferences/shared_preferences.dart';

View File

@ -1,7 +1,8 @@
import 'package:appflowy/plugins/document/presentation/editor_style.dart';
import 'package:appflowy/plugins/document/presentation/more/cubit/document_appearance_cubit.dart';
import 'package:appflowy/workspace/application/settings/appearance/base_appearance.dart';
import 'package:flutter/material.dart';
import 'package:appflowy/plugins/document/application/document_appearance_cubit.dart';
import 'package:appflowy/plugins/document/presentation/editor_style.dart';
import 'package:appflowy/workspace/application/settings/appearance/base_appearance.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:mocktail/mocktail.dart';

View File

@ -1,9 +1,10 @@
import 'package:appflowy/plugins/document/presentation/more/cubit/document_appearance_cubit.dart';
import 'package:flutter/material.dart';
import 'package:appflowy/plugins/document/application/document_appearance_cubit.dart';
import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart';
import 'package:appflowy/workspace/application/settings/appearance/base_appearance.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/settings_appearance/font_family_setting.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';

View File

@ -78,7 +78,12 @@
"large": "large",
"fontSize": "Font size",
"import": "Import",
"moreOptions": "More options"
"moreOptions": "More options",
"wordCount": "Word count: {}",
"charCount": "Character count: {}",
"createdAt": "Created at: {}",
"deleteView": "Delete",
"duplicateView": "Duplicate"
},
"importPanel": {
"textAndMarkdown": "Text & Markdown",
@ -1285,4 +1290,4 @@
"userIcon": "User icon"
},
"noLogFiles": "There're no log files"
}
}