ref: Rename package ch.parano.myice to ch.parano.myicek

Complete the MyIceK rename by updating the Kotlin package from ch.parano.myice to ch.parano.myicek across all source sets (androidMain, commonMain, desktopMain, iosMain, commonTest), the build.gradle.kts (namespace, applicationId, mainClass, desktop packageName) and the iOS Info.plist bundle URL name. The myice:// OAuth scheme is kept unchanged as it depends on the backend.

Also add pull-to-refresh to ScheduleScreen using Material 3 PullToRefreshBox, replacing the manual loading overlay.
This commit is contained in:
2026-08-06 15:20:47 +02:00
parent a2d5a57aec
commit 418a8978f2
52 changed files with 137 additions and 142 deletions
@@ -0,0 +1,135 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// MyIce Kotlin Multiplatform — schedule/convocation viewer
// Copyright (C) 2026 parano.ch
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package ch.parano.myicek.network
import ch.parano.myicek.models.EventDetail
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.HttpHeaders
import io.ktor.http.HttpStatusCode
import io.ktor.http.headersOf
import io.ktor.serialization.kotlinx.json.json
import kotlinx.coroutines.test.runTest
import kotlinx.serialization.json.Json
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNull
class ApiServiceTest {
private val json = Json { ignoreUnknownKeys = true }
private fun mockClient(respondJson: String, requestUrl: String, expectedAuth: String? = "Bearer test-token"): HttpClient {
val engine = MockEngine { request ->
val actualUrl = if (request.url.encodedQuery.isEmpty()) {
request.url.encodedPath
} else {
request.url.encodedPath + "?" + request.url.encodedQuery
}
assertEquals(requestUrl, actualUrl)
if (expectedAuth != null) {
assertEquals(expectedAuth, request.headers["Authorization"])
}
respond(
content = respondJson,
status = HttpStatusCode.OK,
headers = headersOf(HttpHeaders.ContentType, "application/json"),
)
}
return HttpClient(engine) {
install(ContentNegotiation) { json(json) }
}
}
@Test
fun testGetUserInfo() = runTest {
AuthTokenHolder.token = "test-token"
val client = mockClient(
"""{"email":"rene@luria.ch","sub":"abc"}""",
"/userinfo",
)
val api = ApiService(client)
val result = api.getUserInfo()
assertEquals("rene@luria.ch", result.email)
}
@Test
fun testGetAccounts() = runTest {
AuthTokenHolder.token = "test-token"
val client = mockClient(
"""[{"name":"default","label":"Default"},{"name":"leonard","label":"Leonard"}]""",
"/accounts",
)
val api = ApiService(client)
val result = api.getAccounts()
assertEquals(2, result.size)
assertEquals("default", result[0].name)
assertEquals("Leonard", result[1].label)
}
@Test
fun testGetSchedule() = runTest {
AuthTokenHolder.token = "test-token"
val responseJson = """
[{"id_event":"761040","agegroup":"U13 (Elite)","name":"U13 Elite - HC Ajoie","title":"U13 (Elite)\nGame\nArena","opponent":"HC Ajoie","place":"Arena","start":"2024-11-10T14:00:00","end":"2024-11-10T16:15:00","color":"#e4222e","event":"Jeu"}]
""".trimIndent()
val client = mockClient(responseJson, "/schedule?account=default")
val api = ApiService(client)
val result = api.getSchedule("default")
assertEquals(1, result.size)
assertEquals("761040", result[0].idEvent)
assertEquals("Jeu", result[0].eventType)
}
@Test
fun testGetGameDetail() = runTest {
AuthTokenHolder.token = "test-token"
val responseJson = """
{"title":"U13 - Game","type":"games","place":"Arena","time_start":"2024-11-10T14:00:00","time_end":"2024-11-10T16:15:00","convocation":{"available":[{"position":"Gardien","number":1,"fname":"Jean","lname":"Dupont","dob":"2012-03-15"}],"staff":[{"role":"Coach","fname":"Marc","lname":"Blanc"}]}}
""".trimIndent()
val client = mockClient(responseJson, "/game/761040?account=default")
val api = ApiService(client)
val result: EventDetail = api.getGameDetail("761040", "default")
assertEquals("U13 - Game", result.title)
assertEquals("games", result.type)
assertEquals(1, result.convocation.available.size)
assertEquals("1", result.convocation.available[0].number)
assertEquals(1, result.convocation.staff.size)
}
@Test
fun testNoAuthTokenSendsNoAuthHeader() = runTest {
AuthTokenHolder.token = null
val engine = MockEngine { request ->
assertNull(request.headers["Authorization"])
respond(
content = """{"email":""}""",
status = HttpStatusCode.OK,
headers = headersOf(HttpHeaders.ContentType, "application/json"),
)
}
val client = HttpClient(engine) {
install(ContentNegotiation) { json(json) }
}
val api = ApiService(client)
val result = api.getUserInfo()
assertEquals("", result.email)
}
}