Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEAT] Add timeline widget for android #196

Draft
wants to merge 14 commits into
base: main
Choose a base branch
from
4 changes: 4 additions & 0 deletions android/app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -80,13 +80,17 @@ android {
kotlinOptions {
jvmTarget = "17"
}
buildFeatures {
viewBinding = true
}
}

flutter {
source = "../.."
}

dependencies {
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test:runner:1.6.2")
androidTestImplementation("androidx.test.espresso:espresso-core:3.6.1")
Expand Down
80 changes: 55 additions & 25 deletions android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,49 +1,79 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- io.flutter.app.FlutterApplication is an android.app.Application that

<!--
io.flutter.app.FlutterApplication is an android.app.Application that
calls FlutterMain.startInitialization(this); in its onCreate method.
In most cases you can leave this as-is, but you if you want to provide
additional functionality it is fine to subclass or reimplement
FlutterApplication and put your custom class here. -->
<uses-permission
android:name="android.permission.INTERNET"/>
FlutterApplication and put your custom class here.
-->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="28"/>
<uses-permission
android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
android:maxSdkVersion="28" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />

<application
android:name="${applicationName}"
android:label="OTL"
android:icon="@mipmap/ic_launcher"
android:allowBackup="true"
android:fullBackupContent="true">
android:fullBackupContent="true"
android:icon="@mipmap/ic_launcher"
android:label="OTL">
<receiver
android:name=".TimetableWidget"
android:exported="false">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>

<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/timetable_widget_info" />
</receiver>
<receiver
android:name=".NextLectureWidget"
android:exported="false">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>

<meta-data
android:name="android.appwidget.provider"
android:resource="@xml/next_lecture_widget_info" />
</receiver>

<activity
android:name=".MainActivity"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:exported="true"
android:hardwareAccelerated="true"
android:launchMode="singleTop"
android:screenOrientation="portrait"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize"
android:exported="true">
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service
android:name="com.kuku.channel_talk_flutter.PushInterceptService"
android:exported="true"
>

<service
android:name="com.kuku.channel_talk_flutter.PushInterceptService"
android:exported="true">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT"/>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<!--
Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java
-->
<meta-data
android:name="flutterEmbedding"
android:value="2"/>
android:value="2" />
</application>

</manifest>
48 changes: 48 additions & 0 deletions android/app/src/main/java/org/sparcs/otlplus/NextLectureWidget.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package org.sparcs.otlplus

import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProvider
import android.content.Context
import android.widget.RemoteViews
import org.sparcs.otlplus.api.NextLectureData

/**
* Implementation of App Widget functionality.
*/

class NextLectureWidget : AppWidgetProvider() {
override fun onUpdate(
context: Context,
appWidgetManager: AppWidgetManager,
appWidgetIds: IntArray
) {
// There may be multiple widgets active, so update all of them
for (appWidgetId in appWidgetIds) {
updateNextLectureWidget(context, appWidgetManager, appWidgetId)
}
}

override fun onEnabled(context: Context) {
// Enter relevant functionality for when the first widget is created
}

override fun onDisabled(context: Context) {
// Enter relevant functionality for when the last widget is disabled
}
}

internal fun updateNextLectureWidget(
context: Context,
appWidgetManager: AppWidgetManager,
appWidgetId: Int
) {
// Construct the RemoteViews object
RemoteViews(context.packageName, R.layout.next_lecture_widget).let {
it.setTextViewText(R.id.nextLectureDate, NextLectureData.nextLectureDate)
it.setTextViewText(R.id.nextLectureName, NextLectureData.nextLectureName)
it.setTextViewText(R.id.nextLecturePlace, NextLectureData.nextLecturePlace)
it.setTextViewText(R.id.nextLectureProfessor, NextLectureData.nextLectureProfessor)
// Instruct the widget manager to update the widget
appWidgetManager.updateAppWidget(appWidgetId, it)
}
}
112 changes: 112 additions & 0 deletions android/app/src/main/java/org/sparcs/otlplus/TimetableWidget.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package org.sparcs.otlplus

import android.annotation.SuppressLint
import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProvider
import android.content.Context
import android.util.TypedValue
import android.widget.RemoteViews
import org.sparcs.otlplus.api.Lecture
import org.sparcs.otlplus.api.LocalTime
import org.sparcs.otlplus.api.TimetableData
import org.sparcs.otlplus.api.WeekDays
import org.sparcs.otlplus.constants.BlockColor

val timeTableColumns = listOf(
R.id.time_table_column_1,
R.id.time_table_column_2,
R.id.time_table_column_3,
R.id.time_table_column_4,
R.id.time_table_column_5,
)

data class TimeTableElement(
val length: Float,
val lecture: Lecture?
)

/**
* Implementation of App Widget functionality.
*/
class TimetableWidget : AppWidgetProvider() {
override fun onUpdate(
context: Context,
appWidgetManager: AppWidgetManager,
appWidgetIds: IntArray
) {
// There may be multiple widgets active, so update all of them
for (appWidgetId in appWidgetIds) {
updateTimetableWidget(context, appWidgetManager, appWidgetId)
}
}

override fun onEnabled(context: Context) {
// Enter relevant functionality for when the first widget is created
}

override fun onDisabled(context: Context) {
// Enter relevant functionality for when the last widget is disabled
}
}

@SuppressLint("NewApi")
internal fun updateTimetableWidget(
context: Context,
appWidgetManager: AppWidgetManager,
appWidgetId: Int
) {
val views = RemoteViews(context.packageName, R.layout.timetable_widget)

for (timetableColumn in timeTableColumns) {
views.removeAllViews(timetableColumn)
}

val weekTimetable = createTimeTable(TimetableData.lectures)

for ((weekday, dayTimetable) in weekTimetable.withIndex()) {
for (timeTableElement in dayTimetable) {
val blockView = when(timeTableElement.lecture) {
null -> RemoteViews(context.packageName, R.layout.blank_view)
else -> RemoteViews(context.packageName, BlockColor.getLayout(timeTableElement.lecture)).apply {
setTextViewText(R.id.timetable_block_lecture_name, timeTableElement.lecture.name)
}
}

blockView.setViewLayoutHeight(
R.id.timetable_block_root,
timeTableElement.length * 36,
TypedValue.COMPLEX_UNIT_DIP)

views.addView(timeTableColumns[weekday], blockView)
}
}

// Instruct the widget manager to update the widget
appWidgetManager.updateAppWidget(appWidgetId, views)
}

fun createTimeTable(lectures: List<Lecture>): List<List<TimeTableElement>> {
val timetable = List(5) { mutableListOf<TimeTableElement>() }

for (dayIndex in WeekDays.entries.toTypedArray().indices) {
val day = WeekDays.entries[dayIndex]

val dailyLectures = lectures.flatMap { lecture ->
lecture.timeBlocks.filter { it.weekday == day }.map { it to lecture }
}.sortedBy { it.first.start.hoursFloat }

var currentTime = LocalTime(9, 0)

for ((timeBlock, lecture) in dailyLectures) {
if (timeBlock.start.hoursFloat > currentTime.hoursFloat) {
val freeTimeLength = timeBlock.start.hoursFloat - currentTime.hoursFloat
timetable[dayIndex].add(TimeTableElement(freeTimeLength, null))
}
val lectureLength = timeBlock.end.hoursFloat - timeBlock.start.hoursFloat
timetable[dayIndex].add(TimeTableElement(lectureLength, lecture))
currentTime = timeBlock.end
}
}

return timetable
}
31 changes: 31 additions & 0 deletions android/app/src/main/java/org/sparcs/otlplus/api/Lecture.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package org.sparcs.otlplus.api

enum class WeekDays {
Mon,
Tue,
Wed,
Thu,
Fri,
}

data class LocalTime(
val hours: Int,
val minutes: Int,
) {
val hoursFloat: Float
get() = hours + minutes / 60f
}

data class TimeBlock(
val weekday: WeekDays,
val start: LocalTime,
val end: LocalTime
)

data class Lecture(
val name: String,
val timeBlocks: List<TimeBlock>,
val place: String,
val professor: String,
val course: Int,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package org.sparcs.otlplus.api

object NextLectureData { // TODO: Get data using api
val nextLectureDate
get() = "다음주 월요일"

val nextLectureName
get() = "일반물리학"

val nextLecturePlace
get() = "(E11)창의학습관 311"

val nextLectureProfessor
get() = "김교수"
}
43 changes: 43 additions & 0 deletions android/app/src/main/java/org/sparcs/otlplus/api/TimetableData.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package org.sparcs.otlplus.api

object TimetableData {
val lectures: List<Lecture>
get() = listOf(
Lecture(
name = "프로그래밍 기초",
place = "(E11)창의학습관 308",
professor = "김교수",
timeBlocks = listOf(
TimeBlock(
weekday = WeekDays.Mon,
start = LocalTime(9, 0),
end = LocalTime(12, 0),
),
TimeBlock(
weekday = WeekDays.Wed,
start = LocalTime(10, 30),
end = LocalTime(12, 0),
),
),
course = 317
),
Lecture(
name = "지속가능 사회 인프라 시스템과 환경의 이해",
place = "(E11) 창의학습관 208",
professor = "김교수",
timeBlocks = listOf(
TimeBlock(
weekday = WeekDays.Mon,
start = LocalTime(16, 0),
end = LocalTime(17, 30),
),
TimeBlock(
weekday = WeekDays.Wed,
start = LocalTime(16, 0),
end = LocalTime(17, 30),
),
),
course = 100
)
)
}
Comment on lines +1 to +43
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@happycastle114 저 이 부분 API 불러오기 하면 되는데 일단 남겨놓겠습니다 담에 동방 가서 마무리할게요

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

넹 한번 저도 확인해볼게요

Loading
Loading