mirror of
https://github.com/MatomoCamp/matomocamp-companion-android.git
synced 2024-09-19 16:13:46 +02:00
Add dependency injection using Hilt (#68)
Configure Hilt to inject FosdemApi, FosdemAlarmManager, BookmarksDao and ScheduleDao
This commit is contained in:
parent
d5e246ef20
commit
684131fb51
49 changed files with 540 additions and 332 deletions
|
@ -32,6 +32,7 @@ The result apk file will be placed in ```app/build/outputs/apk/```.
|
|||
## Used libraries
|
||||
|
||||
* [Android Jetpack](https://developer.android.com/jetpack) by The Android Open Source Project
|
||||
* [Dagger Hilt](https://dagger.dev/hilt/) by The Dagger Authors
|
||||
* [Material Components for Android](https://material.io/develop/android) by The Android Open Source Project
|
||||
* [OkHttp](https://github.com/square/okhttp) by Square, Inc.
|
||||
* [Moshi](https://github.com/square/moshi) by Square, Inc.
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'kotlin-parcelize'
|
||||
apply plugin: 'kotlin-kapt'
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'kotlin-android'
|
||||
id 'kotlin-parcelize'
|
||||
id 'kotlin-kapt'
|
||||
id 'dagger.hilt.android.plugin'
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion 30
|
||||
|
@ -71,6 +74,8 @@ dependencies {
|
|||
def room_version = "2.3.0"
|
||||
def okhttp_version = "3.12.13"
|
||||
|
||||
implementation "com.google.dagger:hilt-android:$hilt_version"
|
||||
kapt "com.google.dagger:hilt-compiler:$hilt_version"
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.3'
|
||||
implementation 'androidx.core:core-ktx:1.3.2'
|
||||
implementation 'androidx.fragment:fragment-ktx:1.3.3'
|
||||
|
|
|
@ -4,9 +4,15 @@ import android.app.Application
|
|||
import androidx.preference.PreferenceManager
|
||||
import be.digitalia.fosdem.alarms.FosdemAlarmManager
|
||||
import be.digitalia.fosdem.utils.ThemeManager
|
||||
import dagger.hilt.android.HiltAndroidApp
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltAndroidApp
|
||||
class FosdemApplication : Application() {
|
||||
|
||||
@Inject
|
||||
lateinit var alarmManager: FosdemAlarmManager
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
||||
|
@ -15,7 +21,7 @@ class FosdemApplication : Application() {
|
|||
// Light/Dark theme switch (requires settings)
|
||||
ThemeManager.init(this)
|
||||
// Alarms (requires settings)
|
||||
FosdemAlarmManager.init(this)
|
||||
alarmManager.init()
|
||||
}
|
||||
|
||||
}
|
|
@ -28,12 +28,14 @@ import be.digitalia.fosdem.utils.toNfcAppData
|
|||
import be.digitalia.fosdem.viewmodels.BookmarkStatusViewModel
|
||||
import be.digitalia.fosdem.viewmodels.EventViewModel
|
||||
import be.digitalia.fosdem.widgets.setupBookmarkStatus
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
|
||||
/**
|
||||
* Displays a single event passed either as a complete Parcelable object in extras or as an id in data.
|
||||
*
|
||||
* @author Christophe Beyls
|
||||
*/
|
||||
@AndroidEntryPoint
|
||||
class EventDetailsActivity : AppCompatActivity(R.layout.single_event), CreateNfcAppDataCallback {
|
||||
|
||||
private val bookmarkStatusViewModel: BookmarkStatusViewModel by viewModels()
|
||||
|
|
|
@ -8,7 +8,9 @@ import be.digitalia.fosdem.fragments.ExternalBookmarksListFragment
|
|||
import be.digitalia.fosdem.utils.extractNfcAppData
|
||||
import be.digitalia.fosdem.utils.hasNfcAppData
|
||||
import be.digitalia.fosdem.utils.toBookmarks
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
|
||||
@AndroidEntryPoint
|
||||
class ExternalBookmarksActivity : SimpleToolbarActivity() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
|
|
|
@ -33,7 +33,7 @@ import be.digitalia.fosdem.BuildConfig
|
|||
import be.digitalia.fosdem.R
|
||||
import be.digitalia.fosdem.api.FosdemApi
|
||||
import be.digitalia.fosdem.api.FosdemUrls
|
||||
import be.digitalia.fosdem.db.AppDatabase
|
||||
import be.digitalia.fosdem.db.ScheduleDao
|
||||
import be.digitalia.fosdem.fragments.BookmarksListFragment
|
||||
import be.digitalia.fosdem.fragments.LiveFragment
|
||||
import be.digitalia.fosdem.fragments.MapFragment
|
||||
|
@ -49,13 +49,16 @@ import be.digitalia.fosdem.utils.setNfcAppDataPushMessageCallbackIfAvailable
|
|||
import com.google.android.material.navigation.NavigationView
|
||||
import com.google.android.material.progressindicator.BaseProgressIndicator
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Main entry point of the application. Allows to switch between section fragments and update the database.
|
||||
*
|
||||
* @author Christophe Beyls
|
||||
*/
|
||||
@AndroidEntryPoint
|
||||
class MainActivity : AppCompatActivity(R.layout.main), CreateNfcAppDataCallback {
|
||||
|
||||
private enum class Section(val fragmentClass: Class<out Fragment>,
|
||||
|
@ -79,6 +82,11 @@ class MainActivity : AppCompatActivity(R.layout.main), CreateNfcAppDataCallback
|
|||
val drawerLayout: DrawerLayout,
|
||||
val navigationView: NavigationView)
|
||||
|
||||
@Inject
|
||||
lateinit var api: FosdemApi
|
||||
@Inject
|
||||
lateinit var scheduleDao: ScheduleDao
|
||||
|
||||
private lateinit var holder: ViewHolder
|
||||
private lateinit var drawerToggle: ActionBarDrawerToggle
|
||||
private var searchMenuItem: MenuItem? = null
|
||||
|
@ -93,7 +101,7 @@ class MainActivity : AppCompatActivity(R.layout.main), CreateNfcAppDataCallback
|
|||
val progressIndicator: BaseProgressIndicator<*> = findViewById(R.id.progress)
|
||||
|
||||
// Monitor the schedule download
|
||||
FosdemApi.downloadScheduleState.observe(this) { state ->
|
||||
api.downloadScheduleState.observe(this) { state ->
|
||||
when (state) {
|
||||
is LoadingState.Loading -> {
|
||||
with(progressIndicator) {
|
||||
|
@ -119,7 +127,7 @@ class MainActivity : AppCompatActivity(R.layout.main), CreateNfcAppDataCallback
|
|||
val snackbar = when (result) {
|
||||
is DownloadScheduleResult.Error -> {
|
||||
Snackbar.make(contentView, R.string.schedule_loading_error, ERROR_MESSAGE_DISPLAY_DURATION)
|
||||
.setAction(R.string.schedule_loading_retry_action) { FosdemApi.downloadSchedule(this) }
|
||||
.setAction(R.string.schedule_loading_retry_action) { api.downloadSchedule() }
|
||||
}
|
||||
is DownloadScheduleResult.UpToDate -> {
|
||||
Snackbar.make(contentView, R.string.events_download_up_to_date, Snackbar.LENGTH_LONG)
|
||||
|
@ -174,7 +182,7 @@ class MainActivity : AppCompatActivity(R.layout.main), CreateNfcAppDataCallback
|
|||
|
||||
// Latest update date, below the list
|
||||
val latestUpdateTextView: TextView = navigationView.findViewById(R.id.latest_update)
|
||||
AppDatabase.getInstance(this).scheduleDao.latestUpdateTime
|
||||
scheduleDao.latestUpdateTime
|
||||
.observe(this) { time ->
|
||||
val timeString = if (time == -1L) getString(R.string.never)
|
||||
else DateFormat.format(LATEST_UPDATE_DATE_FORMAT, time)
|
||||
|
@ -238,7 +246,7 @@ class MainActivity : AppCompatActivity(R.layout.main), CreateNfcAppDataCallback
|
|||
|
||||
// Scheduled database update
|
||||
val now = System.currentTimeMillis()
|
||||
val latestUpdateTime = AppDatabase.getInstance(this).scheduleDao.latestUpdateTime.value
|
||||
val latestUpdateTime = scheduleDao.latestUpdateTime.value
|
||||
if (latestUpdateTime == null || latestUpdateTime < now - DATABASE_VALIDITY_DURATION) {
|
||||
val prefs = getPreferences(Context.MODE_PRIVATE)
|
||||
val latestAttemptTime = prefs.getLong(PREF_LATEST_AUTO_UPDATE_ATTEMPT_TIME, -1L)
|
||||
|
@ -247,7 +255,7 @@ class MainActivity : AppCompatActivity(R.layout.main), CreateNfcAppDataCallback
|
|||
putLong(PREF_LATEST_AUTO_UPDATE_ATTEMPT_TIME, now)
|
||||
}
|
||||
// Try to update immediately. If it fails, the user gets a message and a retry button.
|
||||
FosdemApi.downloadSchedule(this)
|
||||
api.downloadSchedule()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -289,7 +297,7 @@ class MainActivity : AppCompatActivity(R.layout.main), CreateNfcAppDataCallback
|
|||
item.icon = icon
|
||||
icon.start()
|
||||
}
|
||||
FosdemApi.downloadSchedule(this)
|
||||
api.downloadSchedule()
|
||||
true
|
||||
}
|
||||
else -> false
|
||||
|
|
|
@ -15,7 +15,9 @@ 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
|
||||
|
||||
@AndroidEntryPoint
|
||||
class PersonInfoActivity : AppCompatActivity(R.layout.person_info) {
|
||||
|
||||
private val viewModel: PersonInfoViewModel by viewModels()
|
||||
|
|
|
@ -19,6 +19,8 @@ import be.digitalia.fosdem.utils.configureToolbarColors
|
|||
import be.digitalia.fosdem.utils.invertImageColors
|
||||
import be.digitalia.fosdem.utils.isLightTheme
|
||||
import be.digitalia.fosdem.utils.toSlug
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* A special Activity which is displayed like a dialog and shows a room image.
|
||||
|
@ -26,8 +28,12 @@ import be.digitalia.fosdem.utils.toSlug
|
|||
*
|
||||
* @author Christophe Beyls
|
||||
*/
|
||||
@AndroidEntryPoint
|
||||
class RoomImageDialogActivity : AppCompatActivity(R.layout.dialog_room_image) {
|
||||
|
||||
@Inject
|
||||
lateinit var api: FosdemApi
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
val intent = intent
|
||||
|
@ -40,14 +46,14 @@ class RoomImageDialogActivity : AppCompatActivity(R.layout.dialog_room_image) {
|
|||
}
|
||||
setImageResource(intent.getIntExtra(EXTRA_ROOM_IMAGE_RESOURCE_ID, 0))
|
||||
}
|
||||
configureToolbar(this, findViewById(R.id.toolbar), roomName)
|
||||
configureToolbar(api, this, findViewById(R.id.toolbar), roomName)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val EXTRA_ROOM_NAME = "roomName"
|
||||
const val EXTRA_ROOM_IMAGE_RESOURCE_ID = "imageResId"
|
||||
|
||||
fun configureToolbar(owner: LifecycleOwner, toolbar: Toolbar, roomName: String) {
|
||||
fun configureToolbar(api: FosdemApi, owner: LifecycleOwner, toolbar: Toolbar, roomName: String) {
|
||||
toolbar.title = roomName
|
||||
if (roomName.isNotEmpty()) {
|
||||
val context = toolbar.context
|
||||
|
@ -72,7 +78,7 @@ class RoomImageDialogActivity : AppCompatActivity(R.layout.dialog_room_image) {
|
|||
}
|
||||
|
||||
// Display the room status as subtitle
|
||||
FosdemApi.getRoomStatuses(toolbar.context).observe(owner) { roomStatuses ->
|
||||
api.roomStatuses.observe(owner) { roomStatuses ->
|
||||
val roomStatus = roomStatuses[roomName]
|
||||
toolbar.subtitle = if (roomStatus != null) {
|
||||
SpannableString(context.getString(roomStatus.nameResId)).apply {
|
||||
|
|
|
@ -14,7 +14,9 @@ import be.digitalia.fosdem.fragments.SearchResultListFragment
|
|||
import be.digitalia.fosdem.viewmodels.SearchViewModel
|
||||
import be.digitalia.fosdem.viewmodels.SearchViewModel.Result.QueryTooShort
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
|
||||
@AndroidEntryPoint
|
||||
class SearchResultActivity : SimpleToolbarActivity() {
|
||||
|
||||
private val viewModel: SearchViewModel by viewModels()
|
||||
|
|
|
@ -29,12 +29,14 @@ import be.digitalia.fosdem.utils.toNfcAppData
|
|||
import be.digitalia.fosdem.viewmodels.BookmarkStatusViewModel
|
||||
import be.digitalia.fosdem.viewmodels.TrackScheduleViewModel
|
||||
import be.digitalia.fosdem.widgets.setupBookmarkStatus
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
|
||||
/**
|
||||
* Track Schedule container, works in both single pane and dual pane modes.
|
||||
*
|
||||
* @author Christophe Beyls
|
||||
*/
|
||||
@AndroidEntryPoint
|
||||
class TrackScheduleActivity : AppCompatActivity(R.layout.track_schedule), CreateNfcAppDataCallback {
|
||||
|
||||
private val viewModel: TrackScheduleViewModel by viewModels()
|
||||
|
|
|
@ -33,12 +33,14 @@ import be.digitalia.fosdem.viewmodels.BookmarkStatusViewModel
|
|||
import be.digitalia.fosdem.viewmodels.TrackScheduleEventViewModel
|
||||
import be.digitalia.fosdem.widgets.ContentLoadingViewMediator
|
||||
import be.digitalia.fosdem.widgets.setupBookmarkStatus
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
|
||||
/**
|
||||
* Event view of the track schedule; allows to slide between events of the same track using a ViewPager.
|
||||
*
|
||||
* @author Christophe Beyls
|
||||
*/
|
||||
@AndroidEntryPoint
|
||||
class TrackScheduleEventActivity : AppCompatActivity(R.layout.track_schedule_event), CreateNfcAppDataCallback {
|
||||
|
||||
private val bookmarkStatusViewModel: BookmarkStatusViewModel by viewModels()
|
||||
|
|
|
@ -15,29 +15,31 @@ import androidx.collection.SimpleArrayMap
|
|||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.text.set
|
||||
import androidx.core.view.isGone
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.recyclerview.widget.ListAdapter
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.recyclerview.widget.RecyclerView.AdapterDataObserver
|
||||
import be.digitalia.fosdem.R
|
||||
import be.digitalia.fosdem.activities.EventDetailsActivity
|
||||
import be.digitalia.fosdem.api.FosdemApi
|
||||
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
|
||||
|
||||
class BookmarksAdapter(context: Context, owner: LifecycleOwner,
|
||||
private val multiChoiceHelper: MultiChoiceHelper)
|
||||
: ListAdapter<Event, BookmarksAdapter.ViewHolder>(DIFF_CALLBACK) {
|
||||
class BookmarksAdapter(context: Context, private val multiChoiceHelper: MultiChoiceHelper) :
|
||||
ListAdapter<Event, BookmarksAdapter.ViewHolder>(DIFF_CALLBACK) {
|
||||
|
||||
private val timeDateFormat = DateUtils.getTimeDateFormat(context)
|
||||
|
||||
@ColorInt
|
||||
private val errorColor: Int
|
||||
private val observers = SimpleArrayMap<AdapterDataObserver, BookmarksDataObserverWrapper>()
|
||||
private var roomStatuses: Map<String, RoomStatus>? = null
|
||||
|
||||
var roomStatuses: Map<String, RoomStatus>? = null
|
||||
set(value) {
|
||||
field = value
|
||||
notifyItemRangeChanged(0, itemCount, DETAILS_PAYLOAD)
|
||||
}
|
||||
|
||||
init {
|
||||
setHasStableIds(true)
|
||||
|
@ -45,10 +47,6 @@ class BookmarksAdapter(context: Context, owner: LifecycleOwner,
|
|||
errorColor = getColor(R.styleable.ErrorColors_colorError, 0)
|
||||
recycle()
|
||||
}
|
||||
FosdemApi.getRoomStatuses(context).observe(owner) { statuses ->
|
||||
roomStatuses = statuses
|
||||
notifyItemRangeChanged(0, itemCount, DETAILS_PAYLOAD)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemId(position: Int) = getItem(position).id
|
||||
|
@ -59,7 +57,7 @@ class BookmarksAdapter(context: Context, owner: LifecycleOwner,
|
|||
}
|
||||
|
||||
private fun getRoomStatus(event: Event): RoomStatus? {
|
||||
return roomStatuses?.let { it[event.roomName] }
|
||||
return roomStatuses?.get(event.roomName)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
|
|
|
@ -13,30 +13,26 @@ import androidx.core.content.ContextCompat
|
|||
import androidx.core.text.set
|
||||
import androidx.core.view.isGone
|
||||
import androidx.core.widget.TextViewCompat
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.paging.PagedListAdapter
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import be.digitalia.fosdem.R
|
||||
import be.digitalia.fosdem.activities.EventDetailsActivity
|
||||
import be.digitalia.fosdem.api.FosdemApi
|
||||
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
|
||||
|
||||
class EventsAdapter constructor(context: Context, owner: LifecycleOwner, private val showDay: Boolean = true)
|
||||
: PagedListAdapter<StatusEvent, EventsAdapter.ViewHolder>(DIFF_CALLBACK) {
|
||||
class EventsAdapter constructor(context: Context, private val showDay: Boolean = true) :
|
||||
PagedListAdapter<StatusEvent, EventsAdapter.ViewHolder>(DIFF_CALLBACK) {
|
||||
|
||||
private val timeDateFormat = DateUtils.getTimeDateFormat(context)
|
||||
private var roomStatuses: Map<String, RoomStatus>? = null
|
||||
|
||||
init {
|
||||
FosdemApi.getRoomStatuses(context).observe(owner) { statuses ->
|
||||
roomStatuses = statuses
|
||||
var roomStatuses: Map<String, RoomStatus>? = null
|
||||
set(value) {
|
||||
field = value
|
||||
notifyItemRangeChanged(0, itemCount, DETAILS_PAYLOAD)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemViewType(position: Int) = R.layout.item_event
|
||||
|
||||
|
@ -46,7 +42,7 @@ class EventsAdapter constructor(context: Context, owner: LifecycleOwner, private
|
|||
}
|
||||
|
||||
private fun getRoomStatus(event: Event): RoomStatus? {
|
||||
return roomStatuses?.let { it[event.roomName] }
|
||||
return roomStatuses?.get(event.roomName)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package be.digitalia.fosdem.alarms
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences.OnSharedPreferenceChangeListener
|
||||
|
@ -9,16 +8,18 @@ import androidx.preference.PreferenceManager
|
|||
import be.digitalia.fosdem.model.AlarmInfo
|
||||
import be.digitalia.fosdem.services.AlarmIntentService
|
||||
import be.digitalia.fosdem.utils.PreferenceKeys
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
/**
|
||||
* This class monitors bookmarks and preferences changes to dispatch alarm update work to AlarmIntentService.
|
||||
*
|
||||
* @author Christophe Beyls
|
||||
*/
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
object FosdemAlarmManager {
|
||||
@Singleton
|
||||
class FosdemAlarmManager @Inject constructor(@ApplicationContext private val context: Context) {
|
||||
|
||||
private lateinit var context: Context
|
||||
private val onSharedPreferenceChangeListener = OnSharedPreferenceChangeListener { sharedPreferences, key ->
|
||||
when (key) {
|
||||
PreferenceKeys.NOTIFICATIONS_ENABLED -> {
|
||||
|
@ -35,8 +36,7 @@ object FosdemAlarmManager {
|
|||
}
|
||||
|
||||
@MainThread
|
||||
fun init(context: Context) {
|
||||
this.context = context.applicationContext
|
||||
fun init() {
|
||||
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this.context)
|
||||
isEnabled = sharedPreferences.getBoolean(PreferenceKeys.NOTIFICATIONS_ENABLED, false)
|
||||
sharedPreferences.registerOnSharedPreferenceChangeListener(onSharedPreferenceChangeListener)
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package be.digitalia.fosdem.api
|
||||
|
||||
import android.content.Context
|
||||
import android.os.SystemClock
|
||||
import android.text.format.DateUtils
|
||||
import androidx.annotation.MainThread
|
||||
|
@ -9,7 +8,7 @@ import androidx.lifecycle.MutableLiveData
|
|||
import androidx.lifecycle.liveData
|
||||
import androidx.lifecycle.switchMap
|
||||
import be.digitalia.fosdem.alarms.FosdemAlarmManager
|
||||
import be.digitalia.fosdem.db.AppDatabase
|
||||
import be.digitalia.fosdem.db.ScheduleDao
|
||||
import be.digitalia.fosdem.livedata.LiveDataFactory.scheduler
|
||||
import be.digitalia.fosdem.livedata.SingleEvent
|
||||
import be.digitalia.fosdem.model.DownloadScheduleResult
|
||||
|
@ -26,6 +25,8 @@ import kotlinx.coroutines.Job
|
|||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import okio.buffer
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
import kotlin.math.pow
|
||||
|
||||
/**
|
||||
|
@ -33,18 +34,13 @@ import kotlin.math.pow
|
|||
*
|
||||
* @author Christophe Beyls
|
||||
*/
|
||||
object FosdemApi {
|
||||
// 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
|
||||
|
||||
@Singleton
|
||||
class FosdemApi @Inject constructor(
|
||||
private val scheduleDao: ScheduleDao,
|
||||
private val alarmManager: FosdemAlarmManager
|
||||
) {
|
||||
private var downloadJob: Job? = null
|
||||
private val _downloadScheduleState = MutableLiveData<LoadingState<DownloadScheduleResult>>()
|
||||
private var roomStatuses: LiveData<Map<String, RoomStatus>>? = null
|
||||
|
||||
/**
|
||||
* Download & store the schedule to the database.
|
||||
|
@ -52,12 +48,11 @@ object FosdemApi {
|
|||
* The result will be sent back through downloadScheduleResult LiveData.
|
||||
*/
|
||||
@MainThread
|
||||
fun downloadSchedule(context: Context): Job {
|
||||
fun downloadSchedule(): Job {
|
||||
// Returns the download job in progress, if any
|
||||
return downloadJob ?: run {
|
||||
val appContext = context.applicationContext
|
||||
BackgroundWorkScope.launch {
|
||||
downloadScheduleInternal(appContext)
|
||||
downloadScheduleInternal()
|
||||
downloadJob = null
|
||||
}.also {
|
||||
downloadJob = it
|
||||
|
@ -66,10 +61,9 @@ object FosdemApi {
|
|||
}
|
||||
|
||||
@MainThread
|
||||
private suspend fun downloadScheduleInternal(context: Context) {
|
||||
private suspend fun downloadScheduleInternal() {
|
||||
_downloadScheduleState.value = LoadingState.Loading()
|
||||
val res = try {
|
||||
val scheduleDao = AppDatabase.getInstance(context).scheduleDao
|
||||
val response = HttpUtils.get(FosdemUrls.schedule, scheduleDao.lastModifiedTag) { body, rawResponse ->
|
||||
val length = body.contentLength()
|
||||
val source = if (length > 0L) {
|
||||
|
@ -89,7 +83,7 @@ object FosdemApi {
|
|||
when (response) {
|
||||
is HttpUtils.Response.NotModified -> DownloadScheduleResult.UpToDate // Nothing parsed, the result is up-to-date
|
||||
is HttpUtils.Response.Success -> {
|
||||
FosdemAlarmManager.onScheduleRefreshed()
|
||||
alarmManager.onScheduleRefreshed()
|
||||
DownloadScheduleResult.Success(response.body)
|
||||
}
|
||||
}
|
||||
|
@ -102,28 +96,24 @@ object FosdemApi {
|
|||
val downloadScheduleState: LiveData<LoadingState<DownloadScheduleResult>>
|
||||
get() = _downloadScheduleState
|
||||
|
||||
@MainThread
|
||||
fun getRoomStatuses(context: Context): LiveData<Map<String, RoomStatus>> {
|
||||
return roomStatuses ?: run {
|
||||
// The room statuses will only be loaded when the event is live.
|
||||
// Use the days from the database to determine it.
|
||||
val scheduler = AppDatabase.getInstance(context).scheduleDao.days.switchMap { days ->
|
||||
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
|
||||
}
|
||||
scheduler(*startEndTimestamps)
|
||||
val roomStatuses: LiveData<Map<String, RoomStatus>> by lazy(LazyThreadSafetyMode.NONE) {
|
||||
// The room statuses will only be loaded when the event is live.
|
||||
// Use the days from the database to determine it.
|
||||
val scheduler = scheduleDao.days.switchMap { days ->
|
||||
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
|
||||
}
|
||||
val liveRoomStatuses = buildLiveRoomStatusesLiveData()
|
||||
val offlineRoomStatuses = MutableLiveData(emptyMap<String, RoomStatus>())
|
||||
scheduler.switchMap { isLive -> if (isLive) liveRoomStatuses else offlineRoomStatuses }
|
||||
.also { roomStatuses = it }
|
||||
// Implementors: replace the above code with the next line to disable room status support
|
||||
// MutableLiveData().also { roomStatuses = it }
|
||||
scheduler(*startEndTimestamps)
|
||||
}
|
||||
val liveRoomStatuses = buildLiveRoomStatusesLiveData()
|
||||
val offlineRoomStatuses = MutableLiveData(emptyMap<String, RoomStatus>())
|
||||
scheduler.switchMap { isLive -> if (isLive) liveRoomStatuses else offlineRoomStatuses }
|
||||
// Implementors: replace the above code block with the next line to disable room status support
|
||||
// MutableLiveData()
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -178,4 +168,15 @@ object FosdemApi {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
|
@ -1,13 +1,11 @@
|
|||
package be.digitalia.fosdem.db
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import androidx.room.Database
|
||||
import androidx.room.Room
|
||||
import androidx.room.DatabaseConfiguration
|
||||
import androidx.room.RoomDatabase
|
||||
import androidx.room.TypeConverters
|
||||
import androidx.room.migration.Migration
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||
import be.digitalia.fosdem.alarms.FosdemAlarmManager
|
||||
import be.digitalia.fosdem.db.converters.GlobalTypeConverters
|
||||
import be.digitalia.fosdem.db.entities.Bookmark
|
||||
import be.digitalia.fosdem.db.entities.EventEntity
|
||||
|
@ -17,63 +15,40 @@ import be.digitalia.fosdem.model.Day
|
|||
import be.digitalia.fosdem.model.Link
|
||||
import be.digitalia.fosdem.model.Person
|
||||
import be.digitalia.fosdem.model.Track
|
||||
import be.digitalia.fosdem.utils.SingletonHolder
|
||||
import dagger.hilt.EntryPoint
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.EntryPointAccessors
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Named
|
||||
|
||||
@Database(entities = [EventEntity::class, EventTitles::class, Person::class, EventToPerson::class,
|
||||
Link::class, Track::class, Day::class, Bookmark::class], version = 2, exportSchema = false)
|
||||
@Database(
|
||||
entities = [EventEntity::class, EventTitles::class, Person::class, EventToPerson::class,
|
||||
Link::class, Track::class, Day::class, Bookmark::class], version = 2, exportSchema = false
|
||||
)
|
||||
@TypeConverters(GlobalTypeConverters::class)
|
||||
abstract class AppDatabase : RoomDatabase() {
|
||||
|
||||
lateinit var sharedPreferences: SharedPreferences
|
||||
private set
|
||||
|
||||
abstract val scheduleDao: ScheduleDao
|
||||
abstract val bookmarksDao: BookmarksDao
|
||||
|
||||
companion object : SingletonHolder<AppDatabase, Context>({ context ->
|
||||
val DB_FILE = "fosdem.sqlite"
|
||||
val DB_PREFS_FILE = "database"
|
||||
val MIGRATION_1_2 = object : Migration(1, 2) {
|
||||
override fun migrate(database: SupportSQLiteDatabase) = with(database) {
|
||||
// Events: make primary key and track_id not null
|
||||
execSQL("CREATE TABLE tmp_${EventEntity.TABLE_NAME} (id INTEGER PRIMARY KEY NOT NULL, day_index INTEGER NOT NULL, start_time INTEGER, end_time INTEGER, room_name TEXT, slug TEXT, track_id INTEGER NOT NULL, abstract TEXT, description TEXT)")
|
||||
execSQL("INSERT INTO tmp_${EventEntity.TABLE_NAME} SELECT * FROM ${EventEntity.TABLE_NAME}")
|
||||
execSQL("DROP TABLE ${EventEntity.TABLE_NAME}")
|
||||
execSQL("ALTER TABLE tmp_${EventEntity.TABLE_NAME} RENAME TO ${EventEntity.TABLE_NAME}")
|
||||
execSQL("CREATE INDEX event_day_index_idx ON ${EventEntity.TABLE_NAME} (day_index)")
|
||||
execSQL("CREATE INDEX event_start_time_idx ON ${EventEntity.TABLE_NAME} (start_time)")
|
||||
execSQL("CREATE INDEX event_end_time_idx ON ${EventEntity.TABLE_NAME} (end_time)")
|
||||
execSQL("CREATE INDEX event_track_id_idx ON ${EventEntity.TABLE_NAME} (track_id)")
|
||||
// Links: add explicit primary key
|
||||
execSQL("CREATE TABLE tmp_${Link.TABLE_NAME} (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, event_id INTEGER NOT NULL, url TEXT NOT NULL, description TEXT)")
|
||||
execSQL("INSERT INTO tmp_${Link.TABLE_NAME} SELECT `rowid` AS id, event_id, url, description FROM ${Link.TABLE_NAME}")
|
||||
execSQL("DROP TABLE ${Link.TABLE_NAME}")
|
||||
execSQL("ALTER TABLE tmp_${Link.TABLE_NAME} RENAME TO ${Link.TABLE_NAME}")
|
||||
execSQL("CREATE INDEX link_event_id_idx ON ${Link.TABLE_NAME} (event_id)")
|
||||
// Tracks: make primary key not null
|
||||
execSQL("CREATE TABLE tmp_${Track.TABLE_NAME} (id INTEGER PRIMARY KEY NOT NULL, name TEXT NOT NULL, type TEXT NOT NULL)")
|
||||
execSQL("INSERT INTO tmp_${Track.TABLE_NAME} SELECT * FROM ${Track.TABLE_NAME}")
|
||||
execSQL("DROP TABLE ${Track.TABLE_NAME}")
|
||||
execSQL("ALTER TABLE tmp_${Track.TABLE_NAME} RENAME TO ${Track.TABLE_NAME}")
|
||||
execSQL("CREATE UNIQUE INDEX track_main_idx ON ${Track.TABLE_NAME} (name, type)")
|
||||
// Days: make primary key not null and rename _index to index
|
||||
execSQL("CREATE TABLE tmp_${Day.TABLE_NAME} (`index` INTEGER PRIMARY KEY NOT NULL, date INTEGER NOT NULL)")
|
||||
execSQL("INSERT INTO tmp_${Day.TABLE_NAME} SELECT _index as `index`, date FROM ${Day.TABLE_NAME}")
|
||||
execSQL("DROP TABLE ${Day.TABLE_NAME}")
|
||||
execSQL("ALTER TABLE tmp_${Day.TABLE_NAME} RENAME TO ${Day.TABLE_NAME}")
|
||||
// Bookmarks: make primary key not null
|
||||
execSQL("CREATE TABLE tmp_${Bookmark.TABLE_NAME} (event_id INTEGER PRIMARY KEY NOT NULL)")
|
||||
execSQL("INSERT INTO tmp_${Bookmark.TABLE_NAME} SELECT * FROM ${Bookmark.TABLE_NAME}")
|
||||
execSQL("DROP TABLE ${Bookmark.TABLE_NAME}")
|
||||
execSQL("ALTER TABLE tmp_${Bookmark.TABLE_NAME} RENAME TO ${Bookmark.TABLE_NAME}")
|
||||
}
|
||||
}
|
||||
lateinit var sharedPreferences: SharedPreferences
|
||||
private set
|
||||
lateinit var alarmManager: FosdemAlarmManager
|
||||
private set
|
||||
|
||||
Room.databaseBuilder(context.applicationContext, AppDatabase::class.java, DB_FILE)
|
||||
.addMigrations(MIGRATION_1_2)
|
||||
.setJournalMode(JournalMode.TRUNCATE)
|
||||
.build().apply {
|
||||
sharedPreferences = context.applicationContext.getSharedPreferences(DB_PREFS_FILE, Context.MODE_PRIVATE)
|
||||
}
|
||||
})
|
||||
override fun init(configuration: DatabaseConfiguration) {
|
||||
super.init(configuration)
|
||||
// Manual dependency injection
|
||||
val entryPoint = EntryPointAccessors.fromApplication(configuration.context, AppDatabaseEntryPoint::class.java)
|
||||
sharedPreferences = entryPoint.sharedPreferences
|
||||
alarmManager = entryPoint.alarmManager
|
||||
}
|
||||
|
||||
@EntryPoint
|
||||
@InstallIn(SingletonComponent::class)
|
||||
interface AppDatabaseEntryPoint {
|
||||
@get:Named("Database")
|
||||
val sharedPreferences: SharedPreferences
|
||||
val alarmManager: FosdemAlarmManager
|
||||
}
|
||||
}
|
|
@ -7,7 +7,6 @@ import androidx.room.Insert
|
|||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.Query
|
||||
import androidx.room.withTransaction
|
||||
import be.digitalia.fosdem.alarms.FosdemAlarmManager
|
||||
import be.digitalia.fosdem.db.entities.Bookmark
|
||||
import be.digitalia.fosdem.model.AlarmInfo
|
||||
import be.digitalia.fosdem.model.Event
|
||||
|
@ -16,7 +15,6 @@ import kotlinx.coroutines.launch
|
|||
|
||||
@Dao
|
||||
abstract class BookmarksDao(private val appDatabase: AppDatabase) {
|
||||
|
||||
/**
|
||||
* Returns the bookmarks.
|
||||
*
|
||||
|
@ -65,7 +63,7 @@ abstract class BookmarksDao(private val appDatabase: AppDatabase) {
|
|||
BackgroundWorkScope.launch {
|
||||
val ids = addBookmarksInternal(listOf(Bookmark(event.id)))
|
||||
if (ids[0] != -1L) {
|
||||
FosdemAlarmManager.onBookmarksAdded(listOf(AlarmInfo(eventId = event.id, startTime = event.startTime)))
|
||||
appDatabase.alarmManager.onBookmarksAdded(listOf(AlarmInfo(eventId = event.id, startTime = event.startTime)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -81,7 +79,7 @@ abstract class BookmarksDao(private val appDatabase: AppDatabase) {
|
|||
// Filter out items that were already in bookmarks
|
||||
val addedAlarmInfos = alarmInfos.filterIndexed { index, _ -> ids[index] != -1L }
|
||||
if (addedAlarmInfos.isNotEmpty()) {
|
||||
FosdemAlarmManager.onBookmarksAdded(addedAlarmInfos)
|
||||
appDatabase.alarmManager.onBookmarksAdded(addedAlarmInfos)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -103,7 +101,7 @@ abstract class BookmarksDao(private val appDatabase: AppDatabase) {
|
|||
fun removeBookmarksAsync(eventIds: LongArray) {
|
||||
BackgroundWorkScope.launch {
|
||||
if (removeBookmarksInternal(eventIds) > 0) {
|
||||
FosdemAlarmManager.onBookmarksRemoved(eventIds)
|
||||
appDatabase.alarmManager.onBookmarksRemoved(eventIds)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -268,8 +268,7 @@ abstract class ScheduleDao(private val appDatabase: AppDatabase) {
|
|||
LEFT JOIN persons p ON ep.person_id = p.`rowid`
|
||||
WHERE e.id = :id
|
||||
GROUP BY e.id""")
|
||||
@WorkerThread
|
||||
abstract fun getEvent(id: Long): Event?
|
||||
abstract suspend fun getEvent(id: Long): Event?
|
||||
|
||||
/**
|
||||
* Returns all found events whose id is part of the given list.
|
||||
|
@ -321,7 +320,7 @@ abstract class ScheduleDao(private val appDatabase: AppDatabase) {
|
|||
WHERE e.day_index = :day AND e.track_id = :track
|
||||
GROUP BY e.id
|
||||
ORDER BY e.start_time ASC""")
|
||||
abstract fun getEventsSnapshot(day: Day, track: Track): List<Event>
|
||||
abstract suspend fun getEventsSnapshot(day: Day, track: Track): List<Event>
|
||||
|
||||
/**
|
||||
* Returns events starting in the specified interval, ordered by ascending start time.
|
||||
|
|
|
@ -23,21 +23,27 @@ import androidx.recyclerview.widget.LinearLayoutManager
|
|||
import be.digitalia.fosdem.R
|
||||
import be.digitalia.fosdem.activities.ExternalBookmarksActivity
|
||||
import be.digitalia.fosdem.adapters.BookmarksAdapter
|
||||
import be.digitalia.fosdem.api.FosdemApi
|
||||
import be.digitalia.fosdem.providers.BookmarksExportProvider
|
||||
import be.digitalia.fosdem.utils.CreateNfcAppDataCallback
|
||||
import be.digitalia.fosdem.utils.toBookmarksNfcAppData
|
||||
import be.digitalia.fosdem.viewmodels.BookmarksViewModel
|
||||
import be.digitalia.fosdem.widgets.MultiChoiceHelper
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import java.util.concurrent.CancellationException
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Bookmarks list, optionally filterable.
|
||||
*
|
||||
* @author Christophe Beyls
|
||||
*/
|
||||
@AndroidEntryPoint
|
||||
class BookmarksListFragment : Fragment(R.layout.recyclerview), CreateNfcAppDataCallback {
|
||||
|
||||
@Inject
|
||||
lateinit var api: FosdemApi
|
||||
private val viewModel: BookmarksViewModel by viewModels()
|
||||
private val multiChoiceHelper: MultiChoiceHelper by lazy(LazyThreadSafetyMode.NONE) {
|
||||
MultiChoiceHelper(requireActivity() as AppCompatActivity, this, object : MultiChoiceHelper.MultiChoiceModeListener {
|
||||
|
@ -94,7 +100,7 @@ class BookmarksListFragment : Fragment(R.layout.recyclerview), CreateNfcAppDataC
|
|||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
val adapter = BookmarksAdapter(view.context, viewLifecycleOwner, multiChoiceHelper)
|
||||
val adapter = BookmarksAdapter(view.context, multiChoiceHelper)
|
||||
val holder = RecyclerViewViewHolder(view).apply {
|
||||
recyclerView.apply {
|
||||
layoutManager = LinearLayoutManager(recyclerView.context)
|
||||
|
@ -105,6 +111,9 @@ class BookmarksListFragment : Fragment(R.layout.recyclerview), CreateNfcAppDataC
|
|||
isProgressBarVisible = true
|
||||
}
|
||||
|
||||
api.roomStatuses.observe(viewLifecycleOwner) { statuses ->
|
||||
adapter.roomStatuses = statuses
|
||||
}
|
||||
viewModel.bookmarks.observe(viewLifecycleOwner) { bookmarks ->
|
||||
adapter.submitList(bookmarks)
|
||||
multiChoiceHelper.setAdapter(adapter, viewLifecycleOwner)
|
||||
|
|
|
@ -44,7 +44,10 @@ import be.digitalia.fosdem.utils.roomNameToResourceName
|
|||
import be.digitalia.fosdem.utils.stripHtml
|
||||
import be.digitalia.fosdem.viewmodels.EventDetailsViewModel
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
class EventDetailsFragment : Fragment(R.layout.fragment_event_details) {
|
||||
|
||||
private class ViewHolder(view: View) {
|
||||
|
@ -54,6 +57,8 @@ class EventDetailsFragment : Fragment(R.layout.fragment_event_details) {
|
|||
val linksContainer: ViewGroup = view.findViewById(R.id.links_container)
|
||||
}
|
||||
|
||||
@Inject
|
||||
lateinit var api: FosdemApi
|
||||
private val viewModel: EventDetailsViewModel by viewModels()
|
||||
|
||||
val event by lazy<Event>(LazyThreadSafetyMode.NONE) {
|
||||
|
@ -163,7 +168,7 @@ class EventDetailsFragment : Fragment(R.layout.fragment_event_details) {
|
|||
val roomName = event.roomName
|
||||
if (!roomName.isNullOrEmpty()) {
|
||||
holder.roomStatusTextView.run {
|
||||
FosdemApi.getRoomStatuses(context).observe(viewLifecycleOwner) { roomStatuses ->
|
||||
api.roomStatuses.observe(viewLifecycleOwner) { roomStatuses ->
|
||||
val roomStatus = roomStatuses[roomName]
|
||||
if (roomStatus == null) {
|
||||
text = null
|
||||
|
|
|
@ -15,11 +15,17 @@ import androidx.recyclerview.widget.DividerItemDecoration
|
|||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import be.digitalia.fosdem.R
|
||||
import be.digitalia.fosdem.adapters.EventsAdapter
|
||||
import be.digitalia.fosdem.api.FosdemApi
|
||||
import be.digitalia.fosdem.viewmodels.ExternalBookmarksViewModel
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
class ExternalBookmarksListFragment : Fragment(R.layout.recyclerview) {
|
||||
|
||||
@Inject
|
||||
lateinit var api: FosdemApi
|
||||
private val viewModel: ExternalBookmarksViewModel by viewModels()
|
||||
private var addAllMenuItem: MenuItem? = null
|
||||
|
||||
|
@ -32,7 +38,7 @@ class ExternalBookmarksListFragment : Fragment(R.layout.recyclerview) {
|
|||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
val adapter = EventsAdapter(view.context, viewLifecycleOwner)
|
||||
val adapter = EventsAdapter(view.context)
|
||||
val holder = RecyclerViewViewHolder(view).apply {
|
||||
recyclerView.apply {
|
||||
layoutManager = LinearLayoutManager(context)
|
||||
|
@ -45,6 +51,9 @@ class ExternalBookmarksListFragment : Fragment(R.layout.recyclerview) {
|
|||
|
||||
val bookmarkIds = requireArguments().getLongArray(ARG_BOOKMARK_IDS)!!
|
||||
|
||||
api.roomStatuses.observe(viewLifecycleOwner) { statuses ->
|
||||
adapter.roomStatuses = statuses
|
||||
}
|
||||
with(viewModel) {
|
||||
setBookmarkIds(bookmarkIds)
|
||||
bookmarks.observe(viewLifecycleOwner) { bookmarks ->
|
||||
|
|
|
@ -12,7 +12,9 @@ import be.digitalia.fosdem.utils.recyclerView
|
|||
import be.digitalia.fosdem.utils.viewLifecycleLazy
|
||||
import com.google.android.material.tabs.TabLayout
|
||||
import com.google.android.material.tabs.TabLayoutMediator
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
|
||||
@AndroidEntryPoint
|
||||
class LiveFragment : Fragment(R.layout.fragment_live), RecycledViewPoolProvider {
|
||||
|
||||
private class ViewHolder(view: View) {
|
||||
|
|
|
@ -11,19 +11,25 @@ import androidx.recyclerview.widget.DividerItemDecoration
|
|||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import be.digitalia.fosdem.R
|
||||
import be.digitalia.fosdem.adapters.EventsAdapter
|
||||
import be.digitalia.fosdem.api.FosdemApi
|
||||
import be.digitalia.fosdem.model.StatusEvent
|
||||
import be.digitalia.fosdem.viewmodels.LiveViewModel
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
sealed class LiveListFragment(@StringRes private val emptyTextResId: Int,
|
||||
private val dataSourceProvider: (LiveViewModel) -> LiveData<PagedList<StatusEvent>>)
|
||||
: Fragment(R.layout.recyclerview) {
|
||||
|
||||
@Inject
|
||||
lateinit var api: FosdemApi
|
||||
private val viewModel: LiveViewModel by viewModels({ requireParentFragment() })
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
val adapter = EventsAdapter(view.context, viewLifecycleOwner, false)
|
||||
val adapter = EventsAdapter(view.context, false)
|
||||
val holder = RecyclerViewViewHolder(view).apply {
|
||||
recyclerView.apply {
|
||||
val parent = parentFragment
|
||||
|
@ -39,6 +45,9 @@ sealed class LiveListFragment(@StringRes private val emptyTextResId: Int,
|
|||
isProgressBarVisible = true
|
||||
}
|
||||
|
||||
api.roomStatuses.observe(viewLifecycleOwner) { statuses ->
|
||||
adapter.roomStatuses = statuses
|
||||
}
|
||||
dataSourceProvider(viewModel).observe(viewLifecycleOwner) { events ->
|
||||
adapter.submitList(events) {
|
||||
// Ensure we stay at scroll position 0 so we can see the insertion animation
|
||||
|
|
|
@ -5,22 +5,29 @@ import android.view.LayoutInflater
|
|||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.recyclerview.widget.ConcatAdapter
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import be.digitalia.fosdem.R
|
||||
import be.digitalia.fosdem.adapters.EventsAdapter
|
||||
import be.digitalia.fosdem.api.FosdemApi
|
||||
import be.digitalia.fosdem.viewmodels.PersonInfoViewModel
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
class PersonInfoListFragment : Fragment(R.layout.recyclerview) {
|
||||
|
||||
@Inject
|
||||
lateinit var api: FosdemApi
|
||||
// Fetch data from parent Activity's ViewModel
|
||||
private val viewModel: PersonInfoViewModel by viewModels({ requireActivity() })
|
||||
private val viewModel: PersonInfoViewModel by activityViewModels()
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
val adapter = EventsAdapter(view.context, viewLifecycleOwner)
|
||||
val adapter = EventsAdapter(view.context)
|
||||
val holder = RecyclerViewViewHolder(view).apply {
|
||||
recyclerView.apply {
|
||||
val contentMargin = resources.getDimensionPixelSize(R.dimen.content_margin)
|
||||
|
@ -37,6 +44,9 @@ class PersonInfoListFragment : Fragment(R.layout.recyclerview) {
|
|||
isProgressBarVisible = true
|
||||
}
|
||||
|
||||
api.roomStatuses.observe(viewLifecycleOwner) { statuses ->
|
||||
adapter.roomStatuses = statuses
|
||||
}
|
||||
viewModel.events.observe(viewLifecycleOwner) { events ->
|
||||
adapter.submitList(events)
|
||||
holder.isProgressBarVisible = false
|
||||
|
|
|
@ -17,7 +17,9 @@ import be.digitalia.fosdem.activities.PersonInfoActivity
|
|||
import be.digitalia.fosdem.adapters.createSimpleItemCallback
|
||||
import be.digitalia.fosdem.model.Person
|
||||
import be.digitalia.fosdem.viewmodels.PersonsViewModel
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
|
||||
@AndroidEntryPoint
|
||||
class PersonsListFragment : Fragment(R.layout.recyclerview_fastscroll) {
|
||||
|
||||
private val viewModel: PersonsViewModel by viewModels()
|
||||
|
|
|
@ -10,12 +10,19 @@ import androidx.appcompat.app.AlertDialog
|
|||
import androidx.fragment.app.DialogFragment
|
||||
import be.digitalia.fosdem.R
|
||||
import be.digitalia.fosdem.activities.RoomImageDialogActivity
|
||||
import be.digitalia.fosdem.api.FosdemApi
|
||||
import be.digitalia.fosdem.utils.invertImageColors
|
||||
import be.digitalia.fosdem.utils.isLightTheme
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
class RoomImageDialogFragment : DialogFragment() {
|
||||
|
||||
@Inject
|
||||
lateinit var api: FosdemApi
|
||||
|
||||
@SuppressLint("InflateParams")
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
val args = requireArguments()
|
||||
|
@ -29,7 +36,12 @@ class RoomImageDialogFragment : DialogFragment() {
|
|||
}
|
||||
setImageResource(args.getInt(ARG_ROOM_IMAGE_RESOURCE_ID))
|
||||
}
|
||||
RoomImageDialogActivity.configureToolbar(this, contentView.findViewById(R.id.toolbar), args.getString(ARG_ROOM_NAME)!!)
|
||||
RoomImageDialogActivity.configureToolbar(
|
||||
api,
|
||||
this,
|
||||
contentView.findViewById(R.id.toolbar),
|
||||
args.getString(ARG_ROOM_NAME)!!
|
||||
)
|
||||
|
||||
return dialogBuilder
|
||||
.setView(contentView)
|
||||
|
|
|
@ -8,16 +8,22 @@ import androidx.recyclerview.widget.DividerItemDecoration
|
|||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import be.digitalia.fosdem.R
|
||||
import be.digitalia.fosdem.adapters.EventsAdapter
|
||||
import be.digitalia.fosdem.api.FosdemApi
|
||||
import be.digitalia.fosdem.viewmodels.SearchViewModel
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
class SearchResultListFragment : Fragment(R.layout.recyclerview) {
|
||||
|
||||
@Inject
|
||||
lateinit var api: FosdemApi
|
||||
private val viewModel: SearchViewModel by activityViewModels()
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
val adapter = EventsAdapter(view.context, viewLifecycleOwner)
|
||||
val adapter = EventsAdapter(view.context)
|
||||
val holder = RecyclerViewViewHolder(view).apply {
|
||||
recyclerView.apply {
|
||||
layoutManager = LinearLayoutManager(context)
|
||||
|
@ -28,6 +34,9 @@ class SearchResultListFragment : Fragment(R.layout.recyclerview) {
|
|||
isProgressBarVisible = true
|
||||
}
|
||||
|
||||
api.roomStatuses.observe(viewLifecycleOwner) { statuses ->
|
||||
adapter.roomStatuses = statuses
|
||||
}
|
||||
viewModel.results.observe(viewLifecycleOwner) { result ->
|
||||
adapter.submitList((result as? SearchViewModel.Result.Success)?.list)
|
||||
holder.isProgressBarVisible = false
|
||||
|
|
|
@ -17,7 +17,9 @@ import be.digitalia.fosdem.model.Event
|
|||
import be.digitalia.fosdem.model.Track
|
||||
import be.digitalia.fosdem.viewmodels.TrackScheduleListViewModel
|
||||
import be.digitalia.fosdem.viewmodels.TrackScheduleViewModel
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
|
||||
@AndroidEntryPoint
|
||||
class TrackScheduleListFragment : Fragment(R.layout.recyclerview), TrackScheduleAdapter.EventClickListener {
|
||||
|
||||
private val viewModel: TrackScheduleListViewModel by viewModels()
|
||||
|
|
|
@ -12,7 +12,7 @@ import androidx.recyclerview.widget.RecyclerView
|
|||
import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||
import androidx.viewpager2.widget.ViewPager2
|
||||
import be.digitalia.fosdem.R
|
||||
import be.digitalia.fosdem.db.AppDatabase
|
||||
import be.digitalia.fosdem.db.ScheduleDao
|
||||
import be.digitalia.fosdem.model.Day
|
||||
import be.digitalia.fosdem.utils.enforceSingleScrollDirection
|
||||
import be.digitalia.fosdem.utils.instantiate
|
||||
|
@ -20,7 +20,10 @@ import be.digitalia.fosdem.utils.recyclerView
|
|||
import be.digitalia.fosdem.utils.viewLifecycleLazy
|
||||
import com.google.android.material.tabs.TabLayout
|
||||
import com.google.android.material.tabs.TabLayoutMediator
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
class TracksFragment : Fragment(R.layout.fragment_tracks), RecycledViewPoolProvider {
|
||||
|
||||
private class ViewHolder(view: View) {
|
||||
|
@ -30,6 +33,9 @@ class TracksFragment : Fragment(R.layout.fragment_tracks), RecycledViewPoolProvi
|
|||
val tabs: TabLayout = view.findViewById(R.id.tabs)
|
||||
}
|
||||
|
||||
@Inject
|
||||
lateinit var scheduleDao: ScheduleDao
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
|
@ -46,7 +52,7 @@ class TracksFragment : Fragment(R.layout.fragment_tracks), RecycledViewPoolProvi
|
|||
requireActivity().getPreferences(Context.MODE_PRIVATE).getInt(PREF_CURRENT_PAGE, -1)
|
||||
} else -1
|
||||
|
||||
AppDatabase.getInstance(requireContext()).scheduleDao.days.observe(viewLifecycleOwner) { days ->
|
||||
scheduleDao.days.observe(viewLifecycleOwner) { days ->
|
||||
holder.run {
|
||||
daysAdapter.days = days
|
||||
|
||||
|
|
|
@ -19,7 +19,9 @@ import be.digitalia.fosdem.activities.TrackScheduleActivity
|
|||
import be.digitalia.fosdem.model.Day
|
||||
import be.digitalia.fosdem.model.Track
|
||||
import be.digitalia.fosdem.viewmodels.TracksViewModel
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
|
||||
@AndroidEntryPoint
|
||||
class TracksListFragment : Fragment(R.layout.recyclerview) {
|
||||
|
||||
private val viewModel: TracksViewModel by viewModels()
|
||||
|
|
|
@ -0,0 +1,87 @@
|
|||
package be.digitalia.fosdem.inject
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import androidx.room.Room
|
||||
import androidx.room.RoomDatabase
|
||||
import androidx.room.migration.Migration
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||
import be.digitalia.fosdem.db.AppDatabase
|
||||
import be.digitalia.fosdem.db.BookmarksDao
|
||||
import be.digitalia.fosdem.db.ScheduleDao
|
||||
import be.digitalia.fosdem.db.entities.Bookmark
|
||||
import be.digitalia.fosdem.db.entities.EventEntity
|
||||
import be.digitalia.fosdem.model.Day
|
||||
import be.digitalia.fosdem.model.Link
|
||||
import be.digitalia.fosdem.model.Track
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Named
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
object DatabaseModule {
|
||||
private const val DB_FILE = "fosdem.sqlite"
|
||||
private const val DB_PREFS_FILE = "database"
|
||||
|
||||
@Provides
|
||||
@Named("Database")
|
||||
fun providesSharedPreferences(@ApplicationContext context: Context): SharedPreferences {
|
||||
return context.applicationContext.getSharedPreferences(DB_PREFS_FILE, Context.MODE_PRIVATE)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesAppDatabase(@ApplicationContext context: Context): AppDatabase {
|
||||
val MIGRATION_1_2 = object : Migration(1, 2) {
|
||||
override fun migrate(database: SupportSQLiteDatabase) = with(database) {
|
||||
// Events: make primary key and track_id not null
|
||||
execSQL("CREATE TABLE tmp_${EventEntity.TABLE_NAME} (id INTEGER PRIMARY KEY NOT NULL, day_index INTEGER NOT NULL, start_time INTEGER, end_time INTEGER, room_name TEXT, slug TEXT, track_id INTEGER NOT NULL, abstract TEXT, description TEXT)")
|
||||
execSQL("INSERT INTO tmp_${EventEntity.TABLE_NAME} SELECT * FROM ${EventEntity.TABLE_NAME}")
|
||||
execSQL("DROP TABLE ${EventEntity.TABLE_NAME}")
|
||||
execSQL("ALTER TABLE tmp_${EventEntity.TABLE_NAME} RENAME TO ${EventEntity.TABLE_NAME}")
|
||||
execSQL("CREATE INDEX event_day_index_idx ON ${EventEntity.TABLE_NAME} (day_index)")
|
||||
execSQL("CREATE INDEX event_start_time_idx ON ${EventEntity.TABLE_NAME} (start_time)")
|
||||
execSQL("CREATE INDEX event_end_time_idx ON ${EventEntity.TABLE_NAME} (end_time)")
|
||||
execSQL("CREATE INDEX event_track_id_idx ON ${EventEntity.TABLE_NAME} (track_id)")
|
||||
// Links: add explicit primary key
|
||||
execSQL("CREATE TABLE tmp_${Link.TABLE_NAME} (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, event_id INTEGER NOT NULL, url TEXT NOT NULL, description TEXT)")
|
||||
execSQL("INSERT INTO tmp_${Link.TABLE_NAME} SELECT `rowid` AS id, event_id, url, description FROM ${Link.TABLE_NAME}")
|
||||
execSQL("DROP TABLE ${Link.TABLE_NAME}")
|
||||
execSQL("ALTER TABLE tmp_${Link.TABLE_NAME} RENAME TO ${Link.TABLE_NAME}")
|
||||
execSQL("CREATE INDEX link_event_id_idx ON ${Link.TABLE_NAME} (event_id)")
|
||||
// Tracks: make primary key not null
|
||||
execSQL("CREATE TABLE tmp_${Track.TABLE_NAME} (id INTEGER PRIMARY KEY NOT NULL, name TEXT NOT NULL, type TEXT NOT NULL)")
|
||||
execSQL("INSERT INTO tmp_${Track.TABLE_NAME} SELECT * FROM ${Track.TABLE_NAME}")
|
||||
execSQL("DROP TABLE ${Track.TABLE_NAME}")
|
||||
execSQL("ALTER TABLE tmp_${Track.TABLE_NAME} RENAME TO ${Track.TABLE_NAME}")
|
||||
execSQL("CREATE UNIQUE INDEX track_main_idx ON ${Track.TABLE_NAME} (name, type)")
|
||||
// Days: make primary key not null and rename _index to index
|
||||
execSQL("CREATE TABLE tmp_${Day.TABLE_NAME} (`index` INTEGER PRIMARY KEY NOT NULL, date INTEGER NOT NULL)")
|
||||
execSQL("INSERT INTO tmp_${Day.TABLE_NAME} SELECT _index as `index`, date FROM ${Day.TABLE_NAME}")
|
||||
execSQL("DROP TABLE ${Day.TABLE_NAME}")
|
||||
execSQL("ALTER TABLE tmp_${Day.TABLE_NAME} RENAME TO ${Day.TABLE_NAME}")
|
||||
// Bookmarks: make primary key not null
|
||||
execSQL("CREATE TABLE tmp_${Bookmark.TABLE_NAME} (event_id INTEGER PRIMARY KEY NOT NULL)")
|
||||
execSQL("INSERT INTO tmp_${Bookmark.TABLE_NAME} SELECT * FROM ${Bookmark.TABLE_NAME}")
|
||||
execSQL("DROP TABLE ${Bookmark.TABLE_NAME}")
|
||||
execSQL("ALTER TABLE tmp_${Bookmark.TABLE_NAME} RENAME TO ${Bookmark.TABLE_NAME}")
|
||||
}
|
||||
}
|
||||
|
||||
return Room.databaseBuilder(context, AppDatabase::class.java, DB_FILE)
|
||||
.addMigrations(MIGRATION_1_2)
|
||||
.setJournalMode(RoomDatabase.JournalMode.TRUNCATE)
|
||||
.build()
|
||||
}
|
||||
|
||||
@Provides
|
||||
fun providesScheduleDao(appDatabase: AppDatabase): ScheduleDao = appDatabase.scheduleDao
|
||||
|
||||
@Provides
|
||||
fun providesBookmarksDao(appDatabase: AppDatabase): BookmarksDao = appDatabase.bookmarksDao
|
||||
}
|
|
@ -10,15 +10,21 @@ import android.net.Uri
|
|||
import android.os.ParcelFileDescriptor
|
||||
import android.provider.OpenableColumns
|
||||
import androidx.core.app.ShareCompat
|
||||
import androidx.core.content.ContentProviderCompat
|
||||
import be.digitalia.fosdem.BuildConfig
|
||||
import be.digitalia.fosdem.R
|
||||
import be.digitalia.fosdem.api.FosdemUrls
|
||||
import be.digitalia.fosdem.db.AppDatabase
|
||||
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
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.EntryPointAccessors
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import okio.buffer
|
||||
import okio.sink
|
||||
import java.io.FileNotFoundException
|
||||
|
@ -34,6 +40,19 @@ import java.util.TimeZone
|
|||
*/
|
||||
class BookmarksExportProvider : ContentProvider() {
|
||||
|
||||
private val scheduleDao: ScheduleDao by lazy {
|
||||
EntryPointAccessors.fromApplication(
|
||||
ContentProviderCompat.requireContext(this),
|
||||
BookmarksExportProviderEntryPoint::class.java
|
||||
).scheduleDao
|
||||
}
|
||||
private val bookmarksDao: BookmarksDao by lazy {
|
||||
EntryPointAccessors.fromApplication(
|
||||
ContentProviderCompat.requireContext(this),
|
||||
BookmarksExportProviderEntryPoint::class.java
|
||||
).bookmarksDao
|
||||
}
|
||||
|
||||
override fun onCreate() = true
|
||||
|
||||
override fun insert(uri: Uri, values: ContentValues?) = throw UnsupportedOperationException()
|
||||
|
@ -45,7 +64,7 @@ class BookmarksExportProvider : ContentProvider() {
|
|||
override fun getType(uri: Uri) = TYPE
|
||||
|
||||
override fun query(uri: Uri, projection: Array<String>?, selection: String?, selectionArgs: Array<String>?, sortOrder: String?): Cursor? {
|
||||
val ctx = context!!
|
||||
val ctx = ContentProviderCompat.requireContext(this)
|
||||
val proj = projection ?: COLUMNS
|
||||
val cols = arrayOfNulls<String>(proj.size)
|
||||
val values = arrayOfNulls<Any>(proj.size)
|
||||
|
@ -54,7 +73,7 @@ class BookmarksExportProvider : ContentProvider() {
|
|||
when (col) {
|
||||
OpenableColumns.DISPLAY_NAME -> {
|
||||
cols[columnCount] = OpenableColumns.DISPLAY_NAME
|
||||
values[columnCount++] = ctx.getString(R.string.export_bookmarks_file_name, AppDatabase.getInstance(ctx).scheduleDao.getYear())
|
||||
values[columnCount++] = ctx.getString(R.string.export_bookmarks_file_name, scheduleDao.getYear())
|
||||
}
|
||||
OpenableColumns.SIZE -> {
|
||||
cols[columnCount] = OpenableColumns.SIZE
|
||||
|
@ -72,17 +91,14 @@ class BookmarksExportProvider : ContentProvider() {
|
|||
override fun openFile(uri: Uri, mode: String): ParcelFileDescriptor? {
|
||||
return try {
|
||||
val pipe = ParcelFileDescriptor.createPipe()
|
||||
DownloadThread(
|
||||
ParcelFileDescriptor.AutoCloseOutputStream(pipe[1]),
|
||||
AppDatabase.getInstance(context!!)
|
||||
).start()
|
||||
DownloadThread(ParcelFileDescriptor.AutoCloseOutputStream(pipe[1]), bookmarksDao).start()
|
||||
pipe[0]
|
||||
} catch (e: IOException) {
|
||||
throw FileNotFoundException("Could not open pipe")
|
||||
}
|
||||
}
|
||||
|
||||
private class DownloadThread(private val outputStream: OutputStream, private val appDatabase: AppDatabase) : Thread() {
|
||||
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 {
|
||||
|
@ -93,7 +109,7 @@ class BookmarksExportProvider : ContentProvider() {
|
|||
override fun run() {
|
||||
try {
|
||||
ICalendarWriter(outputStream.sink().buffer()).use { writer ->
|
||||
val bookmarks = appDatabase.bookmarksDao.getBookmarks()
|
||||
val bookmarks = bookmarksDao.getBookmarks()
|
||||
writer.write("BEGIN", "VCALENDAR")
|
||||
writer.write("VERSION", "2.0")
|
||||
writer.write("PRODID", "-//${BuildConfig.APPLICATION_ID}//NONSGML ${BuildConfig.VERSION_NAME}//EN")
|
||||
|
@ -143,6 +159,13 @@ class BookmarksExportProvider : ContentProvider() {
|
|||
}
|
||||
}
|
||||
|
||||
@EntryPoint
|
||||
@InstallIn(SingletonComponent::class)
|
||||
interface BookmarksExportProviderEntryPoint {
|
||||
val scheduleDao: ScheduleDao
|
||||
val bookmarksDao: BookmarksDao
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val TYPE = "text/calendar"
|
||||
private val URI = Uri.Builder()
|
||||
|
|
|
@ -5,7 +5,12 @@ import android.content.ContentProvider
|
|||
import android.content.ContentValues
|
||||
import android.database.Cursor
|
||||
import android.net.Uri
|
||||
import be.digitalia.fosdem.db.AppDatabase
|
||||
import androidx.core.content.ContentProviderCompat
|
||||
import be.digitalia.fosdem.db.ScheduleDao
|
||||
import dagger.hilt.EntryPoint
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.EntryPointAccessors
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
|
||||
/**
|
||||
* Simple content provider responsible for search suggestions.
|
||||
|
@ -14,25 +19,22 @@ import be.digitalia.fosdem.db.AppDatabase
|
|||
*/
|
||||
class SearchSuggestionProvider : ContentProvider() {
|
||||
|
||||
override fun onCreate(): Boolean {
|
||||
return true
|
||||
private val scheduleDao: ScheduleDao by lazy {
|
||||
EntryPointAccessors.fromApplication(
|
||||
ContentProviderCompat.requireContext(this),
|
||||
SearchSuggestionProviderEntryPoint::class.java
|
||||
).scheduleDao
|
||||
}
|
||||
|
||||
override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?): Int {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
override fun onCreate() = true
|
||||
|
||||
override fun insert(uri: Uri, values: ContentValues?): Uri? {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
override fun insert(uri: Uri, values: ContentValues?) = throw UnsupportedOperationException()
|
||||
|
||||
override fun update(uri: Uri, values: ContentValues?, selection: String?, selectionArgs: Array<String>?): Int {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
override fun update(uri: Uri, values: ContentValues?, selection: String?, selectionArgs: Array<String>?) = throw UnsupportedOperationException()
|
||||
|
||||
override fun getType(uri: Uri): String? {
|
||||
return SearchManager.SUGGEST_MIME_TYPE
|
||||
}
|
||||
override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?) = throw UnsupportedOperationException()
|
||||
|
||||
override fun getType(uri: Uri) = SearchManager.SUGGEST_MIME_TYPE
|
||||
|
||||
override fun query(uri: Uri, projection: Array<String>?, selection: String?, selectionArgs: Array<String>?, sortOrder: String?): Cursor? {
|
||||
var query = uri.lastPathSegment ?: return null
|
||||
|
@ -45,7 +47,13 @@ class SearchSuggestionProvider : ContentProvider() {
|
|||
val limitParam = uri.getQueryParameter("limit")
|
||||
val limit = if (limitParam.isNullOrEmpty()) DEFAULT_MAX_RESULTS else limitParam.toInt()
|
||||
|
||||
return AppDatabase.getInstance(context!!).scheduleDao.getSearchSuggestionResults(query, limit)
|
||||
return scheduleDao.getSearchSuggestionResults(query, limit)
|
||||
}
|
||||
|
||||
@EntryPoint
|
||||
@InstallIn(SingletonComponent::class)
|
||||
interface SearchSuggestionProviderEntryPoint {
|
||||
val scheduleDao: ScheduleDao
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
|
|
@ -6,23 +6,29 @@ import android.content.Intent
|
|||
import be.digitalia.fosdem.BuildConfig
|
||||
import be.digitalia.fosdem.alarms.FosdemAlarmManager
|
||||
import be.digitalia.fosdem.services.AlarmIntentService
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* Entry point for system-generated events: boot complete and alarms.
|
||||
*
|
||||
* @author Christophe Beyls
|
||||
*/
|
||||
@AndroidEntryPoint
|
||||
class AlarmReceiver : BroadcastReceiver() {
|
||||
|
||||
@Inject
|
||||
lateinit var alarmManager: FosdemAlarmManager
|
||||
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
when (intent.action) {
|
||||
ACTION_NOTIFY_EVENT -> {
|
||||
val serviceIntent = Intent(ACTION_NOTIFY_EVENT)
|
||||
.setData(intent.data)
|
||||
.setData(intent.data)
|
||||
AlarmIntentService.enqueueWork(context, serviceIntent)
|
||||
}
|
||||
Intent.ACTION_BOOT_COMPLETED -> {
|
||||
val serviceAction = if (FosdemAlarmManager.isEnabled) AlarmIntentService.ACTION_UPDATE_ALARMS
|
||||
val serviceAction = if (alarmManager.isEnabled) AlarmIntentService.ACTION_UPDATE_ALARMS
|
||||
else AlarmIntentService.ACTION_DISABLE_ALARMS
|
||||
val serviceIntent = Intent(serviceAction)
|
||||
AlarmIntentService.enqueueWork(context, serviceIntent)
|
||||
|
|
|
@ -31,20 +31,30 @@ import be.digitalia.fosdem.R
|
|||
import be.digitalia.fosdem.activities.EventDetailsActivity
|
||||
import be.digitalia.fosdem.activities.MainActivity
|
||||
import be.digitalia.fosdem.activities.RoomImageDialogActivity
|
||||
import be.digitalia.fosdem.db.AppDatabase
|
||||
import be.digitalia.fosdem.db.BookmarksDao
|
||||
import be.digitalia.fosdem.db.ScheduleDao
|
||||
import be.digitalia.fosdem.model.AlarmInfo
|
||||
import be.digitalia.fosdem.model.Event
|
||||
import be.digitalia.fosdem.receivers.AlarmReceiver
|
||||
import be.digitalia.fosdem.utils.PreferenceKeys
|
||||
import be.digitalia.fosdem.utils.roomNameToResourceName
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* A service to schedule or unschedule alarms in the background, keeping the app responsive.
|
||||
*
|
||||
* @author Christophe Beyls
|
||||
*/
|
||||
@AndroidEntryPoint
|
||||
class AlarmIntentService : JobIntentService() {
|
||||
|
||||
@Inject
|
||||
lateinit var bookmarksDao: BookmarksDao
|
||||
@Inject
|
||||
lateinit var scheduleDao: ScheduleDao
|
||||
|
||||
private val alarmManager by lazy<AlarmManager> {
|
||||
getSystemService()!!
|
||||
}
|
||||
|
@ -63,7 +73,7 @@ class AlarmIntentService : JobIntentService() {
|
|||
val delay = delay
|
||||
val now = System.currentTimeMillis()
|
||||
var hasAlarms = false
|
||||
for (info in AppDatabase.getInstance(this).bookmarksDao.getBookmarksAlarmInfo(0L)) {
|
||||
for (info in bookmarksDao.getBookmarksAlarmInfo(0L)) {
|
||||
val startTime = info.startTime
|
||||
val notificationTime = if (startTime == null) -1L else startTime.time - delay
|
||||
val pi = getAlarmPendingIntent(info.eventId)
|
||||
|
@ -82,7 +92,7 @@ class AlarmIntentService : JobIntentService() {
|
|||
}
|
||||
ACTION_DISABLE_ALARMS -> {
|
||||
// Cancel alarms of every bookmark in the future
|
||||
for (info in AppDatabase.getInstance(this).bookmarksDao.getBookmarksAlarmInfo(System.currentTimeMillis())) {
|
||||
for (info in bookmarksDao.getBookmarksAlarmInfo(System.currentTimeMillis())) {
|
||||
alarmManager.cancel(getAlarmPendingIntent(info.eventId))
|
||||
}
|
||||
setAlarmReceiverEnabled(false)
|
||||
|
@ -116,7 +126,7 @@ class AlarmIntentService : JobIntentService() {
|
|||
}
|
||||
AlarmReceiver.ACTION_NOTIFY_EVENT -> {
|
||||
val eventId = intent.dataString!!.toLong()
|
||||
val event = AppDatabase.getInstance(this).scheduleDao.getEvent(eventId)
|
||||
val event = runBlocking { scheduleDao.getEvent(eventId) }
|
||||
if (event != null) {
|
||||
NotificationManagerCompat.from(this).notify(eventId.toInt(), buildNotification(event))
|
||||
}
|
||||
|
|
|
@ -1,31 +0,0 @@
|
|||
package be.digitalia.fosdem.utils
|
||||
|
||||
/**
|
||||
* Kotlin Singleton with argument.
|
||||
*
|
||||
* @author Christophe Beyls
|
||||
*/
|
||||
open class SingletonHolder<out T : Any, in A>(creator: (A) -> T) {
|
||||
private var creator: ((A) -> T)? = creator
|
||||
@Volatile
|
||||
private var instance: T? = null
|
||||
|
||||
fun getInstance(arg: A): T {
|
||||
val i = instance
|
||||
if (i != null) {
|
||||
return i
|
||||
}
|
||||
|
||||
return synchronized(this) {
|
||||
val i2 = instance
|
||||
if (i2 != null) {
|
||||
i2
|
||||
} else {
|
||||
val created = creator!!(arg)
|
||||
instance = created
|
||||
creator = null
|
||||
created
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,19 +1,20 @@
|
|||
package be.digitalia.fosdem.viewmodels
|
||||
|
||||
import android.app.Application
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.distinctUntilChanged
|
||||
import androidx.lifecycle.map
|
||||
import androidx.lifecycle.switchMap
|
||||
import be.digitalia.fosdem.db.AppDatabase
|
||||
import be.digitalia.fosdem.db.BookmarksDao
|
||||
import be.digitalia.fosdem.model.BookmarkStatus
|
||||
import be.digitalia.fosdem.model.Event
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import javax.inject.Inject
|
||||
|
||||
class BookmarkStatusViewModel(application: Application) : AndroidViewModel(application) {
|
||||
@HiltViewModel
|
||||
class BookmarkStatusViewModel @Inject constructor(private val bookmarksDao: BookmarksDao) : ViewModel() {
|
||||
|
||||
private val appDatabase = AppDatabase.getInstance(application)
|
||||
private val eventLiveData = MutableLiveData<Event?>()
|
||||
private var firstResultReceived = false
|
||||
|
||||
|
@ -21,13 +22,13 @@ class BookmarkStatusViewModel(application: Application) : AndroidViewModel(appli
|
|||
if (event == null) {
|
||||
MutableLiveData(null)
|
||||
} else {
|
||||
appDatabase.bookmarksDao.getBookmarkStatus(event)
|
||||
.distinctUntilChanged() // Prevent updating the UI when a bookmark is added back or removed back
|
||||
.map { isBookmarked ->
|
||||
val isUpdate = firstResultReceived
|
||||
firstResultReceived = true
|
||||
BookmarkStatus(isBookmarked, isUpdate)
|
||||
}
|
||||
bookmarksDao.getBookmarkStatus(event)
|
||||
.distinctUntilChanged() // Prevent updating the UI when a bookmark is added back or removed back
|
||||
.map { isBookmarked ->
|
||||
val isUpdate = firstResultReceived
|
||||
firstResultReceived = true
|
||||
BookmarkStatus(isBookmarked, isUpdate)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -46,9 +47,9 @@ class BookmarkStatusViewModel(application: Application) : AndroidViewModel(appli
|
|||
// Ignore the action if the status for the current event hasn't been received yet
|
||||
if (event != null && currentStatus != null && firstResultReceived) {
|
||||
if (currentStatus.isBookmarked) {
|
||||
appDatabase.bookmarksDao.removeBookmarkAsync(event)
|
||||
bookmarksDao.removeBookmarkAsync(event)
|
||||
} else {
|
||||
appDatabase.bookmarksDao.addBookmarkAsync(event)
|
||||
bookmarksDao.addBookmarkAsync(event)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,35 +3,42 @@ package be.digitalia.fosdem.viewmodels
|
|||
import android.app.Application
|
||||
import android.net.Uri
|
||||
import android.text.format.DateUtils
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.switchMap
|
||||
import be.digitalia.fosdem.BuildConfig
|
||||
import be.digitalia.fosdem.db.AppDatabase
|
||||
import be.digitalia.fosdem.db.BookmarksDao
|
||||
import be.digitalia.fosdem.db.ScheduleDao
|
||||
import be.digitalia.fosdem.livedata.LiveDataFactory
|
||||
import be.digitalia.fosdem.model.Event
|
||||
import be.digitalia.fosdem.parsers.ExportedBookmarksParser
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import okio.buffer
|
||||
import okio.source
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.inject.Inject
|
||||
|
||||
class BookmarksViewModel(application: Application) : AndroidViewModel(application) {
|
||||
@HiltViewModel
|
||||
class BookmarksViewModel @Inject constructor(
|
||||
private val bookmarksDao: BookmarksDao,
|
||||
private val scheduleDao: ScheduleDao,
|
||||
private val application: Application
|
||||
) : ViewModel() {
|
||||
|
||||
private val appDatabase = AppDatabase.getInstance(application)
|
||||
private val upcomingOnlyLiveData = MutableLiveData<Boolean>()
|
||||
|
||||
val bookmarks: LiveData<List<Event>> = upcomingOnlyLiveData.switchMap { upcomingOnly: Boolean ->
|
||||
if (upcomingOnly) {
|
||||
// Refresh upcoming bookmarks every 2 minutes
|
||||
LiveDataFactory.interval(2L, TimeUnit.MINUTES)
|
||||
.switchMap {
|
||||
appDatabase.bookmarksDao.getBookmarks(System.currentTimeMillis() - TIME_OFFSET)
|
||||
}
|
||||
.switchMap {
|
||||
bookmarksDao.getBookmarks(System.currentTimeMillis() - TIME_OFFSET)
|
||||
}
|
||||
} else {
|
||||
appDatabase.bookmarksDao.getBookmarks(-1L)
|
||||
bookmarksDao.getBookmarks(-1L)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -44,15 +51,14 @@ class BookmarksViewModel(application: Application) : AndroidViewModel(applicatio
|
|||
}
|
||||
|
||||
fun removeBookmarks(eventIds: LongArray) {
|
||||
appDatabase.bookmarksDao.removeBookmarksAsync(eventIds)
|
||||
bookmarksDao.removeBookmarksAsync(eventIds)
|
||||
}
|
||||
|
||||
suspend fun readBookmarkIds(uri: Uri): LongArray {
|
||||
return withContext(Dispatchers.IO) {
|
||||
val parser = ExportedBookmarksParser(BuildConfig.APPLICATION_ID, appDatabase.scheduleDao.getYear())
|
||||
checkNotNull(getApplication<Application>().contentResolver.openInputStream(uri)).source().buffer().use {
|
||||
parser.parse(it)
|
||||
}
|
||||
@Suppress("BlockingMethodInNonBlockingContext")
|
||||
suspend fun readBookmarkIds(uri: Uri): LongArray = withContext(Dispatchers.IO) {
|
||||
val parser = ExportedBookmarksParser(BuildConfig.APPLICATION_ID, scheduleDao.getYear())
|
||||
checkNotNull(application.contentResolver.openInputStream(uri)).source().buffer().use {
|
||||
parser.parse(it)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,21 +1,22 @@
|
|||
package be.digitalia.fosdem.viewmodels
|
||||
|
||||
import android.app.Application
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.switchMap
|
||||
import be.digitalia.fosdem.db.AppDatabase
|
||||
import be.digitalia.fosdem.db.ScheduleDao
|
||||
import be.digitalia.fosdem.model.Event
|
||||
import be.digitalia.fosdem.model.EventDetails
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import javax.inject.Inject
|
||||
|
||||
class EventDetailsViewModel(application: Application) : AndroidViewModel(application) {
|
||||
@HiltViewModel
|
||||
class EventDetailsViewModel @Inject constructor(scheduleDao: ScheduleDao) : ViewModel() {
|
||||
|
||||
private val appDatabase = AppDatabase.getInstance(application)
|
||||
private val eventLiveData = MutableLiveData<Event>()
|
||||
|
||||
val eventDetails: LiveData<EventDetails> = eventLiveData.switchMap { event: Event ->
|
||||
appDatabase.scheduleDao.getEventDetails(event)
|
||||
scheduleDao.getEventDetails(event)
|
||||
}
|
||||
|
||||
fun setEvent(event: Event) {
|
||||
|
|
|
@ -1,27 +1,28 @@
|
|||
package be.digitalia.fosdem.viewmodels
|
||||
|
||||
import android.app.Application
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.liveData
|
||||
import androidx.lifecycle.switchMap
|
||||
import be.digitalia.fosdem.db.AppDatabase
|
||||
import be.digitalia.fosdem.db.ScheduleDao
|
||||
import be.digitalia.fosdem.model.Event
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import javax.inject.Inject
|
||||
|
||||
class EventViewModel(application: Application) : AndroidViewModel(application) {
|
||||
@HiltViewModel
|
||||
class EventViewModel @Inject constructor(scheduleDao: ScheduleDao) : ViewModel() {
|
||||
|
||||
private val appDatabase = AppDatabase.getInstance(application)
|
||||
private val eventIdLiveData = MutableLiveData<Long>()
|
||||
|
||||
val event: LiveData<Event?> = eventIdLiveData.switchMap { id: Long ->
|
||||
MutableLiveData<Event?>().also {
|
||||
appDatabase.queryExecutor.execute {
|
||||
it.postValue(appDatabase.scheduleDao.getEvent(id))
|
||||
}
|
||||
val event: LiveData<Event?> = eventIdLiveData.switchMap { id ->
|
||||
liveData {
|
||||
emit(scheduleDao.getEvent(id))
|
||||
}
|
||||
}
|
||||
|
||||
val isEventIdSet = eventIdLiveData.value != null
|
||||
val isEventIdSet
|
||||
get() = eventIdLiveData.value != null
|
||||
|
||||
fun setEventId(eventId: Long) {
|
||||
if (eventId != eventIdLiveData.value) {
|
||||
|
|
|
@ -1,22 +1,27 @@
|
|||
package be.digitalia.fosdem.viewmodels
|
||||
|
||||
import android.app.Application
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.switchMap
|
||||
import androidx.paging.PagedList
|
||||
import androidx.paging.toLiveData
|
||||
import be.digitalia.fosdem.db.AppDatabase
|
||||
import be.digitalia.fosdem.db.BookmarksDao
|
||||
import be.digitalia.fosdem.db.ScheduleDao
|
||||
import be.digitalia.fosdem.model.StatusEvent
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import javax.inject.Inject
|
||||
|
||||
class ExternalBookmarksViewModel(application: Application) : AndroidViewModel(application) {
|
||||
@HiltViewModel
|
||||
class ExternalBookmarksViewModel @Inject constructor(
|
||||
scheduleDao: ScheduleDao,
|
||||
private val bookmarksDao: BookmarksDao
|
||||
) : ViewModel() {
|
||||
|
||||
private val appDatabase = AppDatabase.getInstance(application)
|
||||
private val bookmarkIdsLiveData = MutableLiveData<LongArray>()
|
||||
|
||||
val bookmarks: LiveData<PagedList<StatusEvent>> = bookmarkIdsLiveData.switchMap { bookmarkIds: LongArray ->
|
||||
appDatabase.scheduleDao.getEvents(bookmarkIds).toLiveData(20)
|
||||
val bookmarks: LiveData<PagedList<StatusEvent>> = bookmarkIdsLiveData.switchMap { bookmarkIds ->
|
||||
scheduleDao.getEvents(bookmarkIds).toLiveData(20)
|
||||
}
|
||||
|
||||
fun setBookmarkIds(bookmarkIds: LongArray) {
|
||||
|
@ -28,6 +33,6 @@ class ExternalBookmarksViewModel(application: Application) : AndroidViewModel(ap
|
|||
|
||||
fun addAll() {
|
||||
val bookmarkIds = bookmarkIdsLiveData.value ?: return
|
||||
appDatabase.bookmarksDao.addBookmarksAsync(bookmarkIds)
|
||||
bookmarksDao.addBookmarksAsync(bookmarkIds)
|
||||
}
|
||||
}
|
|
@ -1,29 +1,30 @@
|
|||
package be.digitalia.fosdem.viewmodels
|
||||
|
||||
import android.app.Application
|
||||
import android.text.format.DateUtils
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.switchMap
|
||||
import androidx.paging.PagedList
|
||||
import androidx.paging.toLiveData
|
||||
import be.digitalia.fosdem.db.AppDatabase
|
||||
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.util.concurrent.TimeUnit
|
||||
import javax.inject.Inject
|
||||
|
||||
class LiveViewModel(application: Application) : AndroidViewModel(application) {
|
||||
@HiltViewModel
|
||||
class LiveViewModel @Inject constructor(scheduleDao: ScheduleDao) : ViewModel() {
|
||||
|
||||
private val appDatabase = AppDatabase.getInstance(application)
|
||||
private val heartbeat = LiveDataFactory.interval(1L, TimeUnit.MINUTES)
|
||||
|
||||
val nextEvents: LiveData<PagedList<StatusEvent>> = heartbeat.switchMap {
|
||||
val now = System.currentTimeMillis()
|
||||
appDatabase.scheduleDao.getEventsWithStartTime(now, now + NEXT_EVENTS_INTERVAL).toLiveData(20)
|
||||
scheduleDao.getEventsWithStartTime(now, now + NEXT_EVENTS_INTERVAL).toLiveData(20)
|
||||
}
|
||||
|
||||
val eventsInProgress: LiveData<PagedList<StatusEvent>> = heartbeat.switchMap {
|
||||
appDatabase.scheduleDao.getEventsInProgress(System.currentTimeMillis()).toLiveData(20)
|
||||
scheduleDao.getEventsInProgress(System.currentTimeMillis()).toLiveData(20)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
|
|
@ -1,23 +1,24 @@
|
|||
package be.digitalia.fosdem.viewmodels
|
||||
|
||||
import android.app.Application
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.switchMap
|
||||
import androidx.paging.PagedList
|
||||
import androidx.paging.toLiveData
|
||||
import be.digitalia.fosdem.db.AppDatabase
|
||||
import be.digitalia.fosdem.db.ScheduleDao
|
||||
import be.digitalia.fosdem.model.Person
|
||||
import be.digitalia.fosdem.model.StatusEvent
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import javax.inject.Inject
|
||||
|
||||
class PersonInfoViewModel(application: Application) : AndroidViewModel(application) {
|
||||
@HiltViewModel
|
||||
class PersonInfoViewModel @Inject constructor(scheduleDao: ScheduleDao) : ViewModel() {
|
||||
|
||||
private val appDatabase = AppDatabase.getInstance(application)
|
||||
private val personLiveData = MutableLiveData<Person>()
|
||||
|
||||
val events: LiveData<PagedList<StatusEvent>> = personLiveData.switchMap { person: Person ->
|
||||
appDatabase.scheduleDao.getEvents(person).toLiveData(20)
|
||||
scheduleDao.getEvents(person).toLiveData(20)
|
||||
}
|
||||
|
||||
fun setPerson(person: Person) {
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
package be.digitalia.fosdem.viewmodels
|
||||
|
||||
import android.app.Application
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.paging.PagedList
|
||||
import androidx.paging.toLiveData
|
||||
import be.digitalia.fosdem.db.AppDatabase
|
||||
import be.digitalia.fosdem.db.ScheduleDao
|
||||
import be.digitalia.fosdem.model.Person
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import javax.inject.Inject
|
||||
|
||||
class PersonsViewModel(application: Application) : AndroidViewModel(application) {
|
||||
@HiltViewModel
|
||||
class PersonsViewModel @Inject constructor(scheduleDao: ScheduleDao) : ViewModel() {
|
||||
|
||||
private val appDatabase = AppDatabase.getInstance(application)
|
||||
|
||||
val persons: LiveData<PagedList<Person>> = appDatabase.scheduleDao.getPersons().toLiveData(100)
|
||||
val persons: LiveData<PagedList<Person>> = scheduleDao.getPersons().toLiveData(100)
|
||||
}
|
|
@ -1,20 +1,21 @@
|
|||
package be.digitalia.fosdem.viewmodels
|
||||
|
||||
import android.app.Application
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.map
|
||||
import androidx.lifecycle.switchMap
|
||||
import androidx.paging.PagedList
|
||||
import androidx.paging.toLiveData
|
||||
import be.digitalia.fosdem.db.AppDatabase
|
||||
import be.digitalia.fosdem.db.ScheduleDao
|
||||
import be.digitalia.fosdem.model.StatusEvent
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import javax.inject.Inject
|
||||
|
||||
class SearchViewModel(application: Application, private val state: SavedStateHandle) : AndroidViewModel(application) {
|
||||
@HiltViewModel
|
||||
class SearchViewModel @Inject constructor(scheduleDao: ScheduleDao, private val state: SavedStateHandle) : ViewModel() {
|
||||
|
||||
private val appDatabase = AppDatabase.getInstance(application)
|
||||
private val queryLiveData: LiveData<String> = state.getLiveData(STATE_QUERY)
|
||||
|
||||
sealed class Result {
|
||||
|
@ -26,9 +27,9 @@ class SearchViewModel(application: Application, private val state: SavedStateHan
|
|||
if (query.length < SEARCH_QUERY_MIN_LENGTH) {
|
||||
MutableLiveData(Result.QueryTooShort)
|
||||
} else {
|
||||
appDatabase.scheduleDao.getSearchResults(query)
|
||||
.toLiveData(20)
|
||||
.map { pagedList -> Result.Success(pagedList) }
|
||||
scheduleDao.getSearchResults(query)
|
||||
.toLiveData(20)
|
||||
.map { pagedList -> Result.Success(pagedList) }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,25 +1,25 @@
|
|||
package be.digitalia.fosdem.viewmodels
|
||||
|
||||
import android.app.Application
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.liveData
|
||||
import androidx.lifecycle.switchMap
|
||||
import be.digitalia.fosdem.db.AppDatabase
|
||||
import be.digitalia.fosdem.db.ScheduleDao
|
||||
import be.digitalia.fosdem.model.Day
|
||||
import be.digitalia.fosdem.model.Event
|
||||
import be.digitalia.fosdem.model.Track
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import javax.inject.Inject
|
||||
|
||||
class TrackScheduleEventViewModel(application: Application) : AndroidViewModel(application) {
|
||||
@HiltViewModel
|
||||
class TrackScheduleEventViewModel @Inject constructor(scheduleDao: ScheduleDao) : ViewModel() {
|
||||
|
||||
private val appDatabase = AppDatabase.getInstance(application)
|
||||
private val dayTrackLiveData = MutableLiveData<Pair<Day, Track>>()
|
||||
|
||||
val scheduleSnapshot: LiveData<List<Event>> = dayTrackLiveData.switchMap { (day, track) ->
|
||||
MutableLiveData<List<Event>>().also {
|
||||
appDatabase.queryExecutor.execute {
|
||||
it.postValue(appDatabase.scheduleDao.getEventsSnapshot(day, track))
|
||||
}
|
||||
liveData {
|
||||
emit(scheduleDao.getEventsSnapshot(day, track))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,46 +1,47 @@
|
|||
package be.digitalia.fosdem.viewmodels
|
||||
|
||||
import android.app.Application
|
||||
import android.text.format.DateUtils
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.map
|
||||
import androidx.lifecycle.switchMap
|
||||
import be.digitalia.fosdem.db.AppDatabase
|
||||
import be.digitalia.fosdem.db.ScheduleDao
|
||||
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 dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.inject.Inject
|
||||
|
||||
class TrackScheduleListViewModel(application: Application) : AndroidViewModel(application) {
|
||||
@HiltViewModel
|
||||
class TrackScheduleListViewModel @Inject constructor(scheduleDao: ScheduleDao) : ViewModel() {
|
||||
|
||||
private val appDatabase = AppDatabase.getInstance(application)
|
||||
private val dayTrackLiveData = MutableLiveData<Pair<Day, Track>>()
|
||||
|
||||
val schedule: LiveData<List<StatusEvent>> = dayTrackLiveData.switchMap { (day, track) ->
|
||||
appDatabase.scheduleDao.getEvents(day, track)
|
||||
scheduleDao.getEvents(day, track)
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The current time during the target day, or -1 outside of the target day.
|
||||
*/
|
||||
val currentTime: LiveData<Long> = dayTrackLiveData
|
||||
.switchMap { (day, _) ->
|
||||
// Auto refresh during the day passed as argument
|
||||
val dayStart = day.date.time
|
||||
LiveDataFactory.scheduler(dayStart, dayStart + DateUtils.DAY_IN_MILLIS)
|
||||
}
|
||||
.switchMap { isOn ->
|
||||
if (isOn) {
|
||||
LiveDataFactory.interval(REFRESH_TIME_INTERVAL, TimeUnit.MILLISECONDS).map {
|
||||
System.currentTimeMillis()
|
||||
}
|
||||
} else {
|
||||
MutableLiveData(-1L)
|
||||
.switchMap { (day, _) ->
|
||||
// Auto refresh during the day passed as argument
|
||||
val dayStart = day.date.time
|
||||
LiveDataFactory.scheduler(dayStart, dayStart + DateUtils.DAY_IN_MILLIS)
|
||||
}
|
||||
.switchMap { isOn ->
|
||||
if (isOn) {
|
||||
LiveDataFactory.interval(REFRESH_TIME_INTERVAL, TimeUnit.MILLISECONDS).map {
|
||||
System.currentTimeMillis()
|
||||
}
|
||||
} else {
|
||||
MutableLiveData(-1L)
|
||||
}
|
||||
}
|
||||
|
||||
fun setDayAndTrack(day: Day, track: Track) {
|
||||
val dayTrack = day to track
|
||||
|
|
|
@ -1,21 +1,22 @@
|
|||
package be.digitalia.fosdem.viewmodels
|
||||
|
||||
import android.app.Application
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.switchMap
|
||||
import be.digitalia.fosdem.db.AppDatabase
|
||||
import be.digitalia.fosdem.db.ScheduleDao
|
||||
import be.digitalia.fosdem.model.Day
|
||||
import be.digitalia.fosdem.model.Track
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import javax.inject.Inject
|
||||
|
||||
class TracksViewModel(application: Application) : AndroidViewModel(application) {
|
||||
@HiltViewModel
|
||||
class TracksViewModel @Inject constructor(scheduleDao: ScheduleDao) : ViewModel() {
|
||||
|
||||
private val appDatabase = AppDatabase.getInstance(application)
|
||||
private val dayLiveData = MutableLiveData<Day>()
|
||||
|
||||
val tracks: LiveData<List<Track>> = dayLiveData.switchMap { day: Day ->
|
||||
appDatabase.scheduleDao.getTracks(day)
|
||||
scheduleDao.getTracks(day)
|
||||
}
|
||||
|
||||
fun setDay(day: Day) {
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
buildscript {
|
||||
ext.kotlin_version = '1.5.0'
|
||||
ext {
|
||||
kotlin_version = '1.5.0'
|
||||
hilt_version = '2.35.1'
|
||||
}
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
|
@ -8,6 +11,7 @@ buildscript {
|
|||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:4.2.1'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue