mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
feat: mobile grid fab (#4093)
This commit is contained in:
parent
04eea26a55
commit
d25830aece
@ -93,20 +93,12 @@ class _MobileViewPageState extends State<MobileViewPage> {
|
|||||||
child: Builder(
|
child: Builder(
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
final view = context.watch<ViewBloc>().state.view;
|
final view = context.watch<ViewBloc>().state.view;
|
||||||
return _buildApp(
|
return _buildApp(view, actions, body);
|
||||||
view,
|
|
||||||
actions,
|
|
||||||
body,
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return _buildApp(
|
return _buildApp(null, [], body);
|
||||||
null,
|
|
||||||
[],
|
|
||||||
body,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -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/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/filter/filter_info.dart';
|
||||||
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/sort/sort_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:dartz/dartz.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
||||||
@ -26,8 +27,15 @@ class GridBloc extends Bloc<GridEvent, GridState> {
|
|||||||
_startListening();
|
_startListening();
|
||||||
await _openGrid(emit);
|
await _openGrid(emit);
|
||||||
},
|
},
|
||||||
createRow: () {
|
createRow: () async {
|
||||||
databaseController.createRow();
|
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 {
|
deleteRow: (rowInfo) async {
|
||||||
await RowBackendService.deleteRow(rowInfo.viewId, rowInfo.rowId);
|
await RowBackendService.deleteRow(rowInfo.viewId, rowInfo.rowId);
|
||||||
@ -142,6 +150,7 @@ class GridBloc extends Bloc<GridEvent, GridState> {
|
|||||||
class GridEvent with _$GridEvent {
|
class GridEvent with _$GridEvent {
|
||||||
const factory GridEvent.initial() = InitialGrid;
|
const factory GridEvent.initial() = InitialGrid;
|
||||||
const factory GridEvent.createRow() = _CreateRow;
|
const factory GridEvent.createRow() = _CreateRow;
|
||||||
|
const factory GridEvent.resetCreatedRow() = _ResetCreatedRow;
|
||||||
const factory GridEvent.deleteRow(RowInfo rowInfo) = _DeleteRow;
|
const factory GridEvent.deleteRow(RowInfo rowInfo) = _DeleteRow;
|
||||||
const factory GridEvent.moveRow(int from, int to) = _MoveRow;
|
const factory GridEvent.moveRow(int from, int to) = _MoveRow;
|
||||||
const factory GridEvent.didLoadRows(
|
const factory GridEvent.didLoadRows(
|
||||||
@ -170,6 +179,7 @@ class GridState with _$GridState {
|
|||||||
required FieldList fields,
|
required FieldList fields,
|
||||||
required List<RowInfo> rowInfos,
|
required List<RowInfo> rowInfos,
|
||||||
required int rowCount,
|
required int rowCount,
|
||||||
|
required RowMetaPB? createdRow,
|
||||||
required LoadingState loadingState,
|
required LoadingState loadingState,
|
||||||
required bool reorderable,
|
required bool reorderable,
|
||||||
required ChangedReason reason,
|
required ChangedReason reason,
|
||||||
@ -181,6 +191,7 @@ class GridState with _$GridState {
|
|||||||
fields: FieldList([]),
|
fields: FieldList([]),
|
||||||
rowInfos: [],
|
rowInfos: [],
|
||||||
rowCount: 0,
|
rowCount: 0,
|
||||||
|
createdRow: null,
|
||||||
grid: none(),
|
grid: none(),
|
||||||
viewId: viewId,
|
viewId: viewId,
|
||||||
reorderable: true,
|
reorderable: true,
|
||||||
|
@ -25,6 +25,7 @@ import 'layout/layout.dart';
|
|||||||
import 'layout/sizes.dart';
|
import 'layout/sizes.dart';
|
||||||
import 'widgets/footer/grid_footer.dart';
|
import 'widgets/footer/grid_footer.dart';
|
||||||
import 'widgets/header/grid_header.dart';
|
import 'widgets/header/grid_header.dart';
|
||||||
|
import 'widgets/mobile_fab.dart';
|
||||||
import 'widgets/row/mobile_row.dart';
|
import 'widgets/row/mobile_row.dart';
|
||||||
import 'widgets/shortcuts.dart';
|
import 'widgets/shortcuts.dart';
|
||||||
|
|
||||||
@ -147,23 +148,50 @@ class _GridPageContentState extends State<GridPageContent> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
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,
|
buildWhen: (previous, current) => previous.fields != current.fields,
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final contentWidth = GridLayout.headerWidth(state.fields.fields);
|
final contentWidth = GridLayout.headerWidth(state.fields.fields);
|
||||||
|
|
||||||
return Column(
|
return Stack(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
Column(
|
||||||
padding: EdgeInsets.only(right: GridSize.leadingHeaderPadding),
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
child:
|
children: [
|
||||||
_GridHeader(headerScrollController: headerScrollController),
|
Padding(
|
||||||
|
padding:
|
||||||
|
EdgeInsets.only(right: GridSize.leadingHeaderPadding),
|
||||||
|
child: _GridHeader(
|
||||||
|
headerScrollController: headerScrollController,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
_GridRows(
|
||||||
|
viewId: state.viewId,
|
||||||
|
contentWidth: contentWidth,
|
||||||
|
scrollController: _scrollController,
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
_GridRows(
|
Positioned(
|
||||||
viewId: state.viewId,
|
bottom: 20,
|
||||||
contentWidth: contentWidth,
|
right: 20,
|
||||||
scrollController: _scrollController,
|
child: getGridFabs(context),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
@ -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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user