447 lines
17 KiB
Dart
Executable File

import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:intl/intl.dart';
import 'package:star_lock/tools/eventBusEventManage.dart';
import 'package:star_lock/translations/current_locale_tool.dart';
import '../../../app_settings/app_settings.dart';
import 'controller.dart';
import 'datetime_util.dart';
part 'date_box.dart';
part 'handlebar.dart';
part 'header.dart';
part 'month_view.dart';
part 'month_view_bean.dart';
part 'week_days.dart';
part 'week_view.dart';
/// Advanced Calendar widget.
class AdvancedCalendar extends StatefulWidget {
const AdvancedCalendar({
Key? key,
this.controller,
this.startWeekDay,
this.events,
this.weekLineHeight = 32.0,
this.preloadMonthViewAmount = 24, //预加载的月视图
this.preloadWeekViewAmount = 21,
this.weeksInMonthViewAmount = 6,
this.todayStyle,
this.headerStyle,
this.onHorizontalDrag,
this.innerDot = false,
this.keepLineSize = false,
this.calendarTextStyle,
}) : assert(
keepLineSize && innerDot ||
innerDot && !keepLineSize ||
!innerDot && !keepLineSize,
'keepLineSize should be used only when innerDot is true',
),
super(key: key);
/// Calendar selection date controller.
final AdvancedCalendarController? controller;
/// Executes on horizontal calendar swipe. Allows to load additional dates.
final Function(DateTime)? onHorizontalDrag;
/// Height of week line.
final double weekLineHeight;
/// Amount of months in month view to preload.
final int preloadMonthViewAmount;
/// Amount of weeks in week view to preload.
final int preloadWeekViewAmount;
/// Weeks lines amount in month view.
final int weeksInMonthViewAmount;
/// List of points for the week and month
final List<DateTime>? events;
/// The first day of the week starts[0-6]
final int? startWeekDay;
/// Style of headers date
final TextStyle? headerStyle;
/// Style of Today button
final TextStyle? todayStyle;
/// Show DateBox event in container.
final bool innerDot;
/// Keeps consistent line size for dates
/// Can't be used without innerDot
final bool keepLineSize;
/// Text style for dates in calendar
final TextStyle? calendarTextStyle;
@override
_AdvancedCalendarState createState() => _AdvancedCalendarState();
}
class _AdvancedCalendarState extends State<AdvancedCalendar>
with SingleTickerProviderStateMixin {
late ValueNotifier<int> _monthViewCurrentPage;
late AnimationController _animationController;
late AdvancedCalendarController _controller;
late double _animationValue;
late List<ViewRange> _monthRangeList;
late List<List<DateTime>> _weekRangeList;
PageController? _monthPageController;
PageController? _weekPageController;
Offset? _captureOffset;
DateTime? _todayDate;
List<String>? _weekNames;
@override
void initState() {
super.initState();
final int monthPageIndex = widget.preloadMonthViewAmount ~/ 2;
_monthViewCurrentPage = ValueNotifier(monthPageIndex);
_monthPageController = PageController(
initialPage: monthPageIndex,
);
final int weekPageIndex = widget.preloadWeekViewAmount ~/ 2;
_weekPageController = PageController(
initialPage: weekPageIndex,
);
_animationController = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 300),
value: 0,
);
_animationValue = _animationController.value;
_controller = widget.controller ?? AdvancedCalendarController.today();
_todayDate = _controller.value;
_monthRangeList = List.generate(
widget.preloadMonthViewAmount,
(int index) => ViewRange.generateDates(
_todayDate!,
_todayDate!.month + (index - _monthPageController!.initialPage),
widget.weeksInMonthViewAmount,
startWeekDay: widget.startWeekDay,
),
);
_weekRangeList = _controller.value.generateWeeks(
widget.preloadWeekViewAmount,
startWeekDay: widget.startWeekDay,
);
_controller.addListener(() {
_weekRangeList = _controller.value.generateWeeks(
widget.preloadWeekViewAmount,
startWeekDay: widget.startWeekDay,
);
_weekPageController!.jumpToPage(widget.preloadWeekViewAmount ~/ 2);
});
if (widget.startWeekDay != null && widget.startWeekDay! < 7) {
final DateTime time = _controller.value.subtract(
Duration(days: _controller.value.weekday - widget.startWeekDay!),
);
final List<DateTime> list = List<DateTime>.generate(
8,
(int index) => time.add(Duration(days: index * 1)),
).toList();
// _weekNames = List<String>.generate(7, (index) {
// return DateFormat("EEEE").format(list[index]).split('').first;
// }
// );
final currentLocaleString = CurrentLocaleTool.getCurrentLocaleString();
if (currentLocaleString == 'zh_CN') {
//by DaisyWu 修改源文件为中文环境下 周一到周日
_weekNames = List<String>.generate(7, (int index) {
String fullWeekName =
DateFormat.E('zh_CN').format(list[index]); // 获取星期的完整形式
return fullWeekName
.substring(fullWeekName.length - 1); // 获取最后一个字符,即星期的简称
});
}
}
}
@override
Widget build(BuildContext context) {
final ThemeData theme = Theme.of(context);
return Material(
color: Colors.transparent,
child: DefaultTextStyle.merge(
style: theme.textTheme.bodyMedium,
child: GestureDetector(
onVerticalDragStart: (DragStartDetails details) {
_captureOffset = details.globalPosition;
},
onVerticalDragUpdate: (DragUpdateDetails details) {
final Offset moveOffset = details.globalPosition;
final double diffY = moveOffset.dy - _captureOffset!.dy;
_animationController.value =
_animationValue + diffY / (widget.weekLineHeight * 5);
},
onVerticalDragEnd: (DragEndDetails details) => _handleFinishDrag(),
onVerticalDragCancel: _handleFinishDrag,
child: Container(
color: Colors.transparent,
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
ValueListenableBuilder<int>(
valueListenable: _monthViewCurrentPage,
builder: (_, int value, __) {
return Header(
monthDate:
_monthRangeList[_monthViewCurrentPage.value].firstDay,
onPressed: _handleTodayPressed,
dateStyle: widget.headerStyle,
todayStyle: widget.todayStyle,
);
},
),
WeekDays(
style: theme.textTheme.bodyLarge?.copyWith(
color: Colors.black,
),
keepLineSize: widget.keepLineSize,
weekNames: _weekNames != null
? _weekNames!
: <String>[
'简写周日'.tr,
'简写周一'.tr,
'简写周二'.tr,
'简写周三'.tr,
'简写周四'.tr,
'简写周五'.tr,
'简写周六'.tr
],
),
AnimatedBuilder(
animation: _animationController,
builder: (_, __) {
final double height = Tween<double>(
begin: widget.weekLineHeight,
end:
widget.weekLineHeight * widget.weeksInMonthViewAmount,
).transform(_animationController.value);
return SizedBox(
height: height,
child: ValueListenableBuilder<DateTime>(
valueListenable: _controller,
builder: (_, selectedDate, __) {
// AppLog.log('****selectedDate: $selectedDate');
return Stack(
alignment: Alignment.center,
children: <Widget>[
IgnorePointer(
ignoring: _animationController.value == 0.0,
child: Opacity(
opacity: Tween<double>(
begin: 0.0,
end: 1.0,
).evaluate(_animationController),
child: PageView.builder(
onPageChanged: (int pageIndex) {
if (widget.onHorizontalDrag != null) {
widget.onHorizontalDrag!(
_monthRangeList[pageIndex].firstDay,
);
}
_monthViewCurrentPage.value = pageIndex;
AppLog.log('调用onPageChanged');
},
controller: _monthPageController,
physics: _animationController.value == 1.0
? const AlwaysScrollableScrollPhysics()
: const NeverScrollableScrollPhysics(),
itemCount: _monthRangeList.length,
itemBuilder: (_, int pageIndex) {
return MonthView(
innerDot: widget.innerDot,
monthView: _monthRangeList[pageIndex],
todayDate: _todayDate,
selectedDate: selectedDate,
weekLineHeight: widget.weekLineHeight,
weeksAmount:
widget.weeksInMonthViewAmount,
onChanged: _handleDateChanged,
events: widget.events,
keepLineSize: widget.keepLineSize,
textStyle: widget.calendarTextStyle,
);
},
),
),
),
ValueListenableBuilder<int>(
valueListenable: _monthViewCurrentPage,
builder: (_, int pageIndex, __) {
final int index = selectedDate.findWeekIndex(
_monthRangeList[_monthViewCurrentPage.value]
.dates,
);
final double offset = index /
(widget.weeksInMonthViewAmount - 1) *
2 -
1.0;
return Align(
alignment: Alignment(0.0, offset),
child: IgnorePointer(
ignoring:
_animationController.value == 1.0,
child: Opacity(
opacity: Tween<double>(
begin: 1.0,
end: 0.0,
).evaluate(_animationController),
child: SizedBox(
height: widget.weekLineHeight,
child: PageView.builder(
onPageChanged: (int indexPage) {
final int pageIndex =
_monthRangeList.indexWhere(
(ViewRange index) =>
index.firstDay.month ==
_weekRangeList[indexPage]
.first
.month,
);
if (widget.onHorizontalDrag !=
null) {
widget.onHorizontalDrag!(
_monthRangeList[pageIndex]
.firstDay,
);
}
_monthViewCurrentPage.value =
pageIndex;
},
controller: _weekPageController,
itemCount: _weekRangeList.length,
physics: _closeMonthScroll(),
itemBuilder: (BuildContext context,
int index) {
return WeekView(
innerDot: widget.innerDot,
dates: _weekRangeList[index],
selectedDate: selectedDate,
lineHeight:
widget.weekLineHeight,
onChanged:
_handleWeekDateChanged,
events: widget.events,
keepLineSize:
widget.keepLineSize,
textStyle:
widget.calendarTextStyle,
);
},
),
),
),
),
);
},
),
],
);
},
),
);
},
),
// HandleBar(
// onPressed: () async {
// await _animationController.forward();
// _animationValue = 1.0;
// },
// ),
],
),
),
),
),
);
}
@override
void dispose() {
_animationController.dispose();
_monthPageController!.dispose();
_monthViewCurrentPage.dispose();
if (widget.controller == null) {
_controller.dispose();
}
super.dispose();
}
void _handleWeekDateChanged(DateTime date) {
_handleDateChanged(date);
_monthViewCurrentPage.value = _monthRangeList.lastIndexWhere(
(ViewRange monthRange) => monthRange.dates.contains(date));
}
void _handleDateChanged(DateTime date) {
_controller.value = date;
AppLog.log('点击日期了');
eventBus.fire(DoorLockLogListRefreshUI(date));
}
void _handleFinishDrag() async {
_captureOffset = null;
if (_animationController.value > 0.5) {
await _animationController.forward();
_animationValue = 1.0;
} else {
await _animationController.reverse();
_animationValue = 0.0;
}
}
void _handleTodayPressed() {
_controller.value = DateTime.now().toZeroTime();
_monthPageController!.jumpToPage(widget.preloadMonthViewAmount ~/ 2);
_weekPageController!.jumpToPage(widget.preloadWeekViewAmount ~/ 2);
}
ScrollPhysics _closeMonthScroll() {
if (_monthViewCurrentPage.value ==
(widget.preloadMonthViewAmount ~/ 2) + 3 ||
_monthViewCurrentPage.value ==
(widget.preloadMonthViewAmount ~/ 2) - 3) {
return const NeverScrollableScrollPhysics();
} else {
return const AlwaysScrollableScrollPhysics();
}
}
}