config navigation widget

This commit is contained in:
appflowy 2021-07-28 18:19:16 +08:00
parent 199fbd8873
commit 23ac4d0249
8 changed files with 205 additions and 509 deletions

View File

@ -1,57 +1,72 @@
import 'package:app_flowy/workspace/domain/page_stack/page_stack_bloc.dart';
import 'package:app_flowy/workspace/presentation/doc/doc_page.dart';
import 'package:equatable/equatable.dart';
import 'package:flowy_sdk/protobuf/flowy-workspace/view_create.pb.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:app_flowy/workspace/presentation/doc/doc_page.dart';
import 'package:app_flowy/workspace/presentation/widgets/blank_page.dart';
import 'package:app_flowy/workspace/presentation/widgets/fading_index_stack.dart';
import 'package:app_flowy/workspace/presentation/widgets/prelude.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
abstract class HomeStackView extends Equatable {
final ViewType type;
final String title;
const HomeStackView({required this.type, required this.title});
final String identifier;
const HomeStackView(
{required this.type, required this.title, required this.identifier});
}
class PageStackNotifier extends ChangeNotifier {
HomeStackView? innerView;
PageStackNotifier({
this.innerView,
});
set view(HomeStackView view) {
innerView = view;
notifyListeners();
}
HomeStackView get view {
return innerView ?? const AnnouncementStackView();
}
}
// HomePageStack is initialized as singleton to controll the page stack.
class HomePageStack {
final PageStackBloc _bloc = PageStackBloc();
final PageStackNotifier _notifier = PageStackNotifier();
HomePageStack();
String title() {
return _bloc.state.stackView.title;
return _notifier.view.title;
}
void setStackView(HomeStackView? stackView) {
_bloc.add(PageStackEvent.setStackView(
stackView ?? const AnnouncementStackView()));
_notifier.view = stackView ?? const AnnouncementStackView();
}
Widget stackTopBar() {
return BlocProvider<PageStackBloc>(
create: (context) => _bloc,
child: BlocBuilder<PageStackBloc, PageStackState>(
builder: (context, state) {
return HomeTopBar(
view: state.stackView,
);
},
),
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => _notifier),
],
child: Consumer(builder: (ctx, PageStackNotifier notifier, child) {
return HomeTopBar(view: notifier.view);
}),
);
}
Widget stackWidget() {
return BlocProvider<PageStackBloc>(
create: (context) => _bloc,
child: BlocBuilder<PageStackBloc, PageStackState>(
builder: (context, state) {
return FadingIndexedStack(
index: pages.indexOf(state.stackView.type),
children: _buildStackWidget(state.stackView),
);
},
),
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => _notifier),
],
child: Consumer(builder: (ctx, PageStackNotifier notifier, child) {
return FadingIndexedStack(
index: pages.indexOf(notifier.view.type),
children: _buildStackWidget(notifier.view),
);
}),
);
}
}

View File

@ -1,35 +0,0 @@
import 'package:app_flowy/workspace/domain/page_stack/page_stack.dart';
import 'package:app_flowy/workspace/presentation/widgets/blank_page.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
part 'page_stack_bloc.freezed.dart';
class PageStackBloc extends Bloc<PageStackEvent, PageStackState> {
PageStackBloc() : super(PageStackState.initial());
@override
Stream<PageStackState> mapEventToState(
PageStackEvent event,
) async* {
yield* event.map(setStackView: (NewPageContext value) async* {
yield state.copyWith(stackView: value.newStackView);
});
}
}
@freezed
abstract class PageStackEvent with _$PageStackEvent {
const factory PageStackEvent.setStackView(HomeStackView newStackView) =
NewPageContext;
}
@freezed
abstract class PageStackState implements _$PageStackState {
const factory PageStackState({
required HomeStackView stackView,
}) = _PageStackState;
factory PageStackState.initial() => const PageStackState(
stackView: AnnouncementStackView(),
);
}

View File

@ -1,337 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target
part of 'page_stack_bloc.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more informations: https://github.com/rrousselGit/freezed#custom-getters-and-methods');
/// @nodoc
class _$PageStackEventTearOff {
const _$PageStackEventTearOff();
NewPageContext setStackView(HomeStackView newStackView) {
return NewPageContext(
newStackView,
);
}
}
/// @nodoc
const $PageStackEvent = _$PageStackEventTearOff();
/// @nodoc
mixin _$PageStackEvent {
HomeStackView get newStackView => throw _privateConstructorUsedError;
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function(HomeStackView newStackView) setStackView,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function(HomeStackView newStackView)? setStackView,
required TResult orElse(),
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(NewPageContext value) setStackView,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(NewPageContext value)? setStackView,
required TResult orElse(),
}) =>
throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$PageStackEventCopyWith<PageStackEvent> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $PageStackEventCopyWith<$Res> {
factory $PageStackEventCopyWith(
PageStackEvent value, $Res Function(PageStackEvent) then) =
_$PageStackEventCopyWithImpl<$Res>;
$Res call({HomeStackView newStackView});
}
/// @nodoc
class _$PageStackEventCopyWithImpl<$Res>
implements $PageStackEventCopyWith<$Res> {
_$PageStackEventCopyWithImpl(this._value, this._then);
final PageStackEvent _value;
// ignore: unused_field
final $Res Function(PageStackEvent) _then;
@override
$Res call({
Object? newStackView = freezed,
}) {
return _then(_value.copyWith(
newStackView: newStackView == freezed
? _value.newStackView
: newStackView // ignore: cast_nullable_to_non_nullable
as HomeStackView,
));
}
}
/// @nodoc
abstract class $NewPageContextCopyWith<$Res>
implements $PageStackEventCopyWith<$Res> {
factory $NewPageContextCopyWith(
NewPageContext value, $Res Function(NewPageContext) then) =
_$NewPageContextCopyWithImpl<$Res>;
@override
$Res call({HomeStackView newStackView});
}
/// @nodoc
class _$NewPageContextCopyWithImpl<$Res>
extends _$PageStackEventCopyWithImpl<$Res>
implements $NewPageContextCopyWith<$Res> {
_$NewPageContextCopyWithImpl(
NewPageContext _value, $Res Function(NewPageContext) _then)
: super(_value, (v) => _then(v as NewPageContext));
@override
NewPageContext get _value => super._value as NewPageContext;
@override
$Res call({
Object? newStackView = freezed,
}) {
return _then(NewPageContext(
newStackView == freezed
? _value.newStackView
: newStackView // ignore: cast_nullable_to_non_nullable
as HomeStackView,
));
}
}
/// @nodoc
class _$NewPageContext implements NewPageContext {
const _$NewPageContext(this.newStackView);
@override
final HomeStackView newStackView;
@override
String toString() {
return 'PageStackEvent.setStackView(newStackView: $newStackView)';
}
@override
bool operator ==(dynamic other) {
return identical(this, other) ||
(other is NewPageContext &&
(identical(other.newStackView, newStackView) ||
const DeepCollectionEquality()
.equals(other.newStackView, newStackView)));
}
@override
int get hashCode =>
runtimeType.hashCode ^ const DeepCollectionEquality().hash(newStackView);
@JsonKey(ignore: true)
@override
$NewPageContextCopyWith<NewPageContext> get copyWith =>
_$NewPageContextCopyWithImpl<NewPageContext>(this, _$identity);
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function(HomeStackView newStackView) setStackView,
}) {
return setStackView(newStackView);
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function(HomeStackView newStackView)? setStackView,
required TResult orElse(),
}) {
if (setStackView != null) {
return setStackView(newStackView);
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(NewPageContext value) setStackView,
}) {
return setStackView(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(NewPageContext value)? setStackView,
required TResult orElse(),
}) {
if (setStackView != null) {
return setStackView(this);
}
return orElse();
}
}
abstract class NewPageContext implements PageStackEvent {
const factory NewPageContext(HomeStackView newStackView) = _$NewPageContext;
@override
HomeStackView get newStackView => throw _privateConstructorUsedError;
@override
@JsonKey(ignore: true)
$NewPageContextCopyWith<NewPageContext> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
class _$PageStackStateTearOff {
const _$PageStackStateTearOff();
_PageStackState call({required HomeStackView stackView}) {
return _PageStackState(
stackView: stackView,
);
}
}
/// @nodoc
const $PageStackState = _$PageStackStateTearOff();
/// @nodoc
mixin _$PageStackState {
HomeStackView get stackView => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$PageStackStateCopyWith<PageStackState> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $PageStackStateCopyWith<$Res> {
factory $PageStackStateCopyWith(
PageStackState value, $Res Function(PageStackState) then) =
_$PageStackStateCopyWithImpl<$Res>;
$Res call({HomeStackView stackView});
}
/// @nodoc
class _$PageStackStateCopyWithImpl<$Res>
implements $PageStackStateCopyWith<$Res> {
_$PageStackStateCopyWithImpl(this._value, this._then);
final PageStackState _value;
// ignore: unused_field
final $Res Function(PageStackState) _then;
@override
$Res call({
Object? stackView = freezed,
}) {
return _then(_value.copyWith(
stackView: stackView == freezed
? _value.stackView
: stackView // ignore: cast_nullable_to_non_nullable
as HomeStackView,
));
}
}
/// @nodoc
abstract class _$PageStackStateCopyWith<$Res>
implements $PageStackStateCopyWith<$Res> {
factory _$PageStackStateCopyWith(
_PageStackState value, $Res Function(_PageStackState) then) =
__$PageStackStateCopyWithImpl<$Res>;
@override
$Res call({HomeStackView stackView});
}
/// @nodoc
class __$PageStackStateCopyWithImpl<$Res>
extends _$PageStackStateCopyWithImpl<$Res>
implements _$PageStackStateCopyWith<$Res> {
__$PageStackStateCopyWithImpl(
_PageStackState _value, $Res Function(_PageStackState) _then)
: super(_value, (v) => _then(v as _PageStackState));
@override
_PageStackState get _value => super._value as _PageStackState;
@override
$Res call({
Object? stackView = freezed,
}) {
return _then(_PageStackState(
stackView: stackView == freezed
? _value.stackView
: stackView // ignore: cast_nullable_to_non_nullable
as HomeStackView,
));
}
}
/// @nodoc
class _$_PageStackState implements _PageStackState {
const _$_PageStackState({required this.stackView});
@override
final HomeStackView stackView;
@override
String toString() {
return 'PageStackState(stackView: $stackView)';
}
@override
bool operator ==(dynamic other) {
return identical(this, other) ||
(other is _PageStackState &&
(identical(other.stackView, stackView) ||
const DeepCollectionEquality()
.equals(other.stackView, stackView)));
}
@override
int get hashCode =>
runtimeType.hashCode ^ const DeepCollectionEquality().hash(stackView);
@JsonKey(ignore: true)
@override
_$PageStackStateCopyWith<_PageStackState> get copyWith =>
__$PageStackStateCopyWithImpl<_PageStackState>(this, _$identity);
}
abstract class _PageStackState implements PageStackState {
const factory _PageStackState({required HomeStackView stackView}) =
_$_PageStackState;
@override
HomeStackView get stackView => throw _privateConstructorUsedError;
@override
@JsonKey(ignore: true)
_$PageStackStateCopyWith<_PageStackState> get copyWith =>
throw _privateConstructorUsedError;
}

View File

@ -60,6 +60,7 @@ class DocPageStackView extends HomeStackView {
: super(
type: view.viewType,
title: view.name,
identifier: view.id,
);
@override

View File

@ -0,0 +1,137 @@
import 'package:app_flowy/workspace/domain/page_stack/page_stack.dart';
import 'package:app_flowy/workspace/presentation/widgets/home_top_bar.dart';
import 'package:flowy_infra_ui/style_widget/text.dart';
import 'package:flowy_infra_ui/style_widget/text_button.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:styled_widget/styled_widget.dart';
typedef NaviAction = void Function();
abstract class NaviItem {
String get identifier;
String get title;
NaviAction get action;
}
class NavigationNotifier extends ChangeNotifier {
PageStackNotifier pageStackNotifier;
NavigationNotifier(this.pageStackNotifier);
void update(PageStackNotifier notifier) {
pageStackNotifier = notifier;
notifyListeners();
}
List<NaviItem> get naviItems {
List<NaviItem> items = [
ViewNaviItemImpl(pageStackNotifier.view),
ViewNaviItemImpl(pageStackNotifier.view),
ViewNaviItemImpl(pageStackNotifier.view),
ViewNaviItemImpl(pageStackNotifier.view),
ViewNaviItemImpl(pageStackNotifier.view),
ViewNaviItemImpl(pageStackNotifier.view)
];
return items;
}
}
class StyledNavigationList extends StatelessWidget {
const StyledNavigationList({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return ChangeNotifierProxyProvider<PageStackNotifier, NavigationNotifier>(
create: (_) => NavigationNotifier(
Provider.of<PageStackNotifier>(
context,
listen: false,
),
),
update: (_, notifier, controller) => controller!..update(notifier),
child: Consumer(builder: (ctx, NavigationNotifier notifier, child) {
return Row(children: _renderChildren(notifier.naviItems));
}),
);
}
List<Widget> _renderChildren(List<NaviItem> items) {
if (items.isEmpty) {
return [];
}
List<NaviItem> newItems = _filter(items);
Widget last = NaviItemWidget(newItems.removeLast());
List<Widget> widgets = List.empty(growable: true);
widgets.addAll(newItems
.map((item) => NaviItemDivider(child: NaviItemWidget(item)))
.toList());
widgets.add(last);
return widgets;
}
List<NaviItem> _filter(List<NaviItem> items) {
final length = items.length;
if (length > 4) {
final first = items[0];
final ellipsisItems = items.getRange(1, length - 2).toList();
final last = items.getRange(length - 2, length).toList();
return [
first,
EllipsisNaviItem(items: ellipsisItems),
...last,
];
} else {
return items;
}
}
}
class NaviItemWidget extends StatelessWidget {
final NaviItem item;
const NaviItemWidget(this.item, {Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return SizedBox(
height: 30,
child: FlowyTextButton(
item.title,
fontSize: 14,
onPressed: () {
debugPrint('show app document');
},
),
);
}
}
class NaviItemDivider extends StatelessWidget {
final Widget child;
const NaviItemDivider({Key? key, required this.child}) : super(key: key);
@override
Widget build(BuildContext context) {
return Row(
children: [child, const Text('/').padding(horizontal: 2)],
);
}
}
class EllipsisNaviItem extends NaviItem {
final List<NaviItem> items;
EllipsisNaviItem({
required this.items,
});
@override
NaviAction get action => throw UnimplementedError();
@override
String get identifier => "Ellipsis";
@override
String get title => "...";
}

View File

@ -3,7 +3,7 @@ import 'package:flowy_sdk/protobuf/flowy-workspace/view_create.pb.dart';
import 'package:flutter/material.dart';
class AnnouncementStackView extends HomeStackView {
const AnnouncementStackView() : super(type: ViewType.Blank, title: 'Blank');
const AnnouncementStackView() : super(type: ViewType.Blank, title: 'Blank', identifier: "Announcement");
@override
List<Object> get props => [];

View File

@ -1,12 +1,14 @@
import 'package:app_flowy/startup/startup.dart';
import 'package:app_flowy/workspace/domain/image.dart';
import 'package:app_flowy/workspace/domain/page_stack/page_stack.dart';
import 'package:app_flowy/workspace/presentation/home/home_sizes.dart';
import 'package:app_flowy/workspace/presentation/home/navigation_list.dart';
import 'package:flowy_infra_ui/widget/rounded_button.dart';
import 'package:flowy_infra_ui/widget/spacing.dart';
import 'package:flowy_sdk/protobuf/flowy-workspace/view_create.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-workspace/view_create.pbenum.dart';
import 'package:flutter/material.dart';
import 'package:flowy_infra_ui/style_widget/icon_button.dart';
// import 'package:flowy_infra_ui/style_widget/styled_navigation_list.dart';
import 'package:flowy_infra_ui/style_widget/extension.dart';
import 'package:flowy_infra_ui/style_widget/text.dart';
@ -21,13 +23,15 @@ class HomeTopBar extends StatelessWidget {
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
HomeTitle(title: view.title, type: view.type),
_renderNavigationList(view),
const Spacer(),
_renderShareButton(),
_renderMoreButton(),
],
)
.padding(horizontal: HomeInsets.topBarTitlePadding)
.padding(
horizontal: HomeInsets.topBarTitlePadding,
)
.bottomBorder(color: Colors.grey.shade300),
);
}
@ -55,8 +59,8 @@ class HomeTopBar extends StatelessWidget {
);
}
Widget _renderNavigationList() {
return Container();
Widget _renderNavigationList(HomeStackView view) {
return const StyledNavigationList();
}
}
@ -87,3 +91,18 @@ class HomeTitle extends StatelessWidget {
);
}
}
class ViewNaviItemImpl extends NaviItem {
final HomeStackView view;
ViewNaviItemImpl(this.view);
@override
NaviAction get action => () => getIt<HomePageStack>().setStackView(view);
@override
String get identifier => view.identifier;
@override
String get title => view.title;
}

View File

@ -1,104 +0,0 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
typedef NaviAction = void Function(String);
abstract class NaviItem {
String get identifier;
NaviAction get action;
}
class StyledNavigationController extends ChangeNotifier {
List<NaviItem> naviItems;
StyledNavigationController({this.naviItems = const []});
}
class StyledNavigationList extends StatelessWidget {
const StyledNavigationList({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => StyledNavigationController()),
],
child: Consumer(builder: (ctx, StyledNavigationController ctrl, child) {
return Row(
children: _buildNaviItemWidget(ctrl.naviItems),
);
}),
);
}
List<Widget> _buildNaviItemWidget(List<NaviItem> items) {
if (items.isEmpty) {
return [];
}
List<NaviItem> newItems = _filter(items);
Widget last = NaviItemWidget(newItems.removeLast());
List<Widget> widgets = newItems
.map((item) => NaviItemDivider(child: NaviItemWidget(item)))
.toList();
widgets.add(last);
return widgets;
}
List<NaviItem> _filter(List<NaviItem> items) {
final length = items.length;
if (length > 4) {
final first = items[0];
final ellipsisItems = items.getRange(1, length - 2).toList();
final last = items.getRange(length - 2, length).toList();
return [
first,
EllipsisNaviItem(items: ellipsisItems),
...last,
];
} else {
return items;
}
}
}
class NaviItemWidget extends StatelessWidget {
final NaviItem item;
const NaviItemWidget(this.item, {Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
child: null,
);
}
}
class NaviItemDivider extends StatelessWidget {
final Widget child;
const NaviItemDivider({Key? key, required this.child}) : super(key: key);
@override
Widget build(BuildContext context) {
return Row(
children: [child, const Text('/')],
);
}
}
class EllipsisNaviItem extends NaviItem {
final List<NaviItem> items;
EllipsisNaviItem({
required this.items,
});
@override
// TODO: implement action
NaviAction get action => throw UnimplementedError();
@override
// TODO: implement identifier
String get identifier => throw UnimplementedError();
}