Skip to content

Commit

Permalink
Merge pull request #641 from EventFahrplan/schedule-update-alarm
Browse files Browse the repository at this point in the history
Simplify build flavor configuration by reading event dates from session data.
  • Loading branch information
johnjohndoe committed May 12, 2024
2 parents 0650a87 + a2a8be5 commit 76aa481
Show file tree
Hide file tree
Showing 18 changed files with 785 additions and 639 deletions.
18 changes: 0 additions & 18 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -96,12 +96,6 @@ android {
buildConfigField "String", "EVENT_WEBSITE_URL", '"https://events.ccc.de/congress/2023/"'
buildConfigField "String", "EVENT_POSTAL_ADDRESS", '"Congressplatz 1, 20355 Hamburg"'
buildConfigField "String", "SERVER_BACKEND_TYPE", '"frab"'
buildConfigField "int", "SCHEDULE_FIRST_DAY_START_YEAR", "2023"
buildConfigField "int", "SCHEDULE_FIRST_DAY_START_MONTH", "12"
buildConfigField "int", "SCHEDULE_FIRST_DAY_START_DAY", "27"
buildConfigField "int", "SCHEDULE_LAST_DAY_END_YEAR", "2023"
buildConfigField "int", "SCHEDULE_LAST_DAY_END_MONTH", "12"
buildConfigField "int", "SCHEDULE_LAST_DAY_END_DAY", "30"
buildConfigField "boolean", "SHOW_APP_DISCLAIMER", "false"
buildConfigField "boolean", "ENGAGE_C3NAV_APP_INSTALLATION", "true"
buildConfigField "String", "C3NAV_URL", '"https://37c3.c3nav.de/l/"'
Expand All @@ -124,12 +118,6 @@ android {
buildConfigField "String", "EVENT_WEBSITE_URL", '"http://events.ccc.de/camp/2023/"'
buildConfigField "String", "EVENT_POSTAL_ADDRESS", '"Ziegeleipark Mildenberg, 16792 Zehdenick"'
buildConfigField "String", "SERVER_BACKEND_TYPE", '"frab"'
buildConfigField "int", "SCHEDULE_FIRST_DAY_START_YEAR", "2023"
buildConfigField "int", "SCHEDULE_FIRST_DAY_START_MONTH", "8"
buildConfigField "int", "SCHEDULE_FIRST_DAY_START_DAY", "15"
buildConfigField "int", "SCHEDULE_LAST_DAY_END_YEAR", "2023"
buildConfigField "int", "SCHEDULE_LAST_DAY_END_MONTH", "8"
buildConfigField "int", "SCHEDULE_LAST_DAY_END_DAY", "19"
buildConfigField "boolean", "SHOW_APP_DISCLAIMER", "false"
buildConfigField "boolean", "ENGAGE_C3NAV_APP_INSTALLATION", "true"
buildConfigField "String", "C3NAV_URL", '"https://camp23.c3nav.de/l/"'
Expand All @@ -151,12 +139,6 @@ android {
buildConfigField "String", "EVENT_URL", '""'
buildConfigField "String", "EVENT_WEBSITE_URL", '"https://events.ccc.de/2022/11/28/dezentral-2022/"'
buildConfigField "String", "SERVER_BACKEND_TYPE", '"pretalx"'
buildConfigField "int", "SCHEDULE_FIRST_DAY_START_YEAR", "2022"
buildConfigField "int", "SCHEDULE_FIRST_DAY_START_MONTH", "12"
buildConfigField "int", "SCHEDULE_FIRST_DAY_START_DAY", "27"
buildConfigField "int", "SCHEDULE_LAST_DAY_END_YEAR", "2022"
buildConfigField "int", "SCHEDULE_LAST_DAY_END_MONTH", "12"
buildConfigField "int", "SCHEDULE_LAST_DAY_END_DAY", "31"
buildConfigField "boolean", "SHOW_APP_DISCLAIMER", "true"
buildConfigField "boolean", "ENABLE_ALTERNATIVE_SCHEDULE_URL", "true"
buildConfigField "boolean", "ENABLE_CHAOSFLIX_EXPORT", "true"
Expand Down
32 changes: 0 additions & 32 deletions app/src/main/java/nerd/tuxmobil/fahrplan/congress/MyApp.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,9 @@ package nerd.tuxmobil.fahrplan.congress

import android.app.Application
import androidx.annotation.CallSuper
import androidx.annotation.VisibleForTesting
import info.metadude.android.eventfahrplan.commons.logging.Logging
import info.metadude.android.eventfahrplan.commons.temporal.Moment
import info.metadude.android.eventfahrplan.commons.temporal.Moment.Companion.toMoment
import nerd.tuxmobil.fahrplan.congress.repositories.AppRepository
import nerd.tuxmobil.fahrplan.congress.utils.ConferenceTimeFrame
import org.ligi.tracedroid.TraceDroid
import org.threeten.bp.LocalDate
import org.threeten.bp.LocalTime
import org.threeten.bp.ZoneId
import org.threeten.bp.ZonedDateTime


class MyApp : Application() {
Expand All @@ -23,30 +15,6 @@ class MyApp : Application() {

companion object {

private val FIRST_DAY_START = getMoment(
"Europe/Paris",
BuildConfig.SCHEDULE_FIRST_DAY_START_YEAR,
BuildConfig.SCHEDULE_FIRST_DAY_START_MONTH,
BuildConfig.SCHEDULE_FIRST_DAY_START_DAY
)

private val LAST_DAY_END = getMoment(
"Europe/Paris",
BuildConfig.SCHEDULE_LAST_DAY_END_YEAR,
BuildConfig.SCHEDULE_LAST_DAY_END_MONTH,
BuildConfig.SCHEDULE_LAST_DAY_END_DAY
)

@VisibleForTesting
fun getMoment(timeZoneId: String, year: Int, month: Int, day: Int): Moment {
val zoneId = ZoneId.of(timeZoneId)
val localDate = LocalDate.of(year, month, day)
val zonedDateTime = ZonedDateTime.of(localDate, LocalTime.MIDNIGHT, zoneId)
return zonedDateTime.toMoment()
}

val conferenceTimeFrame = ConferenceTimeFrame(FIRST_DAY_START, LAST_DAY_END)

@JvmField
var taskRunning = TASKS.NONE

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ package nerd.tuxmobil.fahrplan.congress.alarms
import android.app.AlarmManager
import info.metadude.android.eventfahrplan.commons.logging.Logging
import info.metadude.android.eventfahrplan.commons.temporal.Moment
import nerd.tuxmobil.fahrplan.congress.models.ConferenceTimeFrame
import nerd.tuxmobil.fahrplan.congress.models.ConferenceTimeFrame.Known
import nerd.tuxmobil.fahrplan.congress.models.ConferenceTimeFrame.Unknown
import nerd.tuxmobil.fahrplan.congress.repositories.AppRepository
import nerd.tuxmobil.fahrplan.congress.utils.ConferenceTimeFrame

class AlarmUpdater(

Expand Down Expand Up @@ -41,40 +43,52 @@ class AlarmUpdater(
return developmentRefreshInterval
}

var interval: Long
var nextFetch: Moment
when {
conference.contains(moment) -> {
logging.d(LOG_TAG, "START <= moment < END")
interval = TWO_HOURS
nextFetch = moment.plusMilliseconds(interval)
}
conference.endsAtOrBefore(moment) -> {
logging.d(LOG_TAG, "START < END <= moment")
return when (conference) {
is Unknown -> {
listener.onCancelUpdateAlarm()
return 0
}
else -> {
logging.d(LOG_TAG, "moment < END")
interval = ONE_DAY
nextFetch = moment.plusMilliseconds(interval)
listener.onScheduleUpdateAlarm(ONE_DAY, moment.plusMilliseconds(ONE_DAY))
ONE_DAY
}
}
val shiftedTime = moment.plusDays(1)
if (conference.startsAfter(moment) && conference.startsAtOrBefore(shiftedTime)) {
logging.d(LOG_TAG, "moment < START && START <= shiftedTime")
interval = TWO_HOURS
nextFetch = conference.firstDayStartTime
if (!isInitial) {
listener.onCancelUpdateAlarm()
listener.onScheduleUpdateAlarm(interval, nextFetch)

is Known -> {
var interval: Long
var nextFetch: Moment
when {
conference.contains(moment) -> {
logging.d(LOG_TAG, "START <= moment < END")
interval = TWO_HOURS
nextFetch = moment.plusMilliseconds(interval)
}

conference.endsAtOrBefore(moment) -> {
logging.d(LOG_TAG, "START < END <= moment")
listener.onCancelUpdateAlarm()
return 0
}

else -> {
logging.d(LOG_TAG, "moment < END")
interval = ONE_DAY
nextFetch = moment.plusMilliseconds(interval)
}
}
val shiftedTime = moment.plusDays(1)
if (conference.startsAfter(moment) && conference.startsAtOrBefore(shiftedTime)) {
logging.d(LOG_TAG, "moment < START && START <= shiftedTime")
interval = TWO_HOURS
nextFetch = conference.firstDayStartTime
if (!isInitial) {
listener.onCancelUpdateAlarm()
listener.onScheduleUpdateAlarm(interval, nextFetch)
}
}
if (isInitial) {
listener.onCancelUpdateAlarm()
listener.onScheduleUpdateAlarm(interval, nextFetch)
}
interval
}
}
if (isInitial) {
listener.onCancelUpdateAlarm()
listener.onScheduleUpdateAlarm(interval, nextFetch)
}
return interval
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import nerd.tuxmobil.fahrplan.congress.net.ParseShiftsResult
import nerd.tuxmobil.fahrplan.congress.notifications.NotificationHelper
import nerd.tuxmobil.fahrplan.congress.repositories.AppRepository
import nerd.tuxmobil.fahrplan.congress.schedule.MainActivity
import nerd.tuxmobil.fahrplan.congress.utils.FahrplanMisc
import nerd.tuxmobil.fahrplan.congress.utils.PendingIntentCompat.FLAG_IMMUTABLE
import java.util.concurrent.CountDownLatch

Expand Down Expand Up @@ -133,7 +132,6 @@ class UpdateService : SafeJobIntentService() {
}

private fun fetchSchedule() {
FahrplanMisc.setUpdateAlarm(this, isInitial = false, logging)
fetchFahrplan()
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package nerd.tuxmobil.fahrplan.congress.models

import info.metadude.android.eventfahrplan.commons.temporal.Moment
import nerd.tuxmobil.fahrplan.congress.alarms.AlarmUpdater
import nerd.tuxmobil.fahrplan.congress.schedule.Conference

/**
* Represents the time frame of a conference which is taken into account by the [AlarmUpdater]
* to calculate if and when alarms should fire.
*
* These classes should not be used for other purposes.
* They are not a replacement for the [Conference] class.
*/
sealed interface ConferenceTimeFrame {

data object Unknown : ConferenceTimeFrame
data class Known(val firstDayStartTime: Moment, val lastDayEndTime: Moment) : ConferenceTimeFrame {

private val timeFrame = firstDayStartTime..lastDayEndTime

init {
check(isValid) { "Invalid conference time frame: $this" }
}

val isValid: Boolean
get() = timeFrame.start.isBefore(timeFrame.endInclusive)

operator fun contains(moment: Moment) =
startsAtOrBefore(moment) && timeFrame.endInclusive.isAfter(moment)

fun endsAtOrBefore(moment: Moment) =
timeFrame.endInclusive.isSimultaneousWith(moment) || timeFrame.endInclusive.isBefore(moment)

fun startsAfter(moment: Moment) =
timeFrame.start.isAfter(moment)

fun startsAtOrBefore(moment: Moment) =
timeFrame.start.isSimultaneousWith(moment) || timeFrame.start.isBefore(moment)

override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as Known
return timeFrame == other.timeFrame
}

override fun hashCode() = timeFrame.hashCode()

override fun toString() =
timeFrame.toString()

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package nerd.tuxmobil.fahrplan.congress.models

import info.metadude.android.eventfahrplan.commons.temporal.Moment
import nerd.tuxmobil.fahrplan.congress.schedule.Conference
import nerd.tuxmobil.fahrplan.congress.utils.ConferenceTimeFrame

/**
* Represents a "conference day" that is not bound to a specific date. Sessions can take place on
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ import nerd.tuxmobil.fahrplan.congress.dataconverters.toSessionsAppModel2
import nerd.tuxmobil.fahrplan.congress.dataconverters.toSessionsDatabaseModel
import nerd.tuxmobil.fahrplan.congress.exceptions.AppExceptionHandler
import nerd.tuxmobil.fahrplan.congress.models.Alarm
import nerd.tuxmobil.fahrplan.congress.models.ConferenceTimeFrame
import nerd.tuxmobil.fahrplan.congress.models.ConferenceTimeFrame.Known
import nerd.tuxmobil.fahrplan.congress.models.ConferenceTimeFrame.Unknown
import nerd.tuxmobil.fahrplan.congress.models.ScheduleData
import nerd.tuxmobil.fahrplan.congress.models.Session
import nerd.tuxmobil.fahrplan.congress.net.CustomHttpClient
Expand All @@ -77,6 +80,8 @@ import nerd.tuxmobil.fahrplan.congress.repositories.LoadScheduleState.InitialPar
import nerd.tuxmobil.fahrplan.congress.repositories.LoadScheduleState.ParseFailure
import nerd.tuxmobil.fahrplan.congress.repositories.LoadScheduleState.ParseSuccess
import nerd.tuxmobil.fahrplan.congress.repositories.LoadScheduleState.Parsing
import nerd.tuxmobil.fahrplan.congress.schedule.Conference
import nerd.tuxmobil.fahrplan.congress.schedule.FahrplanViewModel
import nerd.tuxmobil.fahrplan.congress.serialization.ScheduleChanges.Companion.computeSessionsWithChangeFlags
import nerd.tuxmobil.fahrplan.congress.utils.AlarmToneConversion
import nerd.tuxmobil.fahrplan.congress.validation.MetaValidation.validate
Expand Down Expand Up @@ -534,6 +539,17 @@ object AppRepository {
return readSessionBySessionId(sessionId)
}

/**
* Loads the conference time frame derived from the stored session data of all days.
*
* Keep code in sync with [FahrplanViewModel.requestScheduleUpdateAlarm]!
*/
fun loadConferenceTimeFrame(): ConferenceTimeFrame {
val sessions = loadSessionsForAllDays()
val timeFrame = if (sessions.isEmpty()) null else Conference.ofSessions(sessions).timeFrame
return if (timeFrame == null) Unknown else Known(timeFrame.start, timeFrame.endInclusive)
}

/**
* Loads all sessions from the database including Engelsystem shifts.
* The returned list might be empty.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ import nerd.tuxmobil.fahrplan.congress.models.Session
import org.threeten.bp.ZoneOffset

/**
* Represents either a single conference day, a few or all conference days. This class can be
* used in different scenarios. Further details are explained for the scenario of a single day
* but keep in mind to adapt this information to the particular scenario!
*
* Represents "a conference day" by holding the time values of when the first session of that
* conference day starts and when the last session of that conference day ends. Please note
* that "a conference day" does not need to be equivalent with "a natural day" starting at
Expand All @@ -18,7 +22,6 @@ import org.threeten.bp.ZoneOffset
* TODO: TechDebt: This implementation makes it impossible to represent a conference day which spans
* two different time zones. Refactoring this is a topic for a future enhancement.
*/
// TODO Use Moment class, merge with ConferenceTimeFrame class?
data class Conference(

val timeFrame: ClosedRange<Moment>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ import nerd.tuxmobil.fahrplan.congress.repositories.AppRepository
import nerd.tuxmobil.fahrplan.congress.schedule.observables.TimeTextViewParameter
import nerd.tuxmobil.fahrplan.congress.sharing.SessionSharer
import nerd.tuxmobil.fahrplan.congress.utils.ContentDescriptionFormatter
import nerd.tuxmobil.fahrplan.congress.utils.FahrplanMisc
import nerd.tuxmobil.fahrplan.congress.utils.Font
import nerd.tuxmobil.fahrplan.congress.utils.SessionPropertiesFormatter
import nerd.tuxmobil.fahrplan.congress.utils.TypefaceFactory
Expand Down Expand Up @@ -225,6 +226,9 @@ class FahrplanFragment : Fragment(), SessionViewEventsHandler {
val errorMessage = errorMessageFactory.getMessageForEmptySchedule(scheduleVersion)
errorMessage.show(requireContext(), shouldShowLong = false)
}
viewModel.activateScheduleUpdateAlarm.observe(viewLifecycleOwner) { conferenceTimeFrame ->
FahrplanMisc.setUpdateAlarm(requireContext(), conferenceTimeFrame, isInitial = false, logging)
}
viewModel.shareSimple.observe(viewLifecycleOwner) { formattedSession ->
SessionSharer.shareSimple(requireContext(), formattedSession)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,18 @@ import kotlinx.coroutines.channels.SendChannel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.launch
import nerd.tuxmobil.fahrplan.congress.alarms.AlarmServices
import nerd.tuxmobil.fahrplan.congress.alarms.SessionAlarmViewModelDelegate
import nerd.tuxmobil.fahrplan.congress.models.Alarm
import nerd.tuxmobil.fahrplan.congress.models.ConferenceTimeFrame
import nerd.tuxmobil.fahrplan.congress.models.ConferenceTimeFrame.Known
import nerd.tuxmobil.fahrplan.congress.models.ConferenceTimeFrame.Unknown
import nerd.tuxmobil.fahrplan.congress.models.ScheduleData
import nerd.tuxmobil.fahrplan.congress.models.Session
import nerd.tuxmobil.fahrplan.congress.notifications.NotificationHelper
Expand Down Expand Up @@ -76,6 +81,9 @@ internal class FahrplanViewModel(
private val mutableFahrplanEmptyParameter = Channel<FahrplanEmptyParameter>()
val fahrplanEmptyParameter = mutableFahrplanEmptyParameter.receiveAsFlow()

private val mutableActivateScheduleUpdateAlarm = Channel<ConferenceTimeFrame>()
val activateScheduleUpdateAlarm = mutableActivateScheduleUpdateAlarm.receiveAsFlow()

private val mutableShareSimple = Channel<String>()
val shareSimple = mutableShareSimple.receiveAsFlow()

Expand Down Expand Up @@ -107,6 +115,7 @@ internal class FahrplanViewModel(

init {
updateUncanceledSessions()
requestScheduleUpdateAlarm()
}

private fun updateUncanceledSessions() {
Expand All @@ -123,6 +132,27 @@ internal class FahrplanViewModel(
}
}

/**
* Observes all sessions of all days to check if the overall conference time frame changed.
* Actives a schedule update alarm when:
* - no sessions are present
* - sessions are emitted for the first time
* - the earliest start time of all sessions changed
* - the latest end time of all session changed
*
* Keep code in sync with [AppRepository.loadConferenceTimeFrame]!
*/
private fun requestScheduleUpdateAlarm() {
launch {
repository
.sessions
.map { if (it.isEmpty()) null else Conference.ofSessions(it).timeFrame }
.distinctUntilChanged()
.map { if (it == null) Unknown else Known(it.start, it.endInclusive) }
.collect { mutableActivateScheduleUpdateAlarm.sendOneTimeEvent(it) }
}
}

/**
* Rewrites properties to which "Engelshifts" has been applied before
* in ShiftExtensions -> Shift.toSessionAppModel.
Expand Down

0 comments on commit 76aa481

Please sign in to comment.