1
0
mirror of https://github.com/wulkanowy/wulkanowy.git synced 2024-11-14 11:00:49 -06:00

Merge branch 'release/2.4.2'

This commit is contained in:
Mikołaj Pich 2024-02-22 16:15:30 +01:00
commit 3eae3a7667
9 changed files with 86 additions and 44 deletions

View File

@ -27,8 +27,8 @@ android {
testApplicationId "io.github.tests.wulkanowy" testApplicationId "io.github.tests.wulkanowy"
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 34 targetSdkVersion 34
versionCode 147 versionCode 148
versionName "2.4.1" versionName "2.4.2"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
resValue "string", "app_name", "Wulkanowy" resValue "string", "app_name", "Wulkanowy"
@ -164,8 +164,8 @@ play {
defaultToAppBundles = false defaultToAppBundles = false
track = 'production' track = 'production'
releaseStatus = ReleaseStatus.IN_PROGRESS releaseStatus = ReleaseStatus.IN_PROGRESS
userFraction = 0.50d userFraction = 0.99d
updatePriority = 1 updatePriority = 2
enabled.set(false) enabled.set(false)
} }
@ -191,7 +191,7 @@ ext {
room = "2.6.1" room = "2.6.1"
chucker = "4.0.0" chucker = "4.0.0"
mockk = "1.13.9" mockk = "1.13.9"
coroutines = "1.7.3" coroutines = "1.8.0"
} }
dependencies { dependencies {
@ -199,7 +199,7 @@ dependencies {
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4'
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.2" implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines"
implementation 'androidx.core:core-ktx:1.12.0' implementation 'androidx.core:core-ktx:1.12.0'
@ -262,7 +262,7 @@ dependencies {
playImplementation "com.google.android.play:integrity:1.3.0" playImplementation "com.google.android.play:integrity:1.3.0"
playImplementation 'com.google.android.play:app-update-ktx:2.1.0' playImplementation 'com.google.android.play:app-update-ktx:2.1.0'
playImplementation 'com.google.android.play:review-ktx:2.0.1' playImplementation 'com.google.android.play:review-ktx:2.0.1'
playImplementation "com.google.android.ump:user-messaging-platform:2.2.0" playImplementation "com.google.android.ump:user-messaging-platform:2.1.0"
hmsImplementation 'com.huawei.hms:hianalytics:6.12.0.301' hmsImplementation 'com.huawei.hms:hianalytics:6.12.0.301'
hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.9.1.303' hmsImplementation 'com.huawei.agconnect:agconnect-crash:1.9.1.303'

View File

@ -30,8 +30,15 @@ val <T> Resource<T>.dataOrNull: T?
get() = when (this) { get() = when (this) {
is Resource.Success -> this.data is Resource.Success -> this.data
is Resource.Intermediate -> this.data is Resource.Intermediate -> this.data
is Resource.Loading -> null else -> null
is Resource.Error -> null }
val <T> Resource<T>.dataOrThrow: T
get() = when (this) {
is Resource.Success -> this.data
is Resource.Intermediate -> this.data
is Resource.Loading -> throw IllegalStateException("Resource is in loading state")
is Resource.Error -> throw this.error
} }
val <T> Resource<T>.errorOrNull: Throwable? val <T> Resource<T>.errorOrNull: Throwable?

View File

@ -11,10 +11,8 @@ import android.view.View
import android.widget.RemoteViews import android.widget.RemoteViews
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.data.Resource import io.github.wulkanowy.data.dataOrThrow
import io.github.wulkanowy.data.dataOrNull
import io.github.wulkanowy.data.db.SharedPrefProvider import io.github.wulkanowy.data.db.SharedPrefProvider
import io.github.wulkanowy.data.db.entities.LuckyNumber
import io.github.wulkanowy.data.repositories.LuckyNumberRepository import io.github.wulkanowy.data.repositories.LuckyNumberRepository
import io.github.wulkanowy.data.repositories.StudentRepository import io.github.wulkanowy.data.repositories.StudentRepository
import io.github.wulkanowy.data.toFirstResult import io.github.wulkanowy.data.toFirstResult
@ -69,8 +67,7 @@ class LuckyNumberWidgetProvider : AppWidgetProvider() {
appWidgetIds?.forEach { widgetId -> appWidgetIds?.forEach { widgetId ->
val studentId = sharedPref.getLong(getStudentWidgetKey(widgetId), 0) val studentId = sharedPref.getLong(getStudentWidgetKey(widgetId), 0)
val luckyNumberResource = getLuckyNumber(studentId, widgetId) val luckyNumber = getLuckyNumber(studentId, widgetId)?.luckyNumber?.toString()
val luckyNumber = luckyNumberResource.dataOrNull?.luckyNumber?.toString()
val remoteView = RemoteViews(context.packageName, R.layout.widget_luckynumber) val remoteView = RemoteViews(context.packageName, R.layout.widget_luckynumber)
.apply { .apply {
setTextViewText(R.id.luckyNumberWidgetValue, luckyNumber ?: "-") setTextViewText(R.id.luckyNumberWidgetValue, luckyNumber ?: "-")
@ -143,18 +140,18 @@ class LuckyNumberWidgetProvider : AppWidgetProvider() {
sharedPref.putLong(getStudentWidgetKey(appWidgetId), it.id) sharedPref.putLong(getStudentWidgetKey(appWidgetId), it.id)
} }
} }
else -> null else -> null
} }
if (currentStudent != null) { if (currentStudent != null) {
luckyNumberRepository.getLuckyNumber(currentStudent, forceRefresh = false) luckyNumberRepository.getLuckyNumber(currentStudent, forceRefresh = false)
.toFirstResult() .toFirstResult()
} else { .dataOrThrow
Resource.Success<LuckyNumber?>(null) } else null
}
} catch (e: Exception) { } catch (e: Exception) {
Timber.e(e, "An error has occurred in lucky number provider") Timber.e(e, "An error has occurred in lucky number provider")
Resource.Error(e) null
} }
} }

View File

@ -11,7 +11,7 @@ import android.view.View.VISIBLE
import android.widget.RemoteViews import android.widget.RemoteViews
import android.widget.RemoteViewsService import android.widget.RemoteViewsService
import io.github.wulkanowy.R import io.github.wulkanowy.R
import io.github.wulkanowy.data.dataOrNull import io.github.wulkanowy.data.dataOrThrow
import io.github.wulkanowy.data.db.SharedPrefProvider import io.github.wulkanowy.data.db.SharedPrefProvider
import io.github.wulkanowy.data.db.entities.Semester import io.github.wulkanowy.data.db.entities.Semester
import io.github.wulkanowy.data.db.entities.Student import io.github.wulkanowy.data.db.entities.Student
@ -27,6 +27,7 @@ import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetProvider.Co
import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetProvider.Companion.getStudentWidgetKey import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetProvider.Companion.getStudentWidgetKey
import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetProvider.Companion.getTodayLastLessonEndDateTimeWidgetKey import io.github.wulkanowy.ui.modules.timetablewidget.TimetableWidgetProvider.Companion.getTodayLastLessonEndDateTimeWidgetKey
import io.github.wulkanowy.utils.getCompatColor import io.github.wulkanowy.utils.getCompatColor
import io.github.wulkanowy.utils.getErrorString
import io.github.wulkanowy.utils.getPlural import io.github.wulkanowy.utils.getPlural
import io.github.wulkanowy.utils.toFormattedString import io.github.wulkanowy.utils.toFormattedString
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
@ -67,25 +68,31 @@ class TimetableWidgetFactory(
override fun onDestroy() {} override fun onDestroy() {}
override fun onDataSetChanged() { override fun onDataSetChanged() {
intent?.extras?.getInt(EXTRA_APPWIDGET_ID)?.let { appWidgetId -> val appWidgetId = intent?.extras?.getInt(EXTRA_APPWIDGET_ID) ?: return
val date = LocalDate.ofEpochDay(sharedPref.getLong(getDateWidgetKey(appWidgetId), 0)) val date = LocalDate.ofEpochDay(sharedPref.getLong(getDateWidgetKey(appWidgetId), 0))
val studentId = sharedPref.getLong(getStudentWidgetKey(appWidgetId), 0) val studentId = sharedPref.getLong(getStudentWidgetKey(appWidgetId), 0)
runCatching { items = emptyList()
runBlocking { runBlocking {
runCatching {
val student = getStudent(studentId) ?: return@runBlocking val student = getStudent(studentId) ?: return@runBlocking
val semester = semesterRepository.getCurrentSemester(student) val semester = semesterRepository.getCurrentSemester(student)
items = createItems( val lessons = getLessons(student, semester, date)
lessons = getLessons(student, semester, date), val lastSync = timetableRepository.getLastRefreshTimestamp(semester, date, date)
lastSync = timetableRepository.getLastRefreshTimestamp(semester, date, date)
) createItems(lessons, lastSync)
}
.onFailure {
items = listOf(TimetableWidgetItem.Error(it))
Timber.e(it, "An error has occurred in timetable widget factory")
}
.onSuccess {
items = it
if (date == LocalDate.now()) { if (date == LocalDate.now()) {
updateTodayLastLessonEnd(appWidgetId) updateTodayLastLessonEnd(appWidgetId)
} }
} }
}.onFailure {
Timber.e(it, "An error has occurred in timetable widget factory")
}
} }
} }
@ -98,7 +105,7 @@ class TimetableWidgetFactory(
student: Student, semester: Semester, date: LocalDate student: Student, semester: Semester, date: LocalDate
): List<Timetable> { ): List<Timetable> {
val timetable = timetableRepository.getTimetable(student, semester, date, date, false) val timetable = timetableRepository.getTimetable(student, semester, date, date, false)
val lessons = timetable.toFirstResult().dataOrNull?.lessons.orEmpty() val lessons = timetable.toFirstResult().dataOrThrow.lessons
return lessons.sortedBy { it.number } return lessons.sortedBy { it.number }
} }
@ -110,6 +117,7 @@ class TimetableWidgetFactory(
BETWEEN_AND_BEFORE_LESSONS -> 0 BETWEEN_AND_BEFORE_LESSONS -> 0
else -> null else -> null
} }
return buildList { return buildList {
lessons.forEach { lessons.forEach {
if (prefRepository.showTimetableGaps != NO_GAPS && prevNum != null && it.number > prevNum!! + 1) { if (prefRepository.showTimetableGaps != NO_GAPS && prevNum != null && it.number > prevNum!! + 1) {
@ -133,15 +141,12 @@ class TimetableWidgetFactory(
sharedPref.putLong(key, todayLastLessonEnd.epochSecond, true) sharedPref.putLong(key, todayLastLessonEnd.epochSecond, true)
} }
companion object {
const val TIME_FORMAT_STYLE = "HH:mm"
}
override fun getViewAt(position: Int): RemoteViews? { override fun getViewAt(position: Int): RemoteViews? {
return when (val item = items.getOrNull(position) ?: return null) { return when (val item = items.getOrNull(position) ?: return null) {
is TimetableWidgetItem.Normal -> getNormalItemRemoteView(item) is TimetableWidgetItem.Normal -> getNormalItemRemoteView(item)
is TimetableWidgetItem.Empty -> getEmptyItemRemoteView(item) is TimetableWidgetItem.Empty -> getEmptyItemRemoteView(item)
is TimetableWidgetItem.Synchronized -> getSynchronizedItemRemoteView(item) is TimetableWidgetItem.Synchronized -> getSynchronizedItemRemoteView(item)
is TimetableWidgetItem.Error -> getErrorItemRemoteView(item)
} }
} }
@ -213,6 +218,18 @@ class TimetableWidgetFactory(
} }
} }
private fun getErrorItemRemoteView(item: TimetableWidgetItem.Error): RemoteViews {
return RemoteViews(
context.packageName,
R.layout.item_widget_timetable_error
).apply {
setTextViewText(
R.id.timetable_widget_item_error_message,
context.resources.getErrorString(item.error)
)
}
}
private fun updateTheme() { private fun updateTheme() {
when (context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) { when (context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) {
Configuration.UI_MODE_NIGHT_YES -> { Configuration.UI_MODE_NIGHT_YES -> {
@ -300,4 +317,8 @@ class TimetableWidgetFactory(
synchronizationTime, synchronizationTime,
) )
} }
private companion object {
private const val TIME_FORMAT_STYLE = "HH:mm"
}
} }

View File

@ -17,10 +17,15 @@ sealed class TimetableWidgetItem(val type: TimetableWidgetItemType) {
data class Synchronized( data class Synchronized(
val timestamp: Instant, val timestamp: Instant,
) : TimetableWidgetItem(TimetableWidgetItemType.SYNCHRONIZED) ) : TimetableWidgetItem(TimetableWidgetItemType.SYNCHRONIZED)
data class Error(
val error: Throwable
) : TimetableWidgetItem(TimetableWidgetItemType.ERROR)
} }
enum class TimetableWidgetItemType { enum class TimetableWidgetItemType {
NORMAL, NORMAL,
EMPTY, EMPTY,
SYNCHRONIZED, SYNCHRONIZED,
ERROR,
} }

View File

@ -1,5 +1,7 @@
Wersja 2.4.1 Wersja 2.4.2
- drobne poprawki stabilności aplikacji i odświeżania danych - naprawiliśmy crash przy przełączaniu uczniów, motywów i języków
- naprawiliśmy crash przy dodawaniu dodatkowych lekcji
- naprawiliśmy obsługę błędów widżetach
Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases Pełna lista zmian: https://github.com/wulkanowy/wulkanowy/releases

View File

@ -39,7 +39,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:editable="false" android:editable="false"
android:focusable="false" android:focusable="false"
android:inputType="text" android:inputType="none"
tools:ignore="Deprecated" /> tools:ignore="Deprecated" />
</com.google.android.material.textfield.TextInputLayout> </com.google.android.material.textfield.TextInputLayout>
@ -67,7 +67,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:editable="false" android:editable="false"
android:focusable="false" android:focusable="false"
android:inputType="text" android:inputType="none"
tools:ignore="Deprecated" /> tools:ignore="Deprecated" />
</com.google.android.material.textfield.TextInputLayout> </com.google.android.material.textfield.TextInputLayout>
@ -87,7 +87,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:editable="false" android:editable="false"
android:focusable="false" android:focusable="false"
android:inputType="text" android:inputType="none"
tools:ignore="Deprecated" /> tools:ignore="Deprecated" />
</com.google.android.material.textfield.TextInputLayout> </com.google.android.material.textfield.TextInputLayout>

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/timetable_widget_item_error_message"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:paddingHorizontal="12dp"
android:paddingTop="48dp"
android:paddingBottom="12dp"
android:textSize="16sp"
tools:text="@string/error_unknown" />

View File

@ -114,7 +114,5 @@
android:text="@string/widget_timetable_no_items" android:text="@string/widget_timetable_no_items"
android:textAppearance="?attr/textAppearanceBody1" android:textAppearance="?attr/textAppearanceBody1"
android:visibility="gone" /> android:visibility="gone" />
</FrameLayout> </FrameLayout>
</LinearLayout> </LinearLayout>