1,新增门锁日志日历组件源文件
2,优化门锁日志UI参考米家
This commit is contained in:
parent
88cc26a672
commit
c3d55d225b
@ -271,9 +271,9 @@ class DoorLockLogLogic extends BaseGetXController {
|
|||||||
super.onClose();
|
super.onClose();
|
||||||
|
|
||||||
// 获取是否是演示模式 演示模式不获取接口
|
// 获取是否是演示模式 演示模式不获取接口
|
||||||
var isDemoMode = await Storage.getBool(ifIsDemoModeOrNot);
|
// var isDemoMode = await Storage.getBool(ifIsDemoModeOrNot);
|
||||||
if (isDemoMode == false) {
|
// if (isDemoMode == false) {
|
||||||
_replySubscription.cancel();
|
// _replySubscription.cancel();
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_advanced_calendar/flutter_advanced_calendar.dart';
|
|
||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:star_lock/main/lockDetail/doorLockLog/doorLockLog_logic.dart';
|
import 'package:star_lock/main/lockDetail/doorLockLog/doorLockLog_logic.dart';
|
||||||
|
import 'package:star_lock/tools/advancedCalendar/src/widget.dart';
|
||||||
import 'package:timelines/timelines.dart';
|
import 'package:timelines/timelines.dart';
|
||||||
|
|
||||||
import '../../../app_settings/app_colors.dart';
|
import '../../../app_settings/app_colors.dart';
|
||||||
@ -64,10 +64,17 @@ class _DoorLockLogPageState extends State<DoorLockLogPage> {
|
|||||||
endIndent: 30.w,
|
endIndent: 30.w,
|
||||||
),
|
),
|
||||||
eventDropDownWidget(),
|
eventDropDownWidget(),
|
||||||
SizedBox(
|
Expanded(child: timeLineView())
|
||||||
height: 20.h,
|
// Expanded(
|
||||||
),
|
// // 添加 Expanded 来让 ListView 占据剩余的空间
|
||||||
Expanded(child: timeLineView()),
|
// child: ListView.builder(
|
||||||
|
// itemBuilder: (context, index) {
|
||||||
|
// // 返回你想要在 ListView 中显示的小部件
|
||||||
|
// return timeLineView();
|
||||||
|
// },
|
||||||
|
// itemCount: 5, // 替换成你的列表项数量
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -123,7 +130,7 @@ class _DoorLockLogPageState extends State<DoorLockLogPage> {
|
|||||||
value: state.dropdownValue.value,
|
value: state.dropdownValue.value,
|
||||||
icon: const Icon(Icons.arrow_drop_down),
|
icon: const Icon(Icons.arrow_drop_down),
|
||||||
iconSize: 40,
|
iconSize: 40,
|
||||||
// elevation: 12,
|
elevation: 12,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 26.sp,
|
fontSize: 26.sp,
|
||||||
color: Colors.black,
|
color: Colors.black,
|
||||||
@ -149,7 +156,7 @@ class _DoorLockLogPageState extends State<DoorLockLogPage> {
|
|||||||
//时间轴组件
|
//时间轴组件
|
||||||
Widget timeLineView() {
|
Widget timeLineView() {
|
||||||
return Container(
|
return Container(
|
||||||
margin: EdgeInsets.only(left: 20.w, right: 20.w, bottom: 20.h),
|
margin: EdgeInsets.only(left: 20.w, right: 20.w, bottom: 20.h, top: 20.h),
|
||||||
//给contain设置一个10像素的圆角
|
//给contain设置一个10像素的圆角
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.white, borderRadius: BorderRadius.circular(16.w)),
|
color: Colors.white, borderRadius: BorderRadius.circular(16.w)),
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import 'package:flutter_advanced_calendar/flutter_advanced_calendar.dart';
|
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
import 'package:star_lock/tools/advancedCalendar/src/controller.dart';
|
||||||
|
|
||||||
import '../../lockMian/entity/lockListInfo_entity.dart';
|
import '../../lockMian/entity/lockListInfo_entity.dart';
|
||||||
import '../electronicKey/electronicKeyDetail/keyOperationRecordEntity.dart';
|
import '../electronicKey/electronicKeyDetail/keyOperationRecordEntity.dart';
|
||||||
|
|||||||
@ -0,0 +1,4 @@
|
|||||||
|
library flutter_advanced_calendar;
|
||||||
|
|
||||||
|
export 'src/controller.dart';
|
||||||
|
export 'src/widget.dart';
|
||||||
15
star_lock/lib/tools/advancedCalendar/src/controller.dart
Normal file
15
star_lock/lib/tools/advancedCalendar/src/controller.dart
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
|
import 'datetime_util.dart';
|
||||||
|
|
||||||
|
/// Advanced Calendar controller that manage selection date state.
|
||||||
|
class AdvancedCalendarController extends ValueNotifier<DateTime> {
|
||||||
|
/// Generates controller with custom date selected.
|
||||||
|
AdvancedCalendarController(DateTime value) : super(value);
|
||||||
|
|
||||||
|
/// Generates controller with today date selected.
|
||||||
|
AdvancedCalendarController.today() : this(DateTime.now().toZeroTime());
|
||||||
|
|
||||||
|
@override
|
||||||
|
set value(DateTime newValue) => super.value = newValue.toZeroTime();
|
||||||
|
}
|
||||||
94
star_lock/lib/tools/advancedCalendar/src/date_box.dart
Normal file
94
star_lock/lib/tools/advancedCalendar/src/date_box.dart
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
part of 'widget.dart';
|
||||||
|
|
||||||
|
/// Unit of calendar.
|
||||||
|
class DateBox extends StatelessWidget {
|
||||||
|
const DateBox({
|
||||||
|
Key? key,
|
||||||
|
required this.child,
|
||||||
|
this.color,
|
||||||
|
this.width = 24.0,
|
||||||
|
this.height = 24.0,
|
||||||
|
this.borderRadius = const BorderRadius.all(Radius.circular(8.0)),
|
||||||
|
this.onPressed,
|
||||||
|
this.showDot = false,
|
||||||
|
this.isSelected = false,
|
||||||
|
this.isToday = false,
|
||||||
|
this.hasEvent = false,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
/// Child widget.
|
||||||
|
final Widget child;
|
||||||
|
|
||||||
|
/// Background color.
|
||||||
|
final Color? color;
|
||||||
|
|
||||||
|
/// Widget width.
|
||||||
|
final double width;
|
||||||
|
|
||||||
|
/// Widget height.
|
||||||
|
final double height;
|
||||||
|
|
||||||
|
/// Container border radius.
|
||||||
|
final BorderRadius borderRadius;
|
||||||
|
|
||||||
|
/// Pressed callback function.
|
||||||
|
final VoidCallback? onPressed;
|
||||||
|
|
||||||
|
/// Show DateBox event in container.
|
||||||
|
final bool showDot;
|
||||||
|
|
||||||
|
/// DateBox is today.
|
||||||
|
final bool isToday;
|
||||||
|
|
||||||
|
/// DateBox selection.
|
||||||
|
final bool isSelected;
|
||||||
|
|
||||||
|
/// Show event in DateBox.
|
||||||
|
final bool hasEvent;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
return UnconstrainedBox(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: InkResponse(
|
||||||
|
onTap: onPressed,
|
||||||
|
radius: 16.0,
|
||||||
|
borderRadius: borderRadius,
|
||||||
|
highlightShape: BoxShape.rectangle,
|
||||||
|
child: AnimatedContainer(
|
||||||
|
duration: const Duration(milliseconds: 150),
|
||||||
|
width: width,
|
||||||
|
height: height,
|
||||||
|
alignment: Alignment.center,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: isSelected
|
||||||
|
? theme.primaryColor
|
||||||
|
: isToday
|
||||||
|
? theme.highlightColor
|
||||||
|
: null,
|
||||||
|
borderRadius: borderRadius,
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
child,
|
||||||
|
if (showDot && hasEvent)
|
||||||
|
Container(
|
||||||
|
margin: const EdgeInsets.all(2.0),
|
||||||
|
height: 4,
|
||||||
|
width: 4,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
color: isSelected
|
||||||
|
? theme.colorScheme.onPrimary
|
||||||
|
: theme.colorScheme.secondary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
56
star_lock/lib/tools/advancedCalendar/src/datetime_util.dart
Normal file
56
star_lock/lib/tools/advancedCalendar/src/datetime_util.dart
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
extension DateTimeUtil on DateTime {
|
||||||
|
/// Generate a new DateTime instance with a zero time.
|
||||||
|
DateTime toZeroTime() => DateTime.utc(year, month, day, 12);
|
||||||
|
|
||||||
|
int findWeekIndex(List<DateTime> dates) {
|
||||||
|
return dates.indexWhere(isAtSameMomentAs) ~/ 7;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calculates first week date (Sunday) from this date.
|
||||||
|
DateTime firstDayOfWeek({int? startWeekDay}) {
|
||||||
|
final utcDate = DateTime.utc(year, month, day, 12);
|
||||||
|
if (startWeekDay != null && startWeekDay < 7) {
|
||||||
|
return utcDate.subtract(Duration(days: utcDate.weekday - startWeekDay));
|
||||||
|
}
|
||||||
|
return utcDate.subtract(Duration(days: utcDate.weekday % 7));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generates 7 dates according to this date.
|
||||||
|
/// (Supposed that this date is result of [firstDayOfWeek])
|
||||||
|
List<DateTime> weekDates() {
|
||||||
|
return List.generate(
|
||||||
|
7,
|
||||||
|
(index) => add(Duration(days: index)),
|
||||||
|
growable: false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generates list of list with [DateTime]
|
||||||
|
/// according to [date] and [weeksAmount].
|
||||||
|
/// gives the beginning of the day of the week [startWeekDay]
|
||||||
|
List<List<DateTime>> generateWeeks(int weeksAmount, {int? startWeekDay}) {
|
||||||
|
final firstViewDate = firstDayOfWeek(startWeekDay: startWeekDay).subtract(
|
||||||
|
Duration(
|
||||||
|
days: (weeksAmount ~/ 2) * 7,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return List.generate(
|
||||||
|
weeksAmount,
|
||||||
|
(weekIndex) {
|
||||||
|
final firstDateOfNextWeek = firstViewDate.add(
|
||||||
|
Duration(
|
||||||
|
days: weekIndex * 7,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return firstDateOfNextWeek.weekDates();
|
||||||
|
},
|
||||||
|
growable: false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isSameDate(DateTime other) {
|
||||||
|
return year == other.year && month == other.month && day == other.day;
|
||||||
|
}
|
||||||
|
}
|
||||||
39
star_lock/lib/tools/advancedCalendar/src/handlebar.dart
Normal file
39
star_lock/lib/tools/advancedCalendar/src/handlebar.dart
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
part of 'widget.dart';
|
||||||
|
|
||||||
|
class HandleBar extends StatelessWidget {
|
||||||
|
const HandleBar({
|
||||||
|
Key? key,
|
||||||
|
this.decoration,
|
||||||
|
this.margin = const EdgeInsets.only(
|
||||||
|
top: 8.0,
|
||||||
|
),
|
||||||
|
this.onPressed,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
final BoxDecoration? decoration;
|
||||||
|
final EdgeInsetsGeometry margin;
|
||||||
|
final VoidCallback? onPressed;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: onPressed,
|
||||||
|
behavior: HitTestBehavior.translucent,
|
||||||
|
child: Container(
|
||||||
|
margin: margin,
|
||||||
|
alignment: Alignment.center,
|
||||||
|
child: FractionallySizedBox(
|
||||||
|
widthFactor: 0.1,
|
||||||
|
child: Container(
|
||||||
|
height: 4.0,
|
||||||
|
decoration: decoration ??
|
||||||
|
BoxDecoration(
|
||||||
|
color: Theme.of(context).dividerColor,
|
||||||
|
borderRadius: BorderRadius.circular(2.0),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
63
star_lock/lib/tools/advancedCalendar/src/header.dart
Normal file
63
star_lock/lib/tools/advancedCalendar/src/header.dart
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
part of 'widget.dart';
|
||||||
|
|
||||||
|
class Header extends StatelessWidget {
|
||||||
|
const Header({
|
||||||
|
Key? key,
|
||||||
|
required this.monthDate,
|
||||||
|
this.margin = const EdgeInsets.only(
|
||||||
|
left: 16.0,
|
||||||
|
right: 8.0,
|
||||||
|
top: 4.0,
|
||||||
|
bottom: 4.0,
|
||||||
|
),
|
||||||
|
this.onPressed,
|
||||||
|
this.dateStyle,
|
||||||
|
this.todayStyle,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
static final _dateFormatter = DateFormat().add_yMMMM();
|
||||||
|
// static final _dateFormatter = DateFormat('MM月dd日', 'zh_CN');
|
||||||
|
final DateTime monthDate;
|
||||||
|
final EdgeInsetsGeometry margin;
|
||||||
|
final VoidCallback? onPressed;
|
||||||
|
final TextStyle? dateStyle;
|
||||||
|
final TextStyle? todayStyle;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
// initializeDateFormatting('zh_CN', null);
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
margin: margin,
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
// Text(
|
||||||
|
// _dateFormatter.format(monthDate),
|
||||||
|
// style: dateStyle ?? theme.textTheme.titleMedium,
|
||||||
|
// ),
|
||||||
|
InkWell(
|
||||||
|
onTap: onPressed,
|
||||||
|
borderRadius: const BorderRadius.all(
|
||||||
|
Radius.circular(4.0),
|
||||||
|
),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 8.0,
|
||||||
|
vertical: 4.0,
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
'今天',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Colors.black,
|
||||||
|
fontSize: 22.sp,
|
||||||
|
fontWeight: FontWeight.w600),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
62
star_lock/lib/tools/advancedCalendar/src/month_view.dart
Normal file
62
star_lock/lib/tools/advancedCalendar/src/month_view.dart
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
part of 'widget.dart';
|
||||||
|
|
||||||
|
class MonthView extends StatelessWidget {
|
||||||
|
const MonthView({
|
||||||
|
Key? key,
|
||||||
|
required this.monthView,
|
||||||
|
required this.todayDate,
|
||||||
|
required this.selectedDate,
|
||||||
|
required this.weekLineHeight,
|
||||||
|
required this.weeksAmount,
|
||||||
|
required this.innerDot,
|
||||||
|
this.onChanged,
|
||||||
|
this.events,
|
||||||
|
required this.keepLineSize,
|
||||||
|
this.textStyle,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
final ViewRange monthView;
|
||||||
|
final DateTime? todayDate;
|
||||||
|
final DateTime selectedDate;
|
||||||
|
final double weekLineHeight;
|
||||||
|
final int weeksAmount;
|
||||||
|
final ValueChanged<DateTime>? onChanged;
|
||||||
|
final List<DateTime>? events;
|
||||||
|
final bool innerDot;
|
||||||
|
final bool keepLineSize;
|
||||||
|
final TextStyle? textStyle;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final index = selectedDate.findWeekIndex(monthView.dates);
|
||||||
|
final offset = index / (weeksAmount - 1) * 2 - 1.0;
|
||||||
|
|
||||||
|
return OverflowBox(
|
||||||
|
alignment: Alignment(0, offset),
|
||||||
|
minHeight: weekLineHeight,
|
||||||
|
maxHeight: weekLineHeight * weeksAmount,
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: List<Widget>.generate(
|
||||||
|
6,
|
||||||
|
(weekIndex) {
|
||||||
|
final weekStart = weekIndex * 7;
|
||||||
|
|
||||||
|
return WeekView(
|
||||||
|
innerDot: innerDot,
|
||||||
|
dates: monthView.dates.sublist(weekStart, weekStart + 7),
|
||||||
|
selectedDate: selectedDate,
|
||||||
|
highlightMonth: monthView.firstDay.month,
|
||||||
|
lineHeight: weekLineHeight,
|
||||||
|
onChanged: onChanged,
|
||||||
|
events: events,
|
||||||
|
keepLineSize: keepLineSize,
|
||||||
|
textStyle: textStyle,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
growable: false,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,40 @@
|
|||||||
|
part of 'widget.dart';
|
||||||
|
|
||||||
|
class ViewRange {
|
||||||
|
const ViewRange._(this.firstDay, this.dates);
|
||||||
|
|
||||||
|
/// Creates custom filled [ViewRange] instance.
|
||||||
|
const ViewRange.custom(
|
||||||
|
DateTime firstDay,
|
||||||
|
List<DateTime> dates,
|
||||||
|
) : this._(firstDay, dates);
|
||||||
|
|
||||||
|
/// Generates [ViewRange] instance based on [date],
|
||||||
|
/// number of [month] and [weeksAmount].
|
||||||
|
/// gives the beginning of the day of the week [startWeekDay]
|
||||||
|
factory ViewRange.generateDates(
|
||||||
|
DateTime date,
|
||||||
|
int month,
|
||||||
|
int weeksAmount, {
|
||||||
|
int? startWeekDay,
|
||||||
|
}) {
|
||||||
|
final firstMonthDate = DateTime.utc(date.year, month, 1);
|
||||||
|
final firstViewDate =
|
||||||
|
firstMonthDate.firstDayOfWeek(startWeekDay: startWeekDay);
|
||||||
|
|
||||||
|
return ViewRange._(
|
||||||
|
firstMonthDate,
|
||||||
|
List.generate(
|
||||||
|
weeksAmount * 7,
|
||||||
|
(index) => firstViewDate.add(Duration(days: index)),
|
||||||
|
growable: false,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Month view index.
|
||||||
|
final DateTime firstDay;
|
||||||
|
|
||||||
|
/// Month view dates.
|
||||||
|
final List<DateTime> dates;
|
||||||
|
}
|
||||||
36
star_lock/lib/tools/advancedCalendar/src/week_days.dart
Normal file
36
star_lock/lib/tools/advancedCalendar/src/week_days.dart
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
part of 'widget.dart';
|
||||||
|
|
||||||
|
/// Week day names line.
|
||||||
|
class WeekDays extends StatelessWidget {
|
||||||
|
const WeekDays({
|
||||||
|
Key? key,
|
||||||
|
this.weekNames = const <String>['日', '一', '二', '三', '四', '五', '六'],
|
||||||
|
this.style,
|
||||||
|
required this.keepLineSize,
|
||||||
|
}) : assert(weekNames.length == 7, '`weekNames` must have length 7'),
|
||||||
|
super(key: key);
|
||||||
|
|
||||||
|
/// Week day names.
|
||||||
|
final List<String> weekNames;
|
||||||
|
|
||||||
|
/// Text style.
|
||||||
|
final TextStyle? style;
|
||||||
|
|
||||||
|
final bool keepLineSize;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return DefaultTextStyle(
|
||||||
|
style: style!,
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||||
|
children: List.generate(weekNames.length, (index) {
|
||||||
|
return DateBox(
|
||||||
|
child: Text(weekNames[index]),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
137
star_lock/lib/tools/advancedCalendar/src/week_view.dart
Normal file
137
star_lock/lib/tools/advancedCalendar/src/week_view.dart
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
part of 'widget.dart';
|
||||||
|
|
||||||
|
class WeekView extends StatelessWidget {
|
||||||
|
WeekView({
|
||||||
|
Key? key,
|
||||||
|
required this.dates,
|
||||||
|
required this.selectedDate,
|
||||||
|
required this.lineHeight,
|
||||||
|
this.highlightMonth,
|
||||||
|
this.onChanged,
|
||||||
|
this.events,
|
||||||
|
required this.innerDot,
|
||||||
|
required this.keepLineSize,
|
||||||
|
this.textStyle,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
final DateTime todayDate = DateTime.now().toZeroTime();
|
||||||
|
final List<DateTime> dates;
|
||||||
|
final double lineHeight;
|
||||||
|
final int? highlightMonth;
|
||||||
|
final DateTime selectedDate;
|
||||||
|
final ValueChanged<DateTime>? onChanged;
|
||||||
|
final List<DateTime>? events;
|
||||||
|
final bool innerDot;
|
||||||
|
final bool keepLineSize;
|
||||||
|
final TextStyle? textStyle;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
return SizedBox(
|
||||||
|
height: lineHeight,
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: List<Widget>.generate(
|
||||||
|
7,
|
||||||
|
(dayIndex) {
|
||||||
|
final date = dates[dayIndex];
|
||||||
|
final isToday = date.isAtSameMomentAs(todayDate);
|
||||||
|
final isSelected = date.isAtSameMomentAs(selectedDate);
|
||||||
|
final isHighlight = highlightMonth == date.month;
|
||||||
|
|
||||||
|
final hasEvent =
|
||||||
|
events!.indexWhere((element) => element.isSameDate(date));
|
||||||
|
|
||||||
|
if (keepLineSize) {
|
||||||
|
return InkResponse(
|
||||||
|
onTap: onChanged != null ? () => onChanged!(date) : null,
|
||||||
|
child: Container(
|
||||||
|
height: 36,
|
||||||
|
width: 36,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: isSelected
|
||||||
|
? theme.primaryColor
|
||||||
|
: isToday
|
||||||
|
? theme.highlightColor
|
||||||
|
: null,
|
||||||
|
borderRadius: BorderRadius.circular(18),
|
||||||
|
shape: BoxShape.rectangle,
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'${date.day}',
|
||||||
|
style: textStyle?.copyWith(
|
||||||
|
color: isSelected || isToday
|
||||||
|
? theme.colorScheme.onPrimary
|
||||||
|
: isHighlight || highlightMonth == null
|
||||||
|
? null
|
||||||
|
: theme.disabledColor,
|
||||||
|
fontWeight:
|
||||||
|
isSelected && textStyle?.fontWeight != null
|
||||||
|
? FontWeight
|
||||||
|
.values[textStyle!.fontWeight!.index + 2]
|
||||||
|
: textStyle?.fontWeight,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (!hasEvent.isNegative)
|
||||||
|
Container(
|
||||||
|
height: 4,
|
||||||
|
width: 4,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(50),
|
||||||
|
color: isSelected
|
||||||
|
? theme.colorScheme.onPrimary
|
||||||
|
: theme.colorScheme.secondary,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
|
children: [
|
||||||
|
DateBox(
|
||||||
|
width: innerDot ? 32 : 24,
|
||||||
|
height: innerDot ? 32 : 24,
|
||||||
|
showDot: innerDot,
|
||||||
|
onPressed: onChanged != null ? () => onChanged!(date) : null,
|
||||||
|
isSelected: isSelected,
|
||||||
|
isToday: isToday,
|
||||||
|
hasEvent: !hasEvent.isNegative,
|
||||||
|
child: Text(
|
||||||
|
'${date.day}',
|
||||||
|
maxLines: 1,
|
||||||
|
style: TextStyle(
|
||||||
|
color: isSelected || isToday
|
||||||
|
? theme.colorScheme.onPrimary
|
||||||
|
: isHighlight || highlightMonth == null
|
||||||
|
? null
|
||||||
|
: theme.disabledColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (!innerDot && !hasEvent.isNegative)
|
||||||
|
Container(
|
||||||
|
height: 6,
|
||||||
|
width: 6,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(50),
|
||||||
|
color: theme.primaryColor,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
growable: false,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
420
star_lock/lib/tools/advancedCalendar/src/widget.dart
Normal file
420
star_lock/lib/tools/advancedCalendar/src/widget.dart
Normal file
@ -0,0 +1,420 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
|
import 'package:intl/date_symbol_data_local.dart';
|
||||||
|
import 'package:intl/intl.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 = 13,
|
||||||
|
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 monthPageIndex = widget.preloadMonthViewAmount ~/ 2;
|
||||||
|
|
||||||
|
_monthViewCurrentPage = ValueNotifier(monthPageIndex);
|
||||||
|
|
||||||
|
_monthPageController = PageController(
|
||||||
|
initialPage: monthPageIndex,
|
||||||
|
);
|
||||||
|
|
||||||
|
final 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,
|
||||||
|
(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 time = _controller.value.subtract(
|
||||||
|
Duration(days: _controller.value.weekday - widget.startWeekDay!),
|
||||||
|
);
|
||||||
|
final list = List<DateTime>.generate(
|
||||||
|
8,
|
||||||
|
(index) => time.add(Duration(days: index * 1)),
|
||||||
|
).toList();
|
||||||
|
// _weekNames = List<String>.generate(7, (index) {
|
||||||
|
// return DateFormat("EEEE").format(list[index]).split('').first;
|
||||||
|
// }
|
||||||
|
// );
|
||||||
|
//by DaisyWu 修改源文件为中文环境下 周一到周日
|
||||||
|
_weekNames = List<String>.generate(7, (index) {
|
||||||
|
String fullWeekName =
|
||||||
|
DateFormat.E('zh_CN').format(list[index]); // 获取星期的完整形式
|
||||||
|
return fullWeekName
|
||||||
|
.substring(fullWeekName.length - 1); // 获取最后一个字符,即星期的简称
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
return Material(
|
||||||
|
color: Colors.transparent,
|
||||||
|
child: DefaultTextStyle.merge(
|
||||||
|
style: theme.textTheme.bodyMedium,
|
||||||
|
child: GestureDetector(
|
||||||
|
onVerticalDragStart: (details) {
|
||||||
|
_captureOffset = details.globalPosition;
|
||||||
|
},
|
||||||
|
onVerticalDragUpdate: (details) {
|
||||||
|
final moveOffset = details.globalPosition;
|
||||||
|
final diffY = moveOffset.dy - _captureOffset!.dy;
|
||||||
|
|
||||||
|
_animationController.value =
|
||||||
|
_animationValue + diffY / (widget.weekLineHeight * 5);
|
||||||
|
},
|
||||||
|
onVerticalDragEnd: (details) => _handleFinishDrag(),
|
||||||
|
onVerticalDragCancel: _handleFinishDrag,
|
||||||
|
child: Container(
|
||||||
|
color: Colors.transparent,
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
ValueListenableBuilder<int>(
|
||||||
|
valueListenable: _monthViewCurrentPage,
|
||||||
|
builder: (_, 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!
|
||||||
|
: const <String>['日', '一', '二', '三', '四', '五', '六'],
|
||||||
|
),
|
||||||
|
AnimatedBuilder(
|
||||||
|
animation: _animationController,
|
||||||
|
builder: (_, __) {
|
||||||
|
final 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, __) {
|
||||||
|
return Stack(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
children: [
|
||||||
|
IgnorePointer(
|
||||||
|
ignoring: _animationController.value == 0.0,
|
||||||
|
child: Opacity(
|
||||||
|
opacity: Tween<double>(
|
||||||
|
begin: 0.0,
|
||||||
|
end: 1.0,
|
||||||
|
).evaluate(_animationController),
|
||||||
|
child: PageView.builder(
|
||||||
|
onPageChanged: (pageIndex) {
|
||||||
|
if (widget.onHorizontalDrag != null) {
|
||||||
|
widget.onHorizontalDrag!(
|
||||||
|
_monthRangeList[pageIndex].firstDay,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_monthViewCurrentPage.value = pageIndex;
|
||||||
|
},
|
||||||
|
controller: _monthPageController,
|
||||||
|
physics: _animationController.value == 1.0
|
||||||
|
? const AlwaysScrollableScrollPhysics()
|
||||||
|
: const NeverScrollableScrollPhysics(),
|
||||||
|
itemCount: _monthRangeList.length,
|
||||||
|
itemBuilder: (_, 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: (_, pageIndex, __) {
|
||||||
|
final index = selectedDate.findWeekIndex(
|
||||||
|
_monthRangeList[_monthViewCurrentPage.value]
|
||||||
|
.dates,
|
||||||
|
);
|
||||||
|
final 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: (indexPage) {
|
||||||
|
final pageIndex =
|
||||||
|
_monthRangeList.indexWhere(
|
||||||
|
(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: (context, 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((monthRange) => monthRange.dates.contains(date));
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handleDateChanged(DateTime date) {
|
||||||
|
_controller.value = 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -134,7 +134,7 @@ dependencies:
|
|||||||
flutter_voice_processor: ^1.1.0
|
flutter_voice_processor: ^1.1.0
|
||||||
#监听网络连接状态
|
#监听网络连接状态
|
||||||
connectivity_plus: ^5.0.2
|
connectivity_plus: ^5.0.2
|
||||||
flutter_advanced_calendar: ^1.4.1
|
#flutter_advanced_calendar: ^1.4.1
|
||||||
timelines: ^0.1.0
|
timelines: ^0.1.0
|
||||||
#侧滑删除
|
#侧滑删除
|
||||||
flutter_slidable: ^3.0.1
|
flutter_slidable: ^3.0.1
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user