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,115 @@
// 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.auth
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.TimeoutCancellationException
import kotlinx.coroutines.withTimeout
import java.awt.Desktop
import java.io.BufferedReader
import java.io.InputStreamReader
import java.io.PrintWriter
import java.net.InetSocketAddress
import java.net.ServerSocket
import java.net.URI
import java.net.URLEncoder
import kotlin.concurrent.thread
class DesktopOAuthClient : OAuthClient {
override fun redirectUri(): String = "myice://callback"
override suspend fun authenticate(loginUrl: String): String? {
val server = ServerSocket()
server.bind(InetSocketAddress("127.0.0.1", 0))
val serverPort = server.localPort
val realRedirectUri = "http://localhost:$serverPort/callback"
val fullLoginUrl = loginUrl.replace(
"redirect_uri=myice://callback",
"redirect_uri=" + URLEncoder.encode(realRedirectUri, "UTF-8")
)
val deferred = CompletableDeferred<String?>()
thread {
try {
while (true) {
val socket = server.accept()
val reader = BufferedReader(InputStreamReader(socket.getInputStream()))
val writer = PrintWriter(socket.getOutputStream())
val requestLine = reader.readLine() ?: continue
val path = requestLine.split(" ").getOrNull(1) ?: continue
if (path.startsWith("/callback")) {
val html = """
<html><body>
<script>
var hash = window.location.hash.substring(1);
window.location.href = "/token?" + hash;
</script>
</body></html>
""".trimIndent()
writer.print("HTTP/1.1 200 OK\r\n")
writer.print("Content-Type: text/html\r\n")
writer.print("Connection: close\r\n")
writer.print("\r\n")
writer.print(html)
writer.flush()
} else if (path.startsWith("/token")) {
val query = path.substringAfter("?")
val token = parseAccessTokenFromFragment(query)
deferred.complete(token)
writer.print("HTTP/1.1 200 OK\r\n")
writer.print("Content-Type: text/html\r\n")
writer.print("Connection: close\r\n")
writer.print("\r\n")
writer.print("<html><body><h1>Authentication complete. You can close this window.</h1></body></html>")
writer.flush()
socket.close()
break
}
socket.close()
}
} catch (e: Exception) {
deferred.complete(null)
} finally {
server.close()
}
}
try {
Desktop.getDesktop().browse(URI(fullLoginUrl))
} catch (e: Exception) {
server.close()
return null
}
return try {
withTimeout(5 * 60 * 1000L) {
deferred.await()
}
} catch (e: TimeoutCancellationException) {
null
} finally {
server.close()
}
}
}