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