[flutter]: add scrollbar to trash list

This commit is contained in:
appflowy 2021-10-14 18:11:59 +08:00
parent 931c638b93
commit 6e8ae0eca9
8 changed files with 205 additions and 44 deletions

View File

@ -10,7 +10,8 @@ part 'app_bloc.freezed.dart';
class AppBloc extends Bloc<AppEvent, AppState> {
final IApp iAppImpl;
AppBloc(this.iAppImpl) : super(AppState.initial());
final IAppListenr listener;
AppBloc({required this.iAppImpl, required this.listener}) : super(AppState.initial());
@override
Stream<AppState> mapEventToState(
@ -18,6 +19,8 @@ class AppBloc extends Bloc<AppEvent, AppState> {
) async* {
yield* event.map(
initial: (e) async* {
listener.start(viewsChangeCallback: _handleViewsOrFail);
yield* _fetchViews();
},
createView: (CreateView value) async* {
@ -27,6 +30,24 @@ class AppBloc extends Bloc<AppEvent, AppState> {
return state.copyWith(successOrFailure: right(error));
});
},
didReceiveViews: (e) async* {
yield state.copyWith(views: e.views);
},
);
}
@override
Future<void> close() async {
await listener.stop();
return super.close();
}
void _handleViewsOrFail(Either<List<View>, WorkspaceError> viewsOrFail) {
viewsOrFail.fold(
(views) => add(AppEvent.didReceiveViews(views)),
(error) {
Log.error(error);
},
);
}
@ -46,6 +67,7 @@ class AppBloc extends Bloc<AppEvent, AppState> {
class AppEvent with _$AppEvent {
const factory AppEvent.initial() = Initial;
const factory AppEvent.createView(String name, String desc, ViewType viewType) = CreateView;
const factory AppEvent.didReceiveViews(List<View> views) = ReceiveViews;
}
@freezed

View File

@ -27,6 +27,12 @@ class _$AppEventTearOff {
viewType,
);
}
ReceiveViews didReceiveViews(List<View> views) {
return ReceiveViews(
views,
);
}
}
/// @nodoc
@ -39,12 +45,14 @@ mixin _$AppEvent {
required TResult Function() initial,
required TResult Function(String name, String desc, ViewType viewType)
createView,
required TResult Function(List<View> views) didReceiveViews,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? initial,
TResult Function(String name, String desc, ViewType viewType)? createView,
TResult Function(List<View> views)? didReceiveViews,
required TResult orElse(),
}) =>
throw _privateConstructorUsedError;
@ -52,12 +60,14 @@ mixin _$AppEvent {
TResult map<TResult extends Object?>({
required TResult Function(Initial value) initial,
required TResult Function(CreateView value) createView,
required TResult Function(ReceiveViews value) didReceiveViews,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(Initial value)? initial,
TResult Function(CreateView value)? createView,
TResult Function(ReceiveViews value)? didReceiveViews,
required TResult orElse(),
}) =>
throw _privateConstructorUsedError;
@ -118,6 +128,7 @@ class _$Initial implements Initial {
required TResult Function() initial,
required TResult Function(String name, String desc, ViewType viewType)
createView,
required TResult Function(List<View> views) didReceiveViews,
}) {
return initial();
}
@ -127,6 +138,7 @@ class _$Initial implements Initial {
TResult maybeWhen<TResult extends Object?>({
TResult Function()? initial,
TResult Function(String name, String desc, ViewType viewType)? createView,
TResult Function(List<View> views)? didReceiveViews,
required TResult orElse(),
}) {
if (initial != null) {
@ -140,6 +152,7 @@ class _$Initial implements Initial {
TResult map<TResult extends Object?>({
required TResult Function(Initial value) initial,
required TResult Function(CreateView value) createView,
required TResult Function(ReceiveViews value) didReceiveViews,
}) {
return initial(this);
}
@ -149,6 +162,7 @@ class _$Initial implements Initial {
TResult maybeMap<TResult extends Object?>({
TResult Function(Initial value)? initial,
TResult Function(CreateView value)? createView,
TResult Function(ReceiveViews value)? didReceiveViews,
required TResult orElse(),
}) {
if (initial != null) {
@ -250,6 +264,7 @@ class _$CreateView implements CreateView {
required TResult Function() initial,
required TResult Function(String name, String desc, ViewType viewType)
createView,
required TResult Function(List<View> views) didReceiveViews,
}) {
return createView(name, desc, viewType);
}
@ -259,6 +274,7 @@ class _$CreateView implements CreateView {
TResult maybeWhen<TResult extends Object?>({
TResult Function()? initial,
TResult Function(String name, String desc, ViewType viewType)? createView,
TResult Function(List<View> views)? didReceiveViews,
required TResult orElse(),
}) {
if (createView != null) {
@ -272,6 +288,7 @@ class _$CreateView implements CreateView {
TResult map<TResult extends Object?>({
required TResult Function(Initial value) initial,
required TResult Function(CreateView value) createView,
required TResult Function(ReceiveViews value) didReceiveViews,
}) {
return createView(this);
}
@ -281,6 +298,7 @@ class _$CreateView implements CreateView {
TResult maybeMap<TResult extends Object?>({
TResult Function(Initial value)? initial,
TResult Function(CreateView value)? createView,
TResult Function(ReceiveViews value)? didReceiveViews,
required TResult orElse(),
}) {
if (createView != null) {
@ -302,6 +320,126 @@ abstract class CreateView implements AppEvent {
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $ReceiveViewsCopyWith<$Res> {
factory $ReceiveViewsCopyWith(
ReceiveViews value, $Res Function(ReceiveViews) then) =
_$ReceiveViewsCopyWithImpl<$Res>;
$Res call({List<View> views});
}
/// @nodoc
class _$ReceiveViewsCopyWithImpl<$Res> extends _$AppEventCopyWithImpl<$Res>
implements $ReceiveViewsCopyWith<$Res> {
_$ReceiveViewsCopyWithImpl(
ReceiveViews _value, $Res Function(ReceiveViews) _then)
: super(_value, (v) => _then(v as ReceiveViews));
@override
ReceiveViews get _value => super._value as ReceiveViews;
@override
$Res call({
Object? views = freezed,
}) {
return _then(ReceiveViews(
views == freezed
? _value.views
: views // ignore: cast_nullable_to_non_nullable
as List<View>,
));
}
}
/// @nodoc
class _$ReceiveViews implements ReceiveViews {
const _$ReceiveViews(this.views);
@override
final List<View> views;
@override
String toString() {
return 'AppEvent.didReceiveViews(views: $views)';
}
@override
bool operator ==(dynamic other) {
return identical(this, other) ||
(other is ReceiveViews &&
(identical(other.views, views) ||
const DeepCollectionEquality().equals(other.views, views)));
}
@override
int get hashCode =>
runtimeType.hashCode ^ const DeepCollectionEquality().hash(views);
@JsonKey(ignore: true)
@override
$ReceiveViewsCopyWith<ReceiveViews> get copyWith =>
_$ReceiveViewsCopyWithImpl<ReceiveViews>(this, _$identity);
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function() initial,
required TResult Function(String name, String desc, ViewType viewType)
createView,
required TResult Function(List<View> views) didReceiveViews,
}) {
return didReceiveViews(views);
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? initial,
TResult Function(String name, String desc, ViewType viewType)? createView,
TResult Function(List<View> views)? didReceiveViews,
required TResult orElse(),
}) {
if (didReceiveViews != null) {
return didReceiveViews(views);
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(Initial value) initial,
required TResult Function(CreateView value) createView,
required TResult Function(ReceiveViews value) didReceiveViews,
}) {
return didReceiveViews(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(Initial value)? initial,
TResult Function(CreateView value)? createView,
TResult Function(ReceiveViews value)? didReceiveViews,
required TResult orElse(),
}) {
if (didReceiveViews != null) {
return didReceiveViews(this);
}
return orElse();
}
}
abstract class ReceiveViews implements AppEvent {
const factory ReceiveViews(List<View> views) = _$ReceiveViews;
List<View> get views => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$ReceiveViewsCopyWith<ReceiveViews> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
class _$AppStateTearOff {
const _$AppStateTearOff();

View File

@ -1,4 +1,5 @@
import 'package:app_flowy/workspace/domain/i_app.dart';
import 'package:flowy_log/flowy_log.dart';
import 'package:flowy_sdk/protobuf/flowy-workspace/errors.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-workspace/view_create.pb.dart';
import 'package:freezed_annotation/freezed_annotation.dart';

View File

@ -78,7 +78,12 @@ class HomeDepsResolver {
(user, _) => MenuUserBloc(getIt<IUser>(param1: user), getIt<IUserListener>(param1: user)));
// App
getIt.registerFactoryParam<AppBloc, String, void>((appId, _) => AppBloc(getIt<IApp>(param1: appId)));
getIt.registerFactoryParam<AppBloc, String, void>(
(appId, _) => AppBloc(
iAppImpl: getIt<IApp>(param1: appId),
listener: getIt<IAppListenr>(param1: appId),
),
);
getIt.registerFactoryParam<AppListenBloc, String, void>(
(appId, _) => AppListenBloc(getIt<IAppListenr>(param1: appId)));

View File

@ -1,9 +1,13 @@
import 'package:app_flowy/startup/startup.dart';
import 'package:app_flowy/workspace/application/trash/trash_bloc.dart';
import 'package:app_flowy/workspace/domain/page_stack/page_stack.dart';
import 'package:app_flowy/workspace/presentation/stack_page/trash/widget/sizes.dart';
import 'package:app_flowy/workspace/presentation/stack_page/trash/widget/trash_cell.dart';
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart';
import 'package:flowy_infra_ui/style_widget/scrolling/styled_scroll_bar.dart';
import 'package:flowy_infra_ui/style_widget/scrolling/styled_scrollview.dart';
import 'package:flowy_infra_ui/style_widget/text.dart';
import 'package:flowy_infra_ui/style_widget/button.dart';
import 'package:flowy_infra_ui/widget/spacing.dart';
@ -29,7 +33,7 @@ class TrashStackContext extends HomeStackContext {
@override
Widget render() {
return const TrashStackPage(key: ObjectKey('TrashStackPage'));
return const TrashStackPage(key: ValueKey('TrashStackPage'));
}
@override
@ -44,6 +48,7 @@ class TrashStackPage extends StatefulWidget {
}
class _TrashStackPageState extends State<TrashStackPage> {
final ScrollController _scrollController = ScrollController();
@override
Widget build(BuildContext context) {
final theme = context.watch<AppTheme>();
@ -53,12 +58,26 @@ class _TrashStackPageState extends State<TrashStackPage> {
_renderTopBar(theme),
const VSpace(32),
Expanded(
child: CustomScrollView(
controller: ScrollController(),
slivers: [
_renderListHeader(context),
_renderListBody(context),
],
child: ScrollbarListStack(
axis: Axis.vertical,
controller: _scrollController,
barSize: 10,
child: StyledSingleChildScrollView(
controller: ScrollController(),
axis: Axis.horizontal,
child: SizedBox(
width: TrashSizes.totalWidth,
child: CustomScrollView(
shrinkWrap: true,
physics: StyledScrollPhysics(),
controller: _scrollController,
slivers: [
_renderListHeader(context),
_renderListBody(context),
],
),
),
),
),
),
],

View File

@ -3,4 +3,7 @@ class TrashSizes {
static double get fileNameWidth => 320 * scale;
static double get lashModifyWidth => 230 * scale;
static double get createTimeWidth => 230 * scale;
static double get padding => 100 * scale;
static double get totalWidth =>
TrashSizes.fileNameWidth + TrashSizes.lashModifyWidth + TrashSizes.createTimeWidth + TrashSizes.padding;
}

View File

@ -1,12 +1,10 @@
import 'package:app_flowy/workspace/presentation/widgets/menu/widget/app/header.dart';
import 'package:expandable/expandable.dart';
import 'package:flowy_infra_ui/widget/error_page.dart';
import 'package:flowy_sdk/protobuf/flowy-workspace/app_create.pb.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:app_flowy/startup/startup.dart';
import 'package:app_flowy/workspace/application/app/app_bloc.dart';
import 'package:app_flowy/workspace/application/app/app_listen_bloc.dart';
import 'package:app_flowy/workspace/presentation/widgets/menu/menu.dart';
import 'package:provider/provider.dart';
import 'package:styled_widget/styled_widget.dart';
@ -68,36 +66,13 @@ class MenuApp extends MenuItem {
appBloc.add(const AppEvent.initial());
return appBloc;
}),
BlocProvider<AppListenBloc>(create: (context) {
final listener = getIt<AppListenBloc>(param1: appCtx.app.id);
listener.add(const AppListenEvent.started());
return listener;
}),
],
child: MultiBlocListener(
listeners: [
BlocListener<AppListenBloc, AppListenState>(
listenWhen: (p, c) => p != c,
listener: (context, state) => state.map(
initial: (_) => {},
didReceiveViews: (state) => appCtx.viewList.items = state.views,
loadFail: (s) => appCtx.viewList.items = [],
),
),
],
child: BlocBuilder<AppListenBloc, AppListenState>(
builder: (context, state) {
final child = state.map(
initial: (_) => BlocBuilder<AppBloc, AppState>(builder: (context, state) {
appCtx.viewList.items = state.views ?? List.empty(growable: false);
return _renderViewSection(appCtx.viewList);
}),
didReceiveViews: (state) => _renderViewSection(appCtx.viewList),
loadFail: (s) => FlowyErrorPage(s.error.toString()),
);
return expandableWrapper(context, child);
},
),
child: BlocBuilder<AppBloc, AppState>(
builder: (context, state) {
appCtx.viewList.items = state.views ?? List.empty(growable: false);
final child = _renderViewSection(appCtx.viewList);
return expandableWrapper(context, child);
},
),
);
}

View File

@ -23,12 +23,10 @@ class StyledSingleChildScrollView extends StatefulWidget {
}) : super(key: key);
@override
_StyledSingleChildScrollViewState createState() =>
_StyledSingleChildScrollViewState();
_StyledSingleChildScrollViewState createState() => _StyledSingleChildScrollViewState();
}
class _StyledSingleChildScrollViewState
extends State<StyledSingleChildScrollView> {
class _StyledSingleChildScrollViewState extends State<StyledSingleChildScrollView> {
late ScrollController scrollController;
@override