Skip to content

Commit

Permalink
Issue #95: add parsers for dates and translators for weekdays/categor…
Browse files Browse the repository at this point in the history
…ies; add jsonhome request in top bar (success untested); code properly formatted
  • Loading branch information
Jtoliveira committed May 20, 2021
1 parent 6c3f3d6 commit 04960f6
Show file tree
Hide file tree
Showing 36 changed files with 411 additions and 253 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package org.ionproject.android.common

/**
getJsonHome() returns null when the API is unavailable. This makes the default null status of
rootLiveData ambiguous, so this class envelops the results of the getJsonHome() method in order to
properly evaluate the state of the API and to launch the Remote Config strat
Fetch Failure with null value: request was made to API and there was no response
Fetch Failure with throwable: request threw an Exception, if not caught triggers the global Exception Handler
from ExceptionHandlingActivity()
Fetch Success : valid response
*/
sealed class FetchResult<out T>
data class FetchFailure<T>(val throwable: Throwable? = null) : FetchResult<T>()
data class FetchSuccess<T>(val value: T) : FetchResult<T>()
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import androidx.lifecycle.ViewModelProvider
import kotlinx.android.synthetic.main.activity_loading.*
import org.ionproject.android.ExceptionHandlingActivity
import org.ionproject.android.R
import org.ionproject.android.common.FetchFailure
import org.ionproject.android.common.FetchSuccess
import org.ionproject.android.common.IonApplication
import org.ionproject.android.common.addGradientBackground
import org.ionproject.android.common.model.Root
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,13 @@ package org.ionproject.android.loading

import androidx.lifecycle.*
import kotlinx.coroutines.launch
import org.ionproject.android.common.FetchFailure
import org.ionproject.android.common.FetchResult
import org.ionproject.android.common.FetchSuccess
import org.ionproject.android.common.model.Root
import org.ionproject.android.common.repositories.RootRepository
import java.net.URI

// This uri has to be hardcoded there is no other way
private val ROOT_URI_V0 = URI("/")

/**
getJsonHome() returns null when the API is unavailable. This makes the default null status of
rootLiveData ambiguous, so this class envelops the results of the getJsonHome() method in order to
properly evaluate the state of the API and to launch the Remote Config strat
Fetch Failure with null value: request was made to API and there was no response
Fetch Failure with throwable: request threw an Exception, if not caught triggers the global Exception Handler
from ExceptionHandlingActivity()
Fetch Success : valid response
*/
sealed class FetchResult<out T>
data class FetchFailure<T>(val throwable: Throwable? = null) : FetchResult<T>()
data class FetchSuccess<T>(val value: T) : FetchResult<T>()

class LoadingViewModel(
private val rootRepository: RootRepository,
private val remoteConfigRepository: RemoteConfigRepository
Expand All @@ -33,7 +18,7 @@ class LoadingViewModel(
private val remoteConfigLiveData = MutableLiveData<FetchResult<RemoteConfig>>()

init {
getJsonHome(ROOT_URI_V0)
getJsonHome(URI(remoteConfigRepository.preferences.getWebApiHost()))
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,12 @@ package org.ionproject.android.loading
import android.util.Log
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.ionproject.android.common.ionwebapi.*
import org.ionproject.android.common.ionwebapi.IIonWebAPI
import org.ionproject.android.common.ionwebapi.REMOTE_CONFIG_LINK
import org.ionproject.android.settings.Preferences
import retrofit2.Retrofit
import retrofit2.converter.scalars.ScalarsConverterFactory
import java.net.URI

class RemoteConfigRepository(private val preferences: Preferences, private val webAPI: IIonWebAPI) {
class RemoteConfigRepository(val preferences: Preferences, private val webAPI: IIonWebAPI) {

suspend fun getRemoteConfig() =

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,13 @@ package org.ionproject.android.offline

import android.annotation.SuppressLint
import android.app.AlertDialog
import android.app.SearchManager
import android.content.Context
import android.content.Intent
import android.content.pm.ActivityInfo
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.widget.Toast
import androidx.activity.addCallback
import androidx.activity.viewModels
import androidx.appcompat.widget.SearchView
import androidx.appcompat.widget.Toolbar
import androidx.lifecycle.ViewModelProvider
Expand All @@ -23,20 +18,19 @@ import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.setupActionBarWithNavController
import androidx.navigation.ui.setupWithNavController
import kotlinx.android.synthetic.main.activity_catalog_main.*
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.android.synthetic.main.activity_main.bottomnavview_main
import kotlinx.android.synthetic.main.activity_main.toolbar_main
import kotlinx.android.synthetic.main.toolbar_main.*
import org.ionproject.android.ExceptionHandlingActivity
import org.ionproject.android.R
import org.ionproject.android.SharedViewModel
import org.ionproject.android.SharedViewModelProvider
import org.ionproject.android.common.FetchFailure
import org.ionproject.android.common.FetchSuccess
import org.ionproject.android.common.IonApplication
import org.ionproject.android.common.IonApplication.Companion.remoteConfigRepository
import org.ionproject.android.common.addGradientBackground
import org.ionproject.android.common.model.Root
import org.ionproject.android.main.MAIN_ACTIVITY_ROOT_EXTRA
import org.ionproject.android.main.MainActivity
import java.net.URI

class CatalogMainActivity : ExceptionHandlingActivity() {
class CatalogMainActivity : ExceptionHandlingActivity() {

private var searchViewItem: MenuItem? = null

Expand All @@ -47,6 +41,13 @@ class CatalogMainActivity : ExceptionHandlingActivity() {
)[CatalogSharedViewModel::class.java]
}

private val viewModel: CatalogMainActivityViewModel by lazy(LazyThreadSafetyMode.NONE) {
ViewModelProvider(
this,
CatalogMainActivityViewModelProvider()
)[CatalogMainActivityViewModel::class.java]
}

private val navController: NavController by lazy(LazyThreadSafetyMode.NONE) {
findNavController(R.id.catalog_fragment_main_navhost).apply {
addOnDestinationChangedListener { _, destination, _ ->
Expand All @@ -69,6 +70,20 @@ class CatalogMainActivity : ExceptionHandlingActivity() {

AlertDialog.Builder(this).setMessage(R.string.catalog_info_warning)
.setPositiveButton("Ok", null).show()

viewModel.observeRootLiveData(this) {
when (it) {
is FetchSuccess<Root> -> {
val intent = Intent(this, MainActivity::class.java)
intent.putExtra(MAIN_ACTIVITY_ROOT_EXTRA, it.value)
this.startActivity(intent)
}
is FetchFailure<Root> -> { //unsuccessful request to jsonhome
AlertDialog.Builder(this).setMessage(R.string.catalog_info_warning)
.setPositiveButton("Ok", null).show()
}
}
}
}

/**
Expand Down Expand Up @@ -117,6 +132,12 @@ class CatalogMainActivity : ExceptionHandlingActivity() {

val searchView = searchViewItem?.actionView as? SearchView

/**
* Only have the search bar visible if the exam schedule fragment is visible
*/
menu.findItem(R.id.catalog_action_search).isVisible =
supportFragmentManager.findFragmentById(R.id.catalog_exam_schedule)?.isVisible == true

searchView?.apply {
isSubmitButtonEnabled = true

Expand All @@ -126,15 +147,17 @@ class CatalogMainActivity : ExceptionHandlingActivity() {
}

override fun onQueryTextChange(newText: String?): Boolean {
sharedViewModel.setSearchText(newText ?: "")
sharedViewModel.setSearchText(newText ?: "")
return true
}
})
}

/**
* When the user clicks the no connection button, we try to reach the API again
*/
noConnectivity.setOnMenuItemClickListener {
AlertDialog.Builder(this).setMessage(R.string.catalog_info_warning)
.setPositiveButton("Ok", null).show()
viewModel.getJsonHome(URI(remoteConfigRepository.preferences.getWebApiHost()))
true
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package org.ionproject.android.offline

import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.launch
import org.ionproject.android.common.FetchFailure
import org.ionproject.android.common.FetchResult
import org.ionproject.android.common.FetchSuccess
import org.ionproject.android.common.model.Root
import org.ionproject.android.common.repositories.RootRepository
import java.net.URI

class CatalogMainActivityViewModel(private val rootRepository: RootRepository): ViewModel(){

private val rootLiveData = MutableLiveData<FetchResult<Root>>()

/**
* Same function from [LoadingViewModel] that requests the JSONHome
*/
fun getJsonHome(uri: URI) {
viewModelScope.launch {
val result = try {
val root = rootRepository.getJsonHome(uri)
if (root != null) FetchSuccess(root) else FetchFailure<Root>()
} catch (e: Exception) {
FetchFailure<Root>(e)
}

rootLiveData.postValue(result)
}
}

fun observeRootLiveData(lifecycleOwner: LifecycleOwner, onUpdate: (FetchResult<Root>) -> Unit) {
rootLiveData.observe(lifecycleOwner, Observer { onUpdate(it) })
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package org.ionproject.android.offline

import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import org.ionproject.android.common.IonApplication

class CatalogMainActivityViewModelProvider : ViewModelProvider.Factory {

@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return when (modelClass) {
CatalogMainActivityViewModel::class.java -> CatalogMainActivityViewModel(IonApplication.rootRepository)
else -> throw IllegalArgumentException("Class $modelClass is not valid for this provider")
} as T
}

}
Original file line number Diff line number Diff line change
@@ -1,15 +1,10 @@
package org.ionproject.android.offline

import android.os.Build
import android.util.Log
import androidx.annotation.RequiresApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.ionproject.android.common.ionwebapi.IIonWebAPI
import org.ionproject.android.common.ionwebapi.JacksonIonMapper
import org.ionproject.android.offline.models.*
import java.net.URI
import java.util.*

const val linkToCalendar =
"https://raw.githubusercontent.com/i-on-project/integration-data/master/pt.ipl.isel/academic_years/%s/calendar.json" //%s is the selected year
Expand All @@ -20,8 +15,10 @@ const val linkToCatalogProgrammesList =
const val linkToAcademicYears =
"https://api.github.com/repos/i-on-project/integration-data/git/trees/17c361250d14345325587c21c7840b97af944f79"

const val linkToExamSchedule = "https://raw.githubusercontent.com/i-on-project/integration-data/master/pt.ipl.isel/programmes/%s/%s/exam_schedule.json"
const val linkToTimeTable = "https://raw.githubusercontent.com/i-on-project/integration-data/master/pt.ipl.isel/programmes/%s/%s/timetable.json"
const val linkToExamSchedule =
"https://raw.githubusercontent.com/i-on-project/integration-data/master/pt.ipl.isel/programmes/%s/%s/exam_schedule.json"
const val linkToTimeTable =
"https://raw.githubusercontent.com/i-on-project/integration-data/master/pt.ipl.isel/programmes/%s/%s/timetable.json"

class CatalogRepository(private val webAPI: IIonWebAPI) {

Expand All @@ -38,8 +35,6 @@ class CatalogRepository(private val webAPI: IIonWebAPI) {
"application/json"
)

Log.d("Catalog", "catalog programme list: $catalogProgrammeList")

catalogProgrammeList
}

Expand All @@ -56,8 +51,6 @@ class CatalogRepository(private val webAPI: IIonWebAPI) {
"application/json"
)

Log.d("Catalog", "catalog programme: $catalogProgramme")

catalogProgramme
}

Expand All @@ -77,15 +70,13 @@ class CatalogRepository(private val webAPI: IIonWebAPI) {
"application/json"
)

Log.d("Catalog", "term info: $catalogProgrammeTermInfo")

catalogProgrammeTermInfo.files.filter { it.fileName.contains("json") }
}


/**
* NOTE: Since the parsing of the base64 file was causing problems, the non 4head solution is
* just using the code that works with every version instead of this version checking nonsense
* NOTE: Since the parsing of the base64 file was causing problems, we use this
* simpler solution that works for all Android versions
*/
suspend fun <T> getFileFromGithub(
programme: String,
Expand All @@ -99,8 +90,6 @@ class CatalogRepository(private val webAPI: IIonWebAPI) {
else -> ""
}

Log.d("Catalog", "File link: $link")

return webAPI.getFromURI(
URI(link),
klass,
Expand All @@ -118,8 +107,6 @@ class CatalogRepository(private val webAPI: IIonWebAPI) {
"application/json"
)

Log.d("Catalog", "catalog programme list: $catalogAcademicYears")

catalogAcademicYears
}

Expand All @@ -133,8 +120,6 @@ class CatalogRepository(private val webAPI: IIonWebAPI) {
"application/json"
)

Log.d("Catalog", "calendar: $calendar")

calendar
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,26 @@ import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModel
import org.ionproject.android.offline.models.CatalogCalendar
import org.ionproject.android.offline.models.CatalogProgramme
import org.ionproject.android.offline.models.ExamSchedule
import org.ionproject.android.offline.models.Timetable

class CatalogSharedViewModel : ViewModel() {

/**
* Search text used to pass data from search bar to Exam Fragment
*/
*/
private val searchTextLiveData = MutableLiveData<String>()

/**
* Updates SearchTextLiveData
* Query passed in the search action from the top bar .
* We need this object so that the exam fragment
* can know what the string was and filter the exam list accordingly
*/
fun setSearchText(query: String) {
searchTextLiveData.postValue(query)
}

/**
* Observes the live data and calls onUpdate when a change occurs
* Observes the query so we can fire off the filter when the time is right
*/
fun observeSearchText(lifecycleOwner: LifecycleOwner, onUpdate: (String) -> Unit) {
searchTextLiveData.observe(lifecycleOwner, Observer {
Expand All @@ -42,5 +41,8 @@ class CatalogSharedViewModel : ViewModel() {
*/
var selectedCatalogProgrammeTerm: String = ""

/**
* The year the user chose in the programme details fragment
*/
var selectedYear: String = ""
}
Loading

0 comments on commit 04960f6

Please sign in to comment.