diff --git a/android/app/src/main/java/org/sparcs/otlplus/TodayWidget.kt b/android/app/src/main/java/org/sparcs/otlplus/TodayWidget.kt index ae53be2a..4dac3e4f 100644 --- a/android/app/src/main/java/org/sparcs/otlplus/TodayWidget.kt +++ b/android/app/src/main/java/org/sparcs/otlplus/TodayWidget.kt @@ -9,9 +9,13 @@ import android.content.Context import android.content.Intent import android.util.Log import android.view.View -import java.util.Calendar import android.widget.RemoteViews +import java.text.SimpleDateFormat +import java.util.Calendar +import java.util.Locale import kotlin.math.roundToInt +import org.json.JSONArray +import org.json.JSONObject /** * Implementation of App Widget functionality. @@ -102,78 +106,168 @@ internal fun updateTodayWidget( // Construct the RemoteViews object val views = RemoteViews(context.packageName, R.layout.today_widget) - // Add time items - views.removeAllViews(R.id.todayTimetable) + // Check timetable data + val prefs = context.getSharedPreferences("FlutterSharedPreferences", Context.MODE_PRIVATE) - val dottedItem = RemoteViews(context.packageName, R.layout.today_dotted_time_item) - for (time in startHour..endHour) { - val solidItem = RemoteViews(context.packageName, R.layout.today_solid_time_item) - solidItem.setTextViewText(R.id.time, (if (time > 12) time - 12 else time).toString()) - views.addView(R.id.todayTimetable, solidItem) - if (time != endHour) views.addView(R.id.todayTimetable, dottedItem) + fun parseDateFromPrefs(key: String): Calendar? { + val dateString = prefs.getString("flutter.$key", "") + val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()) + return try { + val date = Calendar.getInstance() + date.time = dateFormat.parse(dateString!!)!! + date + } catch (e: Exception) { + null + } } - // Set time indicator - fun isVisible(hour: Int, minute: Int): Boolean = (hour + minute / 60.0).let { time -> - time > startHour && time < endHour - } + fun parseLecturesFromPrefs(): List> { + fun parseClasstimesJson(classtimesArray: JSONArray): List> { + val classtimes = mutableListOf>() + for (i in 0 until classtimesArray.length()) { + val classtime = classtimesArray.getJSONObject(i) + val classtimeMap = mutableMapOf() + classtimeMap["classroom_short"] = classtime.getString("classroom_short") + classtimeMap["classroom_short_en"] = classtime.getString("classroom_short_en") + classtimeMap["day"] = classtime.getInt("day") + classtimeMap["begin"] = classtime.getInt("begin") + classtimeMap["end"] = classtime.getInt("end") + classtimes.add(classtimeMap) + } + return classtimes + } + + fun parseLectureJson(lecture: JSONObject): MutableMap { + val lectureMap = mutableMapOf() + lectureMap["title"] = lecture.getString("title") + lectureMap["title_en"] = lecture.getString("title_en") + lectureMap["course"] = lecture.getInt("course") + lectureMap["professors"] = lecture.getString("professors") + lectureMap["professors_en"] = lecture.getString("professors_en") + lectureMap["classtimes"] = parseClasstimesJson(lecture.getJSONArray("classtimes")) + return lectureMap + } - fun getMargin(hour: Int, minute: Int): Double = - startMargin + (hour - startHour + minute / 60.0) * hourMargin + val lecturesJson = prefs.getString("flutter.lectures", "[]") + val lecturesArray = JSONArray(lecturesJson ?: "[]") - fun getOffset(hour: Int, minute: Int): Double = - ((hour + minute / 60.0).coerceIn(minHour, maxHour) - minHour) * hourMargin + val lectures = mutableListOf>() + for (i in 0 until lecturesArray.length()) { + val lecture = lecturesArray.getJSONObject(i) + val lectureMap = parseLectureJson(lecture) + lectures.add(lectureMap) + } + return lectures + } + val semesterBeginning = parseDateFromPrefs("beginning") + val semesterEnd = parseDateFromPrefs("end") val calendar = Calendar.getInstance() - val hour = calendar.get(Calendar.HOUR_OF_DAY) - val minute = calendar.get(Calendar.MINUTE) - val visibility = isVisible(hour, minute) - val offset = getOffset(hour, minute) - Log.d("TodayWidget", "updated at $hour:$minute") + if (calendar.after(semesterBeginning) && calendar.before(semesterEnd)) { + Log.d("TodayWidget", "Have data") - if (visibility) { - val margin = getMargin(hour, minute) - indicatorWidth / 2.0 + // Add time items + views.removeAllViews(R.id.todayTimetable) - views.setViewPadding(R.id.todayTimetable, (itemMargin - offset).roundToInt(), 0, 0, 0) - views.setViewVisibility(R.id.timeIndicator, View.VISIBLE) - views.setViewPadding(R.id.timeIndicatorContainer, (margin - offset).roundToInt(), 0, 0, 0) - } else { - views.setViewPadding(R.id.todayTimetable, itemMargin, 0, 0, 0) - views.setViewVisibility(R.id.timeIndicator, View.INVISIBLE) - } + val dottedItem = RemoteViews(context.packageName, R.layout.today_dotted_time_item) + for (time in startHour..endHour) { + val solidItem = RemoteViews(context.packageName, R.layout.today_solid_time_item) + solidItem.setTextViewText(R.id.time, (if (time > 12) time - 12 else time).toString()) + views.addView(R.id.todayTimetable, solidItem) + if (time != endHour) views.addView(R.id.todayTimetable, dottedItem) + } - // Add lecture blocks - views.removeAllViews(R.id.todayLectureBlockContainer) - - fun getLayoutId(minute: Int): Int? = when (minute) { - 30 -> R.layout.today_lecture_block_30 - 50 -> R.layout.today_lecture_block_50 - 60 -> R.layout.today_lecture_block_60 - 70 -> R.layout.today_lecture_block_70 - 75 -> R.layout.today_lecture_block_75 - 90 -> R.layout.today_lecture_block_90 - 110 -> R.layout.today_lecture_block_110 - 120 -> R.layout.today_lecture_block_120 - 150 -> R.layout.today_lecture_block_150 - 165 -> R.layout.today_lecture_block_165 - 170 -> R.layout.today_lecture_block_170 - 180 -> R.layout.today_lecture_block_180 - 210 -> R.layout.today_lecture_block_210 - 240 -> R.layout.today_lecture_block_240 - 300 -> R.layout.today_lecture_block_300 - 360 -> R.layout.today_lecture_block_360 - 420 -> R.layout.today_lecture_block_420 - 480 -> R.layout.today_lecture_block_480 - 540 -> R.layout.today_lecture_block_540 - else -> null - } + // Set time indicator + fun isVisible(hour: Int, minute: Int): Boolean = (hour + minute / 60.0).let { time -> + time > startHour && time < endHour + } + + fun getMargin(hour: Int, minute: Int): Double = + startMargin + (hour - startHour + minute / 60.0) * hourMargin - (getLayoutId(110))?.let { layoutId -> - val margin = getMargin(10, 30) + blockMargin - val lectureBlock = RemoteViews(context.packageName, layoutId) - lectureBlock.setViewPadding(R.id.todayLectureBlock, (margin - offset).roundToInt(), 0, 0, 0) - views.addView(R.id.todayLectureBlockContainer, lectureBlock) + fun getOffset(hour: Int, minute: Int): Double = + ((hour + minute / 60.0).coerceIn(minHour, maxHour) - minHour) * hourMargin + + val hour = calendar.get(Calendar.HOUR_OF_DAY) + val minute = calendar.get(Calendar.MINUTE) + val visibility = isVisible(hour, minute) + val offset = getOffset(hour, minute) + + Log.d("TodayWidget", "updated at $hour:$minute") + + if (visibility) { + val margin = getMargin(hour, minute) - indicatorWidth / 2.0 + + views.setViewPadding(R.id.todayTimetable, (itemMargin - offset).roundToInt(), 0, 0, 0) + views.setViewVisibility(R.id.timeIndicator, View.VISIBLE) + views.setViewPadding( + R.id.timeIndicatorContainer, (margin - offset).roundToInt(), 0, 0, 0 + ) + } else { + views.setViewPadding(R.id.todayTimetable, itemMargin, 0, 0, 0) + views.setViewVisibility(R.id.timeIndicator, View.INVISIBLE) + } + + val lectures = parseLecturesFromPrefs() + val timetable = Array(7) { mutableListOf>() } + lectures.forEach { lecture -> + (lecture["classtimes"] as List<*>).forEach { classtime -> + val day = (classtime as Map<*, *>)["day"] as Int // 0 is monday + val updatedLecture = lecture.toMutableMap() + updatedLecture["classroom_short"] = classtime["classroom_short"] as Any + updatedLecture["classroom_short_en"] = classtime["classroom_short_en"] as Any + updatedLecture["begin"] = classtime["begin"] as Any + updatedLecture["end"] = classtime["end"] as Any + timetable[day].add(updatedLecture) + } + } + + // Add lecture blocks + views.removeAllViews(R.id.todayLectureBlockContainer) + + fun getLayoutId(minute: Int): Int? = when (minute) { + 30 -> R.layout.today_lecture_block_30 + 50 -> R.layout.today_lecture_block_50 + 60 -> R.layout.today_lecture_block_60 + 70 -> R.layout.today_lecture_block_70 + 75 -> R.layout.today_lecture_block_75 + 90 -> R.layout.today_lecture_block_90 + 110 -> R.layout.today_lecture_block_110 + 120 -> R.layout.today_lecture_block_120 + 150 -> R.layout.today_lecture_block_150 + 165 -> R.layout.today_lecture_block_165 + 170 -> R.layout.today_lecture_block_170 + 180 -> R.layout.today_lecture_block_180 + 210 -> R.layout.today_lecture_block_210 + 240 -> R.layout.today_lecture_block_240 + 300 -> R.layout.today_lecture_block_300 + 360 -> R.layout.today_lecture_block_360 + 420 -> R.layout.today_lecture_block_420 + 480 -> R.layout.today_lecture_block_480 + 540 -> R.layout.today_lecture_block_540 + else -> null + } + + timetable[(calendar.get(Calendar.DAY_OF_WEEK) + 5) % 7].forEach { lecture -> + val begin = lecture["begin"] as Int + val end = lecture["end"] as Int + (getLayoutId(end - begin))?.let { layoutId -> + val margin = getMargin(begin / 60, begin % 60) + blockMargin + val lectureBlock = RemoteViews(context.packageName, layoutId) + lectureBlock.setTextViewText(R.id.lectureTitle, lecture["title"].toString()) + lectureBlock.setTextViewText( + R.id.lectureRoom, lecture["classroom_short"].toString() + ) + lectureBlock.setViewPadding( + R.id.todayLectureBlock, (margin - offset).roundToInt(), 0, 0, 0 + ) + views.addView(R.id.todayLectureBlockContainer, lectureBlock) + } + } + + } else { + Log.d("TodayWidget", "Need data") } // Instruct the widget manager to update the widget diff --git a/lib/models/classtime.dart b/lib/models/classtime.dart index 9ea282e2..a84a5782 100644 --- a/lib/models/classtime.dart +++ b/lib/models/classtime.dart @@ -50,4 +50,14 @@ class Classtime extends Time { data['end'] = this.end; return data; } + + Map toJsonForWidget() { + final Map data = Map(); + data['classroom_short'] = this.classroomShort; + data['classroom_short_en'] = this.classroomShortEn; + data['day'] = this.day; + data['begin'] = this.begin; + data['end'] = this.end; + return data; + } } diff --git a/lib/models/lecture.dart b/lib/models/lecture.dart index 32de30c0..1584fb28 100644 --- a/lib/models/lecture.dart +++ b/lib/models/lecture.dart @@ -1,3 +1,4 @@ +import 'package:otlplus/extensions/lecture.dart'; import 'package:otlplus/models/classtime.dart'; import 'package:otlplus/models/examtime.dart'; import 'package:otlplus/models/professor.dart'; @@ -175,4 +176,16 @@ class Lecture { data['examtimes'] = this.examtimes.map((v) => v.toJson()).toList(); return data; } + + Map toJsonForWidget() { + final Map data = Map(); + data['title'] = this.title; + data['title_en'] = this.titleEn; + data['course'] = this.course; + data['professors'] = this.professorsStrShort; + data['professors_en'] = this.professorsStrShortEn; + data['classtimes'] = + this.classtimes.map((v) => v.toJsonForWidget()).toList(); + return data; + } } diff --git a/lib/providers/timetable_model.dart b/lib/providers/timetable_model.dart index 10f0922b..7a6eb4e4 100644 --- a/lib/providers/timetable_model.dart +++ b/lib/providers/timetable_model.dart @@ -1,6 +1,8 @@ import 'dart:async'; +import 'dart:convert'; import 'package:dio/dio.dart'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:otlplus/constants/url.dart'; import 'package:otlplus/dio_provider.dart'; @@ -9,20 +11,25 @@ import 'package:otlplus/models/semester.dart'; import 'package:otlplus/models/timetable.dart'; import 'package:otlplus/models/user.dart'; import 'package:otlplus/utils/export_file.dart'; +import 'package:shared_preferences/shared_preferences.dart'; class TimetableModel extends ChangeNotifier { late User _user; + User get user => _user; late List _semesters; late int _selectedSemesterIndex; + Semester get selectedSemester => _semesters[_selectedSemesterIndex]; late List _timetables; + List get timetables => _timetables; Lecture? _tempLecture; + Lecture? get tempLecture => _tempLecture; void setTempLecture(Lecture? lecture) { @@ -31,14 +38,17 @@ class TimetableModel extends ChangeNotifier { } int _selectedTimetableIndex = 1; + int get selectedIndex => _selectedTimetableIndex; Timetable get currentTimetable => _timetables[_selectedTimetableIndex]; int _selectedModeIndex = 0; + int get selectedMode => _selectedModeIndex; bool _isLoaded = false; + bool get isLoaded => _isLoaded; TimetableModel({bool forTest = false}) { @@ -74,7 +84,7 @@ class TimetableModel extends ChangeNotifier { _semesters = semesters; _selectedSemesterIndex = semesters.length - 1; notifyListeners(); - _loadTimetable(); + _loadTimetable().whenComplete(() => _saveTimetable()); } get canGoPreviousSemester => _selectedSemesterIndex > 0; @@ -111,6 +121,18 @@ class TimetableModel extends ChangeNotifier { notifyListeners(); } + Future _saveTimetable() async { + final prefs = await SharedPreferences.getInstance(); + await prefs.setString('beginning', + DateFormat('yyyy-MM-dd').format(selectedSemester.beginning)); + await prefs.setString( + 'end', DateFormat('yyyy-MM-dd').format(selectedSemester.end)); + await prefs.setString( + 'lectures', + jsonEncode( + _timetables[0].lectures.map((e) => e.toJsonForWidget()).toList())); + } + Future _loadTimetable() async { try { final response = await DioProvider().dio.get(