-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
15 changed files
with
418 additions
and
80 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
111 changes: 111 additions & 0 deletions
111
composeApp/src/commonMain/kotlin/in/procyk/shin/component/AbstractComponent.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
package `in`.procyk.shin.component | ||
|
||
import androidx.compose.material3.SnackbarDuration | ||
import androidx.compose.material3.SnackbarHostState | ||
import com.arkivanov.decompose.ComponentContext | ||
import com.arkivanov.decompose.value.Value | ||
import com.arkivanov.essenty.lifecycle.Lifecycle | ||
import `in`.procyk.shin.ui.util.coroutineScope | ||
import kotlinx.coroutines.CoroutineScope | ||
import kotlinx.coroutines.Dispatchers | ||
import kotlinx.coroutines.flow.SharingStarted | ||
import kotlinx.coroutines.flow.StateFlow | ||
import kotlinx.coroutines.flow.stateIn | ||
import kotlinx.coroutines.launch | ||
import kotlin.coroutines.CoroutineContext | ||
import `in`.procyk.shin.ui.util.asValue as asValueUtil | ||
import kotlinx.coroutines.flow.combine as coroutinesFlowCombine | ||
import kotlinx.coroutines.flow.map as coroutinesFlowMap | ||
|
||
interface Component { | ||
val appContext: ShinAppComponentContext | ||
|
||
val snackbarHostState: SnackbarHostState | ||
|
||
fun toast( | ||
message: String, | ||
actionLabel: String? = null, | ||
withDismissAction: Boolean = false, | ||
duration: SnackbarDuration = if (actionLabel == null) SnackbarDuration.Short else SnackbarDuration.Indefinite, | ||
) | ||
} | ||
|
||
abstract class AbstractComponent( | ||
final override val appContext: ShinAppComponentContext, | ||
componentContext: ComponentContext, | ||
) : ComponentContext by componentContext, Component { | ||
|
||
protected val scope: CoroutineScope = coroutineScope() | ||
|
||
override val snackbarHostState: SnackbarHostState | ||
get() = appContext.snackbarHostState | ||
|
||
override fun toast( | ||
message: String, | ||
actionLabel: String?, | ||
withDismissAction: Boolean, | ||
duration: SnackbarDuration, | ||
) { | ||
scope.launch { | ||
appContext.snackbarHostState.showSnackbar(message, actionLabel, withDismissAction, duration) | ||
} | ||
} | ||
|
||
protected fun <T, M> StateFlow<T>.map( | ||
coroutineScope: CoroutineScope = scope, | ||
mapper: (value: T) -> M, | ||
): StateFlow<M> = | ||
coroutinesFlowMap(mapper) | ||
.stateIn( | ||
coroutineScope, | ||
SharingStarted.Eagerly, | ||
mapper(value), | ||
) | ||
|
||
protected fun <T1, T2, R> combine( | ||
flow1: StateFlow<T1>, | ||
flow2: StateFlow<T2>, | ||
coroutineScope: CoroutineScope = scope, | ||
transform: (T1, T2) -> R, | ||
): StateFlow<R> = | ||
coroutinesFlowCombine(flow1, flow2, transform) | ||
.stateIn( | ||
coroutineScope, | ||
SharingStarted.Eagerly, | ||
transform(flow1.value, flow2.value) | ||
) | ||
|
||
protected fun <T1, T2, T3, R> combine( | ||
flow1: StateFlow<T1>, | ||
flow2: StateFlow<T2>, | ||
flow3: StateFlow<T3>, | ||
coroutineScope: CoroutineScope = scope, | ||
transform: (T1, T2, T3) -> R, | ||
): StateFlow<R> = | ||
coroutinesFlowCombine(flow1, flow2, flow3, transform) | ||
.stateIn( | ||
coroutineScope, | ||
SharingStarted.Eagerly, | ||
transform(flow1.value, flow2.value, flow3.value) | ||
) | ||
|
||
protected fun <T1, T2, T3, T4, R> combine( | ||
flow1: StateFlow<T1>, | ||
flow2: StateFlow<T2>, | ||
flow3: StateFlow<T3>, | ||
flow4: StateFlow<T4>, | ||
coroutineScope: CoroutineScope = scope, | ||
transform: (T1, T2, T3, T4) -> R, | ||
): StateFlow<R> = | ||
coroutinesFlowCombine(flow1, flow2, flow3, flow4, transform) | ||
.stateIn( | ||
coroutineScope, | ||
SharingStarted.Eagerly, | ||
transform(flow1.value, flow2.value, flow3.value, flow4.value) | ||
) | ||
|
||
protected fun <T : Any> StateFlow<T>.asValue( | ||
lifecycle: Lifecycle = [email protected], | ||
context: CoroutineContext = Dispatchers.Main.immediate, | ||
): Value<T> = asValueUtil(lifecycle, context) | ||
} |
7 changes: 7 additions & 0 deletions
7
composeApp/src/commonMain/kotlin/in/procyk/shin/component/ShinAppComponentContext.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package `in`.procyk.shin.component | ||
|
||
import androidx.compose.material3.SnackbarHostState | ||
|
||
class ShinAppComponentContext { | ||
val snackbarHostState: SnackbarHostState = SnackbarHostState() | ||
} |
107 changes: 107 additions & 0 deletions
107
composeApp/src/commonMain/kotlin/in/procyk/shin/component/ShinComponent.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
package `in`.procyk.shin.component | ||
|
||
import Option | ||
import Option.Some | ||
import Shorten | ||
import com.arkivanov.decompose.ComponentContext | ||
import com.arkivanov.decompose.value.Value | ||
import `in`.procyk.shin.createHttpClient | ||
import `in`.procyk.shin.model.ShortenedProtocol | ||
import io.ktor.client.* | ||
import io.ktor.client.plugins.resources.* | ||
import io.ktor.client.statement.* | ||
import io.ktor.http.* | ||
import kotlinx.coroutines.flow.MutableStateFlow | ||
import kotlinx.coroutines.flow.update | ||
import kotlinx.coroutines.launch | ||
import kotlinx.datetime.Instant | ||
|
||
interface ShinComponent : Component { | ||
|
||
val expirationDateTime: Value<Option<Instant>> | ||
|
||
val url: Value<String> | ||
|
||
val shortenedUrl: Value<Option<String>> | ||
|
||
val protocol: Value<ShortenedProtocol> | ||
|
||
fun onExpirationDateTimeChange(expirationDateTime: Instant?) | ||
|
||
fun onUrlChange(url: String) | ||
|
||
fun onProtocolChange(protocol: ShortenedProtocol) | ||
|
||
fun onShortenedUrlReset() | ||
|
||
fun onShorten() | ||
} | ||
|
||
class ShinComponentImpl( | ||
appContext: ShinAppComponentContext, | ||
componentContext: ComponentContext, | ||
) : AbstractComponent(appContext, componentContext), ShinComponent { | ||
|
||
private val httpClient: HttpClient = createHttpClient() | ||
|
||
private val _expirationDateTime = MutableStateFlow<Option<Instant>>(Option.None) | ||
override val expirationDateTime: Value<Option<Instant>> = _expirationDateTime.asValue() | ||
|
||
private val _url = MutableStateFlow("") | ||
override val url: Value<String> = _url.asValue() | ||
|
||
private val _shortenedUrl = MutableStateFlow<Option<String>>(Option.None) | ||
override val shortenedUrl: Value<Option<String>> = _shortenedUrl.asValue() | ||
|
||
private val _protocol = MutableStateFlow(ShortenedProtocol.HTTPS) | ||
override val protocol: Value<ShortenedProtocol> = _protocol.asValue() | ||
|
||
override fun onExpirationDateTimeChange(expirationDateTime: Instant?) { | ||
val updatedValue = Option.fromNullable(expirationDateTime) | ||
_expirationDateTime.update { updatedValue } | ||
} | ||
|
||
override fun onUrlChange(url: String) { | ||
val (updatedUrl, updatedProtocol) = ShortenedProtocol.simplifyInputUrl(url) | ||
updatedProtocol?.let { protocol -> _protocol.update { protocol } } | ||
_url.update { updatedUrl } | ||
} | ||
|
||
override fun onProtocolChange(protocol: ShortenedProtocol) { | ||
_protocol.update { protocol } | ||
} | ||
|
||
override fun onShortenedUrlReset() { | ||
_shortenedUrl.update { Option.None } | ||
} | ||
|
||
override fun onShorten() { | ||
scope.launch { | ||
httpClient.requestShortenedUrl( | ||
url = _url.value, | ||
shortenedProtocol = _protocol.value, | ||
onResponse = { response -> | ||
val some = Some(response) | ||
_shortenedUrl.update { some } | ||
}, | ||
onError = { toast(it) } | ||
) | ||
} | ||
} | ||
} | ||
|
||
private suspend fun HttpClient.requestShortenedUrl( | ||
url: String, | ||
shortenedProtocol: ShortenedProtocol, | ||
onResponse: (String) -> Unit, | ||
onError: (String) -> Unit, | ||
) { | ||
try { | ||
post<Shorten>(Shorten(shortenedProtocol.buildUrl(url))) | ||
.takeIf { it.status == HttpStatusCode.OK } | ||
?.bodyAsText() | ||
?.let(onResponse) | ||
} catch (_: Exception) { | ||
onError("Cannot connect to Shin. Try again later…") | ||
} | ||
} |
2 changes: 1 addition & 1 deletion
2
composeApp/src/commonMain/kotlin/in/procyk/shin/model/ShortenedProtocol.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.