feat: mobile grid fab (#4093)

This commit is contained in:
Richard Shiue 2023-12-05 17:24:38 +08:00 committed by GitHub
parent 04eea26a55
commit d25830aece
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 163 additions and 23 deletions

View File

@ -93,20 +93,12 @@ class _MobileViewPageState extends State<MobileViewPage> {
child: Builder(
builder: (context) {
final view = context.watch<ViewBloc>().state.view;
return _buildApp(
view,
actions,
body,
);
return _buildApp(view, actions, body);
},
),
);
} else {
return _buildApp(
null,
[],
body,
);
return _buildApp(null, [], body);
}
},
);

View File

@ -5,6 +5,7 @@ import 'package:appflowy/plugins/database_view/application/row/row_cache.dart';
import 'package:appflowy/plugins/database_view/application/row/row_service.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/filter/filter_info.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/sort/sort_info.dart';
import 'package:appflowy_backend/log.dart';
import 'package:dartz/dartz.dart';
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
@ -26,8 +27,15 @@ class GridBloc extends Bloc<GridEvent, GridState> {
_startListening();
await _openGrid(emit);
},
createRow: () {
databaseController.createRow();
createRow: () async {
final result = await databaseController.createRow();
result.fold(
(createdRow) => emit(state.copyWith(createdRow: createdRow)),
(err) => Log.error(err),
);
},
resetCreatedRow: () {
emit(state.copyWith(createdRow: null));
},
deleteRow: (rowInfo) async {
await RowBackendService.deleteRow(rowInfo.viewId, rowInfo.rowId);
@ -142,6 +150,7 @@ class GridBloc extends Bloc<GridEvent, GridState> {
class GridEvent with _$GridEvent {
const factory GridEvent.initial() = InitialGrid;
const factory GridEvent.createRow() = _CreateRow;
const factory GridEvent.resetCreatedRow() = _ResetCreatedRow;
const factory GridEvent.deleteRow(RowInfo rowInfo) = _DeleteRow;
const factory GridEvent.moveRow(int from, int to) = _MoveRow;
const factory GridEvent.didLoadRows(
@ -170,6 +179,7 @@ class GridState with _$GridState {
required FieldList fields,
required List<RowInfo> rowInfos,
required int rowCount,
required RowMetaPB? createdRow,
required LoadingState loadingState,
required bool reorderable,
required ChangedReason reason,
@ -181,6 +191,7 @@ class GridState with _$GridState {
fields: FieldList([]),
rowInfos: [],
rowCount: 0,
createdRow: null,
grid: none(),
viewId: viewId,
reorderable: true,

View File

@ -25,6 +25,7 @@ import 'layout/layout.dart';
import 'layout/sizes.dart';
import 'widgets/footer/grid_footer.dart';
import 'widgets/header/grid_header.dart';
import 'widgets/mobile_fab.dart';
import 'widgets/row/mobile_row.dart';
import 'widgets/shortcuts.dart';
@ -147,23 +148,50 @@ class _GridPageContentState extends State<GridPageContent> {
@override
Widget build(BuildContext context) {
return BlocBuilder<GridBloc, GridState>(
return BlocConsumer<GridBloc, GridState>(
listenWhen: (previous, current) =>
previous.createdRow != current.createdRow,
listener: (context, state) {
if (state.createdRow == null) {
return;
}
final bloc = context.read<GridBloc>();
context.push(
MobileRowDetailPage.routeName,
extra: {
MobileRowDetailPage.argRowId: state.createdRow!.id,
MobileRowDetailPage.argDatabaseController: bloc.databaseController,
},
);
bloc.add(const GridEvent.resetCreatedRow());
},
buildWhen: (previous, current) => previous.fields != current.fields,
builder: (context, state) {
final contentWidth = GridLayout.headerWidth(state.fields.fields);
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
return Stack(
children: [
Padding(
padding: EdgeInsets.only(right: GridSize.leadingHeaderPadding),
child:
_GridHeader(headerScrollController: headerScrollController),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding:
EdgeInsets.only(right: GridSize.leadingHeaderPadding),
child: _GridHeader(
headerScrollController: headerScrollController,
),
),
_GridRows(
viewId: state.viewId,
contentWidth: contentWidth,
scrollController: _scrollController,
),
],
),
_GridRows(
viewId: state.viewId,
contentWidth: contentWidth,
scrollController: _scrollController,
Positioned(
bottom: 20,
right: 20,
child: getGridFabs(context),
),
],
);

View File

@ -0,0 +1,109 @@
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/mobile/presentation/database/card/card_detail/mobile_card_detail_screen.dart';
import 'package:appflowy/plugins/database_view/grid/application/grid_bloc.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:provider/provider.dart';
Widget getGridFabs(BuildContext context) {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
MobileGridFab(
backgroundColor: Theme.of(context).colorScheme.surface,
foregroundColor: Theme.of(context).primaryColor,
onTap: () {
final bloc = context.read<GridBloc>();
if (bloc.state.rowInfos.isNotEmpty) {
context.push(
MobileRowDetailPage.routeName,
extra: {
MobileRowDetailPage.argRowId: bloc.state.rowInfos.first.rowId,
MobileRowDetailPage.argDatabaseController:
bloc.databaseController,
},
);
}
},
boxShadow: const BoxShadow(
offset: Offset(0, 6),
color: Color(0x145D7D8B),
blurRadius: 18,
),
icon: FlowySvgs.properties_s,
iconSize: const Size.square(24),
),
const HSpace(16),
MobileGridFab(
backgroundColor: Theme.of(context).primaryColor,
foregroundColor: Colors.white,
onTap: () {
context.read<GridBloc>().add(const GridEvent.createRow());
},
overlayColor: const MaterialStatePropertyAll<Color>(Color(0xFF009FD1)),
boxShadow: const BoxShadow(
offset: Offset(0, 8),
color: Color(0x6612BFEF),
blurRadius: 18,
spreadRadius: -5,
),
icon: FlowySvgs.add_s,
iconSize: const Size.square(24),
),
],
);
}
class MobileGridFab extends StatelessWidget {
const MobileGridFab({
super.key,
required this.backgroundColor,
required this.foregroundColor,
required this.boxShadow,
required this.onTap,
required this.icon,
required this.iconSize,
this.overlayColor,
});
final Color backgroundColor;
final Color foregroundColor;
final BoxShadow boxShadow;
final VoidCallback onTap;
final FlowySvgData icon;
final Size iconSize;
final MaterialStateProperty<Color?>? overlayColor;
@override
Widget build(BuildContext context) {
final radius = BorderRadius.circular(20);
return Container(
decoration: BoxDecoration(
color: backgroundColor,
borderRadius: radius,
boxShadow: [boxShadow],
),
child: Material(
borderOnForeground: false,
color: backgroundColor,
borderRadius: radius,
child: InkWell(
borderRadius: radius,
overlayColor: overlayColor,
onTap: onTap,
child: SizedBox.square(
dimension: 56,
child: Center(
child: FlowySvg(
icon,
color: foregroundColor,
size: iconSize,
),
),
),
),
),
);
}
}