feat: optimize calendar for mobile (#3979)

* feat: calendar mobile ui

- Resolves double border on Calendar Cells
- Adds Jump to year quick action for Mobile
- Reduces Cell height in Calendar
- Change out EventList with EventIndicator in Cell

* chore: push card details screen

* fix: navigation to card details

* feat: day events screen update on new event

* fix: changes after merging main

* fix: missing argument

* fix: amend test and remove stack
This commit is contained in:
Mathias Mogensen 2023-11-25 16:31:54 +02:00 committed by GitHub
parent b3dd5fb8bd
commit 7fb1b4f43f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 510 additions and 231 deletions

View File

@ -74,10 +74,10 @@ void main() {
await tester.scrollToToday(); await tester.scrollToToday();
// Hover over today's calendar cell // Hover over today's calendar cell
await tester.hoverOnTodayCalendarCell(); await tester.hoverOnTodayCalendarCell(
// Tap on create new event button
// Tap on create new event button onHover: () async => await tester.tapAddCalendarEventButton(),
await tester.tapAddCalendarEventButton(); );
// Make sure that the event editor popup is shown // Make sure that the event editor popup is shown
tester.assertEventEditorOpen(); tester.assertEventEditorOpen();
@ -90,15 +90,9 @@ void main() {
// Double click on today's calendar cell to create a new event // Double click on today's calendar cell to create a new event
await tester.doubleClickCalendarCell(DateTime.now()); await tester.doubleClickCalendarCell(DateTime.now());
// Make sure that the event editor popup is shown
tester.assertEventEditorOpen();
// Make sure that the event is inserted in the cell // Make sure that the event is inserted in the cell
tester.assertNumberOfEventsInCalendar(2); tester.assertNumberOfEventsInCalendar(2);
// Dismiss the event editor popup
await tester.dismissEventEditor();
// Click on the event // Click on the event
await tester.openCalendarEvent(index: 0); await tester.openCalendarEvent(index: 0);
tester.assertEventEditorOpen(); tester.assertEventEditorOpen();
@ -112,7 +106,7 @@ void main() {
tester.assertNumberOfEventsOnSpecificDay(2, DateTime.now()); tester.assertNumberOfEventsOnSpecificDay(2, DateTime.now());
// Click on the event // Click on the event
await tester.openCalendarEvent(index: 1); await tester.openCalendarEvent(index: 0);
tester.assertEventEditorOpen(); tester.assertEventEditorOpen();
// Click on the open icon // Click on the open icon
@ -137,7 +131,7 @@ void main() {
tester.assertNumberOfEventsOnSpecificDay(2, DateTime.now()); tester.assertNumberOfEventsOnSpecificDay(2, DateTime.now());
// Delete event from row detail page // Delete event from row detail page
await tester.openCalendarEvent(index: 1); await tester.openCalendarEvent(index: 0);
await tester.openEventToRowDetailPage(); await tester.openEventToRowDetailPage();
tester.assertRowDetailPageOpened(); tester.assertRowDetailPageOpened();
@ -163,7 +157,7 @@ void main() {
await tester.dismissEventEditor(); await tester.dismissEventEditor();
// Drag and drop the event onto the next week, same day // Drag and drop the event onto the next week, same day
await tester.dragDropRescheduleCalendarEvent(firstOfThisMonth); await tester.dragDropRescheduleCalendarEvent();
// Make sure that the event has been rescheduled to the new date // Make sure that the event has been rescheduled to the new date
final sameDayNextWeek = firstOfThisMonth.add(const Duration(days: 7)); final sameDayNextWeek = firstOfThisMonth.add(const Duration(days: 7));

View File

@ -1249,12 +1249,14 @@ extension AppFlowyDatabaseTest on WidgetTester {
await pumpAndSettle(const Duration(milliseconds: 300)); await pumpAndSettle(const Duration(milliseconds: 300));
} }
Future<void> hoverOnTodayCalendarCell() async { Future<void> hoverOnTodayCalendarCell({
Future<void> Function()? onHover,
}) async {
final todayCell = find.byWidgetPredicate( final todayCell = find.byWidgetPredicate(
(widget) => widget is CalendarDayCard && widget.isToday, (widget) => widget is CalendarDayCard && widget.isToday,
); );
await hoverOnWidget(todayCell); await hoverOnWidget(todayCell, onHover: onHover);
} }
Future<void> tapAddCalendarEventButton() async { Future<void> tapAddCalendarEventButton() async {
@ -1362,7 +1364,7 @@ extension AppFlowyDatabaseTest on WidgetTester {
await tapButton(button); await tapButton(button);
} }
Future<void> dragDropRescheduleCalendarEvent(DateTime startDate) async { Future<void> dragDropRescheduleCalendarEvent() async {
final findEventCard = find.byType(EventCard); final findEventCard = find.byType(EventCard);
await drag(findEventCard.first, const Offset(0, 300)); await drag(findEventCard.first, const Offset(0, 300));
await pumpAndSettle(); await pumpAndSettle();

View File

@ -0,0 +1,97 @@
import 'package:appflowy/plugins/database_view/application/row/row_cache.dart';
import 'package:appflowy/plugins/database_view/calendar/application/calendar_bloc.dart';
import 'package:appflowy/plugins/database_view/calendar/presentation/calendar_event_card.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/widget/spacing.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class MobileCalendarEventsScreen extends StatefulWidget {
static const routeName = "/calendar-events";
// GoRouter Arguments
static const calendarBlocKey = "calendar_bloc";
static const calendarDateKey = "date";
static const calendarEventsKey = "events";
static const calendarRowCacheKey = "row_cache";
static const calendarViewIdKey = "view_id";
const MobileCalendarEventsScreen({
super.key,
required this.calendarBloc,
required this.date,
required this.events,
required this.rowCache,
required this.viewId,
});
final CalendarBloc calendarBloc;
final DateTime date;
final List<CalendarDayEvent> events;
final RowCache rowCache;
final String viewId;
@override
State<MobileCalendarEventsScreen> createState() =>
_MobileCalendarEventsScreenState();
}
class _MobileCalendarEventsScreenState
extends State<MobileCalendarEventsScreen> {
late final List<CalendarDayEvent> _events = widget.events;
@override
Widget build(BuildContext context) {
return BlocProvider.value(
value: widget.calendarBloc,
child: BlocBuilder<CalendarBloc, CalendarState>(
buildWhen: (p, c) => p.newEvent != c.newEvent,
builder: (context, state) {
if (state.newEvent?.event != null) {
_events.add(state.newEvent!.event!);
}
return Scaffold(
floatingActionButton: FloatingActionButton(
key: const Key('add_event_fab'),
elevation: 6,
backgroundColor: Theme.of(context).colorScheme.primary,
foregroundColor: Theme.of(context).colorScheme.onPrimary,
onPressed: () => widget.calendarBloc
.add(CalendarEvent.createEvent(widget.date)),
child: const Text('+'),
),
appBar: AppBar(
title: Text(
DateFormat.yMMMMd(context.locale.toLanguageTag())
.format(widget.date),
),
),
body: SingleChildScrollView(
child: Column(
children: [
const VSpace(10),
...widget.events.map((event) {
return ListTile(
dense: true,
title: EventCard(
fieldController: widget.calendarBloc.fieldController,
event: event,
viewId: widget.viewId,
rowCache: widget.rowCache,
constraints: const BoxConstraints.expand(),
autoEdit: false,
isDraggable: false,
),
);
}),
const VSpace(24),
],
),
),
);
},
),
);
}
}

View File

@ -146,19 +146,19 @@ class CalendarBloc extends Bloc<CalendarEvent, CalendarState> {
(settings) async { (settings) async {
final dateField = _getCalendarFieldInfo(settings.fieldId); final dateField = _getCalendarFieldInfo(settings.fieldId);
if (dateField != null) { if (dateField != null) {
final newRow = await databaseController.createRow( final newRow = await databaseController
withCells: (builder) { .createRow(
builder.insertDate(dateField, date); withCells: (builder) => builder.insertDate(dateField, date),
}, )
).then( .then(
(result) => result.fold( (result) => result.fold(
(newRow) => newRow, (newRow) => newRow,
(err) { (err) {
Log.error(err); Log.error(err);
return null; return null;
}, },
), ),
); );
if (newRow != null) { if (newRow != null) {
final event = await _loadEvent(newRow.id); final event = await _loadEvent(newRow.id);
@ -207,10 +207,7 @@ class CalendarBloc extends Bloc<CalendarEvent, CalendarState> {
final payload = RowIdPB(viewId: viewId, rowId: rowId); final payload = RowIdPB(viewId: viewId, rowId: rowId);
return DatabaseEventGetCalendarEvent(payload).send().then((result) { return DatabaseEventGetCalendarEvent(payload).send().then((result) {
return result.fold( return result.fold(
(eventPB) { (eventPB) => _calendarEventDataFromEventPB(eventPB),
final calendarEvent = _calendarEventDataFromEventPB(eventPB);
return calendarEvent;
},
(r) { (r) {
Log.error(r); Log.error(r);
return null; return null;

View File

@ -1,6 +1,9 @@
import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/presentation/database/mobile_calendar_events_screen.dart';
import 'package:appflowy/plugins/database_view/application/row/row_cache.dart'; import 'package:appflowy/plugins/database_view/application/row/row_cache.dart';
import 'package:appflowy/util/platform_extension.dart';
import 'package:calendar_view/calendar_view.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/size.dart'; import 'package:flowy_infra/size.dart';
@ -8,6 +11,7 @@ import 'package:flowy_infra/theme_extension.dart';
import 'package:flowy_infra/time/duration.dart'; import 'package:flowy_infra/time/duration.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import '../../grid/presentation/layout/sizes.dart'; import '../../grid/presentation/layout/sizes.dart';
@ -15,6 +19,18 @@ import '../application/calendar_bloc.dart';
import 'calendar_event_card.dart'; import 'calendar_event_card.dart';
class CalendarDayCard extends StatelessWidget { class CalendarDayCard extends StatelessWidget {
const CalendarDayCard({
super.key,
required this.viewId,
required this.isToday,
required this.isInMonth,
required this.date,
required this.rowCache,
required this.events,
required this.onCreateEvent,
required this.position,
});
final String viewId; final String viewId;
final bool isToday; final bool isToday;
final bool isInMonth; final bool isInMonth;
@ -22,24 +38,10 @@ class CalendarDayCard extends StatelessWidget {
final RowCache rowCache; final RowCache rowCache;
final List<CalendarDayEvent> events; final List<CalendarDayEvent> events;
final void Function(DateTime) onCreateEvent; final void Function(DateTime) onCreateEvent;
final CellPosition position;
const CalendarDayCard({
required this.viewId,
required this.isToday,
required this.isInMonth,
required this.date,
required this.onCreateEvent,
required this.rowCache,
required this.events,
super.key,
});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
Color backgroundColor = Colors.transparent;
if (date.isWeekend) {
backgroundColor = AFThemeExtension.of(context).calendarWeekendBGColor;
}
final hoverBackgroundColor = final hoverBackgroundColor =
Theme.of(context).brightness == Brightness.light Theme.of(context).brightness == Brightness.light
? Theme.of(context).colorScheme.secondaryContainer ? Theme.of(context).colorScheme.secondaryContainer
@ -64,55 +66,67 @@ class CalendarDayCard extends StatelessWidget {
const VSpace(6.0), const VSpace(6.0),
// List of cards or empty space // List of cards or empty space
if (events.isNotEmpty) if (events.isNotEmpty && !PlatformExtension.isMobile) ...[
_EventList( _EventList(
events: events, events: events,
viewId: viewId, viewId: viewId,
rowCache: rowCache, rowCache: rowCache,
constraints: constraints, constraints: constraints,
), ),
] else if (events.isNotEmpty && PlatformExtension.isMobile) ...[
const _EventIndicator(),
],
], ],
); );
return Stack( return MouseRegion(
children: <Widget>[ onEnter: (p) => notifyEnter(context, true),
GestureDetector( onExit: (p) => notifyEnter(context, false),
onDoubleTap: () => onCreateEvent(date), opaque: false,
child: Container(color: backgroundColor), hitTestBehavior: HitTestBehavior.translucent,
child: GestureDetector(
onDoubleTap: () => onCreateEvent(date),
onTap: PlatformExtension.isMobile
? () => _mobileOnTap(context)
: null,
behavior: HitTestBehavior.deferToChild,
child: Container(
color: date.isWeekend
? AFThemeExtension.of(context).calendarWeekendBGColor
: Colors.transparent,
child: DragTarget<CalendarDayEvent>(
builder: (context, candidate, __) {
return Stack(
children: [
Container(
width: double.infinity,
height: double.infinity,
decoration: BoxDecoration(
color: candidate.isEmpty
? null
: hoverBackgroundColor,
border: _borderFromPosition(context, position),
),
padding: const EdgeInsets.only(top: 5.0),
child: child,
),
if (candidate.isEmpty && !PlatformExtension.isMobile)
NewEventButton(onCreate: () => onCreateEvent(date)),
],
);
},
onAccept: (CalendarDayEvent event) {
if (event.date == date) {
return;
}
context
.read<CalendarBloc>()
.add(CalendarEvent.moveEvent(event, date));
},
),
), ),
DragTarget<CalendarDayEvent>( ),
builder: (context, candidate, __) {
return Stack(
children: [
Container(
width: double.infinity,
height: double.infinity,
color:
candidate.isEmpty ? null : hoverBackgroundColor,
padding: const EdgeInsets.only(top: 5.0),
child: child,
),
if (candidate.isEmpty)
NewEventButton(onCreate: () => onCreateEvent(date)),
],
);
},
onAccept: (CalendarDayEvent event) {
if (event.date == date) {
return;
}
context
.read<CalendarBloc>()
.add(CalendarEvent.moveEvent(event, date));
},
),
MouseRegion(
onEnter: (p) => notifyEnter(context, true),
onExit: (p) => notifyEnter(context, false),
opaque: false,
hitTestBehavior: HitTestBehavior.translucent,
),
],
); );
}, },
); );
@ -120,42 +134,94 @@ class CalendarDayCard extends StatelessWidget {
); );
} }
notifyEnter(BuildContext context, bool isEnter) { void _mobileOnTap(BuildContext context) {
Provider.of<_CardEnterNotifier>( context.push(
context, MobileCalendarEventsScreen.routeName,
listen: false, extra: {
).onEnter = isEnter; MobileCalendarEventsScreen.calendarBlocKey:
context.read<CalendarBloc>(),
MobileCalendarEventsScreen.calendarDateKey: date,
MobileCalendarEventsScreen.calendarEventsKey: events,
MobileCalendarEventsScreen.calendarRowCacheKey: rowCache,
MobileCalendarEventsScreen.calendarViewIdKey: viewId,
},
);
}
notifyEnter(BuildContext context, bool isEnter) =>
Provider.of<_CardEnterNotifier>(context, listen: false).onEnter = isEnter;
Border _borderFromPosition(BuildContext context, CellPosition position) {
final BorderSide borderSide =
BorderSide(color: Theme.of(context).dividerColor);
return Border(
top: borderSide,
left: borderSide,
bottom: [
CellPosition.bottom,
CellPosition.bottomLeft,
CellPosition.bottomRight,
].contains(position)
? borderSide
: BorderSide.none,
right: [
CellPosition.topRight,
CellPosition.bottomRight,
CellPosition.right,
].contains(position)
? borderSide
: BorderSide.none,
);
}
}
class _EventIndicator extends StatelessWidget {
const _EventIndicator();
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
width: 6,
height: 6,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Theme.of(context).hintColor,
),
),
],
);
} }
} }
class _Header extends StatelessWidget { class _Header extends StatelessWidget {
final bool isToday;
final bool isInMonth;
final DateTime date;
const _Header({ const _Header({
required this.isToday, required this.isToday,
required this.isInMonth, required this.isInMonth,
required this.date, required this.date,
Key? key, });
}) : super(key: key);
final bool isToday;
final bool isInMonth;
final DateTime date;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Padding( return Padding(
padding: const EdgeInsets.symmetric(horizontal: 6.0), padding: const EdgeInsets.symmetric(horizontal: 6.0),
child: _DayBadge( child: _DayBadge(isToday: isToday, isInMonth: isInMonth, date: date),
isToday: isToday,
isInMonth: isInMonth,
date: date,
),
); );
} }
} }
@visibleForTesting @visibleForTesting
class NewEventButton extends StatelessWidget { class NewEventButton extends StatelessWidget {
const NewEventButton({super.key, required this.onCreate});
final VoidCallback onCreate; final VoidCallback onCreate;
const NewEventButton({required this.onCreate, Key? key}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -164,6 +230,7 @@ class NewEventButton extends StatelessWidget {
if (!notifier.onEnter) { if (!notifier.onEnter) {
return const SizedBox.shrink(); return const SizedBox.shrink();
} }
return Padding( return Padding(
padding: const EdgeInsets.all(4.0), padding: const EdgeInsets.all(4.0),
child: FlowyIconButton( child: FlowyIconButton(
@ -210,15 +277,15 @@ class NewEventButton extends StatelessWidget {
} }
class _DayBadge extends StatelessWidget { class _DayBadge extends StatelessWidget {
final bool isToday;
final bool isInMonth;
final DateTime date;
const _DayBadge({ const _DayBadge({
required this.isToday, required this.isToday,
required this.isInMonth, required this.isInMonth,
required this.date, required this.date,
Key? key, });
}) : super(key: key);
final bool isToday;
final bool isInMonth;
final DateTime date;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -239,10 +306,12 @@ class _DayBadge extends StatelessWidget {
return SizedBox( return SizedBox(
height: 18, height: 18,
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.end, mainAxisAlignment: PlatformExtension.isMobile
? MainAxisAlignment.center
: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
if (date.day == 1) if (date.day == 1 && !PlatformExtension.isMobile)
FlowyText.medium( FlowyText.medium(
monthString, monthString,
fontSize: 11, fontSize: 11,
@ -255,7 +324,6 @@ class _DayBadge extends StatelessWidget {
), ),
width: isToday ? 18 : null, width: isToday ? 18 : null,
height: isToday ? 18 : null, height: isToday ? 18 : null,
// padding: GridSize.typeOptionContentInsets,
child: Center( child: Center(
child: FlowyText.medium( child: FlowyText.medium(
dayString, dayString,
@ -271,27 +339,25 @@ class _DayBadge extends StatelessWidget {
} }
class _EventList extends StatelessWidget { class _EventList extends StatelessWidget {
final List<CalendarDayEvent> events;
final String viewId;
final RowCache rowCache;
final BoxConstraints constraints;
const _EventList({ const _EventList({
required this.events, required this.events,
required this.viewId, required this.viewId,
required this.rowCache, required this.rowCache,
required this.constraints, required this.constraints,
Key? key, });
}) : super(key: key);
final List<CalendarDayEvent> events;
final String viewId;
final RowCache rowCache;
final BoxConstraints constraints;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final editingEvent = context.watch<CalendarBloc>().state.editingEvent; final editingEvent = context.watch<CalendarBloc>().state.editingEvent;
return Flexible( return Flexible(
child: ScrollConfiguration( child: ScrollConfiguration(
behavior: ScrollConfiguration.of(context).copyWith( behavior: ScrollConfiguration.of(context).copyWith(scrollbars: true),
scrollbars: true,
),
child: ListView.separated( child: ListView.separated(
itemBuilder: (BuildContext context, int index) { itemBuilder: (BuildContext context, int index) {
final autoEdit = final autoEdit =
@ -307,7 +373,7 @@ class _EventList extends StatelessWidget {
}, },
itemCount: events.length, itemCount: events.length,
padding: const EdgeInsets.fromLTRB(4.0, 0, 4.0, 4.0), padding: const EdgeInsets.fromLTRB(4.0, 0, 4.0, 4.0),
separatorBuilder: (BuildContext context, int index) => separatorBuilder: (_, __) =>
VSpace(GridSize.typeOptionSeparatorHeight), VSpace(GridSize.typeOptionSeparatorHeight),
shrinkWrap: true, shrinkWrap: true,
), ),

View File

@ -1,6 +1,8 @@
import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/presentation/database/card/card_detail/mobile_card_detail_screen.dart';
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart'; import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
import 'package:appflowy/plugins/database_view/application/row/row_cache.dart'; import 'package:appflowy/plugins/database_view/application/row/row_cache.dart';
import 'package:appflowy/plugins/database_view/application/row/row_controller.dart';
import 'package:appflowy/plugins/database_view/widgets/card/card.dart'; import 'package:appflowy/plugins/database_view/widgets/card/card.dart';
import 'package:appflowy/plugins/database_view/widgets/card/card_cell_builder.dart'; import 'package:appflowy/plugins/database_view/widgets/card/card_cell_builder.dart';
import 'package:appflowy/plugins/database_view/widgets/card/cells/card_cell.dart'; import 'package:appflowy/plugins/database_view/widgets/card/cells/card_cell.dart';
@ -8,6 +10,7 @@ import 'package:appflowy/plugins/database_view/widgets/card/cells/number_card_ce
import 'package:appflowy/plugins/database_view/widgets/card/cells/url_card_cell.dart'; import 'package:appflowy/plugins/database_view/widgets/card/cells/url_card_cell.dart';
import 'package:appflowy/plugins/database_view/widgets/row/cells/select_option_cell/extension.dart'; import 'package:appflowy/plugins/database_view/widgets/row/cells/select_option_cell/extension.dart';
import 'package:appflowy/plugins/database_view/widgets/row/cells/text_cell/text_cell_bloc.dart'; import 'package:appflowy/plugins/database_view/widgets/row/cells/text_cell/text_cell_bloc.dart';
import 'package:appflowy/util/platform_extension.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart';
import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
@ -16,27 +19,30 @@ import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/style_widget/hover.dart'; import 'package:flowy_infra_ui/style_widget/hover.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import '../application/calendar_bloc.dart'; import '../application/calendar_bloc.dart';
import 'calendar_event_editor.dart'; import 'calendar_event_editor.dart';
class EventCard extends StatefulWidget { class EventCard extends StatefulWidget {
const EventCard({
super.key,
required this.fieldController,
required this.event,
required this.viewId,
required this.rowCache,
required this.constraints,
required this.autoEdit,
this.isDraggable = true,
});
final FieldController fieldController; final FieldController fieldController;
final CalendarDayEvent event; final CalendarDayEvent event;
final String viewId; final String viewId;
final RowCache rowCache; final RowCache rowCache;
final BoxConstraints constraints; final BoxConstraints constraints;
final bool autoEdit; final bool autoEdit;
final bool isDraggable;
const EventCard({
super.key,
required this.event,
required this.viewId,
required this.rowCache,
required this.constraints,
required this.autoEdit,
required this.fieldController,
});
@override @override
State<EventCard> createState() => _EventCardState(); State<EventCard> createState() => _EventCardState();
@ -75,7 +81,7 @@ class _EventCardState extends State<EventCard> {
); );
final renderHook = _calendarEventCardRenderHook(context); final renderHook = _calendarEventCardRenderHook(context);
final card = RowCard<CalendarDayEvent>( Widget card = RowCard<CalendarDayEvent>(
// Add the key here to make sure the card is rebuilt when the cells // Add the key here to make sure the card is rebuilt when the cells
// in this row are updated. // in this row are updated.
key: ValueKey(widget.event.eventId), key: ValueKey(widget.event.eventId),
@ -85,7 +91,25 @@ class _EventCardState extends State<EventCard> {
cardData: widget.event, cardData: widget.event,
isEditing: false, isEditing: false,
cellBuilder: cellBuilder, cellBuilder: cellBuilder,
openCard: (_) => _popoverController.show(), openCard: (context) {
if (PlatformExtension.isMobile) {
final dataController = RowController(
rowMeta: rowInfo.rowMeta,
viewId: widget.viewId,
rowCache: widget.rowCache,
);
context.push(
MobileCardDetailScreen.routeName,
extra: {
MobileCardDetailScreen.argRowController: dataController,
MobileCardDetailScreen.argFieldController: widget.fieldController,
},
);
} else {
_popoverController.show();
}
},
styleConfiguration: RowCardStyleConfiguration( styleConfiguration: RowCardStyleConfiguration(
showAccessory: false, showAccessory: false,
cellPadding: EdgeInsets.zero, cellPadding: EdgeInsets.zero,
@ -132,50 +156,56 @@ class _EventCardState extends State<EventCard> {
], ],
); );
return Draggable<CalendarDayEvent>( card = AppFlowyPopover(
data: widget.event, triggerActions: PopoverTriggerFlags.none,
feedback: ConstrainedBox( direction: PopoverDirection.rightWithCenterAligned,
constraints: BoxConstraints( controller: _popoverController,
maxWidth: widget.constraints.maxWidth - 8.0, constraints: const BoxConstraints(maxWidth: 360, maxHeight: 348),
), asBarrier: true,
child: Opacity( margin: EdgeInsets.zero,
opacity: 0.6, offset: const Offset(10.0, 0),
child: DecoratedBox( popupBuilder: (BuildContext popoverContext) {
decoration: decoration, final settings = context.watch<CalendarBloc>().state.settings.fold(
child: card, () => null,
), (layoutSettings) => layoutSettings,
), );
), if (settings == null) {
child: AppFlowyPopover( return const SizedBox.shrink();
triggerActions: PopoverTriggerFlags.none, }
direction: PopoverDirection.rightWithCenterAligned, return CalendarEventEditor(
controller: _popoverController, fieldController: widget.fieldController,
constraints: const BoxConstraints(maxWidth: 360, maxHeight: 348), rowCache: widget.rowCache,
asBarrier: true, rowMeta: widget.event.event.rowMeta,
margin: EdgeInsets.zero, viewId: widget.viewId,
offset: const Offset(10.0, 0), layoutSettings: settings,
popupBuilder: (BuildContext popoverContext) { );
final settings = context.watch<CalendarBloc>().state.settings.fold( },
() => null, child: DecoratedBox(
(layoutSettings) => layoutSettings, decoration: decoration,
); child: card,
if (settings == null) {
return const SizedBox.shrink();
}
return CalendarEventEditor(
rowCache: widget.rowCache,
rowMeta: widget.event.event.rowMeta,
viewId: widget.viewId,
layoutSettings: settings,
fieldController: widget.fieldController,
);
},
child: DecoratedBox(
decoration: decoration,
child: card,
),
), ),
); );
if (widget.isDraggable) {
return Draggable<CalendarDayEvent>(
data: widget.event,
feedback: ConstrainedBox(
constraints: BoxConstraints(
maxWidth: widget.constraints.maxWidth - 8.0,
),
child: Opacity(
opacity: 0.6,
child: DecoratedBox(
decoration: decoration,
child: card,
),
),
),
child: card,
);
}
return card;
} }
RowCardRenderHook<CalendarDayEvent> _calendarEventCardRenderHook( RowCardRenderHook<CalendarDayEvent> _calendarEventCardRenderHook(

View File

@ -65,14 +65,15 @@ class CalendarEventEditor extends StatelessWidget {
} }
class EventEditorControls extends StatelessWidget { class EventEditorControls extends StatelessWidget {
final RowController rowController;
final FieldController fieldController;
const EventEditorControls({ const EventEditorControls({
super.key, super.key,
required this.rowController, required this.rowController,
required this.fieldController, required this.fieldController,
}); });
final RowController rowController;
final FieldController fieldController;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Padding( return Padding(

View File

@ -1,11 +1,13 @@
import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/presentation/widgets/show_flowy_mobile_bottom_sheet.dart';
import 'package:appflowy/plugins/database_view/application/database_controller.dart'; import 'package:appflowy/plugins/database_view/application/database_controller.dart';
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart'; import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
import 'package:appflowy/plugins/database_view/calendar/application/calendar_bloc.dart'; import 'package:appflowy/plugins/database_view/calendar/application/calendar_bloc.dart';
import 'package:appflowy/plugins/database_view/calendar/application/unschedule_event_bloc.dart'; import 'package:appflowy/plugins/database_view/calendar/application/unschedule_event_bloc.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/layout/sizes.dart'; import 'package:appflowy/plugins/database_view/grid/presentation/layout/sizes.dart';
import 'package:appflowy/plugins/database_view/tab_bar/tab_bar_view.dart'; import 'package:appflowy/plugins/database_view/tab_bar/tab_bar_view.dart';
import 'package:appflowy/util/platform_extension.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/calendar_entities.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/calendar_entities.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:appflowy_popover/appflowy_popover.dart';
@ -18,6 +20,7 @@ import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart'; import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import '../../application/row/row_cache.dart'; import '../../application/row/row_cache.dart';
import '../../application/row/row_controller.dart'; import '../../application/row/row_controller.dart';
@ -65,24 +68,25 @@ class CalendarPageTabBarBuilderImpl implements DatabaseTabBarItemBuilder {
} }
class CalendarPage extends StatefulWidget { class CalendarPage extends StatefulWidget {
final ViewPB view;
final DatabaseController databaseController;
final bool shrinkWrap;
const CalendarPage({ const CalendarPage({
super.key,
required this.view, required this.view,
required this.databaseController, required this.databaseController,
this.shrinkWrap = false, this.shrinkWrap = false,
super.key,
}); });
final ViewPB view;
final DatabaseController databaseController;
final bool shrinkWrap;
@override @override
State<CalendarPage> createState() => _CalendarPageState(); State<CalendarPage> createState() => _CalendarPageState();
} }
class _CalendarPageState extends State<CalendarPage> { class _CalendarPageState extends State<CalendarPage> {
final _eventController = EventController<CalendarDayEvent>(); final _eventController = EventController<CalendarDayEvent>();
late final CalendarBloc _calendarBloc;
GlobalKey<MonthViewState>? _calendarState; GlobalKey<MonthViewState>? _calendarState;
late CalendarBloc _calendarBloc;
@override @override
void initState() { void initState() {
@ -181,18 +185,21 @@ class _CalendarPageState extends State<CalendarPage> {
int firstDayOfWeek, int firstDayOfWeek,
) { ) {
return Padding( return Padding(
padding: CalendarSize.contentInsets, padding: PlatformExtension.isMobile
? CalendarSize.contentInsetsMobile
: CalendarSize.contentInsets,
child: LayoutBuilder( child: LayoutBuilder(
// must specify MonthView width for useAvailableVerticalSpace to work properly // must specify MonthView width for useAvailableVerticalSpace to work properly
builder: (context, constraints) => ScrollConfiguration( builder: (context, constraints) => ScrollConfiguration(
behavior: ScrollConfiguration.of(context).copyWith(scrollbars: false), behavior: ScrollConfiguration.of(context).copyWith(scrollbars: false),
child: MonthView( child: MonthView(
key: _calendarState, key: _calendarState,
// TODO(Xazin): Border Color on Mobile
controller: _eventController, controller: _eventController,
width: constraints.maxWidth, width: constraints.maxWidth,
cellAspectRatio: 0.6, cellAspectRatio: PlatformExtension.isMobile ? 1 : 0.6,
startDay: _weekdayFromInt(firstDayOfWeek), startDay: _weekdayFromInt(firstDayOfWeek),
borderColor: Theme.of(context).dividerColor, showBorder: false,
headerBuilder: _headerNavigatorBuilder, headerBuilder: _headerNavigatorBuilder,
weekDayBuilder: _headerWeekDayBuilder, weekDayBuilder: _headerWeekDayBuilder,
cellBuilder: _calendarDayBuilder, cellBuilder: _calendarDayBuilder,
@ -209,9 +216,39 @@ class _CalendarPageState extends State<CalendarPage> {
child: Row( child: Row(
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
FlowyText.medium( GestureDetector(
DateFormat('MMMM y', context.locale.toLanguageTag()) onTap: PlatformExtension.isMobile
.format(currentMonth), ? () => showFlowyMobileBottomSheet(
context,
title: LocaleKeys.calendar_quickJumpYear.tr(),
builder: (_) => SizedBox(
height: 200,
child: YearPicker(
firstDate: CalendarConstants.epochDate.withoutTime,
lastDate: CalendarConstants.maxDate.withoutTime,
selectedDate: currentMonth,
initialDate: currentMonth,
currentDate: DateTime.now(),
onChanged: (newDate) {
_calendarState?.currentState?.jumpToMonth(newDate);
context.pop();
},
),
),
)
: null,
child: Row(
children: [
FlowyText.medium(
DateFormat('MMMM y', context.locale.toLanguageTag())
.format(currentMonth),
),
if (PlatformExtension.isMobile) ...[
const HSpace(6),
const FlowySvg(FlowySvgs.arrow_down_s),
],
],
),
), ),
const Spacer(), const Spacer(),
FlowyIconButton( FlowyIconButton(
@ -253,7 +290,12 @@ class _CalendarPageState extends State<CalendarPage> {
Widget _headerWeekDayBuilder(day) { Widget _headerWeekDayBuilder(day) {
// incoming day starts from Monday, the symbols start from Sunday // incoming day starts from Monday, the symbols start from Sunday
final symbols = DateFormat.EEEE(context.locale.toLanguageTag()).dateSymbols; final symbols = DateFormat.EEEE(context.locale.toLanguageTag()).dateSymbols;
final weekDayString = symbols.WEEKDAYS[(day + 1) % 7]; String weekDayString = symbols.WEEKDAYS[(day + 1) % 7];
if (PlatformExtension.isMobile) {
weekDayString = weekDayString.substring(0, 3);
}
return Center( return Center(
child: Padding( child: Padding(
padding: CalendarSize.daysOfWeekInsets, padding: CalendarSize.daysOfWeekInsets,
@ -271,14 +313,14 @@ class _CalendarPageState extends State<CalendarPage> {
List<CalendarEventData<CalendarDayEvent>> calenderEvents, List<CalendarEventData<CalendarDayEvent>> calenderEvents,
isToday, isToday,
isInMonth, isInMonth,
position,
) { ) {
final events = calenderEvents.map((value) => value.event!).toList();
// Sort the events by timestamp. Because the database view is not // Sort the events by timestamp. Because the database view is not
// reserving the order of the events. Reserving the order of the rows/events // reserving the order of the events. Reserving the order of the rows/events
// is implemnted in the develop branch(WIP). Will be replaced with that. // is implemnted in the develop branch(WIP). Will be replaced with that.
events.sort( final events = calenderEvents.map((value) => value.event!).toList()
(a, b) => a.event.timestamp.compareTo(b.event.timestamp), ..sort((a, b) => a.event.timestamp.compareTo(b.event.timestamp));
);
return CalendarDayCard( return CalendarDayCard(
viewId: widget.view.id, viewId: widget.view.id,
isToday: isToday, isToday: isToday,
@ -286,11 +328,9 @@ class _CalendarPageState extends State<CalendarPage> {
events: events, events: events,
date: date, date: date,
rowCache: _calendarBloc.rowCache, rowCache: _calendarBloc.rowCache,
onCreateEvent: (date) { onCreateEvent: (date) =>
_calendarBloc.add( _calendarBloc.add(CalendarEvent.createEvent(date)),
CalendarEvent.createEvent(date), position: position,
);
},
); );
} }

View File

@ -13,6 +13,13 @@ class CalendarSize {
CalendarSize.headerContainerPadding, CalendarSize.headerContainerPadding,
); );
static EdgeInsets get contentInsetsMobile => EdgeInsets.fromLTRB(
GridSize.leadingHeaderPadding / 2,
CalendarSize.headerContainerPadding / 2,
GridSize.leadingHeaderPadding / 2,
CalendarSize.headerContainerPadding / 2,
);
static double get scrollBarSize => 8 * scale; static double get scrollBarSize => 8 * scale;
static double get navigatorButtonWidth => 20 * scale; static double get navigatorButtonWidth => 20 * scale;
static double get navigatorButtonHeight => 24 * scale; static double get navigatorButtonHeight => 24 * scale;

View File

@ -7,14 +7,15 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
class MobileGridSettingButton extends StatelessWidget { class MobileGridSettingButton extends StatelessWidget {
final DatabaseController controller;
final ToggleExtensionNotifier toggleExtension;
const MobileGridSettingButton({ const MobileGridSettingButton({
super.key,
required this.controller, required this.controller,
required this.toggleExtension, required this.toggleExtension,
super.key,
}); });
final DatabaseController controller;
final ToggleExtensionNotifier toggleExtension;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MultiBlocProvider( return MultiBlocProvider(
@ -49,10 +50,18 @@ class MobileGridSettingButton extends StatelessWidget {
if (isLoading) { if (isLoading) {
return const SizedBox.shrink(); return const SizedBox.shrink();
} }
return IconButton(
onPressed: () {}, return SizedBox(
icon: const FlowySvg( height: 24,
FlowySvgs.m_setting_m, width: 24,
child: IconButton(
padding: EdgeInsets.zero,
// TODO(Xazin): Database Settings
onPressed: () {},
icon: const FlowySvg(
FlowySvgs.m_setting_m,
size: Size.square(24),
),
), ),
); );
}, },

View File

@ -24,7 +24,7 @@ class MobileTabBarHeader extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Padding( Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0), padding: const EdgeInsets.symmetric(vertical: 14),
child: Row( child: Row(
children: [ children: [
Expanded( Expanded(

View File

@ -4,11 +4,12 @@ import 'package:appflowy/plugins/database_view/tab_bar/mobile/mobile_tab_bar_hea
import 'package:appflowy/plugins/database_view/widgets/share_button.dart'; import 'package:appflowy/plugins/database_view/widgets/share_button.dart';
import 'package:appflowy/plugins/util.dart'; import 'package:appflowy/plugins/util.dart';
import 'package:appflowy/startup/plugin/plugin.dart'; import 'package:appflowy/startup/plugin/plugin.dart';
import 'package:appflowy/util/platform_extension.dart';
import 'package:appflowy/workspace/presentation/home/home_stack.dart'; import 'package:appflowy/workspace/presentation/home/home_stack.dart';
import 'package:appflowy/workspace/presentation/widgets/tab_bar_item.dart'; import 'package:appflowy/workspace/presentation/widgets/tab_bar_item.dart';
import 'package:appflowy/workspace/presentation/widgets/view_title_bar.dart'; import 'package:appflowy/workspace/presentation/widgets/view_title_bar.dart';
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:flowy_infra_ui/widget/spacing.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
@ -85,6 +86,7 @@ class _DatabaseTabBarViewState extends State<DatabaseTabBarView> {
], ],
child: Column( child: Column(
children: [ children: [
if (PlatformExtension.isMobile) const VSpace(12),
BlocBuilder<DatabaseTabBarBloc, DatabaseTabBarState>( BlocBuilder<DatabaseTabBarBloc, DatabaseTabBarState>(
builder: (context, state) { builder: (context, state) {
return ValueListenableBuilder<bool>( return ValueListenableBuilder<bool>(
@ -96,11 +98,14 @@ class _DatabaseTabBarViewState extends State<DatabaseTabBarView> {
if (value) { if (value) {
return const SizedBox.shrink(); return const SizedBox.shrink();
} }
return Padding( return Padding(
padding: const EdgeInsets.symmetric(horizontal: 40), padding: EdgeInsets.symmetric(
child: PlatformExtension.isDesktop horizontal: PlatformExtension.isMobile ? 20 : 40,
? const TabBarHeader() ),
: const MobileTabBarHeader(), child: PlatformExtension.isMobile
? const MobileTabBarHeader()
: const TabBarHeader(),
); );
}, },
); );

View File

@ -95,11 +95,11 @@ class _DatabaseSettingListPopoverState
return ListView.separated( return ListView.separated(
shrinkWrap: true, shrinkWrap: true,
padding: EdgeInsets.zero,
controller: ScrollController(), controller: ScrollController(),
itemCount: cells.length, itemCount: cells.length,
separatorBuilder: (context, index) { separatorBuilder: (context, index) =>
return VSpace(GridSize.typeOptionSeparatorHeight); VSpace(GridSize.typeOptionSeparatorHeight),
},
physics: StyledScrollPhysics(), physics: StyledScrollPhysics(),
itemBuilder: (BuildContext context, int index) { itemBuilder: (BuildContext context, int index) {
return cells[index]; return cells[index];

View File

@ -3,6 +3,7 @@ import 'package:appflowy/mobile/presentation/database/card/card_detail/mobile_cr
import 'package:appflowy/mobile/presentation/database/card/card_property_edit/card_property_edit_screen.dart'; import 'package:appflowy/mobile/presentation/database/card/card_property_edit/card_property_edit_screen.dart';
import 'package:appflowy/mobile/presentation/database/card/row/cells/cells.dart'; import 'package:appflowy/mobile/presentation/database/card/row/cells/cells.dart';
import 'package:appflowy/mobile/presentation/database/mobile_board_screen.dart'; import 'package:appflowy/mobile/presentation/database/mobile_board_screen.dart';
import 'package:appflowy/mobile/presentation/database/mobile_calendar_events_screen.dart';
import 'package:appflowy/mobile/presentation/database/mobile_calendar_screen.dart'; import 'package:appflowy/mobile/presentation/database/mobile_calendar_screen.dart';
import 'package:appflowy/mobile/presentation/database/mobile_grid_screen.dart'; import 'package:appflowy/mobile/presentation/database/mobile_grid_screen.dart';
import 'package:appflowy/mobile/presentation/favorite/mobile_favorite_page.dart'; import 'package:appflowy/mobile/presentation/favorite/mobile_favorite_page.dart';
@ -77,6 +78,10 @@ GoRouter generateRouter(Widget child) {
_mobileCodeLanguagePickerPageRoute(), _mobileCodeLanguagePickerPageRoute(),
_mobileLanguagePickerPageRoute(), _mobileLanguagePickerPageRoute(),
_mobileFontPickerPageRoute(), _mobileFontPickerPageRoute(),
// calendar related
_mobileCalendarEventsPageRoute(),
_mobileBlockSettingsPageRoute(), _mobileBlockSettingsPageRoute(),
], ],
@ -331,6 +336,25 @@ GoRoute _mobileFontPickerPageRoute() {
); );
} }
GoRoute _mobileCalendarEventsPageRoute() {
return GoRoute(
path: MobileCalendarEventsScreen.routeName,
pageBuilder: (context, state) {
final args = state.extra as Map<String, dynamic>;
return MaterialPage(
child: MobileCalendarEventsScreen(
calendarBloc: args[MobileCalendarEventsScreen.calendarBlocKey],
date: args[MobileCalendarEventsScreen.calendarDateKey],
events: args[MobileCalendarEventsScreen.calendarEventsKey],
rowCache: args[MobileCalendarEventsScreen.calendarRowCacheKey],
viewId: args[MobileCalendarEventsScreen.calendarViewIdKey],
),
);
},
);
}
GoRoute _desktopHomeScreenRoute() { GoRoute _desktopHomeScreenRoute() {
return GoRoute( return GoRoute(
path: DesktopHomeScreen.routeName, path: DesktopHomeScreen.routeName,

View File

@ -8,6 +8,7 @@ import 'package:appflowy/user/application/auth/auth_service.dart';
import 'package:appflowy/user/application/historical_user_bloc.dart'; import 'package:appflowy/user/application/historical_user_bloc.dart';
import 'package:appflowy/user/presentation/router.dart'; import 'package:appflowy/user/presentation/router.dart';
import 'package:appflowy/user/presentation/widgets/widgets.dart'; import 'package:appflowy/user/presentation/widgets/widgets.dart';
import 'package:appflowy/util/platform_extension.dart';
import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart'; import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/settings_language_view.dart'; import 'package:appflowy/workspace/presentation/settings/widgets/settings_language_view.dart';
import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:appflowy_popover/appflowy_popover.dart';
@ -53,7 +54,7 @@ class _SkipLogInScreenState extends State<SkipLogInScreen> {
const Spacer(), const Spacer(),
FlowyLogoTitle( FlowyLogoTitle(
title: LocaleKeys.welcomeText.tr(), title: LocaleKeys.welcomeText.tr(),
logoSize: const Size.square(40), logoSize: Size.square(PlatformExtension.isMobile ? 80 : 40),
), ),
const VSpace(32), const VSpace(32),
GoButton( GoButton(
@ -113,14 +114,14 @@ class SkipLoginPageFooter extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
// The placeholderWidth should be greater than the longest width of the LanguageSelectorOnWelcomePage // The placeholderWidth should be greater than the longest width of the LanguageSelectorOnWelcomePage
const double placeholderWidth = 180; const double placeholderWidth = 180;
return const Padding( return Padding(
padding: EdgeInsets.symmetric(horizontal: 16), padding: const EdgeInsets.symmetric(horizontal: 16),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
HSpace(placeholderWidth), if (!PlatformExtension.isMobile) const HSpace(placeholderWidth),
Expanded(child: SubscribeButtons()), const Expanded(child: SubscribeButtons()),
SizedBox( const SizedBox(
width: placeholderWidth, width: placeholderWidth,
height: 28, height: 28,
child: Row( child: Row(

View File

@ -5,14 +5,15 @@ import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart'; import 'package:google_fonts/google_fonts.dart';
class FlowyLogoTitle extends StatelessWidget { class FlowyLogoTitle extends StatelessWidget {
final String title;
final Size logoSize;
const FlowyLogoTitle({ const FlowyLogoTitle({
super.key, super.key,
required this.title, required this.title,
this.logoSize = const Size.square(40), this.logoSize = const Size.square(40),
}); });
final String title;
final Size logoSize;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return SizedBox( return SizedBox(

View File

@ -205,11 +205,12 @@ packages:
calendar_view: calendar_view:
dependency: "direct main" dependency: "direct main"
description: description:
name: calendar_view path: "."
sha256: "58a8b851ac0a2d62770fd06ad30f06683bd40848a5dd1a1eca332f5a6064bd82" ref: "6fe0c98"
url: "https://pub.dev" resolved-ref: "6fe0c989289b077569858d5472f3f7ec05b7746f"
source: hosted url: "https://github.com/Xazin/flutter_calendar_view"
version: "1.0.3" source: git
version: "1.0.5"
characters: characters:
dependency: transitive dependency: transitive
description: description:

View File

@ -47,7 +47,7 @@ dependencies:
appflowy_editor: appflowy_editor:
git: git:
url: https://github.com/AppFlowy-IO/appflowy-editor.git url: https://github.com/AppFlowy-IO/appflowy-editor.git
ref: '31acaff' ref: "31acaff"
appflowy_popover: appflowy_popover:
path: packages/appflowy_popover path: packages/appflowy_popover
@ -94,7 +94,10 @@ dependencies:
shared_preferences: ^2.1.1 shared_preferences: ^2.1.1
google_fonts: ^4.0.5 google_fonts: ^4.0.5
percent_indicator: ^4.2.3 percent_indicator: ^4.2.3
calendar_view: ^1.0.3 calendar_view:
git:
url: https://github.com/Xazin/flutter_calendar_view
ref: "6fe0c98"
window_manager: ^0.3.4 window_manager: ^0.3.4
http: ^1.0.0 http: ^1.0.0
path: ^1.8.3 path: ^1.8.3

View File

@ -854,7 +854,8 @@
"clickToAdd": "Click to add to the calendar", "clickToAdd": "Click to add to the calendar",
"name": "Calendar layout" "name": "Calendar layout"
}, },
"referencedCalendarPrefix": "View of" "referencedCalendarPrefix": "View of",
"quickJumpYear": "Jump to"
}, },
"errorDialog": { "errorDialog": {
"title": "AppFlowy Error", "title": "AppFlowy Error",