forked from GitHub-Mirror/riotX-android
Introduce MvRx in the application + start managing UI
This commit is contained in:
parent
d0a241bd2d
commit
e5fc1e3412
BIN
.idea/caches/build_file_checksums.ser
generated
BIN
.idea/caches/build_file_checksums.ser
generated
Binary file not shown.
2
.idea/gradle.xml
generated
2
.idea/gradle.xml
generated
@ -10,7 +10,7 @@
|
|||||||
<option value="$PROJECT_DIR$" />
|
<option value="$PROJECT_DIR$" />
|
||||||
<option value="$PROJECT_DIR$/app" />
|
<option value="$PROJECT_DIR$/app" />
|
||||||
<option value="$PROJECT_DIR$/matrix-sdk-android" />
|
<option value="$PROJECT_DIR$/matrix-sdk-android" />
|
||||||
<option value="$PROJECT_DIR$/matrix-sdk-rx" />
|
<option value="$PROJECT_DIR$/matrix-sdk-android-rx" />
|
||||||
</set>
|
</set>
|
||||||
</option>
|
</option>
|
||||||
<option name="resolveModulePerSourceSet" value="false" />
|
<option name="resolveModulePerSourceSet" value="false" />
|
||||||
|
@ -41,6 +41,7 @@ dependencies {
|
|||||||
|
|
||||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||||
implementation project(":matrix-sdk-android")
|
implementation project(":matrix-sdk-android")
|
||||||
|
implementation project(":matrix-sdk-android-rx")
|
||||||
|
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||||
implementation 'com.android.support:appcompat-v7:28.0.0'
|
implementation 'com.android.support:appcompat-v7:28.0.0'
|
||||||
@ -51,11 +52,11 @@ dependencies {
|
|||||||
implementation("com.airbnb.android:epoxy:$epoxy_version")
|
implementation("com.airbnb.android:epoxy:$epoxy_version")
|
||||||
kapt "com.airbnb.android:epoxy-processor:$epoxy_version"
|
kapt "com.airbnb.android:epoxy-processor:$epoxy_version"
|
||||||
implementation "com.airbnb.android:epoxy-paging:$epoxy_version"
|
implementation "com.airbnb.android:epoxy-paging:$epoxy_version"
|
||||||
|
implementation 'com.airbnb.android:mvrx:0.6.0'
|
||||||
|
|
||||||
|
|
||||||
implementation "org.koin:koin-android:$koin_version"
|
implementation "org.koin:koin-android:$koin_version"
|
||||||
implementation "org.koin:koin-android-scope:$koin_version"
|
implementation "org.koin:koin-android-scope:$koin_version"
|
||||||
implementation "org.koin:koin-android-viewmodel:$koin_version"
|
|
||||||
|
|
||||||
testImplementation 'junit:junit:4.12'
|
testImplementation 'junit:junit:4.12'
|
||||||
androidTestImplementation 'com.android.support.test:runner:1.0.2'
|
androidTestImplementation 'com.android.support.test:runner:1.0.2'
|
||||||
|
@ -0,0 +1,45 @@
|
|||||||
|
package im.vector.riotredesign.core.platform
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.support.constraint.ConstraintLayout
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.Checkable
|
||||||
|
|
||||||
|
class CheckableConstraintLayout : ConstraintLayout, Checkable {
|
||||||
|
|
||||||
|
private var mChecked = false
|
||||||
|
|
||||||
|
constructor(context: Context) : super(context) {}
|
||||||
|
|
||||||
|
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {}
|
||||||
|
|
||||||
|
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {}
|
||||||
|
|
||||||
|
override fun isChecked(): Boolean {
|
||||||
|
return mChecked
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setChecked(b: Boolean) {
|
||||||
|
if (b != mChecked) {
|
||||||
|
mChecked = b
|
||||||
|
refreshDrawableState()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toggle() {
|
||||||
|
isChecked = !mChecked
|
||||||
|
}
|
||||||
|
|
||||||
|
public override fun onCreateDrawableState(extraSpace: Int): IntArray {
|
||||||
|
val drawableState = super.onCreateDrawableState(extraSpace + 1)
|
||||||
|
if (isChecked) {
|
||||||
|
View.mergeDrawableStates(drawableState, CHECKED_STATE_SET)
|
||||||
|
}
|
||||||
|
return drawableState
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val CHECKED_STATE_SET = intArrayOf(android.R.attr.state_checked)
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,5 @@
|
|||||||
package im.vector.riotredesign.core.platform
|
package im.vector.riotredesign.core.platform
|
||||||
|
|
||||||
import android.support.v7.app.AppCompatActivity
|
import com.airbnb.mvrx.BaseMvRxActivity
|
||||||
|
|
||||||
open class RiotActivity : AppCompatActivity() {
|
abstract class RiotActivity : BaseMvRxActivity()
|
||||||
|
|
||||||
}
|
|
@ -1,6 +1,12 @@
|
|||||||
package im.vector.riotredesign.core.platform
|
package im.vector.riotredesign.core.platform
|
||||||
|
|
||||||
import android.support.v4.app.Fragment
|
import com.airbnb.mvrx.BaseMvRxFragment
|
||||||
|
|
||||||
|
abstract class RiotFragment : BaseMvRxFragment() {
|
||||||
|
|
||||||
|
override fun invalidate() {
|
||||||
|
//no-ops by default
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
open class RiotFragment : Fragment() {
|
|
||||||
}
|
}
|
81
app/src/main/java/im/vector/riotredesign/core/platform/StateView.kt
Executable file
81
app/src/main/java/im/vector/riotredesign/core/platform/StateView.kt
Executable file
@ -0,0 +1,81 @@
|
|||||||
|
package im.vector.riotredesign.core.platform
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.FrameLayout
|
||||||
|
import im.vector.riotredesign.R
|
||||||
|
import kotlinx.android.synthetic.main.view_state.view.*
|
||||||
|
|
||||||
|
class StateView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0)
|
||||||
|
: FrameLayout(context, attrs, defStyle) {
|
||||||
|
|
||||||
|
sealed class State {
|
||||||
|
object Content : State()
|
||||||
|
object Loading : State()
|
||||||
|
data class Empty(val message: CharSequence? = null) : State()
|
||||||
|
data class Error(val message: CharSequence? = null) : State()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private var eventCallback: EventCallback? = null
|
||||||
|
|
||||||
|
var contentView: View? = null
|
||||||
|
|
||||||
|
var state: State = State.Empty()
|
||||||
|
set(newState) {
|
||||||
|
if (newState != state) {
|
||||||
|
update(newState)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface EventCallback {
|
||||||
|
fun onRetryClicked()
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
View.inflate(context, R.layout.view_state, this)
|
||||||
|
layoutParams = FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
|
||||||
|
errorRetryView.setOnClickListener {
|
||||||
|
eventCallback?.onRetryClicked()
|
||||||
|
}
|
||||||
|
state = State.Content
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun update(newState: State) {
|
||||||
|
when (newState) {
|
||||||
|
is StateView.State.Content -> {
|
||||||
|
progressBar.visibility = View.INVISIBLE
|
||||||
|
errorView.visibility = View.INVISIBLE
|
||||||
|
emptyView.visibility = View.INVISIBLE
|
||||||
|
contentView?.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
is StateView.State.Loading -> {
|
||||||
|
progressBar.visibility = View.VISIBLE
|
||||||
|
errorView.visibility = View.INVISIBLE
|
||||||
|
emptyView.visibility = View.INVISIBLE
|
||||||
|
contentView?.visibility = View.INVISIBLE
|
||||||
|
}
|
||||||
|
is StateView.State.Empty -> {
|
||||||
|
progressBar.visibility = View.INVISIBLE
|
||||||
|
errorView.visibility = View.INVISIBLE
|
||||||
|
emptyView.visibility = View.VISIBLE
|
||||||
|
emptyMessageView.text = newState.message
|
||||||
|
if (contentView != null) {
|
||||||
|
contentView!!.visibility = View.INVISIBLE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is StateView.State.Error -> {
|
||||||
|
progressBar.visibility = View.INVISIBLE
|
||||||
|
errorView.visibility = View.VISIBLE
|
||||||
|
emptyView.visibility = View.INVISIBLE
|
||||||
|
errorMessageView.text = newState.message
|
||||||
|
if (contentView != null) {
|
||||||
|
contentView!!.visibility = View.INVISIBLE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -3,23 +3,37 @@ package im.vector.riotredesign.features.home
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.view.Gravity
|
||||||
import im.vector.riotredesign.R
|
import im.vector.riotredesign.R
|
||||||
import im.vector.riotredesign.core.extensions.replaceFragment
|
import im.vector.riotredesign.core.extensions.replaceFragment
|
||||||
import im.vector.riotredesign.core.platform.RiotActivity
|
import im.vector.riotredesign.core.platform.RiotActivity
|
||||||
|
import im.vector.riotredesign.features.home.detail.LoadingRoomDetailFragment
|
||||||
|
import im.vector.riotredesign.features.home.detail.RoomDetailFragment
|
||||||
|
import im.vector.riotredesign.features.home.list.RoomListFragment
|
||||||
|
import kotlinx.android.synthetic.main.activity_home.*
|
||||||
|
import org.koin.standalone.StandAloneContext.loadKoinModules
|
||||||
|
|
||||||
|
|
||||||
class HomeActivity : RiotActivity() {
|
class HomeActivity : RiotActivity(), HomeNavigator {
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setContentView(R.layout.activity_home)
|
setContentView(R.layout.activity_home)
|
||||||
|
loadKoinModules(listOf(HomeModule(this)))
|
||||||
if (savedInstanceState == null) {
|
if (savedInstanceState == null) {
|
||||||
val roomListFragment = RoomListFragment.newInstance()
|
val roomListFragment = RoomListFragment.newInstance()
|
||||||
replaceFragment(roomListFragment, R.id.homeFragmentContainer)
|
val loadingDetail = LoadingRoomDetailFragment.newInstance()
|
||||||
|
replaceFragment(loadingDetail, R.id.homeDetailFragmentContainer)
|
||||||
|
replaceFragment(roomListFragment, R.id.homeDrawerFragmentContainer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun openRoomDetail(roomId: String) {
|
||||||
|
val roomDetailFragment = RoomDetailFragment.newInstance(roomId)
|
||||||
|
replaceFragment(roomDetailFragment, R.id.homeDetailFragmentContainer)
|
||||||
|
drawerLayout.closeDrawer(Gravity.LEFT)
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun newIntent(context: Context): Intent {
|
fun newIntent(context: Context): Intent {
|
||||||
return Intent(context, HomeActivity::class.java)
|
return Intent(context, HomeActivity::class.java)
|
||||||
|
@ -0,0 +1,16 @@
|
|||||||
|
package im.vector.riotredesign.features.home
|
||||||
|
|
||||||
|
import org.koin.dsl.context.ModuleDefinition
|
||||||
|
import org.koin.dsl.module.Module
|
||||||
|
import org.koin.dsl.module.module
|
||||||
|
|
||||||
|
class HomeModule(private val homeActivity: HomeActivity) : Module {
|
||||||
|
|
||||||
|
override fun invoke(): ModuleDefinition = module(override = true) {
|
||||||
|
|
||||||
|
factory {
|
||||||
|
homeActivity as HomeNavigator
|
||||||
|
}
|
||||||
|
|
||||||
|
}.invoke()
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
package im.vector.riotredesign.features.home
|
||||||
|
|
||||||
|
interface HomeNavigator {
|
||||||
|
|
||||||
|
fun openRoomDetail(roomId: String)
|
||||||
|
|
||||||
|
}
|
@ -1,51 +0,0 @@
|
|||||||
package im.vector.riotredesign.features.home
|
|
||||||
|
|
||||||
import android.arch.lifecycle.Observer
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import im.vector.matrix.android.api.Matrix
|
|
||||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
|
||||||
import im.vector.riotredesign.R
|
|
||||||
import im.vector.riotredesign.core.extensions.addFragmentToBackstack
|
|
||||||
import im.vector.riotredesign.core.platform.RiotFragment
|
|
||||||
import kotlinx.android.synthetic.main.fragment_room_list.*
|
|
||||||
import org.koin.android.ext.android.inject
|
|
||||||
|
|
||||||
class RoomListFragment : RiotFragment(), RoomSummaryController.Callback {
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
|
|
||||||
fun newInstance(): RoomListFragment {
|
|
||||||
return RoomListFragment()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private val matrix by inject<Matrix>()
|
|
||||||
private val currentSession = matrix.currentSession!!
|
|
||||||
private lateinit var roomController: RoomSummaryController
|
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
|
||||||
return inflater.inflate(R.layout.fragment_room_list, container, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
|
||||||
super.onActivityCreated(savedInstanceState)
|
|
||||||
roomController = RoomSummaryController(this)
|
|
||||||
epoxyRecyclerView.setController(roomController)
|
|
||||||
currentSession.liveRoomSummaries().observe(this, Observer<List<RoomSummary>> { renderRooms(it) })
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun renderRooms(rooms: List<RoomSummary>?) {
|
|
||||||
roomController.setData(rooms)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onRoomSelected(room: RoomSummary) {
|
|
||||||
val detailFragment = RoomDetailFragment.newInstance(room.roomId)
|
|
||||||
addFragmentToBackstack(detailFragment, R.id.homeFragmentContainer)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
@ -1,21 +0,0 @@
|
|||||||
package im.vector.riotredesign.features.home
|
|
||||||
|
|
||||||
import com.airbnb.epoxy.TypedEpoxyController
|
|
||||||
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
|
||||||
|
|
||||||
class RoomSummaryController(private val callback: Callback? = null
|
|
||||||
) : TypedEpoxyController<List<RoomSummary>>() {
|
|
||||||
|
|
||||||
override fun buildModels(data: List<RoomSummary>?) {
|
|
||||||
data?.forEach {
|
|
||||||
RoomItem(it.displayName, listener = { callback?.onRoomSelected(it) })
|
|
||||||
.id(it.roomId)
|
|
||||||
.addTo(this)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Callback {
|
|
||||||
fun onRoomSelected(room: RoomSummary)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,4 +1,4 @@
|
|||||||
package im.vector.riotredesign.features.home
|
package im.vector.riotredesign.features.home.detail
|
||||||
|
|
||||||
import android.support.v7.util.DiffUtil
|
import android.support.v7.util.DiffUtil
|
||||||
import im.vector.matrix.android.api.session.events.model.EnrichedEvent
|
import im.vector.matrix.android.api.session.events.model.EnrichedEvent
|
@ -0,0 +1,24 @@
|
|||||||
|
package im.vector.riotredesign.features.home.detail
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import im.vector.riotredesign.R
|
||||||
|
import im.vector.riotredesign.core.platform.RiotFragment
|
||||||
|
|
||||||
|
class LoadingRoomDetailFragment : RiotFragment() {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
fun newInstance(): LoadingRoomDetailFragment {
|
||||||
|
return LoadingRoomDetailFragment()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||||
|
return inflater.inflate(R.layout.fragment_loading_room_detail, container, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package im.vector.riotredesign.features.home
|
package im.vector.riotredesign.features.home.detail
|
||||||
|
|
||||||
import android.arch.lifecycle.Observer
|
import android.arch.lifecycle.Observer
|
||||||
import android.arch.paging.PagedList
|
import android.arch.paging.PagedList
|
@ -1,4 +1,4 @@
|
|||||||
package im.vector.riotredesign.features.home
|
package im.vector.riotredesign.features.home.detail
|
||||||
|
|
||||||
import android.arch.paging.PagedList
|
import android.arch.paging.PagedList
|
||||||
import android.arch.paging.PagedListAdapter
|
import android.arch.paging.PagedListAdapter
|
@ -1,9 +1,10 @@
|
|||||||
package im.vector.riotredesign.features.home
|
package im.vector.riotredesign.features.home.detail
|
||||||
|
|
||||||
import com.airbnb.epoxy.EpoxyAsyncUtil
|
import com.airbnb.epoxy.EpoxyAsyncUtil
|
||||||
import com.airbnb.epoxy.EpoxyModel
|
import com.airbnb.epoxy.EpoxyModel
|
||||||
import com.airbnb.epoxy.paging.PagedListEpoxyController
|
import com.airbnb.epoxy.paging.PagedListEpoxyController
|
||||||
import im.vector.matrix.android.api.session.events.model.Event
|
import im.vector.matrix.android.api.session.events.model.Event
|
||||||
|
import im.vector.riotredesign.features.home.LoadingItemModel_
|
||||||
|
|
||||||
class TimelineEventController : PagedListEpoxyController<Event>(
|
class TimelineEventController : PagedListEpoxyController<Event>(
|
||||||
diffingHandler = EpoxyAsyncUtil.getAsyncBackgroundHandler()
|
diffingHandler = EpoxyAsyncUtil.getAsyncBackgroundHandler()
|
@ -1,4 +1,4 @@
|
|||||||
package im.vector.riotredesign.features.home
|
package im.vector.riotredesign.features.home.detail
|
||||||
|
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import im.vector.riotredesign.R
|
import im.vector.riotredesign.R
|
@ -0,0 +1,10 @@
|
|||||||
|
package im.vector.riotredesign.features.home.list
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||||
|
|
||||||
|
sealed class RoomListActions {
|
||||||
|
|
||||||
|
data class SelectRoom(val roomSummary: RoomSummary) : RoomListActions()
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,82 @@
|
|||||||
|
package im.vector.riotredesign.features.home.list
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import com.airbnb.mvrx.*
|
||||||
|
import im.vector.matrix.android.api.failure.Failure
|
||||||
|
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||||
|
import im.vector.riotredesign.R
|
||||||
|
import im.vector.riotredesign.core.platform.RiotFragment
|
||||||
|
import im.vector.riotredesign.core.platform.StateView
|
||||||
|
import im.vector.riotredesign.features.home.HomeNavigator
|
||||||
|
import kotlinx.android.synthetic.main.fragment_room_list.*
|
||||||
|
import org.koin.android.ext.android.inject
|
||||||
|
|
||||||
|
class RoomListFragment : RiotFragment(), RoomSummaryController.Callback {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun newInstance(): RoomListFragment {
|
||||||
|
return RoomListFragment()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val homeNavigator by inject<HomeNavigator>()
|
||||||
|
private val viewModel: RoomListViewModel by fragmentViewModel()
|
||||||
|
private lateinit var roomController: RoomSummaryController
|
||||||
|
|
||||||
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||||
|
return inflater.inflate(R.layout.fragment_room_list, container, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||||
|
super.onActivityCreated(savedInstanceState)
|
||||||
|
roomController = RoomSummaryController(this)
|
||||||
|
stateView.contentView = epoxyRecyclerView
|
||||||
|
epoxyRecyclerView.setController(roomController)
|
||||||
|
viewModel.subscribe { renderState(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun renderState(state: RoomListViewState) {
|
||||||
|
when (state.roomSummaries) {
|
||||||
|
is Incomplete -> renderLoading()
|
||||||
|
is Success -> renderSuccess(state.roomSummaries(), state.selectedRoom)
|
||||||
|
is Fail -> renderFailure(state.roomSummaries.error)
|
||||||
|
}
|
||||||
|
if (state.showLastSelectedRoom && state.selectedRoom != null) {
|
||||||
|
homeNavigator.openRoomDetail(state.selectedRoom.roomId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun renderSuccess(roomSummaries: List<RoomSummary>?, selectedRoom: RoomSummary?) {
|
||||||
|
if (roomSummaries.isNullOrEmpty()) {
|
||||||
|
stateView.state = StateView.State.Empty("Rejoignez une room pour commencer à utiliser l'application")
|
||||||
|
} else {
|
||||||
|
stateView.state = StateView.State.Content
|
||||||
|
}
|
||||||
|
roomController.setData(roomSummaries, selectedRoom)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun renderLoading() {
|
||||||
|
stateView.state = StateView.State.Loading
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun renderFailure(error: Throwable) {
|
||||||
|
val message = when (error) {
|
||||||
|
is Failure.NetworkConnection -> "Pas de connexion internet"
|
||||||
|
else -> "Une erreur est survenue"
|
||||||
|
}
|
||||||
|
stateView.state = StateView.State.Error(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRoomSelected(room: RoomSummary) {
|
||||||
|
withState(viewModel) {
|
||||||
|
if (it.selectedRoom != room) {
|
||||||
|
viewModel.accept(RoomListActions.SelectRoom(room))
|
||||||
|
homeNavigator.openRoomDetail(room.roomId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,54 @@
|
|||||||
|
package im.vector.riotredesign.features.home.list
|
||||||
|
|
||||||
|
import android.support.v4.app.FragmentActivity
|
||||||
|
import com.airbnb.mvrx.BaseMvRxViewModel
|
||||||
|
import com.airbnb.mvrx.MvRxViewModelFactory
|
||||||
|
import im.vector.matrix.android.api.Matrix
|
||||||
|
import im.vector.matrix.android.api.session.Session
|
||||||
|
import im.vector.matrix.rx.rx
|
||||||
|
import org.koin.android.ext.android.get
|
||||||
|
|
||||||
|
class RoomListViewModel(initialState: RoomListViewState,
|
||||||
|
private val session: Session
|
||||||
|
) : BaseMvRxViewModel<RoomListViewState>(initialState) {
|
||||||
|
|
||||||
|
companion object : MvRxViewModelFactory<RoomListViewState> {
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
override fun create(activity: FragmentActivity, state: RoomListViewState): RoomListViewModel {
|
||||||
|
val matrix = activity.get<Matrix>()
|
||||||
|
val currentSession = matrix.currentSession!!
|
||||||
|
return RoomListViewModel(state, currentSession)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
observeRoomSummaries()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun accept(action: RoomListActions) {
|
||||||
|
when (action) {
|
||||||
|
is RoomListActions.SelectRoom -> handleSelectRoom(action)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PRIVATE METHODS *****************************************************************************
|
||||||
|
|
||||||
|
private fun handleSelectRoom(action: RoomListActions.SelectRoom) {
|
||||||
|
session.saveLastSelectedRoom(action.roomSummary)
|
||||||
|
setState { copy(selectedRoom = action.roomSummary) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun observeRoomSummaries() {
|
||||||
|
session
|
||||||
|
.rx().liveRoomSummaries()
|
||||||
|
.execute {
|
||||||
|
val selectedRoom = selectedRoom
|
||||||
|
?: session.lastSelectedRoom()
|
||||||
|
?: it.invoke()?.firstOrNull()
|
||||||
|
|
||||||
|
copy(roomSummaries = it, selectedRoom = selectedRoom)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
package im.vector.riotredesign.features.home.list
|
||||||
|
|
||||||
|
import com.airbnb.mvrx.Async
|
||||||
|
import com.airbnb.mvrx.MvRxState
|
||||||
|
import com.airbnb.mvrx.Uninitialized
|
||||||
|
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||||
|
|
||||||
|
data class RoomListViewState(
|
||||||
|
val roomSummaries: Async<List<RoomSummary>> = Uninitialized,
|
||||||
|
val selectedRoom: RoomSummary? = null
|
||||||
|
) : MvRxState {
|
||||||
|
|
||||||
|
var showLastSelectedRoom: Boolean = true
|
||||||
|
private set
|
||||||
|
get() {
|
||||||
|
if (field) {
|
||||||
|
field = false
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
package im.vector.riotredesign.features.home.list
|
||||||
|
|
||||||
|
import com.airbnb.epoxy.Typed2EpoxyController
|
||||||
|
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||||
|
|
||||||
|
class RoomSummaryController(private val callback: Callback? = null
|
||||||
|
|
||||||
|
) : Typed2EpoxyController<List<RoomSummary>, RoomSummary>() {
|
||||||
|
|
||||||
|
override fun buildModels(summaries: List<RoomSummary>?, selected: RoomSummary?) {
|
||||||
|
summaries?.forEach {
|
||||||
|
RoomSummaryItem(
|
||||||
|
it.displayName,
|
||||||
|
isSelected = it == selected,
|
||||||
|
listener = { callback?.onRoomSelected(it) }
|
||||||
|
)
|
||||||
|
.id(it.roomId)
|
||||||
|
.addTo(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Callback {
|
||||||
|
fun onRoomSelected(room: RoomSummary)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,17 +1,22 @@
|
|||||||
package im.vector.riotredesign.features.home
|
package im.vector.riotredesign.features.home.list
|
||||||
|
|
||||||
|
import android.support.v4.content.ContextCompat
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import im.vector.riotredesign.R
|
import im.vector.riotredesign.R
|
||||||
import im.vector.riotredesign.core.epoxy.KotlinModel
|
import im.vector.riotredesign.core.epoxy.KotlinModel
|
||||||
|
import im.vector.riotredesign.core.platform.CheckableConstraintLayout
|
||||||
|
|
||||||
data class RoomItem(
|
data class RoomSummaryItem(
|
||||||
val title: CharSequence,
|
val title: CharSequence,
|
||||||
|
val isSelected: Boolean,
|
||||||
val listener: (() -> Unit)? = null
|
val listener: (() -> Unit)? = null
|
||||||
) : KotlinModel(R.layout.item_room) {
|
) : KotlinModel(R.layout.item_room) {
|
||||||
|
|
||||||
val titleView by bind<TextView>(R.id.titleView)
|
val titleView by bind<TextView>(R.id.titleView)
|
||||||
|
val rootView by bind<CheckableConstraintLayout>(R.id.itemRoomLayout)
|
||||||
|
|
||||||
override fun bind() {
|
override fun bind() {
|
||||||
|
rootView.isChecked = isSelected
|
||||||
titleView.setOnClickListener { listener?.invoke() }
|
titleView.setOnClickListener { listener?.invoke() }
|
||||||
titleView.text = title
|
titleView.text = title
|
||||||
}
|
}
|
19
app/src/main/res/drawable/bg_room_item.xml
Normal file
19
app/src/main/res/drawable/bg_room_item.xml
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:state_pressed="true">
|
||||||
|
<shape>
|
||||||
|
<solid android:color="@android:color/white"/>
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
<item android:state_checked="true">
|
||||||
|
<shape>
|
||||||
|
<solid android:color="@android:color/white"/>
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<shape>
|
||||||
|
<solid android:color="@android:color/transparent"/>
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
|
||||||
|
</selector>
|
@ -1,15 +1,21 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/drawerLayout"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:orientation="vertical"
|
tools:openDrawer="start">
|
||||||
tools:context=".features.login.LoginActivity">
|
|
||||||
|
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
android:id="@+id/homeFragmentContainer"
|
android:id="@+id/homeDetailFragmentContainer"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent" />
|
android:layout_height="match_parent" />
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:id="@+id/homeDrawerFragmentContainer"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_gravity="start"
|
||||||
|
android:layout_marginRight="24dp" />
|
||||||
|
|
||||||
</android.support.constraint.ConstraintLayout>
|
</android.support.v4.widget.DrawerLayout>
|
20
app/src/main/res/layout/fragment_loading_room_detail.xml
Normal file
20
app/src/main/res/layout/fragment_loading_room_detail.xml
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/progressBar"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
</android.support.constraint.ConstraintLayout>
|
@ -1,11 +1,13 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<im.vector.riotredesign.core.platform.StateView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:id="@+id/stateView"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent"
|
||||||
|
android:background="@color/pale_grey">
|
||||||
|
|
||||||
<com.airbnb.epoxy.EpoxyRecyclerView
|
<com.airbnb.epoxy.EpoxyRecyclerView
|
||||||
android:id="@+id/epoxyRecyclerView"
|
android:id="@+id/epoxyRecyclerView"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent" />
|
android:layout_height="match_parent" />
|
||||||
|
|
||||||
</FrameLayout>
|
</im.vector.riotredesign.core.platform.StateView>
|
@ -1,10 +1,28 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
|
<im.vector.riotredesign.core.platform.CheckableConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:id="@+id/itemRoomLayout"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:id="@+id/titleView"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="80dp"
|
android:layout_height="wrap_content"
|
||||||
android:gravity="center_vertical"
|
android:background="@drawable/bg_room_item"
|
||||||
android:padding="16dp"
|
android:minHeight="80dp">
|
||||||
android:textSize="14sp"
|
|
||||||
tools:text="Room name" />
|
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/titleView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="24dp"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
android:textSize="14sp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:text="Room name" />
|
||||||
|
|
||||||
|
</im.vector.riotredesign.core.platform.CheckableConstraintLayout>
|
73
app/src/main/res/layout/view_state.xml
Normal file
73
app/src/main/res/layout/view_state.xml
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<merge xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:minHeight="40dp"
|
||||||
|
android:padding="8dp"
|
||||||
|
tools:parentTag="android.widget.FrameLayout">
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/progressBar"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center_horizontal" />
|
||||||
|
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/errorView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="8dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/errorMessageView"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:gravity="center"
|
||||||
|
android:textColor="@android:color/black"
|
||||||
|
android:textSize="16sp"
|
||||||
|
tools:text="Une erreur est survenue" />
|
||||||
|
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/errorRetryView"
|
||||||
|
android:layout_width="190dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:text="@string/global_retry"
|
||||||
|
android:textColor="@android:color/white" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/emptyView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="8dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/emptyMessageView"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:gravity="center"
|
||||||
|
android:textColor="@android:color/black"
|
||||||
|
android:textSize="16sp" />
|
||||||
|
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/emptyImageView"
|
||||||
|
android:layout_width="190dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:layout_marginTop="8dp" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</merge>
|
@ -3,4 +3,16 @@
|
|||||||
<color name="colorPrimary">#3F51B5</color>
|
<color name="colorPrimary">#3F51B5</color>
|
||||||
<color name="colorPrimaryDark">#303F9F</color>
|
<color name="colorPrimaryDark">#303F9F</color>
|
||||||
<color name="colorAccent">#FF4081</color>
|
<color name="colorAccent">#FF4081</color>
|
||||||
|
|
||||||
|
|
||||||
|
<color name="pale_grey">#f2f5f8</color>
|
||||||
|
<color name="dark">#2e3649</color>
|
||||||
|
<color name="pale_teal">#7ac9a1</color>
|
||||||
|
<color name="black">#212121</color>
|
||||||
|
<color name="deep_sky_blue">#007aff</color>
|
||||||
|
<color name="rosy_pink">#f56679</color>
|
||||||
|
<color name="bluey_grey">#a5a5a6</color>
|
||||||
|
<color name="slate_grey">#5f6268</color>
|
||||||
|
<color name="sky_blue">#7bb2ea</color>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -1,3 +1,6 @@
|
|||||||
<resources>
|
<resources>
|
||||||
<string name="app_name">Riot Redesign</string>
|
<string name="app_name">Riot Redesign</string>
|
||||||
|
|
||||||
|
<string name="global_retry">Réessayer</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
40
matrix-sdk-android-rx/build.gradle
Normal file
40
matrix-sdk-android-rx/build.gradle
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
apply plugin: 'com.android.library'
|
||||||
|
apply plugin: 'kotlin-android'
|
||||||
|
apply plugin: 'kotlin-kapt'
|
||||||
|
|
||||||
|
android {
|
||||||
|
compileSdkVersion 28
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
minSdkVersion 16
|
||||||
|
targetSdkVersion 28
|
||||||
|
versionCode 1
|
||||||
|
versionName "1.0"
|
||||||
|
|
||||||
|
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
minifyEnabled false
|
||||||
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||||
|
implementation project(":matrix-sdk-android")
|
||||||
|
implementation 'com.android.support:appcompat-v7:28.0.0'
|
||||||
|
implementation 'io.reactivex.rxjava2:rxkotlin:2.3.0'
|
||||||
|
implementation 'io.reactivex.rxjava2:rxandroid:2.0.2'
|
||||||
|
|
||||||
|
|
||||||
|
testImplementation 'junit:junit:4.12'
|
||||||
|
androidTestImplementation 'com.android.support.test:runner:1.0.2'
|
||||||
|
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
|
||||||
|
}
|
21
matrix-sdk-android-rx/proguard-rules.pro
vendored
Normal file
21
matrix-sdk-android-rx/proguard-rules.pro
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
# Add project specific ProGuard rules here.
|
||||||
|
# You can control the set of applied configuration files using the
|
||||||
|
# proguardFiles setting in build.gradle.
|
||||||
|
#
|
||||||
|
# For more details, see
|
||||||
|
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||||
|
|
||||||
|
# If your project uses WebView with JS, uncomment the following
|
||||||
|
# and specify the fully qualified class name to the JavaScript interface
|
||||||
|
# class:
|
||||||
|
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||||
|
# public *;
|
||||||
|
#}
|
||||||
|
|
||||||
|
# Uncomment this to preserve the line number information for
|
||||||
|
# debugging stack traces.
|
||||||
|
#-keepattributes SourceFile,LineNumberTable
|
||||||
|
|
||||||
|
# If you keep the line number information, uncomment this to
|
||||||
|
# hide the original source file name.
|
||||||
|
#-renamesourcefileattribute SourceFile
|
@ -0,0 +1,26 @@
|
|||||||
|
package im.vector.matrix.rx;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.support.test.InstrumentationRegistry;
|
||||||
|
import android.support.test.runner.AndroidJUnit4;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instrumented test, which will execute on an Android device.
|
||||||
|
*
|
||||||
|
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||||
|
*/
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
public class ExampleInstrumentedTest {
|
||||||
|
@Test
|
||||||
|
public void useAppContext() {
|
||||||
|
// Context of the app under test.
|
||||||
|
Context appContext = InstrumentationRegistry.getTargetContext();
|
||||||
|
|
||||||
|
assertEquals("im.vector.matrix.rx.test", appContext.getPackageName());
|
||||||
|
}
|
||||||
|
}
|
2
matrix-sdk-android-rx/src/main/AndroidManifest.xml
Normal file
2
matrix-sdk-android-rx/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="im.vector.matrix.rx" />
|
@ -0,0 +1,45 @@
|
|||||||
|
package im.vector.matrix.rx
|
||||||
|
|
||||||
|
import android.arch.lifecycle.LiveData
|
||||||
|
import android.arch.lifecycle.Observer
|
||||||
|
import io.reactivex.Observable
|
||||||
|
import io.reactivex.android.MainThreadDisposable
|
||||||
|
|
||||||
|
private class LiveDataObservable<T>(
|
||||||
|
private val liveData: LiveData<T>,
|
||||||
|
private val valueIfNull: T? = null
|
||||||
|
) : Observable<T>() {
|
||||||
|
|
||||||
|
override fun subscribeActual(observer: io.reactivex.Observer<in T>) {
|
||||||
|
val relay = RemoveObserverInMainThread(observer)
|
||||||
|
observer.onSubscribe(relay)
|
||||||
|
liveData.observeForever(relay)
|
||||||
|
}
|
||||||
|
|
||||||
|
private inner class RemoveObserverInMainThread(private val observer: io.reactivex.Observer<in T>)
|
||||||
|
: MainThreadDisposable(), Observer<T> {
|
||||||
|
|
||||||
|
override fun onChanged(t: T?) {
|
||||||
|
if (!isDisposed) {
|
||||||
|
if (t == null) {
|
||||||
|
if (valueIfNull != null) {
|
||||||
|
observer.onNext(valueIfNull)
|
||||||
|
} else {
|
||||||
|
observer.onError(NullPointerException(
|
||||||
|
"convert liveData value t to RxJava onNext(t), t cannot be null"))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
observer.onNext(t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDispose() {
|
||||||
|
liveData.removeObserver(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T> LiveData<T>.asObservable(): Observable<T> {
|
||||||
|
return LiveDataObservable(this)
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
package im.vector.matrix.rx
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.session.Session
|
||||||
|
import im.vector.matrix.android.api.session.room.model.RoomSummary
|
||||||
|
import io.reactivex.Observable
|
||||||
|
|
||||||
|
class RxSession(private val session: Session) {
|
||||||
|
|
||||||
|
fun liveRoomSummaries(): Observable<List<RoomSummary>> {
|
||||||
|
return session.liveRoomSummaries().asObservable()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Session.rx(): RxSession {
|
||||||
|
return RxSession(this)
|
||||||
|
}
|
3
matrix-sdk-android-rx/src/main/res/values/strings.xml
Normal file
3
matrix-sdk-android-rx/src/main/res/values/strings.xml
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<resources>
|
||||||
|
<string name="app_name">matrix-sdk-android-rx</string>
|
||||||
|
</resources>
|
@ -0,0 +1,17 @@
|
|||||||
|
package im.vector.matrix.rx;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Example local unit test, which will execute on the development machine (host).
|
||||||
|
*
|
||||||
|
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||||
|
*/
|
||||||
|
public class ExampleUnitTest {
|
||||||
|
@Test
|
||||||
|
public void addition_isCorrect() {
|
||||||
|
assertEquals(4, 2 + 2);
|
||||||
|
}
|
||||||
|
}
|
@ -2,22 +2,12 @@ package im.vector.matrix.android.api.failure
|
|||||||
|
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
|
||||||
sealed class Failure {
|
sealed class Failure(cause: Throwable? = null) : Throwable(cause = cause) {
|
||||||
|
|
||||||
data class Unknown(val exception: Exception? = null) : Failure()
|
data class Unknown(val throwable: Throwable? = null) : Failure(throwable)
|
||||||
data class NetworkConnection(val ioException: IOException) : Failure()
|
data class NetworkConnection(val ioException: IOException) : Failure(ioException)
|
||||||
data class ServerError(val error: MatrixError) : Failure()
|
data class ServerError(val error: MatrixError) : Failure(RuntimeException(error.toString()))
|
||||||
|
|
||||||
abstract class FeatureFailure : Failure()
|
abstract class FeatureFailure : Failure()
|
||||||
|
|
||||||
fun toException(): Exception {
|
|
||||||
return when (this) {
|
|
||||||
is Unknown -> this.exception ?: RuntimeException("Unknown error")
|
|
||||||
is NetworkConnection -> this.ioException
|
|
||||||
is ServerError -> RuntimeException(this.error.toString())
|
|
||||||
is FeatureFailure -> RuntimeException("Feature error")
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
@ -13,5 +13,9 @@ interface RoomService {
|
|||||||
|
|
||||||
fun liveRoomSummaries(): LiveData<List<RoomSummary>>
|
fun liveRoomSummaries(): LiveData<List<RoomSummary>>
|
||||||
|
|
||||||
|
fun lastSelectedRoom(): RoomSummary?
|
||||||
|
|
||||||
|
fun saveLastSelectedRoom(roomSummary: RoomSummary)
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
@ -13,8 +13,20 @@ object RoomSummaryMapper {
|
|||||||
roomSummaryEntity.topic ?: ""
|
roomSummaryEntity.topic ?: ""
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal fun map(roomSummary: RoomSummary): RoomSummaryEntity {
|
||||||
|
return RoomSummaryEntity(
|
||||||
|
roomSummary.roomId,
|
||||||
|
roomSummary.displayName,
|
||||||
|
roomSummary.topic
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun RoomSummaryEntity.asDomain(): RoomSummary {
|
fun RoomSummaryEntity.asDomain(): RoomSummary {
|
||||||
return RoomSummaryMapper.map(this)
|
return RoomSummaryMapper.map(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun RoomSummaryEntity.asEntity(): RoomSummary {
|
||||||
|
return RoomSummaryMapper.map(this)
|
||||||
}
|
}
|
@ -11,7 +11,8 @@ open class RoomSummaryEntity(@PrimaryKey var roomId: String = "",
|
|||||||
var lastMessage: EventEntity? = null,
|
var lastMessage: EventEntity? = null,
|
||||||
var heroes: RealmList<String> = RealmList(),
|
var heroes: RealmList<String> = RealmList(),
|
||||||
var joinedMembersCount: Int? = 0,
|
var joinedMembersCount: Int? = 0,
|
||||||
var invitedMembersCount: Int? = 0
|
var invitedMembersCount: Int? = 0,
|
||||||
|
var isLatestSelected: Boolean = false
|
||||||
) : RealmObject() {
|
) : RealmObject() {
|
||||||
|
|
||||||
companion object
|
companion object
|
||||||
|
@ -13,3 +13,9 @@ fun RoomSummaryEntity.Companion.where(realm: Realm, roomId: String? = null): Rea
|
|||||||
}
|
}
|
||||||
return query
|
return query
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun RoomSummaryEntity.Companion.lastSelected(realm: Realm): RoomSummaryEntity? {
|
||||||
|
return realm.where<RoomSummaryEntity>()
|
||||||
|
.equalTo(RoomSummaryEntityFields.IS_LATEST_SELECTED, true)
|
||||||
|
.findFirst()
|
||||||
|
}
|
||||||
|
@ -75,6 +75,14 @@ class DefaultSession(override val sessionParams: SessionParams) : Session, KoinC
|
|||||||
return roomService.liveRoomSummaries()
|
return roomService.liveRoomSummaries()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun lastSelectedRoom(): RoomSummary? {
|
||||||
|
return roomService.lastSelectedRoom()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun saveLastSelectedRoom(roomSummary: RoomSummary) {
|
||||||
|
roomService.saveLastSelectedRoom(roomSummary)
|
||||||
|
}
|
||||||
|
|
||||||
// Private methods *****************************************************************************
|
// Private methods *****************************************************************************
|
||||||
|
|
||||||
private fun checkIsMainThread() {
|
private fun checkIsMainThread() {
|
||||||
|
@ -8,6 +8,7 @@ import im.vector.matrix.android.api.session.room.model.RoomSummary
|
|||||||
import im.vector.matrix.android.internal.database.mapper.asDomain
|
import im.vector.matrix.android.internal.database.mapper.asDomain
|
||||||
import im.vector.matrix.android.internal.database.model.RoomEntity
|
import im.vector.matrix.android.internal.database.model.RoomEntity
|
||||||
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
|
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
|
||||||
|
import im.vector.matrix.android.internal.database.query.lastSelected
|
||||||
import im.vector.matrix.android.internal.database.query.where
|
import im.vector.matrix.android.internal.database.query.where
|
||||||
|
|
||||||
class DefaultRoomService(private val monarchy: Monarchy) : RoomService {
|
class DefaultRoomService(private val monarchy: Monarchy) : RoomService {
|
||||||
@ -42,5 +43,20 @@ class DefaultRoomService(private val monarchy: Monarchy) : RoomService {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun lastSelectedRoom(): RoomSummary? {
|
||||||
|
var lastSelected: RoomSummary? = null
|
||||||
|
monarchy.doWithRealm { realm ->
|
||||||
|
lastSelected = RoomSummaryEntity.lastSelected(realm)?.asDomain()
|
||||||
|
}
|
||||||
|
return lastSelected
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun saveLastSelectedRoom(roomSummary: RoomSummary) {
|
||||||
|
monarchy.writeAsync { realm ->
|
||||||
|
val lastSelected = RoomSummaryEntity.lastSelected(realm)
|
||||||
|
val roomSummaryEntity = RoomSummaryEntity.where(realm, roomSummary.roomId).findFirst()
|
||||||
|
lastSelected?.isLatestSelected = false
|
||||||
|
roomSummaryEntity?.isLatestSelected = true
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -53,7 +53,7 @@ class TimelineBoundaryCallback(private val roomId: String,
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onFailure(failure: Failure) {
|
override fun onFailure(failure: Failure) {
|
||||||
pagingRequestCallback.recordFailure(failure.toException())
|
pagingRequestCallback.recordFailure(failure)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +0,0 @@
|
|||||||
apply plugin: 'java-library'
|
|
||||||
apply plugin: "kotlin"
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
|
||||||
implementation 'io.reactivex.rxjava2:rxkotlin:2.3.0'
|
|
||||||
|
|
||||||
}
|
|
@ -1,3 +0,0 @@
|
|||||||
package im.vector.matrix.rx
|
|
||||||
|
|
||||||
class MatrixRx
|
|
@ -1 +1 @@
|
|||||||
include ':app', ':matrix-sdk-rx', ':matrix-sdk-android'
|
include ':app', ':matrix-sdk-android', ':matrix-sdk-android-rx'
|
||||||
|
Loading…
Reference in New Issue
Block a user