controller.dart 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372
  1. import 'dart:math';
  2. import 'package:calendar_view/utils/calendar_util.dart';
  3. import 'package:calendar_view/utils/event_type.dart';
  4. import 'package:flutter/material.dart';
  5. import 'package:get/get.dart';
  6. class CalendarController extends GetxController {
  7. CalendarController();
  8. /// 虚拟的日程数据
  9. final List<DateTime> mockSchedule = [
  10. DateTime(2022, 12, 1),
  11. DateTime(2022, 12, 4),
  12. DateTime(2022, 12, 5),
  13. DateTime(2022, 12, 6),
  14. DateTime(2022, 12, 7),
  15. DateTime(2022, 12, 12),
  16. DateTime(2022, 12, 13),
  17. DateTime(2022, 12, 14),
  18. DateTime(2022, 12, 15),
  19. DateTime(2022, 12, 16),
  20. DateTime(2022, 12, 17),
  21. DateTime(2022, 12, 18),
  22. DateTime(2022, 12, 19),
  23. DateTime(2022, 12, 20),
  24. DateTime(2022, 12, 27),
  25. DateTime(2022, 12, 28),
  26. DateTime(2022, 12, 29),
  27. DateTime(2022, 12, 30),
  28. DateTime(2022, 12, 31),
  29. ];
  30. /// 数据项:当前年份,当前月份,当前日期
  31. int currentYear = 2022; // 当前年份
  32. int currentMonth = 12; // 当前月份
  33. int selectedDayIndex = 0; // 当前选择的日期的下标,从0开始,范围是0-34 有时是 0-41
  34. int maxDaysLength = 35; // 最大的日期数组长度,有时是42
  35. List<Schedule> scheduleList = []; // 当前维护的日程列表【数据源】
  36. List<ScheduleType> scheduleTypeList = []; // 当前维护的一个日程类型列表
  37. List<String> needDisplayTypeNameList = []; // 维护一个需要展示的日程类型列表
  38. List<MiniViewDayStructure> miniViewDaysList = []; // 当前维护的迷你日历列表
  39. /// 日程列表与日期列表拼装,即可变成月视图大列表,将日程塞入每一天
  40. List<MonthViewDayStructure> get monthViewDaysList {
  41. final List<MonthViewDayStructure> monthViewDaysList = [];
  42. for (int i = 0; i < maxDaysLength; i++) {
  43. final MiniViewDayStructure miniViewDayStructure = miniViewDaysList[i];
  44. final MonthViewDayStructure monthViewDayStructure = MonthViewDayStructure(
  45. index: miniViewDayStructure.index,
  46. date: miniViewDayStructure.date,
  47. isToday: miniViewDayStructure.isToday,
  48. isCurrentMonth: miniViewDayStructure.isCurrentMonth,
  49. isSelected: miniViewDayStructure.isSelected,
  50. scheduleList: [],
  51. );
  52. monthViewDaysList.add(monthViewDayStructure);
  53. }
  54. for (final Schedule schedule in scheduleList) {
  55. final DateTime scheduleDate = schedule.day;
  56. // final int scheduleDay = scheduleDate.day;
  57. for (int i = 0; i < maxDaysLength; i++) {
  58. final MiniViewDayStructure miniViewDayStructure = miniViewDaysList[i];
  59. final MonthViewDayStructure monthViewDayStructure =
  60. monthViewDaysList[i];
  61. if (miniViewDayStructure.isCurrentMonth &&
  62. miniViewDayStructure.date == scheduleDate) {
  63. monthViewDayStructure.scheduleList.add(schedule);
  64. }
  65. }
  66. }
  67. return monthViewDaysList;
  68. }
  69. /// TODO:[Gavin] i18n
  70. String get currentYearMonth => '$currentYear年$currentMonth月';
  71. bool get isToday => miniViewDaysList[selectedDayIndex].isToday;
  72. int get calendarLines => maxDaysLength ~/ 7;
  73. /// 数据变更事件通知
  74. FEventHandler<void> onDaysListChange = FEventHandler<void>();
  75. @override
  76. void onInit() {
  77. super.onInit();
  78. /// 获取当前时间判断年月
  79. final DateTime now = DateTime.now();
  80. currentYear = now.year;
  81. currentMonth = now.month;
  82. miniViewDaysList = _countDaysList(currentYear, currentMonth);
  83. selectToday();
  84. _initMockScheduleType();
  85. _initMockSchedule(mockSchedule);
  86. // 设置mini日历的日程下标
  87. updateScheduleTypeList();
  88. _setMiniCalendarSchedule();
  89. // print("CalendarController 全局临时控制器初始化");
  90. }
  91. /// TODO:[Gavin] 改为真实数据接口
  92. /// 创建日程类型,填充到scheduleTypeList
  93. void _initMockScheduleType() {
  94. scheduleTypeList = [
  95. ScheduleType(
  96. typeName: '我的日程',
  97. color: Colors.red,
  98. isSelected: true,
  99. ),
  100. ScheduleType(
  101. typeName: '工作日程',
  102. color: Colors.blue,
  103. isSelected: true,
  104. ),
  105. ScheduleType(
  106. typeName: '会议日程',
  107. color: Colors.green,
  108. isSelected: true,
  109. ),
  110. ScheduleType(
  111. typeName: '其他日程',
  112. color: Colors.orange,
  113. isSelected: true,
  114. ),
  115. ];
  116. }
  117. /// 更新日程列表显示情况【只接收状态更新,不修改 scheduleTypeList】
  118. void updateScheduleTypeList() {
  119. // 更新 needDisplayTypeNameList
  120. needDisplayTypeNameList = [];
  121. for (var element in scheduleTypeList) {
  122. if (element.isSelected) {
  123. needDisplayTypeNameList.add(element.typeName);
  124. }
  125. }
  126. _setMiniCalendarSchedule();
  127. // 通知更新
  128. onDaysListChange.emit(this, null);
  129. }
  130. /// TODO:[Gavin] 改为真实数据接口
  131. void _initMockSchedule(List<DateTime> mockSchedule) {
  132. // 遍历mockSchedule的每一天,向 scheduleList 中添加日程,每天添加3个日程
  133. for (final DateTime date in mockSchedule) {
  134. int num = _getRandomInt(0, 20);
  135. for (int i = 0; i < num; i++) {
  136. int typeIndex = _getRandomInt(0, scheduleTypeList.length);
  137. final Schedule schedule = Schedule(
  138. day: date,
  139. type: scheduleTypeList[typeIndex],
  140. title: '${scheduleTypeList[typeIndex].typeName}-Mock标题',
  141. content: '日程内容',
  142. );
  143. scheduleList.add(schedule);
  144. }
  145. }
  146. }
  147. /// 取随机整数
  148. int _getRandomInt(int min, int max) {
  149. final Random random = Random();
  150. return min + random.nextInt(max - min);
  151. }
  152. /// 传入起始年月,返回35个日期结构体
  153. List<MiniViewDayStructure> _countDaysList(int year, int month) {
  154. maxDaysLength = _isNeedExtraRow(year, month) ? 42 : 35;
  155. final miniViewDaysList = <MiniViewDayStructure>[];
  156. final firstDay = DateTime(year, month, 1);
  157. final firstDayWeek = firstDay.weekday;
  158. final lastDay = DateTime(year, month + 1, 0);
  159. final lastDayOfMonth = lastDay.day;
  160. final lastDayOfLastMonth = DateTime(year, month, 0).day;
  161. final today = DateTime.now();
  162. /// 转换后的 firstDayWeek
  163. final firstDayWeekIndex = (firstDayWeek) % 7;
  164. int nextMonthDay = 1;
  165. // 上个月的日期
  166. for (var i = 0; i < firstDayWeekIndex; i++) {
  167. miniViewDaysList.add(
  168. MiniViewDayStructure(
  169. index: i,
  170. date: DateTime(
  171. year, month - 1, lastDayOfLastMonth - firstDayWeekIndex + i + 1),
  172. isCurrentMonth: false,
  173. ),
  174. );
  175. }
  176. // 当月的日期
  177. for (var i = 0;
  178. (i < lastDayOfMonth) && miniViewDaysList.length < maxDaysLength;
  179. i++) {
  180. miniViewDaysList.add(
  181. MiniViewDayStructure(
  182. index: i + firstDayWeekIndex,
  183. date: DateTime(year, month, i + 1),
  184. isToday:
  185. today.year == year && today.month == month && today.day == i + 1,
  186. ),
  187. );
  188. }
  189. while (miniViewDaysList.length < maxDaysLength) {
  190. miniViewDaysList.add(MiniViewDayStructure(
  191. index: miniViewDaysList.length,
  192. date: DateTime(month == 12 ? year + 1 : year,
  193. month == 12 ? 1 : month + 1, nextMonthDay),
  194. isCurrentMonth: false,
  195. ));
  196. nextMonthDay++;
  197. }
  198. return miniViewDaysList;
  199. }
  200. /// 计算是否需要显示六行日期,一般为5行,但是有时候会有6行
  201. bool _isNeedExtraRow(int year, int month) {
  202. final firstDay = DateTime(year, month, 1);
  203. final firstDayWeek = firstDay.weekday;
  204. final lastDay = DateTime(year, month + 1, 0);
  205. final lastDayOfMonth = lastDay.day;
  206. /// 转换后的firstDayWeek,%7是因为DateTime的weekday是从周日开始的,而我们的日历是从周一开始的
  207. final firstDayWeekIndex = (firstDayWeek) % 7;
  208. return firstDayWeekIndex + lastDayOfMonth > 35;
  209. }
  210. /// 通过下标选中日期,下标从0开始,允许的范围是0-34,有时是 0-42
  211. void handleSelectedDayByIndex(int index) {
  212. assert(index >= 0 && index < maxDaysLength,
  213. 'index must be in 0-$maxDaysLength');
  214. assert(miniViewDaysList.length == maxDaysLength,
  215. 'miniViewDaysList length must be maxDaysLength');
  216. for (var element in miniViewDaysList) {
  217. if (element.index == index) {
  218. element.isSelected = true;
  219. } else {
  220. element.isSelected = false;
  221. }
  222. }
  223. selectedDayIndex = index;
  224. onDaysListChange.emit(this, null);
  225. }
  226. /// 选中当月第一天
  227. void _selectFirstDay() {
  228. for (var element in miniViewDaysList) {
  229. if (element.isCurrentMonth) {
  230. handleSelectedDayByIndex(element.index);
  231. break;
  232. }
  233. }
  234. }
  235. /// 根据日期选中指定的一天
  236. void _selectDay(int day, List<MiniViewDayStructure> newList) {
  237. for (var element in newList) {
  238. if (element.date.day == day && element.isCurrentMonth) {
  239. handleSelectedDayByIndex(element.index);
  240. return;
  241. }
  242. }
  243. int nextSearchDay = day - 1;
  244. while (nextSearchDay > 0) {
  245. for (var element in newList) {
  246. if (element.date.day == nextSearchDay && element.isCurrentMonth) {
  247. handleSelectedDayByIndex(element.index);
  248. return;
  249. }
  250. }
  251. nextSearchDay--;
  252. }
  253. }
  254. /// 选中今天
  255. void selectToday() {
  256. /// 如果当前不是当月,则切换到当月
  257. final DateTime now = DateTime.now();
  258. if (currentYear != now.year || currentMonth != now.month) {
  259. currentYear = now.year;
  260. currentMonth = now.month;
  261. miniViewDaysList = _countDaysList(currentYear, currentMonth);
  262. _setMiniCalendarSchedule();
  263. }
  264. for (var element in miniViewDaysList) {
  265. if (element.isToday) {
  266. handleSelectedDayByIndex(element.index);
  267. break;
  268. }
  269. }
  270. }
  271. /// 给日期设置日程的值
  272. void _setMiniCalendarSchedule() {
  273. /// 如果 scheduleList 中存在 daysList 中的日期,则设置 hasSchedule 为 true
  274. for (var day in miniViewDaysList) {
  275. day.hasSchedule = false;
  276. for (var element in scheduleList) {
  277. var existSchedule = false;
  278. if (element.day.year == day.date.year &&
  279. element.day.month == day.date.month &&
  280. element.day.day == day.date.day) {
  281. if (_isDisplayScheduleType(element.type.typeName)) {
  282. existSchedule = true;
  283. }
  284. }
  285. if (existSchedule) {
  286. day.hasSchedule = true;
  287. break;
  288. }
  289. }
  290. }
  291. }
  292. /// 上个月 (keepSelectedDay 是否保持选中的日期)
  293. void handleLastMonth([bool keepSelectedDay = false]) {
  294. final currDay = miniViewDaysList[selectedDayIndex].date.day;
  295. if (currentMonth == 1) {
  296. currentMonth = 12;
  297. currentYear--;
  298. } else {
  299. currentMonth--;
  300. }
  301. miniViewDaysList = _countDaysList(currentYear, currentMonth);
  302. _setMiniCalendarSchedule();
  303. if (keepSelectedDay) {
  304. _selectDay(currDay, miniViewDaysList);
  305. } else {
  306. _selectFirstDay();
  307. }
  308. onDaysListChange.emit(this, null);
  309. }
  310. /// 下个月 (keepSelectedDay 是否保持选中的日期)
  311. void handleNextMonth([bool keepSelectedDay = false]) {
  312. final currDay = miniViewDaysList[selectedDayIndex].date.day;
  313. if (currentMonth == 12) {
  314. currentMonth = 1;
  315. currentYear++;
  316. } else {
  317. currentMonth++;
  318. }
  319. miniViewDaysList = _countDaysList(currentYear, currentMonth);
  320. _setMiniCalendarSchedule();
  321. if (keepSelectedDay) {
  322. _selectDay(currDay, miniViewDaysList);
  323. } else {
  324. _selectFirstDay();
  325. }
  326. onDaysListChange.emit(this, null);
  327. }
  328. /// 日程数据筛选器【仅筛选出需要显示的日程】
  329. List<Schedule> scheduleListFilter(List<Schedule> scheduleList) {
  330. final List<Schedule> result = [];
  331. for (Schedule element in scheduleList) {
  332. if (_isDisplayScheduleType(element.type.typeName)) {
  333. result.add(element);
  334. }
  335. }
  336. return result;
  337. }
  338. /// 是否为需要显示的日程
  339. bool _isDisplayScheduleType(String typeName) {
  340. if (needDisplayTypeNameList.contains(typeName)) {
  341. return true;
  342. }
  343. return false;
  344. }
  345. }