? = null
22 | set(value) {
23 | when (value) {
24 | is Lce.Loading -> children.forEach { it.visibleOrGone = it == loading }
25 | is Lce.Success -> {
26 | children.forEach { it.visibleOrGone = it != loading && it != error }
27 | updateListener?.invoke(value.data)
28 | }
29 | is Lce.Error -> {
30 | children.forEach { it.visibleOrGone = it == error }
31 | errorMessage.text = value.message
32 | }
33 | }
34 | }
35 |
36 | private var updateListener: ((T) -> Unit)? = null
37 |
38 | fun setRetryAction(retryAction: Runnable?) {
39 | retry.setOnClickListener { retryAction?.run() }
40 | }
41 |
42 | fun setUpdateListener(listener: ((T) -> Unit)) {
43 | updateListener = listener
44 | }
45 |
46 | constructor(context: Context) : super(context)
47 |
48 | constructor(context: Context, retryAction: () -> Unit) : super(context) {
49 | retry.setOnClickListener { retryAction() }
50 | }
51 |
52 | constructor(context: Context, attrs: AttributeSet?) : super(context, attrs, 0)
53 |
54 | init {
55 | val inflater = LayoutInflater.from(context)
56 | loading = inflater.inflate(R.layout.loading, this, false).also { addView(it) }
57 | error = inflater.inflate(R.layout.error, this, false).also { addView(it) }
58 | errorMessage = error.findViewById(R.id.error_message)
59 | retry = error.findViewById(R.id.retry)
60 | }
61 | }
--------------------------------------------------------------------------------
/viewlib/src/main/java/it/codingjam/github/util/NavigationSignal.kt:
--------------------------------------------------------------------------------
1 | package it.codingjam.github.util
2 |
3 | data class NavigationSignal(val destination: Any, val params: P) : Signal()
--------------------------------------------------------------------------------
/viewlib/src/main/java/it/codingjam/github/util/ViewModelUtils.kt:
--------------------------------------------------------------------------------
1 | package it.codingjam.github.util
2 |
3 | import androidx.fragment.app.Fragment
4 | import androidx.fragment.app.FragmentActivity
5 | import androidx.lifecycle.ViewModel
6 | import androidx.lifecycle.ViewModelProvider
7 | import androidx.lifecycle.ViewModelProviders
8 |
9 |
10 | inline fun Fragment.viewModel(crossinline provider: () -> VM): Lazy {
11 | return lazy {
12 | val factory = object : ViewModelProvider.Factory {
13 | override fun create(aClass: Class): T1 {
14 | val viewModel = provider.invoke()
15 | return viewModel as T1
16 | }
17 | }
18 | ViewModelProviders.of(this, factory).get(VM::class.java)
19 | }
20 | }
21 |
22 | inline fun FragmentActivity.viewModel(crossinline provider: () -> VM): Lazy {
23 | return lazy {
24 | val factory = object : ViewModelProvider.Factory {
25 | override fun create(aClass: Class): T1 {
26 | val viewModel = provider.invoke()
27 | return viewModel as T1
28 | }
29 | }
30 | ViewModelProviders.of(this, factory).get(VM::class.java)
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/viewlib/src/main/java/it/codingjam/github/util/ViewStateStore.kt:
--------------------------------------------------------------------------------
1 | package it.codingjam.github.util
2 |
3 |
4 | import androidx.lifecycle.LifecycleOwner
5 | import androidx.lifecycle.MutableLiveData
6 | import androidx.lifecycle.Observer
7 | import kotlinx.coroutines.*
8 | import kotlinx.coroutines.flow.collect
9 | import kotlinx.coroutines.flow.flowOn
10 |
11 | class ViewStateStoreFactory(
12 | private val dispatcher: CoroutineDispatcher
13 | ) {
14 | operator fun invoke(initialState: T, scope: CoroutineScope) =
15 | ViewStateStore(initialState, scope, dispatcher)
16 | }
17 |
18 | class ViewStateStore(
19 | initialState: T,
20 | private val scope: CoroutineScope,
21 | private val dispatcher: CoroutineDispatcher
22 | ) {
23 |
24 | private val stateLiveData = MutableLiveData().apply {
25 | value = initialState
26 | }
27 |
28 | private val signalsLiveData = EventsLiveData()
29 |
30 | fun observe(owner: LifecycleOwner, observer: (T) -> Unit) =
31 | stateLiveData.observe(owner, Observer { observer(it!!) })
32 |
33 | fun observeSignals(owner: LifecycleOwner, observer: (Signal) -> Unit) =
34 | signalsLiveData.observe(owner) { observer(it) }
35 |
36 | fun dispatchState(state: T) {
37 | scope.launch(Dispatchers.Main.immediate) {
38 | stateLiveData.value = state
39 | }
40 | }
41 |
42 | fun dispatchSignal(action: Signal) {
43 | signalsLiveData.addEvent(action)
44 | }
45 |
46 | private fun dispatch(action: Action) {
47 | if (action is StateAction) {
48 | stateLiveData.value = action(invoke())
49 | } else if (action is Signal) {
50 | signalsLiveData.addEvent(action)
51 | }
52 | }
53 |
54 | fun dispatchAction(f: suspend () -> Action) {
55 | // dispatchActions(flow { emit(f()) })
56 | scope.launch {
57 | val action = withContext(dispatcher) {
58 | f()
59 | }
60 | dispatch(action)
61 | }
62 | }
63 |
64 | fun dispatchActions(flow: ActionsFlow) {
65 | scope.launch {
66 | flow
67 | .flowOn(dispatcher)
68 | .collect { action ->
69 | dispatch(action)
70 | }
71 | }
72 | }
73 |
74 | operator fun invoke() = stateLiveData.value!!
75 | }
--------------------------------------------------------------------------------
/viewlib/src/main/java/it/codingjam/github/vo/Lce.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2017 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package it.codingjam.github.vo
18 |
19 | import it.codingjam.github.util.ActionsFlow
20 | import it.codingjam.github.util.actionsFlow
21 |
22 | sealed class Lce {
23 |
24 | open val data: T? = null
25 |
26 | abstract fun map(f: (T) -> R): Lce
27 |
28 | inline fun doOnData(f: (T) -> Unit) {
29 | if (this is Success) {
30 | f(data)
31 | }
32 | }
33 |
34 | data class Success(override val data: T) : Lce() {
35 | override fun map(f: (T) -> R): Lce = Success(f(data))
36 | }
37 |
38 | data class Error(val message: String) : Lce() {
39 | constructor(t: Throwable) : this(t.message ?: "")
40 |
41 | override fun map(f: (Nothing) -> R): Lce = this
42 | }
43 |
44 | object Loading : Lce() {
45 | override fun map(f: (Nothing) -> R): Lce = this
46 | }
47 | }
48 |
49 | inline fun lce(crossinline f: suspend () -> S): ActionsFlow> {
50 | return actionsFlow {
51 | emit { Lce.Loading }
52 | try {
53 | val result = f()
54 | emit { Lce.Success(result) }
55 | } catch (e: Exception) {
56 | emit { Lce.Error(e) }
57 | }
58 | }
59 | }
--------------------------------------------------------------------------------
/viewlib/src/main/res/layout/error.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
25 |
26 |
35 |
36 |
41 |
--------------------------------------------------------------------------------
/viewlib/src/main/res/layout/loading.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
21 |
22 |
28 |
--------------------------------------------------------------------------------
/viewlib/src/main/res/layout/repo_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
21 |
22 |
23 |
24 |
27 |
28 |
31 |
32 |
33 |
37 |
38 |
41 |
42 |
53 |
54 |
66 |
67 |
81 |
82 |
83 |
--------------------------------------------------------------------------------
/viewlib/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 | 64dp
20 | 8dp
21 |
--------------------------------------------------------------------------------
/viewlib/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Github
3 | Retry
4 | Unknown Error
5 | user photo
6 | user name
7 | search repositories
8 | %s/%s
9 | No results for %s
10 | contributor avatar
11 |
12 |
--------------------------------------------------------------------------------