1
0
Fork 0
mirror of https://github.com/MatomoCamp/matomocamp-companion-android.git synced 2024-09-19 16:13:46 +02:00

Replace Date and Calendar classes with Java 8 time APIs (#74)

Enable multidex and Java 8 APIs desugaring in the project.
Also bump minSDK version to 18 to allow calling DateFormat.getBestDateTimePattern().
This commit is contained in:
Christophe Beyls 2021-12-31 18:01:18 +01:00 committed by GitHub
parent d35d8257ec
commit 5440860340
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
31 changed files with 267 additions and 237 deletions

View file

@ -12,8 +12,9 @@ android {
defaultConfig {
applicationId "be.digitalia.fosdem"
minSdkVersion 17
minSdkVersion 18
targetSdkVersion 31
multiDexEnabled = true
versionCode 1700205
versionName "2.0.5"
// Supported languages
@ -48,15 +49,15 @@ android {
}
packagingOptions {
exclude 'androidsupportmultidexversion.txt'
exclude 'DebugProbesKt.bin'
exclude 'kotlin-tooling-metadata.json'
}
}
debug {
multiDexEnabled = true
}
}
compileOptions {
coreLibraryDesugaringEnabled true
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
@ -80,6 +81,8 @@ dependencies {
def room_version = "2.4.0"
def okhttp_version = "3.12.13"
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
implementation "androidx.multidex:multidex:2.0.1"
implementation "com.google.dagger:hilt-android:$hilt_version"
kapt "com.google.dagger:hilt-compiler:$hilt_version"

View file

@ -28,8 +28,6 @@ class FosdemApplication : Application() {
override fun attachBaseContext(base: Context?) {
super.attachBaseContext(base)
if (BuildConfig.DEBUG) {
MultiDex.install(this)
}
MultiDex.install(this)
}
}

View file

@ -9,8 +9,6 @@ import android.net.Uri
import android.nfc.NdefRecord
import android.os.Build
import android.os.Bundle
import android.text.format.DateFormat
import android.text.format.DateUtils
import android.view.Menu
import android.view.MenuItem
import android.view.View
@ -47,9 +45,12 @@ import com.google.android.material.progressindicator.BaseProgressIndicator
import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import java.time.Duration
import java.time.Instant
import java.time.ZoneId
import java.time.format.DateTimeFormatter
import javax.inject.Inject
import javax.inject.Named
@ -92,6 +93,8 @@ class MainActivity : AppCompatActivity(R.layout.main), CreateNfcAppDataCallback
@Inject
lateinit var scheduleDao: ScheduleDao
private val latestUpdateDateTimeFormatter = DateTimeFormatter.ofPattern(LATEST_UPDATE_DATE_TIME_FORMAT)
private lateinit var holder: ViewHolder
private lateinit var drawerToggle: ActionBarDrawerToggle
@ -188,7 +191,7 @@ class MainActivity : AppCompatActivity(R.layout.main), CreateNfcAppDataCallback
val latestUpdateTextView: TextView = navigationView.findViewById(R.id.latest_update)
lifecycleScope.launch {
scheduleDao.latestUpdateTime.collect { time ->
val timeString = time?.let { DateFormat.format(LATEST_UPDATE_DATE_FORMAT, it) }
val timeString = time?.atZone(ZoneId.systemDefault())?.format(latestUpdateDateTimeFormatter)
?: getString(R.string.never)
latestUpdateTextView.text = getString(R.string.last_update, timeString)
}
@ -251,13 +254,15 @@ class MainActivity : AppCompatActivity(R.layout.main), CreateNfcAppDataCallback
// Scheduled database update
lifecycleScope.launch {
val now = System.currentTimeMillis()
val now = Instant.now()
val latestUpdateTime = scheduleDao.latestUpdateTime.first()
if (latestUpdateTime == null || latestUpdateTime.time < now - DATABASE_VALIDITY_DURATION) {
val latestAttemptTime = preferences.getLong(LATEST_UPDATE_ATTEMPT_TIME_PREF_KEY, -1L)
if (latestAttemptTime == -1L || latestAttemptTime < now - AUTO_UPDATE_SNOOZE_DURATION) {
if (latestUpdateTime == null || latestUpdateTime < now - DATABASE_VALIDITY_DURATION) {
val latestAttemptTime = Instant.ofEpochMilli(
preferences.getLong(LATEST_UPDATE_ATTEMPT_TIME_PREF_KEY, 0L)
)
if (latestAttemptTime == Instant.EPOCH || latestAttemptTime < now - AUTO_UPDATE_SNOOZE_DURATION) {
preferences.edit {
putLong(LATEST_UPDATE_ATTEMPT_TIME_PREF_KEY, now)
putLong(LATEST_UPDATE_ATTEMPT_TIME_PREF_KEY, now.toEpochMilli())
}
// Try to update immediately. If it fails, the user gets a message and a retry button.
api.downloadSchedule()
@ -358,9 +363,9 @@ class MainActivity : AppCompatActivity(R.layout.main), CreateNfcAppDataCallback
const val ACTION_SHORTCUT_LIVE = "${BuildConfig.APPLICATION_ID}.intent.action.SHORTCUT_LIVE"
private const val ERROR_MESSAGE_DISPLAY_DURATION = 5000
private const val DATABASE_VALIDITY_DURATION = DateUtils.DAY_IN_MILLIS
private const val AUTO_UPDATE_SNOOZE_DURATION = DateUtils.DAY_IN_MILLIS
private val DATABASE_VALIDITY_DURATION = Duration.ofDays(1L)
private val AUTO_UPDATE_SNOOZE_DURATION = Duration.ofDays(1L)
private const val LATEST_UPDATE_ATTEMPT_TIME_PREF_KEY = "latest_update_attempt_time"
private const val LATEST_UPDATE_DATE_FORMAT = "d MMM yyyy kk:mm:ss"
private const val LATEST_UPDATE_DATE_TIME_FORMAT = "d MMM yyyy kk:mm:ss"
}
}

View file

@ -12,7 +12,6 @@ import androidx.fragment.app.commit
import be.digitalia.fosdem.R
import be.digitalia.fosdem.fragments.PersonInfoListFragment
import be.digitalia.fosdem.model.Person
import be.digitalia.fosdem.utils.DateUtils
import be.digitalia.fosdem.utils.configureToolbarColors
import be.digitalia.fosdem.viewmodels.PersonInfoViewModel
import dagger.hilt.android.AndroidEntryPoint
@ -36,8 +35,7 @@ class PersonInfoActivity : AppCompatActivity(R.layout.person_info) {
// Look for the first non-placeholder event in the paged list
val statusEvent = viewModel.events.value?.firstOrNull { it != null }
if (statusEvent != null) {
val year = DateUtils.getYear(statusEvent.event.day.date.time)
val url = person.getUrl(year)
val url = person.getUrl(statusEvent.event.day.date.year)
if (url != null) {
try {
CustomTabsIntent.Builder()

View file

@ -24,12 +24,12 @@ import be.digitalia.fosdem.model.Event
import be.digitalia.fosdem.model.RoomStatus
import be.digitalia.fosdem.utils.DateUtils
import be.digitalia.fosdem.widgets.MultiChoiceHelper
import java.text.DateFormat
import java.time.format.DateTimeFormatter
class BookmarksAdapter(context: Context, private val multiChoiceHelper: MultiChoiceHelper) :
ListAdapter<Event, BookmarksAdapter.ViewHolder>(DIFF_CALLBACK) {
private val timeDateFormat = DateUtils.getTimeDateFormat(context)
private val timeFormatter = DateUtils.getTimeFormatter(context)
@ColorInt
private val errorColor: Int
@ -53,7 +53,7 @@ class BookmarksAdapter(context: Context, private val multiChoiceHelper: MultiCho
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_event, parent, false)
return ViewHolder(view, multiChoiceHelper, timeDateFormat, errorColor)
return ViewHolder(view, multiChoiceHelper, timeFormatter, errorColor)
}
private fun getRoomStatus(event: Event): RoomStatus? {
@ -101,7 +101,7 @@ class BookmarksAdapter(context: Context, private val multiChoiceHelper: MultiCho
}
class ViewHolder(itemView: View, helper: MultiChoiceHelper,
private val timeDateFormat: DateFormat, @ColorInt private val errorColor: Int)
private val timeFormatter: DateTimeFormatter, @ColorInt private val errorColor: Int)
: MultiChoiceHelper.ViewHolder(itemView, helper), View.OnClickListener {
private val title: TextView = itemView.findViewById(R.id.title)
private val persons: TextView = itemView.findViewById(R.id.persons)
@ -130,10 +130,8 @@ class BookmarksAdapter(context: Context, private val multiChoiceHelper: MultiCho
fun bindDetails(event: Event, previous: Event?, next: Event?, roomStatus: RoomStatus?) {
val context = details.context
val startTime = event.startTime
val endTime = event.endTime
val startTimeString = if (startTime != null) timeDateFormat.format(startTime) else "?"
val endTimeString = if (endTime != null) timeDateFormat.format(endTime) else "?"
val startTimeString = event.startTime?.atZone(DateUtils.conferenceZoneId)?.format(timeFormatter) ?: "?"
val endTimeString = event.endTime?.atZone(DateUtils.conferenceZoneId)?.format(timeFormatter) ?: "?"
val roomName = event.roomName.orEmpty()
val detailsText: CharSequence = "${event.day.shortName}, $startTimeString$endTimeString | $roomName"
val detailsSpannable = SpannableString(detailsText)

View file

@ -21,12 +21,12 @@ import be.digitalia.fosdem.model.Event
import be.digitalia.fosdem.model.RoomStatus
import be.digitalia.fosdem.model.StatusEvent
import be.digitalia.fosdem.utils.DateUtils
import java.text.DateFormat
import java.time.format.DateTimeFormatter
class EventsAdapter constructor(context: Context, private val showDay: Boolean = true) :
PagedListAdapter<StatusEvent, EventsAdapter.ViewHolder>(DIFF_CALLBACK) {
private val timeDateFormat = DateUtils.getTimeDateFormat(context)
private val timeFormatter = DateUtils.getTimeFormatter(context)
var roomStatuses: Map<String, RoomStatus>? = null
set(value) {
@ -38,7 +38,7 @@ class EventsAdapter constructor(context: Context, private val showDay: Boolean =
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_event, parent, false)
return ViewHolder(view, timeDateFormat)
return ViewHolder(view, timeFormatter)
}
private fun getRoomStatus(event: Event): RoomStatus? {
@ -70,7 +70,7 @@ class EventsAdapter constructor(context: Context, private val showDay: Boolean =
}
}
class ViewHolder(itemView: View, private val timeDateFormat: DateFormat)
class ViewHolder(itemView: View, private val timeFormatter: DateTimeFormatter)
: RecyclerView.ViewHolder(itemView), View.OnClickListener {
private val title: TextView = itemView.findViewById(R.id.title)
private val persons: TextView = itemView.findViewById(R.id.persons)
@ -112,10 +112,8 @@ class EventsAdapter constructor(context: Context, private val showDay: Boolean =
fun bindDetails(event: Event, showDay: Boolean, roomStatus: RoomStatus?) {
val context = details.context
val startTime = event.startTime
val endTime = event.endTime
val startTimeString = if (startTime != null) timeDateFormat.format(startTime) else "?"
val endTimeString = if (endTime != null) timeDateFormat.format(endTime) else "?"
val startTimeString = event.startTime?.atZone(DateUtils.conferenceZoneId)?.format(timeFormatter) ?: "?"
val endTimeString = event.endTime?.atZone(DateUtils.conferenceZoneId)?.format(timeFormatter) ?: "?"
val roomName = event.roomName.orEmpty()
var detailsText: CharSequence = if (showDay) {
"${event.day.shortName}, $startTimeString$endTimeString | $roomName"

View file

@ -18,6 +18,7 @@ import be.digitalia.fosdem.R
import be.digitalia.fosdem.model.Event
import be.digitalia.fosdem.model.StatusEvent
import be.digitalia.fosdem.utils.DateUtils
import java.time.Instant
class TrackScheduleAdapter(context: Context, private val listener: EventClickListener? = null)
: ListAdapter<StatusEvent, TrackScheduleAdapter.ViewHolder>(EventsAdapter.DIFF_CALLBACK) {
@ -26,7 +27,7 @@ class TrackScheduleAdapter(context: Context, private val listener: EventClickLis
fun onEventClick(event: Event)
}
private val timeDateFormat = DateUtils.getTimeDateFormat(context)
private val timeFormatter = DateUtils.getTimeFormatter(context)
@ColorInt
private val timeBackgroundColor: Int = ContextCompat.getColor(context, R.color.schedule_time_background)
@ColorInt
@ -46,7 +47,7 @@ class TrackScheduleAdapter(context: Context, private val listener: EventClickLis
}
}
var currentTime: Long = -1L
var currentTime: Instant? = null
set(value) {
if (field != value) {
field = value
@ -127,7 +128,7 @@ class TrackScheduleAdapter(context: Context, private val listener: EventClickLis
val context = itemView.context
this.event = event
time.text = event.startTime?.let { timeDateFormat.format(it) }
time.text = event.startTime?.atZone(DateUtils.conferenceZoneId)?.format(timeFormatter)
title.text = event.title
val bookmarkDrawable = if (isBookmarked) AppCompatResources.getDrawable(context, R.drawable.ic_bookmark_white_24dp) else null
TextViewCompat.setCompoundDrawablesRelativeWithIntrinsicBounds(title, null, null, bookmarkDrawable, null)
@ -141,8 +142,8 @@ class TrackScheduleAdapter(context: Context, private val listener: EventClickLis
room.contentDescription = context.getString(R.string.room_content_description, event.roomName.orEmpty())
}
fun bindTimeColors(event: Event, currentTime: Long) {
if (currentTime != -1L && event.isRunningAtTime(currentTime)) {
fun bindTimeColors(event: Event, currentTime: Instant?) {
if (currentTime != null && event.isRunningAtTime(currentTime)) {
// Contrast colors for running event
time.setBackgroundColor(timeRunningBackgroundColor)
time.setTextColor(timeRunningForegroundColor)

View file

@ -1,7 +1,6 @@
package be.digitalia.fosdem.api
import android.os.SystemClock
import android.text.format.DateUtils
import androidx.annotation.MainThread
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
@ -20,6 +19,7 @@ import be.digitalia.fosdem.parsers.EventsParser
import be.digitalia.fosdem.parsers.RoomStatusesParser
import be.digitalia.fosdem.utils.BackgroundWorkScope
import be.digitalia.fosdem.utils.ByteCountSource
import be.digitalia.fosdem.utils.DateUtils
import be.digitalia.fosdem.utils.network.HttpClient
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Job
@ -27,6 +27,8 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import okio.buffer
import java.time.LocalTime
import java.util.concurrent.TimeUnit
import javax.inject.Inject
import javax.inject.Singleton
import kotlin.math.pow
@ -103,9 +105,12 @@ class FosdemApi @Inject constructor(
val startEndTimestamps = LongArray(days.size * 2)
var index = 0
for (day in days) {
val dayStart = day.date.time
startEndTimestamps[index++] = dayStart + DAY_START_TIME
startEndTimestamps[index++] = dayStart + DAY_END_TIME
startEndTimestamps[index++] = day.date.atTime(DAY_START_TIME)
.atZone(DateUtils.conferenceZoneId)
.toEpochSecond() * 1000L
startEndTimestamps[index++] = day.date.atTime(DAY_END_TIME)
.atZone(DateUtils.conferenceZoneId)
.toEpochSecond() * 1000L
}
scheduler(*startEndTimestamps)
}
@ -170,13 +175,10 @@ class FosdemApi @Inject constructor(
}
companion object {
// 8:30 (local time)
private const val DAY_START_TIME = 8 * DateUtils.HOUR_IN_MILLIS + 30 * DateUtils.MINUTE_IN_MILLIS
// 19:00 (local time)
private const val DAY_END_TIME = 19 * DateUtils.HOUR_IN_MILLIS
private const val ROOM_STATUS_REFRESH_DELAY = 90L * DateUtils.SECOND_IN_MILLIS
private const val ROOM_STATUS_FIRST_RETRY_DELAY = 30L * DateUtils.SECOND_IN_MILLIS
private const val ROOM_STATUS_EXPIRATION_DELAY = 6L * DateUtils.MINUTE_IN_MILLIS
private val DAY_START_TIME = LocalTime.of(8, 30)
private val DAY_END_TIME = LocalTime.of(19, 0)
private val ROOM_STATUS_REFRESH_DELAY = TimeUnit.SECONDS.toMillis(90L)
private val ROOM_STATUS_FIRST_RETRY_DELAY = TimeUnit.SECONDS.toMillis(30L)
private val ROOM_STATUS_EXPIRATION_DELAY = TimeUnit.MINUTES.toMillis(6L)
}
}

View file

@ -6,19 +6,22 @@ import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.TypeConverters
import androidx.room.withTransaction
import be.digitalia.fosdem.db.converters.NonNullInstantTypeConverters
import be.digitalia.fosdem.db.entities.Bookmark
import be.digitalia.fosdem.model.AlarmInfo
import be.digitalia.fosdem.model.Event
import be.digitalia.fosdem.utils.BackgroundWorkScope
import kotlinx.coroutines.launch
import java.time.Instant
@Dao
abstract class BookmarksDao(private val appDatabase: AppDatabase) {
/**
* Returns the bookmarks.
*
* @param minStartTime When greater than 0, only return the events starting after this time.
* @param minStartTime When greater than Instant.EPOCH, only return the events starting after this time.
*/
@Query("""SELECT e.id, e.start_time, e.end_time, e.room_name, e.slug, et.title, et.subtitle, e.abstract, e.description,
GROUP_CONCAT(p.name, ', ') AS persons, e.day_index, d.date AS day_date, e.track_id, t.name AS track_name, t.type AS track_type
@ -32,7 +35,8 @@ abstract class BookmarksDao(private val appDatabase: AppDatabase) {
WHERE e.start_time > :minStartTime
GROUP BY e.id
ORDER BY e.start_time ASC""")
abstract fun getBookmarks(minStartTime: Long): LiveData<List<Event>>
@TypeConverters(NonNullInstantTypeConverters::class)
abstract fun getBookmarks(minStartTime: Instant): LiveData<List<Event>>
@Query("""SELECT e.id, e.start_time, e.end_time, e.room_name, e.slug, et.title, et.subtitle, e.abstract, e.description,
GROUP_CONCAT(p.name, ', ') AS persons, e.day_index, d.date AS day_date, e.track_id, t.name AS track_name, t.type AS track_type
@ -54,7 +58,8 @@ abstract class BookmarksDao(private val appDatabase: AppDatabase) {
WHERE e.start_time > :minStartTime
ORDER BY e.start_time ASC""")
@WorkerThread
abstract fun getBookmarksAlarmInfo(minStartTime: Long): Array<AlarmInfo>
@TypeConverters(NonNullInstantTypeConverters::class)
abstract fun getBookmarksAlarmInfo(minStartTime: Instant): Array<AlarmInfo>
@Query("SELECT COUNT(*) FROM bookmarks WHERE event_id = :event")
abstract fun getBookmarkStatus(event: Event): LiveData<Boolean>

View file

@ -12,6 +12,8 @@ import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Transaction
import androidx.room.TypeConverters
import be.digitalia.fosdem.db.converters.NonNullInstantTypeConverters
import be.digitalia.fosdem.db.entities.EventEntity
import be.digitalia.fosdem.db.entities.EventTitles
import be.digitalia.fosdem.db.entities.EventToPerson
@ -24,7 +26,6 @@ import be.digitalia.fosdem.model.Person
import be.digitalia.fosdem.model.StatusEvent
import be.digitalia.fosdem.model.Track
import be.digitalia.fosdem.utils.BackgroundWorkScope
import be.digitalia.fosdem.utils.DateUtils
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow
@ -33,7 +34,8 @@ import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.runBlocking
import java.util.Date
import java.time.Instant
import java.time.LocalDate
import java.util.HashSet
@Dao
@ -42,8 +44,8 @@ abstract class ScheduleDao(private val appDatabase: AppDatabase) {
/**
* @return The latest update time, or null if not available.
*/
val latestUpdateTime: Flow<Date?> = appDatabase.dataStore.data.map { prefs ->
prefs[LATEST_UPDATE_TIME_PREF_KEY]?.let { Date(it) }
val latestUpdateTime: Flow<Instant?> = appDatabase.dataStore.data.map { prefs ->
prefs[LATEST_UPDATE_TIME_PREF_KEY]?.let { Instant.ofEpochMilli(it) }
}
/**
@ -69,11 +71,11 @@ abstract class ScheduleDao(private val appDatabase: AppDatabase) {
0
}
if (totalEvents > 0) { // Set last update time and server's last modified tag
val now = System.currentTimeMillis()
val now = Instant.now()
runBlocking {
appDatabase.dataStore.edit { prefs ->
prefs.clear()
prefs[LATEST_UPDATE_TIME_PREF_KEY] = now
prefs[LATEST_UPDATE_TIME_PREF_KEY] = now.toEpochMilli()
if (lastModifiedTag != null) {
prefs[LAST_MODIFIED_TAG_PREF] = lastModifiedTag
}
@ -230,16 +232,9 @@ abstract class ScheduleDao(private val appDatabase: AppDatabase) {
protected abstract fun getDaysInternal(): Flow<List<Day>>
suspend fun getYear(): Int {
// Compute from days if available
val days = days.first()
val date = if (days.isNotEmpty()) {
days[0].date.time
} else {
// Use the current year by default
System.currentTimeMillis()
}
return DateUtils.getYear(date)
// Compute from days if available, fall back to current year
val date = days.first().firstOrNull()?.date ?: LocalDate.now()
return date.year
}
@Query("""SELECT t.id, t.name, t.type FROM tracks t
@ -332,7 +327,8 @@ abstract class ScheduleDao(private val appDatabase: AppDatabase) {
WHERE e.start_time BETWEEN :minStartTime AND :maxStartTime
GROUP BY e.id
ORDER BY e.start_time ASC""")
abstract fun getEventsWithStartTime(minStartTime: Long, maxStartTime: Long): DataSource.Factory<Int, StatusEvent>
@TypeConverters(NonNullInstantTypeConverters::class)
abstract fun getEventsWithStartTime(minStartTime: Instant, maxStartTime: Instant): DataSource.Factory<Int, StatusEvent>
/**
* Returns events in progress at the specified time, ordered by descending start time.
@ -350,7 +346,8 @@ abstract class ScheduleDao(private val appDatabase: AppDatabase) {
WHERE e.start_time <= :time AND :time < e.end_time
GROUP BY e.id
ORDER BY e.start_time DESC""")
abstract fun getEventsInProgress(time: Long): DataSource.Factory<Int, StatusEvent>
@TypeConverters(NonNullInstantTypeConverters::class)
abstract fun getEventsInProgress(time: Instant): DataSource.Factory<Int, StatusEvent>
/**
* Returns the events presented by the specified person.

View file

@ -1,14 +0,0 @@
package be.digitalia.fosdem.db.converters
import androidx.room.TypeConverter
import java.util.Date
object NonNullDateTypeConverters {
@JvmStatic
@TypeConverter
fun toDate(value: Long): Date = Date(value)
@JvmStatic
@TypeConverter
fun fromDate(value: Date): Long = value.time
}

View file

@ -0,0 +1,14 @@
package be.digitalia.fosdem.db.converters
import androidx.room.TypeConverter
import java.time.Instant
object NonNullInstantTypeConverters {
@JvmStatic
@TypeConverter
fun toInstant(value: Long): Instant = Instant.ofEpochSecond(value)
@JvmStatic
@TypeConverter
fun fromInstant(value: Instant): Long = value.epochSecond
}

View file

@ -0,0 +1,14 @@
package be.digitalia.fosdem.db.converters
import androidx.room.TypeConverter
import java.time.LocalDate
object NonNullLocalDateTypeConverters {
@JvmStatic
@TypeConverter
fun toLocalDate(value: Long): LocalDate = LocalDate.ofEpochDay(value)
@JvmStatic
@TypeConverter
fun fromLocalDate(value: LocalDate): Long = value.toEpochDay()
}

View file

@ -1,14 +0,0 @@
package be.digitalia.fosdem.db.converters
import androidx.room.TypeConverter
import java.util.Date
object NullableDateTypeConverters {
@JvmStatic
@TypeConverter
fun toDate(value: Long?): Date? = value?.let { Date(it) }
@JvmStatic
@TypeConverter
fun fromDate(value: Date?): Long? = value?.time
}

View file

@ -0,0 +1,14 @@
package be.digitalia.fosdem.db.converters
import androidx.room.TypeConverter
import java.time.Instant
object NullableInstantTypeConverters {
@JvmStatic
@TypeConverter
fun toInstant(value: Long?): Instant? = value?.let { Instant.ofEpochSecond(it) }
@JvmStatic
@TypeConverter
fun fromInstant(value: Instant?): Long? = value?.epochSecond
}

View file

@ -5,8 +5,8 @@ import androidx.room.Entity
import androidx.room.Index
import androidx.room.PrimaryKey
import androidx.room.TypeConverters
import be.digitalia.fosdem.db.converters.NullableDateTypeConverters
import java.util.Date
import be.digitalia.fosdem.db.converters.NullableInstantTypeConverters
import java.time.Instant
@Entity(tableName = EventEntity.TABLE_NAME, indices = [
Index(value = ["day_index"], name = "event_day_index_idx"),
@ -20,11 +20,11 @@ class EventEntity(
@ColumnInfo(name = "day_index")
val dayIndex: Int,
@ColumnInfo(name = "start_time")
@field:TypeConverters(NullableDateTypeConverters::class)
val startTime: Date?,
@field:TypeConverters(NullableInstantTypeConverters::class)
val startTime: Instant?,
@ColumnInfo(name = "end_time")
@field:TypeConverters(NullableDateTypeConverters::class)
val endTime: Date?,
@field:TypeConverters(NullableInstantTypeConverters::class)
val endTime: Instant?,
@ColumnInfo(name = "room_name")
val roomName: String?,
val slug: String?,

View file

@ -99,9 +99,9 @@ class EventDetailsFragment : Fragment(R.layout.fragment_event_details) {
}
view.findViewById<TextView>(R.id.time).apply {
val timeDateFormat = DateUtils.getTimeDateFormat(context)
val startTime = event.startTime?.let { timeDateFormat.format(it) } ?: "?"
val endTime = event.endTime?.let { timeDateFormat.format(it) } ?: "?"
val timeFormatter = DateUtils.getTimeFormatter(context)
val startTime = event.startTime?.atZone(DateUtils.conferenceZoneId)?.format(timeFormatter) ?: "?"
val endTime = event.endTime?.atZone(DateUtils.conferenceZoneId)?.format(timeFormatter) ?: "?"
text = "${event.day}, $startTime$endTime"
contentDescription = getString(R.string.time_content_description, text)
}
@ -226,8 +226,8 @@ class EventDetailsFragment : Fragment(R.layout.fragment_event_details) {
description = "$speakersLabel: $personsSummary\n\n$description"
}
putExtra(CalendarContract.Events.DESCRIPTION, description)
event.startTime?.let { putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, it.time) }
event.endTime?.let { putExtra(CalendarContract.EXTRA_EVENT_END_TIME, it.time) }
event.startTime?.let { putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, it.toEpochMilli()) }
event.endTime?.let { putExtra(CalendarContract.EXTRA_EVENT_END_TIME, it.toEpochMilli()) }
}
try {

View file

@ -5,14 +5,13 @@ import android.os.SystemClock
import androidx.core.os.HandlerCompat
import androidx.lifecycle.LiveData
import java.util.Arrays
import java.util.concurrent.TimeUnit
object LiveDataFactory {
private val handler = HandlerCompat.createAsync(Looper.getMainLooper())
fun interval(period: Long, unit: TimeUnit): LiveData<Long> {
return IntervalLiveData(unit.toMillis(period))
fun interval(periodInMillis: Long): LiveData<Long> {
return IntervalLiveData(periodInMillis)
}
/**

View file

@ -3,17 +3,17 @@ package be.digitalia.fosdem.model
import android.os.Parcelable
import androidx.room.ColumnInfo
import androidx.room.TypeConverters
import be.digitalia.fosdem.db.converters.NullableDateTypeConverters
import be.digitalia.fosdem.utils.DateParceler
import be.digitalia.fosdem.db.converters.NullableInstantTypeConverters
import be.digitalia.fosdem.utils.InstantParceler
import kotlinx.parcelize.Parcelize
import kotlinx.parcelize.WriteWith
import java.util.Date
import java.time.Instant
@Parcelize
data class AlarmInfo(
@ColumnInfo(name = "event_id")
val eventId: Long,
@ColumnInfo(name = "start_time")
@field:TypeConverters(NullableDateTypeConverters::class)
val startTime: @WriteWith<DateParceler> Date?
@field:TypeConverters(NullableInstantTypeConverters::class)
val startTime: @WriteWith<InstantParceler> Instant?
) : Parcelable

View file

@ -4,13 +4,12 @@ import android.os.Parcelable
import androidx.room.Entity
import androidx.room.PrimaryKey
import androidx.room.TypeConverters
import be.digitalia.fosdem.db.converters.NonNullDateTypeConverters
import be.digitalia.fosdem.utils.DateParceler
import be.digitalia.fosdem.utils.DateUtils.withBelgiumTimeZone
import be.digitalia.fosdem.db.converters.NonNullLocalDateTypeConverters
import be.digitalia.fosdem.utils.LocalDateParceler
import kotlinx.parcelize.Parcelize
import kotlinx.parcelize.WriteWith
import java.text.SimpleDateFormat
import java.util.Date
import java.time.LocalDate
import java.time.format.DateTimeFormatter
import java.util.Locale
@Entity(tableName = Day.TABLE_NAME)
@ -18,8 +17,8 @@ import java.util.Locale
data class Day(
@PrimaryKey
val index: Int,
@field:TypeConverters(NonNullDateTypeConverters::class)
val date: @WriteWith<DateParceler> Date
@field:TypeConverters(NonNullLocalDateTypeConverters::class)
val date: @WriteWith<LocalDateParceler> LocalDate
) : Comparable<Day>, Parcelable {
val name: String
@ -37,6 +36,6 @@ data class Day(
companion object {
const val TABLE_NAME = "days"
private val DAY_DATE_FORMAT = SimpleDateFormat("EEEE", Locale.US).withBelgiumTimeZone()
private val DAY_DATE_FORMAT = DateTimeFormatter.ofPattern("EEEE", Locale.US)
}
}

View file

@ -5,12 +5,12 @@ import androidx.room.ColumnInfo
import androidx.room.Embedded
import androidx.room.TypeConverters
import be.digitalia.fosdem.api.FosdemUrls
import be.digitalia.fosdem.db.converters.NullableDateTypeConverters
import be.digitalia.fosdem.utils.DateParceler
import be.digitalia.fosdem.utils.DateUtils
import be.digitalia.fosdem.db.converters.NullableInstantTypeConverters
import be.digitalia.fosdem.utils.InstantParceler
import kotlinx.parcelize.Parcelize
import kotlinx.parcelize.WriteWith
import java.util.Date
import java.time.Duration
import java.time.Instant
@Parcelize
data class Event(
@ -18,11 +18,11 @@ data class Event(
@Embedded(prefix = "day_")
val day: Day,
@ColumnInfo(name = "start_time")
@field:TypeConverters(NullableDateTypeConverters::class)
val startTime: @WriteWith<DateParceler> Date? = null,
@field:TypeConverters(NullableInstantTypeConverters::class)
val startTime: @WriteWith<InstantParceler> Instant? = null,
@ColumnInfo(name = "end_time")
@field:TypeConverters(NullableDateTypeConverters::class)
val endTime: @WriteWith<DateParceler> Date? = null,
@field:TypeConverters(NullableInstantTypeConverters::class)
val endTime: @WriteWith<InstantParceler> Instant? = null,
@ColumnInfo(name = "room_name")
val roomName: String?,
val slug: String?,
@ -38,22 +38,21 @@ data class Event(
val personsSummary: String?
) : Parcelable {
fun isRunningAtTime(time: Long): Boolean {
return startTime != null && endTime != null && time in startTime.time..endTime.time
fun isRunningAtTime(time: Instant): Boolean {
return startTime != null && endTime != null && time in startTime..endTime
}
/**
* @return The event duration in minutes
*/
val duration: Int
val duration: Duration
get() = if (startTime == null || endTime == null) {
0
} else ((endTime.time - startTime.time) / android.text.format.DateUtils.MINUTE_IN_MILLIS).toInt()
Duration.ZERO
} else {
Duration.between(startTime, endTime)
}
val url: String?
get() {
val s = slug ?: return null
return FosdemUrls.getEvent(s, DateUtils.getYear(day.date.time))
return FosdemUrls.getEvent(s, day.date.year)
}
override fun toString(): String = title.orEmpty()

View file

@ -20,8 +20,7 @@ data class Person(
) : Parcelable {
fun getUrl(year: Int): String? {
val n = name ?: return null
return FosdemUrls.getPerson(n.toSlug(), year)
return name?.let { FosdemUrls.getPerson(it.toSlug(), year) }
}
override fun toString(): String = name.orEmpty()

View file

@ -7,8 +7,7 @@ import be.digitalia.fosdem.model.EventDetails
import be.digitalia.fosdem.model.Link
import be.digitalia.fosdem.model.Person
import be.digitalia.fosdem.model.Track
import be.digitalia.fosdem.utils.DateUtils.belgiumTimeZone
import be.digitalia.fosdem.utils.DateUtils.withBelgiumTimeZone
import be.digitalia.fosdem.utils.DateUtils
import be.digitalia.fosdem.utils.isEndDocument
import be.digitalia.fosdem.utils.isNextEndTag
import be.digitalia.fosdem.utils.isStartTag
@ -16,10 +15,9 @@ import be.digitalia.fosdem.utils.skipToEndTag
import be.digitalia.fosdem.utils.xmlPullParserFactory
import okio.BufferedSource
import org.xmlpull.v1.XmlPullParser
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Date
import java.util.Locale
import java.time.Duration
import java.time.Instant
import java.time.LocalDate
/**
* Main parser for FOSDEM schedule data in pentabarf XML format.
@ -28,10 +26,6 @@ import java.util.Locale
*/
class EventsParser : Parser<Sequence<DetailedEvent>> {
private val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.US).withBelgiumTimeZone()
// Calendar used to compute the events time, according to Belgium timezone
private val calendar = Calendar.getInstance(belgiumTimeZone, Locale.US)
override fun parse(source: BufferedSource): Sequence<DetailedEvent> {
val parser: XmlPullParser = xmlPullParserFactory.newPullParser().apply {
setInput(source.inputStream(), null)
@ -48,7 +42,7 @@ class EventsParser : Parser<Sequence<DetailedEvent>> {
"day" -> {
currentDay = Day(
index = parser.getAttributeValue(null, "index")!!.toInt(),
date = dateFormat.parse(parser.getAttributeValue(null, "date"))!!
date = LocalDate.parse(parser.getAttributeValue(null, "date"))
)
}
"room" -> currentRoomName = parser.getAttributeValue(null, "name")
@ -65,7 +59,7 @@ class EventsParser : Parser<Sequence<DetailedEvent>> {
private fun parseEvent(parser: XmlPullParser, day: Day, roomName: String?): DetailedEvent {
val id = parser.getAttributeValue(null, "id")!!.toLong()
var startTime: Date? = null
var startTime: Instant? = null
var duration: String? = null
var slug: String? = null
var title: String? = null
@ -83,12 +77,10 @@ class EventsParser : Parser<Sequence<DetailedEvent>> {
"start" -> {
val timeString = parser.nextText()
if (!timeString.isNullOrEmpty()) {
startTime = with(calendar) {
time = day.date
set(Calendar.HOUR_OF_DAY, getHours(timeString))
set(Calendar.MINUTE, getMinutes(timeString))
time
}
startTime = day.date
.atTime(getHours(timeString), getMinutes(timeString))
.atZone(DateUtils.conferenceZoneId)
.toInstant()
}
}
"duration" -> duration = parser.nextText()
@ -128,11 +120,7 @@ class EventsParser : Parser<Sequence<DetailedEvent>> {
}
val endTime = if (startTime != null && !duration.isNullOrEmpty()) {
with(calendar) {
add(Calendar.HOUR_OF_DAY, getHours(duration))
add(Calendar.MINUTE, getMinutes(duration))
time
}
startTime + Duration.ofMinutes(getHours(duration) * 60L + getMinutes(duration))
} else null
val event = Event(

View file

@ -18,7 +18,6 @@ import be.digitalia.fosdem.db.BookmarksDao
import be.digitalia.fosdem.db.ScheduleDao
import be.digitalia.fosdem.ical.ICalendarWriter
import be.digitalia.fosdem.model.Event
import be.digitalia.fosdem.utils.DateUtils
import be.digitalia.fosdem.utils.stripHtml
import be.digitalia.fosdem.utils.toSlug
import dagger.hilt.EntryPoint
@ -31,10 +30,10 @@ import okio.sink
import java.io.FileNotFoundException
import java.io.IOException
import java.io.OutputStream
import java.text.SimpleDateFormat
import java.util.Calendar
import java.time.LocalDateTime
import java.time.ZoneOffset
import java.time.format.DateTimeFormatter
import java.util.Locale
import java.util.TimeZone
/**
* Content Provider generating the current bookmarks list in iCalendar format.
@ -101,12 +100,7 @@ class BookmarksExportProvider : ContentProvider() {
}
private class DownloadThread(private val outputStream: OutputStream, private val bookmarksDao: BookmarksDao) : Thread() {
private val calendar = Calendar.getInstance(DateUtils.belgiumTimeZone, Locale.US)
// Format all times in GMT
private val dateFormat = SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'", Locale.US).apply {
timeZone = TimeZone.getTimeZone("GMT+0")
}
private val dtStamp = dateFormat.format(System.currentTimeMillis())
private val dtStamp = LocalDateTime.now(ZoneOffset.UTC).format(DATE_TIME_FORMAT)
override fun run() {
try {
@ -130,11 +124,11 @@ class BookmarksExportProvider : ContentProvider() {
private fun writeEvent(writer: ICalendarWriter, event: Event) = with(writer) {
write("BEGIN", "VEVENT")
val year = DateUtils.getYear(event.day.date.time, calendar)
val year = event.day.date.year
write("UID", "${event.id}@$year@${BuildConfig.APPLICATION_ID}")
write("DTSTAMP", dtStamp)
event.startTime?.let { write("DTSTART", dateFormat.format(it)) }
event.endTime?.let { write("DTEND", dateFormat.format(it)) }
event.startTime?.let { write("DTSTART", it.atOffset(ZoneOffset.UTC).format(DATE_TIME_FORMAT)) }
event.endTime?.let { write("DTEND", it.atOffset(ZoneOffset.UTC).format(DATE_TIME_FORMAT)) }
write("SUMMARY", event.title)
var description = event.abstractText
if (description.isNullOrEmpty()) {
@ -176,6 +170,7 @@ class BookmarksExportProvider : ContentProvider() {
.appendPath("bookmarks.ics")
.build()
private val COLUMNS = arrayOf(OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE)
private val DATE_TIME_FORMAT = DateTimeFormatter.ofPattern("yyyyMMdd'T'HHmmss'Z'", Locale.US)
fun getIntent(activity: Activity): Intent {
// Supports granting read permission for the attached shared file

View file

@ -40,6 +40,7 @@ import be.digitalia.fosdem.utils.roomNameToResourceName
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking
import java.time.Instant
import javax.inject.Inject
/**
@ -75,9 +76,9 @@ class AlarmIntentService : JobIntentService() {
val delay = runBlocking { userSettingsProvider.notificationsDelayInMillis.first() }
val now = System.currentTimeMillis()
var hasAlarms = false
for (info in bookmarksDao.getBookmarksAlarmInfo(0L)) {
for (info in bookmarksDao.getBookmarksAlarmInfo(Instant.EPOCH)) {
val startTime = info.startTime
val notificationTime = if (startTime == null) -1L else startTime.time - delay
val notificationTime = if (startTime == null) -1L else startTime.toEpochMilli() - delay
val pi = getAlarmPendingIntent(info.eventId)
if (notificationTime < now) {
// Cancel pending alarms that are now scheduled in the past, if any
@ -94,7 +95,7 @@ class AlarmIntentService : JobIntentService() {
}
ACTION_DISABLE_ALARMS -> {
// Cancel alarms of every bookmark in the future
for (info in bookmarksDao.getBookmarksAlarmInfo(System.currentTimeMillis())) {
for (info in bookmarksDao.getBookmarksAlarmInfo(Instant.now())) {
alarmManager.cancel(getAlarmPendingIntent(info.eventId))
}
setAlarmReceiverEnabled(false)
@ -107,7 +108,7 @@ class AlarmIntentService : JobIntentService() {
var isFirstAlarm = true
for ((eventId, startTime) in alarmInfos) {
// Only schedule future events. If they start before the delay, the alarm will go off immediately
if (startTime != null && startTime.time >= now) {
if (startTime != null && startTime.toEpochMilli() >= now) {
if (isFirstAlarm) {
setAlarmReceiverEnabled(true)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
@ -115,7 +116,10 @@ class AlarmIntentService : JobIntentService() {
}
isFirstAlarm = false
}
AlarmManagerCompat.setExactAndAllowWhileIdle(alarmManager, AlarmManager.RTC_WAKEUP, startTime.time - delay, getAlarmPendingIntent(eventId))
AlarmManagerCompat.setExactAndAllowWhileIdle(
alarmManager, AlarmManager.RTC_WAKEUP,
startTime.toEpochMilli() - delay, getAlarmPendingIntent(eventId)
)
}
}
}
@ -187,7 +191,7 @@ class AlarmIntentService : JobIntentService() {
val notificationBuilder = NotificationCompat.Builder(this, NOTIFICATION_CHANNEL)
.setSmallIcon(R.drawable.ic_stat_fosdem)
.setColor(notificationColor)
.setWhen(event.startTime?.time ?: System.currentTimeMillis())
.setWhen(event.startTime?.toEpochMilli() ?: System.currentTimeMillis())
.setContentTitle(event.title)
.setContentText(contentText)
.setStyle(NotificationCompat.BigTextStyle().bigText(bigText).setSummaryText(trackName))

View file

@ -1,12 +1,12 @@
package be.digitalia.fosdem.settings
import android.content.Context
import android.text.format.DateUtils
import androidx.preference.PreferenceManager
import be.digitalia.fosdem.R
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import java.util.concurrent.TimeUnit
import javax.inject.Inject
import javax.inject.Singleton
@ -35,6 +35,6 @@ class UserSettingsProvider @Inject constructor(@ApplicationContext context: Cont
get() = sharedPreferences.getStringAsFlow(PreferenceKeys.NOTIFICATIONS_DELAY)
.map {
// Convert from minutes to milliseconds
(it?.toLong() ?: 0L) * DateUtils.MINUTE_IN_MILLIS
TimeUnit.MINUTES.toMillis(it?.toLong() ?: 0L)
}
}

View file

@ -1,25 +1,19 @@
package be.digitalia.fosdem.utils
import android.content.Context
import java.text.DateFormat
import java.util.Calendar
import java.util.Locale
import java.util.TimeZone
import android.text.format.DateFormat
import androidx.core.os.ConfigurationCompat
import java.time.ZoneId
import java.time.ZoneOffset
import java.time.format.DateTimeFormatter
object DateUtils {
val belgiumTimeZone: TimeZone = TimeZone.getTimeZone("GMT+1")
val conferenceZoneId: ZoneId = ZoneOffset.ofHours(1)
fun DateFormat.withBelgiumTimeZone(): DateFormat {
timeZone = belgiumTimeZone
return this
}
fun getTimeDateFormat(context: Context): DateFormat {
return android.text.format.DateFormat.getTimeFormat(context).withBelgiumTimeZone()
}
fun getYear(timestamp: Long, calendar: Calendar = Calendar.getInstance(belgiumTimeZone, Locale.US)): Int {
calendar.timeInMillis = timestamp
return calendar.get(Calendar.YEAR)
fun getTimeFormatter(context: Context): DateTimeFormatter {
val primaryLocale = ConfigurationCompat.getLocales(context.resources.configuration)[0]
val basePattern = if (DateFormat.is24HourFormat(context)) "Hm" else "hm"
val bestPattern = DateFormat.getBestDateTimePattern(primaryLocale, basePattern)
return DateTimeFormatter.ofPattern(bestPattern, primaryLocale)
}
}

View file

@ -2,14 +2,41 @@ package be.digitalia.fosdem.utils
import android.os.Parcel
import kotlinx.parcelize.Parceler
import java.util.Date
import java.time.Instant
import java.time.LocalDate
object DateParceler : Parceler<Date?> {
object InstantParceler : Parceler<Instant?> {
override fun create(parcel: Parcel): Date? {
val value = parcel.readLong()
return if (value == -1L) null else Date(value)
override fun create(parcel: Parcel): Instant? {
val nanoAdjustment = parcel.readInt()
if (nanoAdjustment == Int.MIN_VALUE) {
return null
}
val epochSecond = parcel.readLong()
return Instant.ofEpochSecond(epochSecond, nanoAdjustment.toLong())
}
override fun Date?.write(parcel: Parcel, flags: Int) = parcel.writeLong(this?.time ?: -1L)
override fun Instant?.write(parcel: Parcel, flags: Int) {
if (this == null) {
parcel.writeInt(Int.MIN_VALUE)
} else {
parcel.writeInt(nano)
parcel.writeLong(epochSecond)
}
}
}
object LocalDateParceler : Parceler<LocalDate> {
override fun create(parcel: Parcel): LocalDate {
val year = parcel.readInt()
val monthDay = parcel.readInt()
return LocalDate.of(year, monthDay shr 16, monthDay and 0xFFFF)
}
override fun LocalDate.write(parcel: Parcel, flags: Int) {
parcel.writeInt(year)
// pack month and day into a single int
parcel.writeInt((monthValue shl 16) or dayOfMonth)
}
}

View file

@ -2,7 +2,6 @@ package be.digitalia.fosdem.viewmodels
import android.app.Application
import android.net.Uri
import android.text.format.DateUtils
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
@ -18,6 +17,8 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import okio.buffer
import okio.source
import java.time.Duration
import java.time.Instant
import java.util.concurrent.TimeUnit
import javax.inject.Inject
@ -33,12 +34,12 @@ class BookmarksViewModel @Inject constructor(
val bookmarks: LiveData<List<Event>> = upcomingOnlyLiveData.switchMap { upcomingOnly: Boolean ->
if (upcomingOnly) {
// Refresh upcoming bookmarks every 2 minutes
LiveDataFactory.interval(2L, TimeUnit.MINUTES)
LiveDataFactory.interval(REFRESH_PERIOD)
.switchMap {
bookmarksDao.getBookmarks(System.currentTimeMillis() - TIME_OFFSET)
bookmarksDao.getBookmarks(Instant.now() - TIME_OFFSET)
}
} else {
bookmarksDao.getBookmarks(-1L)
bookmarksDao.getBookmarks(Instant.EPOCH)
}
}
@ -63,7 +64,8 @@ class BookmarksViewModel @Inject constructor(
}
companion object {
private val REFRESH_PERIOD = TimeUnit.MINUTES.toMillis(2L)
// In upcomingOnly mode, events that just started are still shown for 5 minutes
private const val TIME_OFFSET = 5L * DateUtils.MINUTE_IN_MILLIS
private val TIME_OFFSET = Duration.ofMinutes(5L)
}
}

View file

@ -1,6 +1,5 @@
package be.digitalia.fosdem.viewmodels
import android.text.format.DateUtils
import androidx.lifecycle.LiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.switchMap
@ -10,24 +9,27 @@ import be.digitalia.fosdem.db.ScheduleDao
import be.digitalia.fosdem.livedata.LiveDataFactory
import be.digitalia.fosdem.model.StatusEvent
import dagger.hilt.android.lifecycle.HiltViewModel
import java.time.Duration
import java.time.Instant
import java.util.concurrent.TimeUnit
import javax.inject.Inject
@HiltViewModel
class LiveViewModel @Inject constructor(scheduleDao: ScheduleDao) : ViewModel() {
private val heartbeat = LiveDataFactory.interval(1L, TimeUnit.MINUTES)
private val heartbeat = LiveDataFactory.interval(REFRESH_PERIOD)
val nextEvents: LiveData<PagedList<StatusEvent>> = heartbeat.switchMap {
val now = System.currentTimeMillis()
val now = Instant.now()
scheduleDao.getEventsWithStartTime(now, now + NEXT_EVENTS_INTERVAL).toLiveData(20)
}
val eventsInProgress: LiveData<PagedList<StatusEvent>> = heartbeat.switchMap {
scheduleDao.getEventsInProgress(System.currentTimeMillis()).toLiveData(20)
scheduleDao.getEventsInProgress(Instant.now()).toLiveData(20)
}
companion object {
private const val NEXT_EVENTS_INTERVAL = 30L * DateUtils.MINUTE_IN_MILLIS
private val REFRESH_PERIOD = TimeUnit.MINUTES.toMillis(1L)
private val NEXT_EVENTS_INTERVAL = Duration.ofMinutes(30L)
}
}

View file

@ -1,6 +1,5 @@
package be.digitalia.fosdem.viewmodels
import android.text.format.DateUtils
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
@ -11,7 +10,10 @@ import be.digitalia.fosdem.livedata.LiveDataFactory
import be.digitalia.fosdem.model.Day
import be.digitalia.fosdem.model.StatusEvent
import be.digitalia.fosdem.model.Track
import be.digitalia.fosdem.utils.DateUtils
import dagger.hilt.android.lifecycle.HiltViewModel
import java.time.Duration
import java.time.Instant
import java.util.concurrent.TimeUnit
import javax.inject.Inject
@ -25,21 +27,22 @@ class TrackScheduleListViewModel @Inject constructor(scheduleDao: ScheduleDao) :
}
/**
* @return The current time during the target day, or -1 outside of the target day.
* @return The current time during the target day, or null outside of the target day.
*/
val currentTime: LiveData<Long> = dayTrackLiveData
val currentTime: LiveData<Instant?> = dayTrackLiveData
.switchMap { (day, _) ->
// Auto refresh during the day passed as argument
val dayStart = day.date.time
LiveDataFactory.scheduler(dayStart, dayStart + DateUtils.DAY_IN_MILLIS)
val dayStart = day.date.atStartOfDay(DateUtils.conferenceZoneId).toInstant()
LiveDataFactory.scheduler(
dayStart.toEpochMilli(),
(dayStart + Duration.ofDays(1L)).toEpochMilli()
)
}
.switchMap { isOn ->
if (isOn) {
LiveDataFactory.interval(REFRESH_TIME_INTERVAL, TimeUnit.MILLISECONDS).map {
System.currentTimeMillis()
}
LiveDataFactory.interval(TIME_REFRESH_PERIOD).map { Instant.now() }
} else {
MutableLiveData(-1L)
MutableLiveData(null)
}
}
@ -51,6 +54,6 @@ class TrackScheduleListViewModel @Inject constructor(scheduleDao: ScheduleDao) :
}
companion object {
private const val REFRESH_TIME_INTERVAL = DateUtils.MINUTE_IN_MILLIS
private val TIME_REFRESH_PERIOD = TimeUnit.MINUTES.toMillis(1L)
}
}