feat: save menu appearance (#1707)

* feat: save menu offset and menu visibility

* refactor: remove collapsedNotifier
This commit is contained in:
abichinger 2023-01-18 07:30:39 +01:00 committed by GitHub
parent 3d56a0a843
commit d36aea648c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 131 additions and 124 deletions

View File

@ -1,13 +1,13 @@
import 'dart:async';
import 'package:app_flowy/user/application/user_settings_service.dart';
import 'package:appflowy_backend/log.dart';
import 'package:appflowy_backend/protobuf/flowy-user/user_setting.pb.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/size.dart';
import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra/theme_extension.dart';
import 'package:appflowy_backend/log.dart';
import 'package:appflowy_backend/protobuf/flowy-user/user_setting.pb.dart';
import 'package:flutter/material.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
@ -28,6 +28,8 @@ class AppearanceSettingsCubit extends Cubit<AppearanceSettingsState> {
setting.font,
setting.monospaceFont,
setting.locale,
setting.isMenuCollapsed,
setting.menuOffset,
));
/// Update selected theme in the user's settings and emit an updated state
@ -64,6 +66,18 @@ class AppearanceSettingsCubit extends Cubit<AppearanceSettingsState> {
}
}
// Saves the menus current visibility
void saveIsMenuCollapsed(bool collapsed) {
_setting.isMenuCollapsed = collapsed;
_saveAppearanceSettings();
}
// Saves the current resize offset of the menu
void saveMenuOffset(double offset) {
_setting.menuOffset = offset;
_saveAppearanceSettings();
}
/// Saves key/value setting to disk.
/// Removes the key if the passed in value is null
void setKeyValue(String key, String? value) {
@ -151,6 +165,8 @@ class AppearanceSettingsState with _$AppearanceSettingsState {
required String font,
required String monospaceFont,
required Locale locale,
required bool isMenuCollapsed,
required double menuOffset,
}) = _AppearanceSettingsState;
factory AppearanceSettingsState.initial(
@ -159,6 +175,8 @@ class AppearanceSettingsState with _$AppearanceSettingsState {
String font,
String monospaceFont,
LocaleSettingsPB localePB,
bool isMenuCollapsed,
double menuOffset,
) {
return AppearanceSettingsState(
appTheme: AppTheme.fromName(themeName),
@ -166,6 +184,8 @@ class AppearanceSettingsState with _$AppearanceSettingsState {
monospaceFont: monospaceFont,
themeMode: _themeModeFromPB(themeModePB),
locale: Locale(localePB.languageCode, localePB.countryCode),
isMenuCollapsed: isMenuCollapsed,
menuOffset: menuOffset,
);
}

View File

@ -1,22 +1,30 @@
import 'package:app_flowy/user/application/user_listener.dart';
import 'package:app_flowy/workspace/application/appearance.dart';
import 'package:app_flowy/workspace/application/edit_panel/edit_context.dart';
import 'package:flowy_infra/time/duration.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/workspace.pb.dart'
show WorkspaceSettingPB;
import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart';
import 'package:dartz/dartz.dart';
import 'package:flowy_infra/time/duration.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:dartz/dartz.dart';
part 'home_setting_bloc.freezed.dart';
class HomeSettingBloc extends Bloc<HomeSettingEvent, HomeSettingState> {
final UserWorkspaceListener _listener;
final AppearanceSettingsCubit _appearanceSettingsCubit;
HomeSettingBloc(
UserProfilePB user,
WorkspaceSettingPB workspaceSetting,
AppearanceSettingsCubit appearanceSettingsCubit,
) : _listener = UserWorkspaceListener(userProfile: user),
super(HomeSettingState.initial(workspaceSetting)) {
_appearanceSettingsCubit = appearanceSettingsCubit,
super(HomeSettingState.initial(
workspaceSetting,
appearanceSettingsCubit.state,
)) {
on<HomeSettingEvent>(
(event, emit) async {
await event.map(
@ -27,14 +35,13 @@ class HomeSettingBloc extends Bloc<HomeSettingEvent, HomeSettingState> {
dismissEditPanel: (value) async {
emit(state.copyWith(panelContext: none()));
},
forceCollapse: (e) async {
emit(state.copyWith(forceCollapse: e.forceCollapse));
},
didReceiveWorkspaceSetting: (_DidReceiveWorkspaceSetting value) {
emit(state.copyWith(workspaceSetting: value.setting));
},
collapseMenu: (_CollapseMenu e) {
emit(state.copyWith(isMenuCollapsed: !state.isMenuCollapsed));
var isMenuCollapsed = !state.isMenuCollapsed;
_appearanceSettingsCubit.saveIsMenuCollapsed(isMenuCollapsed);
emit(state.copyWith(isMenuCollapsed: isMenuCollapsed));
},
editPanelResizeStart: (_EditPanelResizeStart e) {
emit(state.copyWith(
@ -50,6 +57,7 @@ class HomeSettingBloc extends Bloc<HomeSettingEvent, HomeSettingState> {
}
},
editPanelResizeEnd: (_EditPanelResizeEnd e) {
_appearanceSettingsCubit.saveMenuOffset(state.resizeOffset);
emit(state.copyWith(resizeType: MenuResizeType.slide));
},
);
@ -83,8 +91,6 @@ extension MenuResizeTypeExtension on MenuResizeType {
@freezed
class HomeSettingEvent with _$HomeSettingEvent {
const factory HomeSettingEvent.initial() = _Initial;
const factory HomeSettingEvent.forceCollapse(bool forceCollapse) =
_ForceCollapse;
const factory HomeSettingEvent.setEditPanel(EditPanelContext editContext) =
_ShowEditPanel;
const factory HomeSettingEvent.dismissEditPanel() = _DismissEditPanel;
@ -100,7 +106,6 @@ class HomeSettingEvent with _$HomeSettingEvent {
@freezed
class HomeSettingState with _$HomeSettingState {
const factory HomeSettingState({
required bool forceCollapse,
required Option<EditPanelContext> panelContext,
required WorkspaceSettingPB workspaceSetting,
required bool unauthorized,
@ -110,14 +115,16 @@ class HomeSettingState with _$HomeSettingState {
required MenuResizeType resizeType,
}) = _HomeSettingState;
factory HomeSettingState.initial(WorkspaceSettingPB workspaceSetting) =>
factory HomeSettingState.initial(
WorkspaceSettingPB workspaceSetting,
AppearanceSettingsState appearanceSettingsState,
) =>
HomeSettingState(
forceCollapse: false,
panelContext: none(),
workspaceSetting: workspaceSetting,
unauthorized: false,
isMenuCollapsed: false,
resizeOffset: 0,
isMenuCollapsed: appearanceSettingsState.isMenuCollapsed,
resizeOffset: appearanceSettingsState.menuOffset,
resizeStart: 0,
resizeType: MenuResizeType.slide,
);

View File

@ -3,9 +3,9 @@ import 'dart:io' show Platform;
import 'package:app_flowy/workspace/application/home/home_setting_bloc.dart';
import 'package:flowy_infra/size.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
// ignore: import_of_legacy_library_into_null_safe
import 'package:sized_context/sized_context.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'home_sizes.dart';
@ -32,7 +32,7 @@ class HomeLayout {
menuWidth += homeSetting.resizeOffset;
if (homeSetting.forceCollapse) {
if (homeSetting.isMenuCollapsed) {
showMenu = false;
} else {
showMenu = true;

View File

@ -1,26 +1,25 @@
import 'package:app_flowy/plugins/blank/blank.dart';
import 'package:app_flowy/startup/plugin/plugin.dart';
import 'package:app_flowy/startup/startup.dart';
import 'package:app_flowy/workspace/application/appearance.dart';
import 'package:app_flowy/workspace/application/home/home_bloc.dart';
import 'package:app_flowy/workspace/application/home/home_service.dart';
import 'package:app_flowy/workspace/application/home/home_setting_bloc.dart';
import 'package:app_flowy/workspace/presentation/home/hotkeys.dart';
import 'package:app_flowy/workspace/application/view/view_ext.dart';
import 'package:app_flowy/workspace/presentation/home/hotkeys.dart';
import 'package:app_flowy/workspace/presentation/widgets/edit_panel/panel_animation.dart';
import 'package:app_flowy/workspace/presentation/widgets/float_bubble/question_bubble.dart';
import 'package:app_flowy/startup/startup.dart';
import 'package:appflowy_backend/log.dart';
import 'package:flowy_infra_ui/style_widget/container.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart';
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart'
show UserProfilePB;
import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart';
import 'package:flowy_infra_ui/style_widget/container.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:styled_widget/styled_widget.dart';
import '../widgets/edit_panel/edit_panel.dart';
import 'home_layout.dart';
import 'home_stack.dart';
import 'menu/menu.dart';
@ -48,8 +47,11 @@ class _HomeScreenState extends State<HomeScreen> {
),
BlocProvider<HomeSettingBloc>(
create: (context) {
return HomeSettingBloc(widget.user, widget.workspaceSetting)
..add(const HomeSettingEvent.initial());
return HomeSettingBloc(
widget.user,
widget.workspaceSetting,
context.read<AppearanceSettingsCubit>(),
)..add(const HomeSettingEvent.initial());
},
),
],
@ -87,16 +89,8 @@ class _HomeScreenState extends State<HomeScreen> {
child: BlocBuilder<HomeSettingBloc, HomeSettingState>(
buildWhen: (previous, current) => previous != current,
builder: (context, state) {
final collapsedNotifier =
getIt<HomeStackManager>().collapsedNotifier;
collapsedNotifier.addPublishListener((isCollapsed) {
context
.read<HomeSettingBloc>()
.add(HomeSettingEvent.forceCollapse(isCollapsed));
});
return FlowyContainer(
Theme.of(context).colorScheme.surface,
// Colors.white,
child: _buildBody(context),
);
},
@ -146,7 +140,6 @@ class _HomeScreenState extends State<HomeScreen> {
final homeMenu = HomeMenu(
user: widget.user,
workspaceSetting: workspaceSetting,
collapsedNotifier: getIt<HomeStackManager>().collapsedNotifier,
);
return FocusTraversalGroup(child: RepaintBoundary(child: homeMenu));

View File

@ -1,17 +1,17 @@
import 'package:app_flowy/startup/startup.dart';
import 'package:app_flowy/core/frameless_window.dart';
import 'package:app_flowy/plugins/blank/blank.dart';
import 'package:app_flowy/startup/plugin/plugin.dart';
import 'package:app_flowy/startup/startup.dart';
import 'package:app_flowy/workspace/presentation/home/home_sizes.dart';
import 'package:app_flowy/workspace/presentation/home/navigation.dart';
import 'package:app_flowy/workspace/presentation/home/toast.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:flowy_infra_ui/style_widget/extension.dart';
import 'package:flowy_infra_ui/widget/spacing.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:time/time.dart';
import 'package:app_flowy/startup/plugin/plugin.dart';
import 'package:app_flowy/workspace/presentation/home/home_sizes.dart';
import 'package:app_flowy/workspace/presentation/home/navigation.dart';
import 'package:app_flowy/core/frameless_window.dart';
import 'package:flowy_infra_ui/widget/spacing.dart';
import 'package:flowy_infra_ui/style_widget/extension.dart';
import 'package:flowy_infra/notifier.dart';
import 'home_layout.dart';
typedef NavigationCallback = void Function(String id);
@ -111,7 +111,6 @@ abstract class NavigationItem {
class HomeStackNotifier extends ChangeNotifier {
Plugin _plugin;
PublishNotifier<bool> collapsedNotifier = PublishNotifier();
Widget get titleWidget => _plugin.display.leftBarItem;
@ -143,7 +142,6 @@ class HomeStackManager {
return _notifier.plugin.display.leftBarItem;
}
PublishNotifier<bool> get collapsedNotifier => _notifier.collapsedNotifier;
Plugin get plugin => _notifier.plugin;
void setPlugin(Plugin newPlugin) {

View File

@ -1,8 +1,6 @@
import 'dart:io';
import 'package:app_flowy/startup/startup.dart';
import 'package:app_flowy/workspace/application/home/home_setting_bloc.dart';
import 'package:app_flowy/workspace/presentation/home/home_stack.dart';
import 'package:flutter/material.dart';
import 'package:hotkey_manager/hotkey_manager.dart';
import 'package:provider/provider.dart';
@ -25,8 +23,6 @@ class HomeHotKeys extends StatelessWidget {
context
.read<HomeSettingBloc>()
.add(const HomeSettingEvent.collapseMenu());
getIt<HomeStackManager>().collapsedNotifier.value =
!getIt<HomeStackManager>().collapsedNotifier.currentValue!;
},
);
return child;

View File

@ -1,40 +1,39 @@
export './app/header/header.dart';
export './app/menu_app.dart';
import 'dart:io' show Platform;
import 'package:app_flowy/core/frameless_window.dart';
import 'package:app_flowy/generated/locale_keys.g.dart';
import 'package:app_flowy/plugins/trash/menu.dart';
import 'package:app_flowy/startup/startup.dart';
import 'package:app_flowy/workspace/application/home/home_setting_bloc.dart';
import 'package:app_flowy/workspace/application/menu/menu_bloc.dart';
import 'package:app_flowy/workspace/presentation/home/home_sizes.dart';
import 'package:app_flowy/workspace/presentation/home/home_stack.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/notifier.dart';
import 'package:flowy_infra/size.dart';
import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart';
import 'package:flowy_infra_ui/widget/spacing.dart';
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart'
show UserProfilePB;
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/workspace.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart'
show UserProfilePB;
import 'package:easy_localization/easy_localization.dart';
import 'package:expandable/expandable.dart';
// import 'package:app_flowy/workspace/presentation/home/home_sizes.dart';
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/size.dart';
import 'package:flowy_infra/time/duration.dart';
import 'package:flowy_infra_ui/style_widget/icon_button.dart';
import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart';
import 'package:flowy_infra_ui/widget/spacing.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:styled_widget/styled_widget.dart';
import 'package:expandable/expandable.dart';
import 'package:flowy_infra/time/duration.dart';
import 'package:app_flowy/startup/startup.dart';
import 'package:app_flowy/workspace/application/menu/menu_bloc.dart';
import 'package:app_flowy/core/frameless_window.dart';
// import 'package:app_flowy/workspace/presentation/home/home_sizes.dart';
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra_ui/style_widget/icon_button.dart';
import '../navigation.dart';
import 'app/menu_app.dart';
import 'app/create_button.dart';
import 'app/menu_app.dart';
import 'menu_user.dart';
export './app/header/header.dart';
export './app/menu_app.dart';
class HomeMenu extends StatelessWidget {
final PublishNotifier<bool> _collapsedNotifier;
final UserProfilePB user;
final WorkspaceSettingPB workspaceSetting;
@ -42,9 +41,7 @@ class HomeMenu extends StatelessWidget {
Key? key,
required this.user,
required this.workspaceSetting,
required PublishNotifier<bool> collapsedNotifier,
}) : _collapsedNotifier = collapsedNotifier,
super(key: key);
}) : super(key: key);
@override
Widget build(BuildContext context) {
@ -69,12 +66,6 @@ class HomeMenu extends StatelessWidget {
getIt<HomeStackManager>().setPlugin(state.plugin);
},
),
BlocListener<HomeSettingBloc, HomeSettingState>(
listenWhen: (p, c) => p.isMenuCollapsed != c.isMenuCollapsed,
listener: (context, state) {
_collapsedNotifier.value = state.isMenuCollapsed;
},
)
],
child: BlocBuilder<MenuBloc, MenuState>(
builder: (context, state) => _renderBody(context),

View File

@ -3,25 +3,23 @@ import 'dart:io';
import 'package:app_flowy/generated/locale_keys.g.dart';
import 'package:app_flowy/workspace/application/home/home_setting_bloc.dart';
import 'package:app_flowy/workspace/presentation/home/home_stack.dart';
import 'package:flowy_infra/theme_extension.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/notifier.dart';
import 'package:flowy_infra/size.dart';
import 'package:flowy_infra/theme_extension.dart';
import 'package:flowy_infra_ui/style_widget/icon_button.dart';
import 'package:flowy_infra_ui/style_widget/text.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:provider/provider.dart';
import 'package:styled_widget/styled_widget.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:textstyle_extensions/textstyle_extensions.dart';
typedef NaviAction = void Function();
class NavigationNotifier with ChangeNotifier {
List<NavigationItem> navigationItems;
PublishNotifier<bool> collapasedNotifier;
NavigationNotifier(
{required this.navigationItems, required this.collapasedNotifier});
NavigationNotifier({required this.navigationItems});
void update(HomeStackNotifier notifier) {
bool shouldNotify = false;
@ -46,16 +44,12 @@ class FlowyNavigation extends StatelessWidget {
final notifier = Provider.of<HomeStackNotifier>(context, listen: false);
return NavigationNotifier(
navigationItems: notifier.plugin.display.navigationItems,
collapasedNotifier: notifier.collapsedNotifier,
);
},
update: (_, notifier, controller) => controller!..update(notifier),
child: Expanded(
child: Row(children: [
Selector<NavigationNotifier, PublishNotifier<bool>>(
selector: (context, notifier) => notifier.collapasedNotifier,
builder: (ctx, collapsedNotifier, child) =>
_renderCollapse(ctx, collapsedNotifier)),
_renderCollapse(context),
Selector<NavigationNotifier, List<NavigationItem>>(
selector: (context, notifier) => notifier.navigationItems,
builder: (ctx, items, child) => Expanded(
@ -70,41 +64,37 @@ class FlowyNavigation extends StatelessWidget {
);
}
Widget _renderCollapse(
BuildContext context, PublishNotifier<bool> collapsedNotifier) {
return ChangeNotifierProvider.value(
value: collapsedNotifier,
child: Consumer(
builder: (ctx, PublishNotifier<bool> notifier, child) {
if (notifier.currentValue ?? false) {
return RotationTransition(
turns: const AlwaysStoppedAnimation(180 / 360),
child: Tooltip(
richMessage: sidebarTooltipTextSpan(
context,
LocaleKeys.sideBar_openSidebar.tr(),
Widget _renderCollapse(BuildContext context) {
return BlocBuilder<HomeSettingBloc, HomeSettingState>(
buildWhen: (p, c) => p.isMenuCollapsed != c.isMenuCollapsed,
builder: (context, state) {
if (state.isMenuCollapsed) {
return RotationTransition(
turns: const AlwaysStoppedAnimation(180 / 360),
child: Tooltip(
richMessage: sidebarTooltipTextSpan(
context,
LocaleKeys.sideBar_openSidebar.tr(),
),
child: FlowyIconButton(
width: 24,
hoverColor: Colors.transparent,
onPressed: () {
context
.read<HomeSettingBloc>()
.add(const HomeSettingEvent.collapseMenu());
},
iconPadding: const EdgeInsets.fromLTRB(2, 2, 2, 2),
icon: svgWidget(
"home/hide_menu",
color: Theme.of(context).colorScheme.onSurface,
),
child: FlowyIconButton(
width: 24,
hoverColor: Colors.transparent,
onPressed: () {
notifier.value = false;
ctx
.read<HomeSettingBloc>()
.add(const HomeSettingEvent.collapseMenu());
},
iconPadding: const EdgeInsets.fromLTRB(2, 2, 2, 2),
icon: svgWidget(
"home/hide_menu",
color: Theme.of(context).colorScheme.onSurface,
),
)),
);
} else {
return Container();
}
},
),
)),
);
} else {
return Container();
}
},
);
}

View File

@ -37,6 +37,14 @@ pub struct AppearanceSettingsPB {
#[pb(index = 7)]
#[serde(default)]
pub setting_key_value: HashMap<String, String>,
#[pb(index = 8)]
#[serde(default)]
pub is_menu_collapsed: bool,
#[pb(index = 9)]
#[serde(default)]
pub menu_offset: f64,
}
const DEFAULT_RESET_VALUE: fn() -> bool = || APPEARANCE_RESET_AS_DEFAULT;
@ -76,6 +84,8 @@ pub const APPEARANCE_DEFAULT_THEME: &str = "light";
pub const APPEARANCE_DEFAULT_FONT: &str = "Poppins";
pub const APPEARANCE_DEFAULT_MONOSPACE_FONT: &str = "SF Mono";
const APPEARANCE_RESET_AS_DEFAULT: bool = true;
const APPEARANCE_DEFAULT_IS_MENU_COLLAPSED: bool = false;
const APPEARANCE_DEFAULT_MENU_OFFSET: f64 = 0.0;
impl std::default::Default for AppearanceSettingsPB {
fn default() -> Self {
@ -87,6 +97,8 @@ impl std::default::Default for AppearanceSettingsPB {
locale: LocaleSettingsPB::default(),
reset_to_default: APPEARANCE_RESET_AS_DEFAULT,
setting_key_value: HashMap::default(),
is_menu_collapsed: APPEARANCE_DEFAULT_IS_MENU_COLLAPSED,
menu_offset: APPEARANCE_DEFAULT_MENU_OFFSET,
}
}
}