Skip to content

Commit

Permalink
Merge pull request #38 from inovait/next_update
Browse files Browse the repository at this point in the history
Next update
  • Loading branch information
matejdro committed Jun 26, 2023
2 parents 46f6f37 + bc14a54 commit 7d5794d
Show file tree
Hide file tree
Showing 40 changed files with 237 additions and 52 deletions.
7 changes: 5 additions & 2 deletions buildSrc/src/main/kotlin/multiplatform-module.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,10 @@ kotlin {
}
}
val commonMain by getting
val jvmMain by getting
val jvmCommon by creating
val jvmMain by getting {
dependsOn(jvmCommon)
}
val jvmTest by getting {
dependencies {
implementation(libs.turbine)
Expand All @@ -59,7 +62,7 @@ kotlin {
}
}
val androidMain by getting {
dependsOn(jvmMain)
dependsOn(jvmCommon)
}
val androidUnitTest by getting {
dependsOn(jvmTest)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* Copyright 2023 INOVA IT d.o.o.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy,
* modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
* is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

package si.inova.kotlinova.core.data

import androidx.compose.runtime.StableMarker

/**
* Multiplatform marker that marks this class as Immutable. On some platforms (such as Android's Jetpack Compose)
* this can be used for extra optimizations.
*/
@StableMarker
actual annotation class Immutable()
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ object DefaultAndroidTimeProvider : AndroidTimeProvider {
return System.currentTimeMillis()
}

override fun currentMonotonicTimeMillis(): Long {
return SystemClock.elapsedRealtime()
}

override fun currentLocalDate(): LocalDate {
return LocalDate.now()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ class FakeAndroidTimeProvider(
return currentMilliseconds()
}

override fun currentMonotonicTimeMillis(): Long {
return currentMilliseconds()
}

override fun elapsedRealtimeNanos(): Long {
return currentMilliseconds()
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Copyright 2023 INOVA IT d.o.o.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy,
* modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
* is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

package si.inova.kotlinova.core.data

/**
* Multiplatform marker that marks this class as Immutable. On some platforms (such as Android's Jetpack Compose)
* this can be used for extra optimizations.
*/
expect annotation class Immutable()
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,10 @@ fun <T> Outcome<T>.downgradeTo(
this is Outcome.Progress -> {
if (targetType is Outcome.Progress) {
val combinedProgress = targetType.progress?.let { progress?.times(it) }
val style = if (targetType.style == LoadingStyle.ADDITIONAL_DATA || this.style == LoadingStyle.ADDITIONAL_DATA) {
LoadingStyle.ADDITIONAL_DATA
val style = if (targetType.style != LoadingStyle.NORMAL) {
targetType.style
} else if (this.style != LoadingStyle.NORMAL) {
this.style
} else {
LoadingStyle.NORMAL
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,23 +16,34 @@

package si.inova.kotlinova.core.outcome

import si.inova.kotlinova.core.data.Immutable
import si.inova.kotlinova.core.outcome.Outcome.Error
import si.inova.kotlinova.core.outcome.Outcome.Progress
import si.inova.kotlinova.core.outcome.Outcome.Success

/**
* Standard wrapper for an operation. It can be either [Progress], [Success] or [Error].
*/
@Immutable
sealed class Outcome<out T> {
abstract val data: T?

@Immutable
data class Progress<out T>(
override val data: T? = null,
val progress: Float? = null,
val style: LoadingStyle = LoadingStyle.NORMAL
) : Outcome<T>()

@Immutable
data class Success<out T>(override val data: T) : Outcome<T>()

@Immutable
data class Error<out T>(val exception: CauseException, override val data: T? = null) : Outcome<T>()
}

enum class LoadingStyle {
NORMAL,
ADDITIONAL_DATA
ADDITIONAL_DATA,
USER_REQUESTED_REFRESH
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,17 @@ import java.time.LocalDateTime
import java.time.LocalTime
import java.time.ZoneId
import java.time.ZonedDateTime
import kotlin.time.Duration.Companion.nanoseconds

object DefaultTimeProvider : TimeProvider {
override fun currentTimeMillis(): Long {
return System.currentTimeMillis()
}

override fun currentMonotonicTimeMillis(): Long {
return System.nanoTime().nanoseconds.inWholeMilliseconds
}

override fun currentLocalDate(): LocalDate {
return LocalDate.now()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,18 @@ interface TimeProvider {
*/
fun currentTimeMillis(): Long

/**
* The value returned represents milliseconds since some fixed but arbitrary origin time
* (perhaps in the future, so values may be negative).
*
* This is not related to any other notion of system or wall-clock time,
* meaning the value will not change if user changes system time settings. That's why
* it is recommended to use this to measure elapsed time instead of [currentTimeMillis].
*
* This value should not be stored persistently between reboots / process instances. It should be kept in-memory only.
*/
fun currentMonotonicTimeMillis(): Long

fun currentLocalDate(): LocalDate

fun currentLocalDateTime(): LocalDateTime
Expand Down
24 changes: 24 additions & 0 deletions core/src/jvmMain/kotlin/si/inova/kotlinova/core/data/Immutable.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* Copyright 2023 INOVA IT d.o.o.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy,
* modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
* is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

package si.inova.kotlinova.core.data

/**
* Multiplatform marker that marks this class as Immutable. On some platforms (such as Android's Jetpack Compose)
* this can be used for extra optimizations.
*/
// No-op for JVM
actual annotation class Immutable
Original file line number Diff line number Diff line change
Expand Up @@ -24,41 +24,45 @@ import io.kotest.assertions.errorCollector
import io.kotest.assertions.intellijFormatError
import io.kotest.assertions.print.Printed
import io.kotest.assertions.withClue
import io.kotest.matchers.should
import io.kotest.matchers.shouldBe
import io.kotest.matchers.types.beInstanceOf
import io.kotest.matchers.types.shouldBeInstanceOf
import si.inova.kotlinova.core.outcome.CauseException
import si.inova.kotlinova.core.outcome.Outcome

infix fun <T> Outcome<T>.shouldBeSuccessWithData(expectedData: T) {
assertSoftly {
this
.shouldBeInstanceOf<Outcome.Success<T>>()
.data
.let {
withClue("Outcome's data does not match") {
it.shouldBe(expectedData)
}
}
this should beInstanceOf<Outcome.Success<T>>()

if (this is Outcome.Error) {
errorCollector.collectOrThrow(exception)
if (this is Outcome.Success) {
this
.data
.let {
withClue("Outcome's data does not match") {
it.shouldBe(expectedData)
}
}
} else if (this is Outcome.Error) {
reportContainingError()
}
}
}

infix fun <T> Outcome<T>.shouldBeProgressWithData(expectedData: T?) {
assertSoftly {
this
.shouldBeInstanceOf<Outcome.Progress<T>>()
.data
.let {
withClue("Outcome's data does not match") {
it.shouldBe(expectedData)
}
}
this should beInstanceOf<Outcome.Progress<T>>()

if (this is Outcome.Error) {
errorCollector.collectOrThrow(exception)
if (this is Outcome.Progress) {
this
.data
.let {
withClue("Outcome's data does not match") {
it.shouldBe(expectedData)
}
}
} else if (this is Outcome.Error) {
reportContainingError()
}
}
}
Expand All @@ -68,29 +72,28 @@ fun <T> Outcome<T>.shouldBeProgressWith(
expectedProgress: Float? = null
) {
assertSoftly {
this
.shouldBeInstanceOf<Outcome.Progress<T>>()
.apply {
data
.let {
withClue("Outcome's data does not match") {
it.shouldBe(expectedData)
}
this should beInstanceOf<Outcome.Progress<T>>()

if (this is Outcome.Progress) {
this
.data
.let {
withClue("Outcome's data does not match") {
it.shouldBe(expectedData)
}
}
.apply {
if (expectedProgress != null) {
progress
.let {
withClue("Outcome's progress does not match") {
it.shouldBe(expectedProgress)
}
.apply {
if (expectedProgress != null) {
progress
.let {
withClue("Outcome's progress does not match") {
it.shouldBe(expectedProgress)
}
}
}
}
}
}

if (this is Outcome.Error) {
errorCollector.collectOrThrow(exception)
} else if (this is Outcome.Error) {
reportContainingError()
}
}
}
Expand Down Expand Up @@ -143,3 +146,7 @@ fun <T> Outcome<T>.shouldBeErrorWith(

return returnException
}

private fun Outcome.Error<*>.reportContainingError() {
errorCollector.collectOrThrow(AssertionError("Reported error: $exception", exception))
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ class FakeTimeProvider(
return currentMilliseconds()
}

override fun currentMonotonicTimeMillis(): Long {
return currentMilliseconds()
}

override fun currentLocalDate(): LocalDate {
return currentLocalDate.invoke()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import com.squareup.kotlinpoet.FileSpec
import com.squareup.kotlinpoet.FunSpec
import com.squareup.kotlinpoet.KModifier
import com.squareup.kotlinpoet.ParameterSpec
import com.squareup.kotlinpoet.ParameterizedTypeName
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
import com.squareup.kotlinpoet.STAR
import com.squareup.kotlinpoet.TypeSpec
Expand Down Expand Up @@ -197,13 +198,21 @@ class ScreenInjectionGenerator : CodeGenerator {
}

val screenBindingFunction = if (contributeScreenBindingAnnotation != null) {
val boundType = contributeScreenBindingAnnotation
var boundType = contributeScreenBindingAnnotation
.argumentAt("boundType", 1)
?.value<ClassReference>()
?.asTypeName()
?: clas.getFirstScreenParent()?.asTypeName()
?: error("Invalid @ContributesScreenBinding annotation: $clas does not extend Screen")

if (boundType is ParameterizedTypeName &&
boundType.rawType == SCREEN_BASE_CLASS &&
boundType.typeArguments.firstOrNull() !is ClassName
) {
val screenKey = clas.getScreenKeyIfItExists() ?: error("Unknown screen key for $clas")
boundType = SCREEN_BASE_CLASS.parameterizedBy(screenKey.asTypeName())
}

val returnType = SCREEN_FACTORY.parameterizedBy(boundType)
FunSpec.builder("bindsScreenFactoryToParentType")
.returns(returnType)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,13 @@ abstract class FragmentScreen<K>(

DisposableEffect(key, fragmentViewId) {
val fragmentManager = activity.supportFragmentManager
if (fragmentManager.isStateSaved) {
// Fragment manager's state has already been saved. If we do anything, we will crash
// Exit here, activity is likely being closed anyway, so Fragment will re-appear
// on next open
return@DisposableEffect onDispose { }
}

var currentFragment = fragmentManager.findFragmentByTag(key.tag)

if (currentFragment == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ data class OpenScreen(val screen: ScreenKey) : NavigationInstruction() {
if (backstack.lastOrNull() == screen) {
error(
"Cannot add $screen to the backstack twice back to back. If you want same screen on the" +
" backstack twice, add a random identifier to the key, such as an UUID."
" backstack twice, add a random identifier to the key, such as an UUID. Current backstack $backstack"
)
}
NavigationResult(backstack + screen, StateChange.FORWARD)
Expand Down
Loading

0 comments on commit 7d5794d

Please sign in to comment.