Skip to content

Commit

Permalink
feat: show today lectures using sharedPreferences
Browse files Browse the repository at this point in the history
  • Loading branch information
SungyeopJeong authored and happycastle114 committed Nov 4, 2024
1 parent 5c6bfab commit bf49e6e
Show file tree
Hide file tree
Showing 4 changed files with 201 additions and 62 deletions.
216 changes: 155 additions & 61 deletions android/app/src/main/java/org/sparcs/otlplus/TodayWidget.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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<MutableMap<String, Any>> {
fun parseClasstimesJson(classtimesArray: JSONArray): List<Map<String, Any>> {
val classtimes = mutableListOf<Map<String, Any>>()
for (i in 0 until classtimesArray.length()) {
val classtime = classtimesArray.getJSONObject(i)
val classtimeMap = mutableMapOf<String, Any>()
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<String, Any> {
val lectureMap = mutableMapOf<String, Any>()
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<MutableMap<String, Any>>()
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<Map<String, Any>>() }
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
Expand Down
10 changes: 10 additions & 0 deletions lib/models/classtime.dart
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,14 @@ class Classtime extends Time {
data['end'] = this.end;
return data;
}

Map<String, dynamic> toJsonForWidget() {
final Map<String, dynamic> data = Map<String, dynamic>();
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;
}
}
13 changes: 13 additions & 0 deletions lib/models/lecture.dart
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -175,4 +176,16 @@ class Lecture {
data['examtimes'] = this.examtimes.map((v) => v.toJson()).toList();
return data;
}

Map<String, dynamic> toJsonForWidget() {
final Map<String, dynamic> data = Map<String, dynamic>();
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;
}
}
24 changes: 23 additions & 1 deletion lib/providers/timetable_model.dart
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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<Semester> _semesters;

late int _selectedSemesterIndex;

Semester get selectedSemester => _semesters[_selectedSemesterIndex];

late List<Timetable> _timetables;

List<Timetable> get timetables => _timetables;

Lecture? _tempLecture;

Lecture? get tempLecture => _tempLecture;

void setTempLecture(Lecture? lecture) {
Expand All @@ -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}) {
Expand Down Expand Up @@ -74,7 +84,7 @@ class TimetableModel extends ChangeNotifier {
_semesters = semesters;
_selectedSemesterIndex = semesters.length - 1;
notifyListeners();
_loadTimetable();
_loadTimetable().whenComplete(() => _saveTimetable());
}

get canGoPreviousSemester => _selectedSemesterIndex > 0;
Expand Down Expand Up @@ -111,6 +121,18 @@ class TimetableModel extends ChangeNotifier {
notifyListeners();
}

Future<void> _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<bool> _loadTimetable() async {
try {
final response = await DioProvider().dio.get(
Expand Down

0 comments on commit bf49e6e

Please sign in to comment.