chore: add calendar view plugin along with backend data (#1611)

* chore: create build-in calendar data

* feat: add new calendar view to plugins

* chore: add create calendar page test

* chore: disable for creation for now

* fix: rebase regression

Co-authored-by: nathan <nathan@appflowy.io>
This commit is contained in:
Richard Shiue 2023-01-08 20:51:19 +08:00 committed by GitHub
parent e479425297
commit e8b21955f8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 199 additions and 14 deletions

View File

@ -313,5 +313,8 @@
"column": {
"create_new_card": "New"
}
},
"calendar": {
"menuName": "Calendar"
}
}

View File

@ -0,0 +1,111 @@
import 'package:app_flowy/generated/locale_keys.g.dart';
import 'package:app_flowy/startup/plugin/plugin.dart';
import 'package:app_flowy/workspace/presentation/home/home_stack.dart';
import 'package:app_flowy/workspace/presentation/widgets/left_bar_item.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:flutter/material.dart';
import '../util.dart';
class CalendarPluginBuilder extends PluginBuilder {
@override
Plugin build(dynamic data) {
if (data is ViewPB) {
return CalendarPlugin(pluginType: pluginType, view: data);
} else {
throw FlowyPluginException.invalidData;
}
}
@override
String get menuName => LocaleKeys.calendar_menuName.tr();
@override
String get menuIcon => "editor/date";
@override
PluginType get pluginType => PluginType.calendar;
@override
ViewDataFormatPB get dataFormatType => ViewDataFormatPB.DatabaseFormat;
@override
ViewLayoutTypePB? get layoutType => ViewLayoutTypePB.Calendar;
}
class CalendarPluginConfig implements PluginConfig {
@override
bool get creatable => false;
}
class CalendarPlugin extends Plugin {
@override
final ViewPluginNotifier notifier;
final PluginType _pluginType;
CalendarPlugin({
required ViewPB view,
required PluginType pluginType,
}) : _pluginType = pluginType,
notifier = ViewPluginNotifier(view: view);
@override
PluginDisplay get display => CalendarPluginDisplay(notifier: notifier);
@override
PluginId get id => notifier.view.id;
@override
PluginType get ty => _pluginType;
}
class CalendarPluginDisplay extends PluginDisplay {
final ViewPluginNotifier notifier;
CalendarPluginDisplay({required this.notifier, Key? key});
ViewPB get view => notifier.view;
@override
Widget get leftBarItem => ViewLeftBarItem(view: view);
@override
Widget buildWidget(PluginContext context) {
notifier.isDeleted.addListener(() {
notifier.isDeleted.value.fold(() => null, (deletedView) {
if (deletedView.hasIndex()) {
context.onDeleted(view, deletedView.index);
}
});
});
return BlankPage(key: ValueKey(view.id));
// return BoardPage(key: ValueKey(view.id), view: view);
}
@override
List<NavigationItem> get navigationItems => [this];
}
// mark for removal
class BlankPage extends StatefulWidget {
const BlankPage({Key? key}) : super(key: key);
@override
State<BlankPage> createState() => _BlankPageState();
}
class _BlankPageState extends State<BlankPage> {
@override
Widget build(BuildContext context) {
return SizedBox.expand(
child: Container(
color: Theme.of(context).colorScheme.surface,
child: Padding(
padding: const EdgeInsets.all(10),
child: Container(),
),
),
);
}
}

View File

@ -14,6 +14,7 @@ enum PluginType {
trash,
grid,
board,
calendar,
}
typedef PluginId = String;

View File

@ -1,3 +1,4 @@
import 'package:app_flowy/plugins/calendar/calendar.dart';
import 'package:app_flowy/startup/plugin/plugin.dart';
import 'package:app_flowy/startup/startup.dart';
import 'package:app_flowy/plugins/blank/blank.dart';
@ -17,5 +18,7 @@ class PluginLoadTask extends LaunchTask {
registerPlugin(builder: DocumentPluginBuilder());
registerPlugin(builder: GridPluginBuilder(), config: GridPluginConfig());
registerPlugin(builder: BoardPluginBuilder(), config: BoardPluginConfig());
registerPlugin(
builder: CalendarPluginBuilder(), config: CalendarPluginConfig());
}
}

View File

@ -44,6 +44,8 @@ extension ViewExtension on ViewPB {
switch (layout) {
case ViewLayoutTypePB.Board:
return PluginType.board;
case ViewLayoutTypePB.Calendar:
return PluginType.calendar;
case ViewLayoutTypePB.Document:
return PluginType.editor;
case ViewLayoutTypePB.Grid:

View File

@ -1,4 +1,5 @@
import 'package:app_flowy/plugins/board/board.dart';
import 'package:app_flowy/plugins/calendar/calendar.dart';
import 'package:app_flowy/plugins/document/document.dart';
import 'package:app_flowy/plugins/grid/grid.dart';
import 'package:app_flowy/workspace/application/app/app_bloc.dart';
@ -37,6 +38,7 @@ void main() {
assert(bloc.state.views.last.name == "Test grid");
assert(bloc.state.views.last.layout == ViewLayoutTypePB.Grid);
});
test('create a kanban', () async {
final app = await testContext.createTestApp();
final bloc = AppBloc(app: app)..add(const AppEvent.initial());
@ -49,4 +51,17 @@ void main() {
assert(bloc.state.views.last.name == "Test board");
assert(bloc.state.views.last.layout == ViewLayoutTypePB.Board);
});
test('create a calendar', () async {
final app = await testContext.createTestApp();
final bloc = AppBloc(app: app)..add(const AppEvent.initial());
await blocResponseFuture();
bloc.add(AppEvent.createView("Test calendar", CalendarPluginBuilder()));
await blocResponseFuture();
assert(bloc.state.views.length == 1);
assert(bloc.state.views.last.name == "Test calendar");
assert(bloc.state.views.last.layout == ViewLayoutTypePB.Calendar);
});
}

View File

@ -11,7 +11,7 @@ use flowy_folder::{
};
use flowy_grid::entities::GridLayout;
use flowy_grid::manager::{make_grid_view_data, GridManager};
use flowy_grid::util::{make_default_board, make_default_grid};
use flowy_grid::util::{make_default_board, make_default_calendar, make_default_grid};
use flowy_http_model::revision::Revision;
use flowy_http_model::ws_data::ClientRevisionWSData;
use flowy_net::ClientServerConfiguration;
@ -264,6 +264,7 @@ impl ViewDataProcessor for GridViewDataProcessor {
let (build_context, layout) = match layout {
ViewLayoutTypePB::Grid => (make_default_grid(), GridLayout::Table),
ViewLayoutTypePB::Board => (make_default_board(), GridLayout::Board),
ViewLayoutTypePB::Calendar => (make_default_calendar(), GridLayout::Calendar),
ViewLayoutTypePB::Document => {
return FutureResult::new(async move {
Err(FlowyError::internal().context(format!("Can't handle {:?} layout type", layout)))
@ -293,6 +294,7 @@ impl ViewDataProcessor for GridViewDataProcessor {
let layout = match layout {
ViewLayoutTypePB::Grid => GridLayout::Table,
ViewLayoutTypePB::Board => GridLayout::Board,
ViewLayoutTypePB::Calendar => GridLayout::Calendar,
ViewLayoutTypePB::Document => {
return FutureResult::new(async move {
Err(FlowyError::internal().context(format!("Can't handle {:?} layout type", layout)))

View File

@ -86,6 +86,7 @@ pub enum ViewLayoutTypePB {
Document = 0,
Grid = 3,
Board = 4,
Calendar = 5,
}
impl std::default::Default for ViewLayoutTypePB {
@ -100,6 +101,7 @@ impl std::convert::From<ViewLayoutTypeRevision> for ViewLayoutTypePB {
ViewLayoutTypeRevision::Grid => ViewLayoutTypePB::Grid,
ViewLayoutTypeRevision::Board => ViewLayoutTypePB::Board,
ViewLayoutTypeRevision::Document => ViewLayoutTypePB::Document,
ViewLayoutTypeRevision::Calendar => ViewLayoutTypePB::Calendar,
}
}
}
@ -110,6 +112,7 @@ impl std::convert::From<ViewLayoutTypePB> for ViewLayoutTypeRevision {
ViewLayoutTypePB::Grid => ViewLayoutTypeRevision::Grid,
ViewLayoutTypePB::Board => ViewLayoutTypeRevision::Board,
ViewLayoutTypePB::Document => ViewLayoutTypeRevision::Document,
ViewLayoutTypePB::Calendar => ViewLayoutTypeRevision::Calendar,
}
}
}

View File

@ -570,7 +570,7 @@ impl FieldType {
}
pub fn can_be_group(&self) -> bool {
self.is_select_option()
self.is_select_option() || self.is_checkbox()
}
}

View File

@ -49,6 +49,7 @@ impl GridLayoutPB {
pub enum GridLayout {
Table = 0,
Board = 1,
Calendar = 2,
}
impl std::default::Default for GridLayout {
@ -62,6 +63,7 @@ impl std::convert::From<LayoutRevision> for GridLayout {
match rev {
LayoutRevision::Table => GridLayout::Table,
LayoutRevision::Board => GridLayout::Board,
LayoutRevision::Calendar => GridLayout::Calendar,
}
}
}
@ -71,6 +73,7 @@ impl std::convert::From<GridLayout> for LayoutRevision {
match layout {
GridLayout::Table => LayoutRevision::Table,
GridLayout::Board => LayoutRevision::Board,
GridLayout::Calendar => LayoutRevision::Calendar,
}
}
}

View File

@ -74,17 +74,14 @@ where
Ok(group_controller)
}
pub fn find_group_field(field_revs: &[Arc<FieldRevision>], layout: &LayoutRevision) -> Option<Arc<FieldRevision>> {
match layout {
LayoutRevision::Table => field_revs.iter().find(|field_rev| field_rev.is_primary).cloned(),
LayoutRevision::Board => field_revs
.iter()
.find(|field_rev| {
let field_type: FieldType = field_rev.ty.into();
field_type.can_be_group()
})
.cloned(),
}
pub fn find_group_field(field_revs: &[Arc<FieldRevision>], _layout: &LayoutRevision) -> Option<Arc<FieldRevision>> {
field_revs
.iter()
.find(|field_rev| {
let field_type: FieldType = field_rev.ty.into();
field_type.can_be_group()
})
.cloned()
}
/// Returns a `default` group configuration for the [FieldRevision]

View File

@ -70,6 +70,34 @@ pub fn make_default_board() -> BuildGridContext {
grid_builder.build()
}
pub fn make_default_calendar() -> BuildGridContext {
let mut grid_builder = GridBuilder::new();
// text
let text_field = FieldBuilder::new(RichTextTypeOptionBuilder::default())
.name("Description")
.visibility(true)
.primary(true)
.build();
grid_builder.add_field(text_field);
// date
let date_type_option = DateTypeOptionBuilder::default();
let date_field = FieldBuilder::new(date_type_option)
.name("Date")
.visibility(true)
.build();
grid_builder.add_field(date_field);
// single select
let multi_select_type_option = MultiSelectTypeOptionBuilder::default();
let multi_select_field = FieldBuilder::new(multi_select_type_option)
.name("Tags")
.visibility(true)
.build();
grid_builder.add_field(multi_select_field);
grid_builder.build()
}
#[allow(dead_code)]
pub fn make_default_board_2() -> BuildGridContext {
let mut grid_builder = GridBuilder::new();

View File

@ -57,6 +57,11 @@ impl GridEditorTest {
let view_data: Bytes = build_context.into();
ViewTest::new_board_view(&sdk, view_data.to_vec()).await
}
GridLayout::Calendar => {
let build_context = make_test_calendar();
let view_data: Bytes = build_context.into();
ViewTest::new_calendar_view(&sdk, view_data.to_vec()).await
}
};
let editor = sdk.grid_manager.open_grid(&test.view.id).await.unwrap();
@ -103,7 +108,7 @@ impl GridEditorTest {
}
/// returns the first `FieldRevision` in the build-in test grid.
/// Not support duplicate `FieldType` in test grid yet.
/// Not support duplicate `FieldType` in test grid yet.
pub fn get_first_field_rev(&self, field_type: FieldType) -> &Arc<FieldRevision> {
self.field_revs
.iter()
@ -388,6 +393,7 @@ fn make_test_grid() -> BuildGridContext {
grid_builder.build()
}
// Kanban board unit test mock data
fn make_test_board() -> BuildGridContext {
let mut grid_builder = GridBuilder::new();
// Iterate through the FieldType to create the corresponding Field.
@ -557,3 +563,8 @@ fn make_test_board() -> BuildGridContext {
}
grid_builder.build()
}
// Calendar unit test mock data
fn make_test_calendar() -> BuildGridContext {
todo!()
}

View File

@ -51,6 +51,10 @@ impl ViewTest {
Self::new(sdk, ViewDataFormatPB::DatabaseFormat, ViewLayoutTypePB::Board, data).await
}
pub async fn new_calendar_view(sdk: &FlowySDKTest, data: Vec<u8>) -> Self {
Self::new(sdk, ViewDataFormatPB::DatabaseFormat, ViewLayoutTypePB::Calendar, data).await
}
pub async fn new_document_view(sdk: &FlowySDKTest) -> Self {
let view_data_format = match sdk.document_version() {
DocumentVersionPB::V0 => ViewDataFormatPB::DeltaFormat,

View File

@ -75,6 +75,7 @@ pub enum ViewLayoutTypeRevision {
// The for historical reasons, the value of Grid is not 1.
Grid = 3,
Board = 4,
Calendar = 5,
}
impl std::default::Default for ViewLayoutTypeRevision {

View File

@ -13,6 +13,7 @@ pub fn gen_grid_view_id() -> String {
pub enum LayoutRevision {
Table = 0,
Board = 1,
Calendar = 2,
}
impl ToString for LayoutRevision {