Commit ad8d76a4 authored by Filip Wiesner's avatar Filip Wiesner

Switch to Microservices

parent 8d47aa52
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<Objective-C-extensions>
<file>
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Import" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Macro" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Typedef" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Enum" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Constant" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Global" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Struct" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="FunctionPredecl" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Function" />
</file>
<class>
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Property" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="Synthesize" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InitMethod" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="StaticMethod" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="InstanceMethod" />
<option name="com.jetbrains.cidr.lang.util.OCDeclarationKind" value="DeallocMethod" />
</class>
<extensions>
<pair source="cpp" header="h" fileNamingConvention="NONE" />
<pair source="c" header="h" fileNamingConvention="NONE" />
</extensions>
</Objective-C-extensions>
</code_scheme>
</component>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding" addBOMForNewFiles="with NO BOM" />
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
......
......@@ -21,6 +21,10 @@ android {
}
}
buildToolsVersion "28.0.3"
compileOptions {
sourceCompatibility = '1.8'
targetCompatibility = '1.8'
}
}
kapt {
......@@ -33,9 +37,9 @@ kapt {
dependencies {
def anko_version = "0.10.5"
def room_version = "2.1.0-alpha03"
def room_version = "2.1.0-alpha04"
def dagger_version = "2.16"
def nav_version = "1.0.0-alpha09"
def nav_version = "1.0.0-beta02"
implementation fileTree(include: ["*.jar"], dir: "libs")
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
......@@ -67,6 +71,8 @@ dependencies {
kapt "com.google.dagger:dagger-compiler:$dagger_version"
kapt "com.google.dagger:dagger-android-processor:$dagger_version"
implementation "org.kodein.di:kodein-di-generic-jvm:6.1.0"
// Networking
implementation "com.squareup.retrofit2:retrofit:2.4.0"
implementation "com.squareup.retrofit2:converter-gson:2.4.0"
......@@ -90,7 +96,7 @@ dependencies {
// Database
implementation "androidx.room:room-runtime:$room_version"
implementation "androidx.paging:paging-runtime:2.1.0-rc01"
implementation "androidx.paging:paging-runtime:2.1.0"
kapt "androidx.room:room-compiler:$room_version"
// Debug
......@@ -100,7 +106,6 @@ dependencies {
debugImplementation "com.willowtreeapps.hyperion:hyperion-build-config:0.9.24"
debugImplementation "com.willowtreeapps.hyperion:hyperion-crash:0.9.24"
debugImplementation "com.willowtreeapps.hyperion:hyperion-measurement:0.9.24"
debugImplementation "com.willowtreeapps.hyperion:hyperion-geiger-counter:0.9.24"
}
kotlin {
experimental {
......
......@@ -8,25 +8,30 @@ import com.cvut.blackbird.dinjection.components.ApplicationComponent
import com.cvut.blackbird.dinjection.components.DaggerApplicationComponent
import com.cvut.blackbird.dinjection.modules.RoomModule
import com.cvut.blackbird.dinjection.modules.ServicesModule
import com.cvut.blackbird.dinjection.setupDI
import net.danlew.android.joda.JodaTimeAndroid
import org.kodein.di.Kodein
class BlackBirdAC: Application() {
companion object {
const val LOG_TAG = "BLACK_BIRD"
//platformStatic allow access it from java code
@JvmStatic lateinit var graph: ApplicationComponent
lateinit var graph: ApplicationComponent
lateinit var kodein: Kodein
fun log(message: String) = Log.i(LOG_TAG, message)
}
override fun onCreate() {
super.onCreate()
FragmentManager.enableDebugLogging(true)
graph = DaggerApplicationComponent.builder()
.roomModule(RoomModule(this))
.servicesModule(ServicesModule(this))
.build()
// FragmentManager.enableDebugLogging(true)
kodein = setupDI()
// graph = DaggerApplicationComponent.builder()
// .roomModule(RoomModule(this))
// .servicesModule(ServicesModule(this))
// .build()
JodaTimeAndroid.init(this)
Kotpref.init(this)
......
package com.cvut.blackbird.dinjection
import android.app.Application
import android.content.Context
import androidx.room.Room
import com.cvut.blackbird.model.database.BlackBirdDB
import com.cvut.blackbird.model.database.CourseDao
import com.cvut.blackbird.model.database.EventDao
import com.cvut.blackbird.model.database.TeacherDao
import com.cvut.blackbird.model.services.AuthService
import com.cvut.blackbird.model.services.KosService
import com.cvut.blackbird.model.services.SiriusService
import com.cvut.blackbird.model.services.auth.InitToken
import com.cvut.blackbird.model.services.auth.RefreshTokenGuarded
import com.cvut.blackbird.model.services.kos.LoadCourses
import com.cvut.blackbird.model.services.kos.LoadTeachers
import com.cvut.blackbird.model.services.kos.LoadUser
import com.cvut.blackbird.model.services.kos.LoadEvents
import com.cvut.blackbird.model.services.local.WipeData
import com.cvut.blackbird.model.services.other.FetchNews
import com.google.gson.GsonBuilder
import com.google.gson.JsonDeserializer
import com.google.gson.JsonPrimitive
import com.google.gson.JsonSerializer
import com.tickaroo.tikxml.TikXml
import com.tickaroo.tikxml.retrofit.TikXmlConverterFactory
import org.joda.time.DateTime
import org.joda.time.format.ISODateTimeFormat
import org.kodein.di.Kodein
import org.kodein.di.generic.bind
import org.kodein.di.generic.instance
import org.kodein.di.generic.provider
import org.kodein.di.generic.singleton
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
fun Application.setupDI(): Kodein = Kodein {
bind<Context>() with provider { this@setupDI.applicationContext }
setupDB(this@setupDI)
setupServices()
setupMicroServices()
}
/** Database */
private fun Kodein.MainBuilder.setupDB(context: Context) {
bind<BlackBirdDB>() with singleton { Room
.databaseBuilder(context, BlackBirdDB::class.java, "blackbird_DB")
.fallbackToDestructiveMigration()
.build() }
bind<EventDao>() with singleton { instance<BlackBirdDB>().eventDao() }
bind<CourseDao>() with singleton { instance<BlackBirdDB>().courseDao() }
bind<TeacherDao>() with singleton { instance<BlackBirdDB>().teacherDao() }
}
/** SERVICES */
private fun Kodein.MainBuilder.setupServices() {
bind<AuthService>() with singleton { Retrofit.Builder().apply {
baseUrl(AuthService.url)
addConverterFactory(GsonConverterFactory.create())
}.build().create(AuthService::class.java) }
bind<KosService>() with singleton { Retrofit.Builder().apply {
baseUrl(KosService.url)
addConverterFactory(TikXmlConverterFactory.create(TikXml.Builder()
.exceptionOnUnreadXml(false)
.build()))
}.build().create(KosService::class.java) }
bind<SiriusService>() with singleton { Retrofit.Builder().apply {
val dateTimeSer: JsonSerializer<DateTime> = JsonSerializer { src, _, _ ->
if (src == null) null else JsonPrimitive(src.millis)
}
val dateTimeDeser: JsonDeserializer<DateTime> = JsonDeserializer<DateTime> { json, _, _ ->
if (json == null) null
else try { ISODateTimeFormat.dateTime().parseDateTime(json.asString) }
catch (e: UnsupportedOperationException) { DateTime(json.asLong) }
}
baseUrl(SiriusService.url)
addConverterFactory(GsonConverterFactory.create(
GsonBuilder()
.registerTypeAdapter(DateTime::class.java, dateTimeSer)
.registerTypeAdapter(DateTime::class.java, dateTimeDeser)
.create()
))
}.build().create(SiriusService::class.java) }
}
/** MICRO-SERVICES */
private fun Kodein.MainBuilder.setupMicroServices() {
//Auth
bind<InitToken>() with singleton { InitToken(instance()) }
bind<RefreshTokenGuarded>() with singleton { RefreshTokenGuarded(instance()) }
//KOS
bind<LoadEvents>() with singleton { LoadEvents(instance(), instance(), instance()) }
bind<LoadCourses>() with singleton { LoadCourses(instance(), instance(), instance()) }
bind<LoadTeachers>() with singleton { LoadTeachers(instance(), instance(), instance(), instance()) }
bind<LoadUser>() with singleton { LoadUser(instance(), instance()) }
//Local
bind<WipeData>() with singleton { WipeData(instance(), instance(), instance()) }
//Other
bind<FetchNews>() with singleton { FetchNews() }
}
\ No newline at end of file
......@@ -4,7 +4,6 @@ import com.cvut.blackbird.dinjection.modules.ContextModule
import com.cvut.blackbird.dinjection.modules.ServicesModule
import com.cvut.blackbird.dinjection.modules.RoomModule
import com.cvut.blackbird.model.BlackBirdModel
import com.cvut.blackbird.model.flows.AuthModel
import com.cvut.blackbird.model.flows.*
import dagger.Component
import javax.inject.Singleton
......@@ -14,7 +13,6 @@ import javax.inject.Singleton
interface ApplicationComponent {
fun inject(model: BlackBirdModel)
fun inject(model: AuthModel)
fun inject(model: TasksModel)
fun inject(model: TimetableModel)
fun inject(model: SearchModel)
......
......@@ -28,7 +28,7 @@ infix fun<T> MutableLiveData<T>.withDefault(init: T) = apply { value = init }
fun <T> LiveData<T>.haveValue() = value != null
fun <T> LiveData<T>.dontHaveValue(): Boolean {
fun <T> LiveData<T>.hasNoValue(): Boolean {
return value == null
}
......
......@@ -24,24 +24,9 @@ class BlackBirdMain : AppCompatActivity() {
supportActionBar?.hide()
navController = NavHostFragment.findNavController(navHost)
// setupViewModels()
if (UserInfo.username.isBlank())
navController.navigate(R.id.toAuth)
}
private fun setupViewModels() {
ViewModelProviders.of(this).also { provider ->
provider.get(TasksViewModel::class.java)
provider.get(TimetableViewModel::class.java)
provider.get(SearchViewModel::class.java)
provider.get(ProfileViewModel::class.java)
provider.get(EventDetailViewModel::class.java)
}
}
override fun onSupportNavigateUp() = findNavController(navHost.id).navigateUp()
override fun onSupportNavigateUp() = navController.navigateUp() //findNavController(navHost.id).navigateUp()
}
\ No newline at end of file
......@@ -3,19 +3,19 @@ package com.cvut.blackbird.flows
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.cvut.blackbird.BlackBirdAC
import com.cvut.blackbird.extensions.JobStrategy
import com.cvut.blackbird.extensions.doWith
import com.cvut.blackbird.extensions.withDefault
import kotlinx.coroutines.*
import kotlinx.coroutines.android.Main
import org.kodein.di.KodeinAware
import kotlin.coroutines.CoroutineContext
sealed class LateInit<T>
class Initialized<T>(val data: T): LateInit<T>()
open class BlackBirdVM: ViewModel(), CoroutineScope {
abstract class BlackBirdVM: ViewModel(), CoroutineScope, KodeinAware {
override val kodein = BlackBirdAC.kodein
override val coroutineContext: CoroutineContext
get() = Dispatchers.Default
get() = Dispatchers.IO
private val jobs = hashMapOf<String, Job>()
......@@ -39,11 +39,12 @@ open class BlackBirdVM: ViewModel(), CoroutineScope {
doWith(jobs[key], strategy) { launch { work() } }
else // if there is no job with this tag, create it
launch { work() }
).apply {
indicateStart() // Indicate start of this job and plan end
invokeOnCompletion { indicateEnd() }
jobs[key] = this // assign created job (in if-else expr.) to given tag
}
)
.apply {
indicateStart() // Indicate start of this job and plan end
invokeOnCompletion { indicateEnd() }
jobs[key] = this // assign created job (in if-else expr.) to given tag
}
private fun indicateStart() = _globalProgress.postValue(true)
......
......@@ -2,25 +2,37 @@ package com.cvut.blackbird.flows.authentication
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.cvut.blackbird.extensions.*
import com.cvut.blackbird.extensions.asProgressTo
import com.cvut.blackbird.extensions.passTo
import com.cvut.blackbird.extensions.withDefault
import com.cvut.blackbird.flows.BlackBirdVM
import com.cvut.blackbird.model.Failure
import com.cvut.blackbird.model.NotYet
import com.cvut.blackbird.model.Result
import com.cvut.blackbird.model.Success
import com.cvut.blackbird.model.flows.AuthModel
import com.cvut.blackbird.model.services.auth.InitToken
import com.cvut.blackbird.model.services.auth.RefreshTokenGuarded
import com.cvut.blackbird.model.services.kos.LoadCourses
import com.cvut.blackbird.model.services.kos.LoadEvents
import com.cvut.blackbird.model.services.kos.LoadTeachers
import com.cvut.blackbird.model.services.kos.LoadUser
import com.cvut.blackbird.model.support.AuthResult
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import org.kodein.di.generic.instance
class AuthViewModel : BlackBirdVM() {
private val model: AuthModel = AuthModel()
private val initTokenMS by instance<InitToken>()
private val refreshTokenGuardedMS by instance<RefreshTokenGuarded>()
private val loadUserMS by instance<LoadUser>()
private val loadEventsMS by instance<LoadEvents>()
private val loadTeachersMS by instance<LoadTeachers>()
private val loadCoursesMS by instance<LoadCourses>()
//Output
private val _authStatus = MutableLiveData<AuthResult>() withDefault AuthResult.NOT_YET
private val _authLoadingStatus = MutableLiveData<Boolean>() withDefault false
private val _userInitResult = MutableLiveData<Result<Unit>>() withDefault NotYet()
private val _userInitState = MutableLiveData<String>() withDefault "Waiting for login"
private val _userInitState = MutableLiveData<String>() withDefault "Starting login process"
val authStatus: LiveData<AuthResult> get() = _authStatus
val authLoadingStatus: LiveData<Boolean> get() = _authLoadingStatus
......@@ -33,35 +45,35 @@ class AuthViewModel : BlackBirdVM() {
fun initToken(code: String) {
startJob("token") {
val token = model.initToken(code)
val token = initTokenMS(code)
_authStatus.postValue(token)
}
}
fun refreshToken() {
startJob("refresh") {
_authStatus.postValue(model.refreshTokenGuarded())
_authStatus.postValue(refreshTokenGuardedMS())
}
}
fun initUser() { startJob("init") {
_userInitResult asProgressTo {
_userInitState.postValue("Looking up your account")
val user = model.loadUser()
val user = loadUserMS()
val events = if (user is Success) {
_userInitState.postValue("Downloading your timetable")
model.loadEvents()
loadEventsMS()
} else return@asProgressTo user as Failure
val teachers = if (events is Success) {
_userInitState.postValue("Downloading all your teachers")
model.loadTeachers()
loadTeachersMS()
} else return@asProgressTo events as Failure
val courses = if (teachers is Success) {
_userInitState.postValue("Downloading your courses")
model.loadCourses()
loadCoursesMS()
} else return@asProgressTo teachers as Failure
courses
......
......@@ -8,6 +8,7 @@ import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProviders
import androidx.navigation.fragment.findNavController
import com.cvut.blackbird.R
import com.cvut.blackbird.extensions.courseAbbr
......@@ -18,9 +19,8 @@ import kotlinx.android.synthetic.main.event_detail_fragment.*
class EventDetailFragment : Fragment() {
lateinit var detailedEvent: DetailedEvent
private lateinit var viewModel: EventDetailVM
private lateinit var detailedEvent: DetailedEvent
private lateinit var viewModel: EventDetailViewModel
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
......@@ -37,10 +37,13 @@ class EventDetailFragment : Fragment() {
// bindText(detail_notePersonalContent) changeTo viewModel::changeEventNote
}
private fun setupEvent(event: DetailedEvent) {
this.detailedEvent = event
private fun setupEvent(detEvent: DetailedEvent) {
if (viewModel.eventInitialized && detEvent.event == Event.empty)
findNavController().navigateUp()
this.detailedEvent = detEvent
detail_abbr.background = when(event.event.eventType) {
detail_abbr.background = when(detEvent.event.eventType) {
EventType.LECTURE -> context?.resources?.getDrawable(
R.drawable.ic_timetable_lecture, context?.theme)
EventType.TUTORIAL -> context?.resources?.getDrawable(
......@@ -49,20 +52,20 @@ class EventDetailFragment : Fragment() {
R.drawable.ic_timetable_info, context?.theme)
}
detail_abbr.text = event.courseEnt?.code?.courseAbbr ?: "-"
detail_codeName.text = event.courseEnt?.code
detail_sequenceNum.text = event.event.sequenceNum.toString()
detail_lengthTxt.text = "${event.event.endsAt.minuteOfDay - event.event.startsAt.minuteOfDay} min"
detail_capacityTxt.text = "${event.event.occupied}/${event.event.capacity}"
detail_timeTxt.text = "${event.event.startsAt.toString("HH:mm")} - ${event.event.endsAt.toString("HH:mm")}"
detail_teachersContent.text = event.event.linked?.teachers?.firstOrNull() ?: "None"
detail_parallelContent.text = if (event.event.parallel.isNotBlank()) event.event.parallel else "-"
detail_roomContent.text = event.event.linked?.room ?: "None"
detail_noteContent.text = event.event.note?.cs ?: "None"
detail_notePersonalContent.setText(event.userNotes.firstOrNull()?.note ?: "")
setupCourse(event.courseEnt)
setupTeachers(event.teacherList)
detail_abbr.text = detEvent.courseEnt?.code?.courseAbbr ?: "-"
detail_codeName.text = detEvent.courseEnt?.code
detail_sequenceNum.text = detEvent.event.sequenceNum.toString()
detail_lengthTxt.text = "${detEvent.event.endsAt.minuteOfDay - detEvent.event.startsAt.minuteOfDay} min"
detail_capacityTxt.text = "${detEvent.event.occupied}/${detEvent.event.capacity}"
detail_timeTxt.text = "${detEvent.event.startsAt.toString("HH:mm")} - ${detEvent.event.endsAt.toString("HH:mm")}"
detail_teachersContent.text = detEvent.event.linked?.teachers?.firstOrNull() ?: "None"
detail_parallelContent.text = if (detEvent.event.parallel.isNotBlank()) detEvent.event.parallel else "-"
detail_roomContent.text = detEvent.event.linked?.room ?: "None"
detail_noteContent.text = detEvent.event.note?.cs ?: "None"
detail_notePersonalContent.setText(detEvent.userNotes.firstOrNull()?.note ?: "")
setupCourse(detEvent.courseEnt)
setupTeachers(detEvent.teacherList)
refreshPinTint()
}
......@@ -89,12 +92,14 @@ class EventDetailFragment : Fragment() {
refreshPinTint()
}
private fun refreshPinTint() = detail_pin.apply {
imageTintList =
private fun refreshPinTint() = detail_pin.let {
it.imageTintList =
if (detailedEvent.event.isPinned) ColorStateList.valueOf(getColor(R.color.colorTextLight))
else ColorStateList.valueOf(getColor(R.color.colorTextDark))
}
override fun onPause() {
super.onPause()
viewModel.changeEventNote(detail_notePersonalContent.text.toString())
......
......@@ -2,49 +2,55 @@ package com.cvut.blackbird.flows.detail
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.cvut.blackbird.extensions.dontHaveValue
import com.cvut.blackbird.extensions.hasNoValue
import com.cvut.blackbird.extensions.withDefault
import com.cvut.blackbird.flows.BlackBirdVM
import com.cvut.blackbird.model.database.EventDao
import com.cvut.blackbird.model.database.TeacherDao
import com.cvut.blackbird.model.entities.DetailedEvent
import com.cvut.blackbird.model.entities.Event
import com.cvut.blackbird.model.flows.EventDetailModel
import kotlinx.coroutines.launch
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.android.Main
import kotlinx.coroutines.withContext
import org.kodein.di.generic.instance
interface EventDetailVM {
val detailedEvent: LiveData<DetailedEvent>
fun setEvent(event: Event)
fun changeEventNote(text: String)
}
class EventDetailViewModel: BlackBirdVM(), EventDetailVM {
private val model = EventDetailModel()
//TODO: Implement Event init State so you don't have to write "detailedEvent.value!!"
class EventDetailViewModel: BlackBirdVM() {
private val eventDao by instance<EventDao>()
private val teacherDao by instance<TeacherDao>()
/**
* Observables
*/
private val _detailedEvent = MutableLiveData<DetailedEvent>() withDefault DetailedEvent.empty()
override val detailedEvent: LiveData<DetailedEvent> get() = _detailedEvent
val detailedEvent: LiveData<DetailedEvent> get() = _detailedEvent
var eventInitialized = false
private set
/**
* INPUT
*/
override fun setEvent(event: Event) {
fun setEvent(event: Event) {
startJob("set") {
_detailedEvent.postValue( model.getDetailedEvent(event.id) )
_detailedEvent.postValue(eventDao.getDetailedEvent(event.id).apply {
initTeachers(teacherDao)