diff --git a/build.gradle.kts b/build.gradle.kts
index ff2121f1..803258cd 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -7,6 +7,7 @@ buildscript {
repositories {
google()
mavenCentral()
+ gradlePluginPortal()
}
dependencies {
@@ -15,6 +16,7 @@ buildscript {
classpath(libs.com.android.tools.build.gradle)
classpath(libs.org.jetbrains.kotlin.serialization)
classpath(libs.com.squareup.sqldelight.gradle.plugin)
+ classpath(libs.dev.icerock.moko.resources.generator)
}
}
diff --git a/data/network/build.gradle.kts b/data/network/build.gradle.kts
index 45e1a8da..5e653c88 100644
--- a/data/network/build.gradle.kts
+++ b/data/network/build.gradle.kts
@@ -3,6 +3,7 @@ plugins {
id("com.android.library")
kotlin("plugin.serialization")
id("com.diffplug.spotless") version "6.11.0"
+ id("dev.icerock.mobile.multiplatform-resources")
}
spotless {
@@ -55,6 +56,7 @@ kotlin {
implementation(libs.io.ktor.serialization.kotlinx.json)
implementation(libs.io.ktor.client.content.negotiation)
implementation(libs.org.jetbrains.kotlinx.serialization.json)
+ implementation(libs.dev.icerock.moko.resources.common)
}
}
@@ -115,7 +117,13 @@ kotlin {
implementation(libs.org.jetbrains.kotlin.test.common)
implementation(libs.org.jetbrains.kotlin.test.annotations.common)
implementation(libs.org.jetbrains.kotlinx.coroutines.test)
+ implementation(libs.dev.icerock.moko.resources.test)
}
}
}
}
+
+multiplatformResources {
+ multiplatformResourcesPackage = "social.androiddev.common.network" // required
+ multiplatformResourcesSourceSet = "commonTest" // optional, default "commonMain"
+}
\ No newline at end of file
diff --git a/data/network/src/commonMain/kotlin/social/androiddev/common/network/MastodonApiImpl.kt b/data/network/src/commonMain/kotlin/social/androiddev/common/network/MastodonApiImpl.kt
index 8771cd2f..a87cd657 100644
--- a/data/network/src/commonMain/kotlin/social/androiddev/common/network/MastodonApiImpl.kt
+++ b/data/network/src/commonMain/kotlin/social/androiddev/common/network/MastodonApiImpl.kt
@@ -13,6 +13,7 @@ import io.ktor.client.HttpClient
import io.ktor.client.call.body
import io.ktor.client.plugins.ResponseException
import io.ktor.client.request.get
+import io.ktor.serialization.*
import kotlinx.serialization.SerializationException
import social.androiddev.common.network.model.Instance
@@ -33,6 +34,8 @@ class MastodonApiImpl(
Result.failure(exception = exception)
} catch (exception: ResponseException) {
Result.failure(exception = exception)
+ } catch (exception: JsonConvertException) {
+ Result.failure(exception = exception)
}
}
}
diff --git a/data/network/src/commonTest/resources/response_account_required.json b/data/network/src/commonTest/resources/MR/files/response_account_required.json
similarity index 100%
rename from data/network/src/commonTest/resources/response_account_required.json
rename to data/network/src/commonTest/resources/MR/files/response_account_required.json
diff --git a/data/network/src/commonTest/resources/response_context_required.json b/data/network/src/commonTest/resources/MR/files/response_context_required.json
similarity index 100%
rename from data/network/src/commonTest/resources/response_context_required.json
rename to data/network/src/commonTest/resources/MR/files/response_context_required.json
diff --git a/data/network/src/commonTest/resources/response_conversation_required.json b/data/network/src/commonTest/resources/MR/files/response_conversation_required.json
similarity index 100%
rename from data/network/src/commonTest/resources/response_conversation_required.json
rename to data/network/src/commonTest/resources/MR/files/response_conversation_required.json
diff --git a/data/network/src/commonTest/resources/response_instance_invalid.json b/data/network/src/commonTest/resources/MR/files/response_instance_invalid.json
similarity index 100%
rename from data/network/src/commonTest/resources/response_instance_invalid.json
rename to data/network/src/commonTest/resources/MR/files/response_instance_invalid.json
diff --git a/data/network/src/commonTest/resources/response_instance_valid.json b/data/network/src/commonTest/resources/MR/files/response_instance_valid.json
similarity index 100%
rename from data/network/src/commonTest/resources/response_instance_valid.json
rename to data/network/src/commonTest/resources/MR/files/response_instance_valid.json
diff --git a/data/network/src/commonTest/resources/response_notification_required.json b/data/network/src/commonTest/resources/MR/files/response_notification_required.json
similarity index 100%
rename from data/network/src/commonTest/resources/response_notification_required.json
rename to data/network/src/commonTest/resources/MR/files/response_notification_required.json
diff --git a/data/network/src/commonTest/resources/response_status_required.json b/data/network/src/commonTest/resources/MR/files/response_status_required.json
similarity index 100%
rename from data/network/src/commonTest/resources/response_status_required.json
rename to data/network/src/commonTest/resources/MR/files/response_status_required.json
diff --git a/data/network/src/commonTest/kotlin/social/androiddev/common/network/MastodonApiTests.kt b/data/network/src/desktopTest/kotlin/social/androiddev/common/network/MastodonApiTests.kt
similarity index 70%
rename from data/network/src/commonTest/kotlin/social/androiddev/common/network/MastodonApiTests.kt
rename to data/network/src/desktopTest/kotlin/social/androiddev/common/network/MastodonApiTests.kt
index 577fb572..297caae2 100644
--- a/data/network/src/commonTest/kotlin/social/androiddev/common/network/MastodonApiTests.kt
+++ b/data/network/src/desktopTest/kotlin/social/androiddev/common/network/MastodonApiTests.kt
@@ -9,38 +9,34 @@
*/
package social.androiddev.common.network
-import io.ktor.client.HttpClient
-import io.ktor.client.engine.mock.MockEngine
-import io.ktor.client.engine.mock.respond
-import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
-import io.ktor.http.ContentType
-import io.ktor.http.HttpHeaders
-import io.ktor.http.HttpStatusCode
-import io.ktor.http.headersOf
-import io.ktor.serialization.kotlinx.json.json
-import io.ktor.utils.io.ByteReadChannel
+import io.ktor.client.*
+import io.ktor.client.engine.mock.*
+import io.ktor.client.plugins.contentnegotiation.*
+import io.ktor.http.*
+import io.ktor.serialization.kotlinx.json.*
+import io.ktor.utils.io.*
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import kotlinx.serialization.json.Json
-import kotlin.test.Ignore
-import kotlin.test.Test
-import kotlin.test.assertFalse
-import kotlin.test.assertNotNull
-import kotlin.test.assertNull
-import kotlin.test.assertTrue
+import kotlin.test.*
+/**
+ * I was unable to read a .json file from commonTest, and so API tests must be done from
+ * desktopTest, androidTest, and/or iosTest.
+ */
@OptIn(ExperimentalCoroutinesApi::class)
class MastodonApiTests {
- // TODO: fix loading json from resources
+
@Test
- @Ignore
fun `Instance request should fail with invalid response`() = runTest {
// given
- // val content: String = javaClass.classLoader.getResource("response_instance_invalid.json").readText()
- val content: String = ""
+
+ // given
+ val content = MR.files.response_account_required.readText()
val mastodonApi = MastodonApiImpl(
httpClient = createMockClient(
- statusCode = HttpStatusCode.Unauthorized, content = ByteReadChannel(text = content)
+ statusCode = HttpStatusCode.Unauthorized,
+ content = ByteReadChannel(text = content)
)
)
@@ -52,14 +48,10 @@ class MastodonApiTests {
assertNull(actual = result.getOrNull()?.uri)
}
- // TODO: fix loading json from resources
@Test
- @Ignore
fun `Instance request should succeed with required field response`() = runTest {
// given
- // val content: String = javaClass.classLoader.getResource("response_instance_valid.json").readText()
- val content: String = ""
-
+ val content = MR.files.response_instance_valid.readText()
val mastodonApi = MastodonApiImpl(
httpClient = createMockClient(
statusCode = HttpStatusCode.Unauthorized, content = ByteReadChannel(text = content)
@@ -72,6 +64,7 @@ class MastodonApiTests {
// then
assertTrue(actual = result.isSuccess)
assertNotNull(actual = result.getOrNull()?.uri)
+
}
private fun createMockClient(
@@ -97,4 +90,5 @@ class MastodonApiTests {
}
}
}
+
}
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 1fb1dffc..0eb52de3 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -14,6 +14,7 @@ androidx-appcompat = "1.5.1"
androidx-activity = "1.6.1"
androidx-compose-foundation = "1.2.1"
com-arkivanov-decompose = "1.0.0-alpha-05"
+dev-icerock-moko-resources = "0.20.1"
[libraries]
@@ -23,6 +24,7 @@ org-jetbrains-kotlin-gradle-plugin = { module = "org.jetbrains.kotlin:kotlin-gra
com-android-tools-build-gradle = { module = "com.android.tools.build:gradle", version.ref = "com-android-tools-build" }
org-jetbrains-kotlin-serialization = { module = "org.jetbrains.kotlin:kotlin-serialization", version.ref = "org-jetbrains-kotlin" }
com-squareup-sqldelight-gradle-plugin = { module = "com.squareup.sqldelight:gradle-plugin", version.ref = "com-squareup-sqldelight" }
+dev-icerock-moko-resources-generator = { module = "dev.icerock.moko:resources-generator", version.ref = "dev-icerock-moko-resources"}
# libraries
io-ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "io-ktor" }
@@ -41,6 +43,10 @@ org-jetbrains-kotlin-test-junit = { module = "org.jetbrains.kotlin:kotlin-test-j
org-jetbrains-kotlin-test-common = { module = "org.jetbrains.kotlin:kotlin-test-common", version.ref = "org-jetbrains-kotlin" }
org-jetbrains-kotlin-test-annotations-common = { module = "org.jetbrains.kotlin:kotlin-test-annotations-common", version.ref = "org-jetbrains-kotlin" }
+dev-icerock-moko-resources-common = { module = "dev.icerock.moko:resources", version.ref = "dev-icerock-moko-resources"}
+dev-icerock-moko-resources-jvm = { module = "dev.icerock.moko:resources-compose", version.ref = "dev-icerock-moko-resources"}
+dev-icerock-moko-resources-test = { module = "dev.icerock.moko:resources-test", version.ref = "dev-icerock-moko-resources"}
+
com-squareup-sqldelight-android-driver = { module = "com.squareup.sqldelight:android-driver", version.ref = "com-squareup-sqldelight" }
com-squareup-sqldelight-native-driver = { module = "com.squareup.sqldelight:native-driver", version.ref = "com-squareup-sqldelight" }
com-squareup-sqldelight-sqlite-driver = { module = "com.squareup.sqldelight:sqlite-driver", version.ref = "com-squareup-sqldelight" }
diff --git a/ui/common/build.gradle.kts b/ui/common/build.gradle.kts
index 98cec28c..f3322279 100644
--- a/ui/common/build.gradle.kts
+++ b/ui/common/build.gradle.kts
@@ -3,6 +3,7 @@ plugins {
id("org.jetbrains.compose")
id("com.android.library")
id("com.diffplug.spotless") version "6.11.0"
+ id("dev.icerock.mobile.multiplatform-resources")
}
spotless {
kotlin {
@@ -51,6 +52,7 @@ kotlin {
implementation(compose.material)
implementation(libs.com.arkivanov.decompose)
implementation(libs.com.arkivanov.decompose.extensions.compose.jetbrains)
+ implementation(libs.dev.icerock.moko.resources.common)
}
}
@@ -58,12 +60,23 @@ kotlin {
dependencies {
// Workaround for https://github.com/JetBrains/compose-jb/issues/2340
implementation(libs.androidx.compose.foundation)
+ implementation(libs.dev.icerock.moko.resources.jvm)
}
}
named("desktopMain") {
dependencies {
implementation(compose.desktop.common)
+ implementation(libs.dev.icerock.moko.resources.jvm)
+ }
+ }
+
+ //Testing
+ named("commonTest") {
+ dependencies {
+ implementation(kotlin("test"))
+ implementation(libs.org.jetbrains.kotlin.test.common)
+ implementation(libs.org.jetbrains.kotlin.test.annotations.common)
}
}
}
@@ -72,3 +85,11 @@ kotlin {
kotlinOptions.jvmTarget = "11"
}
}
+
+multiplatformResources {
+ multiplatformResourcesPackage = "social.androiddev.common" // required
+// multiplatformResourcesClassName = "SharedRes" // optional, default MR
+// multiplatformResourcesVisibility = dev.icerock.gradle.MRVisibility.Public // optional, default Public
+// iosBaseLocalizationRegion = "en" // optional, default "en"
+// multiplatformResourcesSourceSet = "commonMain" // optional, default "commonMain"
+}
\ No newline at end of file
diff --git a/ui/common/src/commonMain/kotlin/social/androiddev/common/sharedres/SharedResources.kt b/ui/common/src/commonMain/kotlin/social/androiddev/common/sharedres/SharedResources.kt
new file mode 100644
index 00000000..745486b0
--- /dev/null
+++ b/ui/common/src/commonMain/kotlin/social/androiddev/common/sharedres/SharedResources.kt
@@ -0,0 +1,46 @@
+package social.androiddev.common.sharedres
+
+import dev.icerock.moko.graphics.Color
+import dev.icerock.moko.resources.ImageResource
+import dev.icerock.moko.resources.desc.Resource
+import dev.icerock.moko.resources.desc.StringDesc
+import dev.icerock.moko.resources.getImageByFileName
+import social.androiddev.common.MR
+
+object SharedResources {
+
+ /**
+ * A String that can be localised.
+ * On Android, call `toString(context)`, on iOS, call `localized()`
+ *
+ * @see https://github.com/icerockdev/moko-resources#example-1---simple-localization-string
+ */
+ fun getLocalisedHello(): StringDesc {
+
+ return StringDesc.Resource(MR.strings.my_string)
+ }
+
+ /**
+ * Get an image resource by the file name, and falls back to mastodon logo if it fails
+ *
+ * @see https://github.com/icerockdev/moko-resources#example-7---pass-image
+ */
+ fun getImageByFileName(name: String): ImageResource {
+ val fallbackImage = MR.images.mastodon_logo
+ return MR.images.getImageByFileName(name) ?: fallbackImage
+ }
+
+ /**
+ * Android: val color: Color = MR.colors.valueColor.getColor(context = this)
+ *
+ * iOS:val color: UIColor = MR.colors.valueColor.getColor(UIScreen.main.traitCollection.userInterfaceStyle)
+ *
+ * @see https://github.com/icerockdev/moko-resources#example-9---pass-colors
+ */
+ fun getValueColor(): Color {
+ return MR.colors.valueColor.color
+ }
+
+
+
+}
\ No newline at end of file
diff --git a/ui/common/src/commonMain/resources/MR/base/strings.xml b/ui/common/src/commonMain/resources/MR/base/strings.xml
new file mode 100644
index 00000000..74d2165c
--- /dev/null
+++ b/ui/common/src/commonMain/resources/MR/base/strings.xml
@@ -0,0 +1,4 @@
+
+
+ My default localization string
+
\ No newline at end of file
diff --git a/ui/common/src/commonMain/resources/MR/colors/colors.xml b/ui/common/src/commonMain/resources/MR/colors/colors.xml
new file mode 100644
index 00000000..cc914249
--- /dev/null
+++ b/ui/common/src/commonMain/resources/MR/colors/colors.xml
@@ -0,0 +1,14 @@
+
+
+
+ #B02743FF
+ @color/valueColor
+
+ 0xB92743FF
+ 7CCFEEFF
+
+
+ @color/valueColor
+ @color/referenceColor
+
+
\ No newline at end of file
diff --git a/ui/common/src/commonMain/resources/MR/images/mastodon_logo@1.5x.png b/ui/common/src/commonMain/resources/MR/images/mastodon_logo@1.5x.png
new file mode 100644
index 00000000..b6fe79b1
Binary files /dev/null and b/ui/common/src/commonMain/resources/MR/images/mastodon_logo@1.5x.png differ
diff --git a/ui/common/src/commonMain/resources/MR/images/mastodon_logo@1x.png b/ui/common/src/commonMain/resources/MR/images/mastodon_logo@1x.png
new file mode 100644
index 00000000..d5651c72
Binary files /dev/null and b/ui/common/src/commonMain/resources/MR/images/mastodon_logo@1x.png differ
diff --git a/ui/common/src/commonMain/resources/MR/images/mastodon_logo@2x.png b/ui/common/src/commonMain/resources/MR/images/mastodon_logo@2x.png
new file mode 100644
index 00000000..b6af9e9f
Binary files /dev/null and b/ui/common/src/commonMain/resources/MR/images/mastodon_logo@2x.png differ
diff --git a/ui/common/src/commonMain/resources/MR/images/mastodon_logo@3x.png b/ui/common/src/commonMain/resources/MR/images/mastodon_logo@3x.png
new file mode 100644
index 00000000..d29cd0dc
Binary files /dev/null and b/ui/common/src/commonMain/resources/MR/images/mastodon_logo@3x.png differ
diff --git a/ui/common/src/commonMain/resources/MR/images/mastodon_logo@4x.png b/ui/common/src/commonMain/resources/MR/images/mastodon_logo@4x.png
new file mode 100644
index 00000000..bb6dd7de
Binary files /dev/null and b/ui/common/src/commonMain/resources/MR/images/mastodon_logo@4x.png differ
diff --git a/ui/common/src/commonMain/resources/MR/ru/strings.xml b/ui/common/src/commonMain/resources/MR/ru/strings.xml
new file mode 100644
index 00000000..bac38a5a
--- /dev/null
+++ b/ui/common/src/commonMain/resources/MR/ru/strings.xml
@@ -0,0 +1,4 @@
+
+
+ Моя строка локализации по умолчанию
+
\ No newline at end of file
diff --git a/ui/common/src/commonTest/kotlin/social/androiddev/common/sharedres/SharedResourcesTest.kt b/ui/common/src/commonTest/kotlin/social/androiddev/common/sharedres/SharedResourcesTest.kt
new file mode 100644
index 00000000..eb6bedba
--- /dev/null
+++ b/ui/common/src/commonTest/kotlin/social/androiddev/common/sharedres/SharedResourcesTest.kt
@@ -0,0 +1,38 @@
+package social.androiddev.common.sharedres
+
+import dev.icerock.moko.graphics.Color
+import kotlin.test.assertEquals
+import kotlin.test.Test
+
+/**
+ * These tests aren't ideal, but they show that there's some file access going on
+ */
+class SharedResourcesTest {
+
+ @Test
+ fun `getLocalisedHello returns a stringresource with unknown content`() {
+
+ // This test only confirms that the resource is there. To get the content, it must run on androidTest, desktopTest
+ assertEquals(
+ "ResourceStringDesc(stringRes=StringResource(resourceId=2132017188))",
+ SharedResources.getLocalisedHello().toString()
+ )
+
+ }
+
+ @Test
+ fun `getting empty image still returns a fallback image`() {
+ assertEquals(
+ "dev.icerock.moko.resources.ImageResource@821330f",
+ SharedResources.getImageByFileName("").toString()
+ )
+ }
+
+ @Test
+ fun `getValueColor returns a specific Color class`() {
+ assertEquals(
+ Color(red=176, green=39, blue=67, alpha=255),
+ SharedResources.getValueColor()
+ )
+ }
+}
\ No newline at end of file