Compare commits
9
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
efaa93d907
|
||
|
|
418a8978f2
|
||
|
|
a2d5a57aec
|
||
|
|
ea15c0423c
|
||
|
|
3dc5890a7a
|
||
|
|
fd8fcb9b4c
|
||
|
|
1e5c2fd864
|
||
|
|
9524bc93ca
|
||
|
|
1795337d01 |
@@ -3,7 +3,6 @@
|
||||
## Project
|
||||
|
||||
MyIce — Kotlin Multiplatform ice hockey schedule/convocation viewer (Android, iOS, Desktop).
|
||||
Port of the Flutter app at `~/work/gitea.parano.ch/herel/myice/myice_mobile/`.
|
||||
|
||||
## Build Commands
|
||||
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
.PHONY: android desktop android-usb android-emulator
|
||||
|
||||
ADB := $(shell command -v adb 2>/dev/null || echo "$(HOME)/Library/Android/sdk/platform-tools/adb")
|
||||
APK := composeApp/build/outputs/apk/debug/composeApp-debug.apk
|
||||
|
||||
android:
|
||||
./gradlew composeApp:assembleDebug
|
||||
|
||||
desktop:
|
||||
./gradlew composeApp:run
|
||||
|
||||
android-usb: android
|
||||
$(ADB) -d install -r $(APK)
|
||||
|
||||
android-emulator: android
|
||||
$(ADB) -e install -r $(APK)
|
||||
@@ -1,7 +1,7 @@
|
||||
# MyIce Kotlin Multiplatform
|
||||
|
||||
Ice hockey schedule and convocation viewer for Android, iOS, and Desktop (macOS/Windows/Linux).
|
||||
Port of the [Flutter app](https://gitea.parano.ch/herel/myice/myice_mobile) to Kotlin Multiplatform with Compose Multiplatform.
|
||||
Kotlin Multiplatform with Compose Multiplatform.
|
||||
|
||||
## Features
|
||||
|
||||
|
||||
@@ -83,11 +83,11 @@ kotlin {
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "ch.parano.myice"
|
||||
namespace = "ch.parano.myicek"
|
||||
compileSdk = 36
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "ch.parano.myice"
|
||||
applicationId = "ch.parano.myicek"
|
||||
minSdk = 24
|
||||
targetSdk = 35
|
||||
versionCode = 1
|
||||
@@ -118,10 +118,10 @@ android {
|
||||
|
||||
compose.desktop {
|
||||
application {
|
||||
mainClass = "ch.parano.myice.MainKt"
|
||||
mainClass = "ch.parano.myicek.MainKt"
|
||||
nativeDistributions {
|
||||
targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb)
|
||||
packageName = "myice"
|
||||
packageName = "myicek"
|
||||
packageVersion = "1.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,9 +4,10 @@
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
||||
<application
|
||||
android:label="MyIce"
|
||||
android:label="MyIceK"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:supportsRtl="true"
|
||||
android:enableOnBackInvokedCallback="true"
|
||||
android:theme="@android:style/Theme.Material.Light.NoActionBar">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
|
||||
+2
-2
@@ -15,13 +15,13 @@
|
||||
// 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.myice
|
||||
package ch.parano.myicek
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import ch.parano.myice.auth.AndroidOAuthClient
|
||||
import ch.parano.myicek.auth.AndroidOAuthClient
|
||||
|
||||
class MainActivity : ComponentActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
+2
-2
@@ -15,12 +15,12 @@
|
||||
// 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.myice
|
||||
package ch.parano.myicek
|
||||
|
||||
import android.app.Activity
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import ch.parano.myice.auth.OAuthCallbackHolder
|
||||
import ch.parano.myicek.auth.OAuthCallbackHolder
|
||||
|
||||
class OAuthCallbackActivity : Activity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
// 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.myice.auth
|
||||
package ch.parano.myicek.auth
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
// 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.myice.network
|
||||
package ch.parano.myicek.network
|
||||
|
||||
import io.ktor.client.engine.HttpClientEngineConfig
|
||||
import io.ktor.client.engine.HttpClientEngineFactory
|
||||
@@ -0,0 +1,26 @@
|
||||
// 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.ui
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.runtime.Composable
|
||||
|
||||
@Composable
|
||||
actual fun PlatformBackHandler(enabled: Boolean, onBack: () -> Unit) {
|
||||
BackHandler(enabled = enabled, onBack = onBack)
|
||||
}
|
||||
+25
-17
@@ -15,7 +15,7 @@
|
||||
// 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.myice
|
||||
package ch.parano.myicek
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
@@ -24,25 +24,31 @@ import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import ch.parano.myice.auth.AuthSettings
|
||||
import ch.parano.myice.auth.OAuthClient
|
||||
import ch.parano.myice.cache.ScheduleCache
|
||||
import ch.parano.myice.network.ApiService
|
||||
import ch.parano.myice.network.createHttpClient
|
||||
import ch.parano.myice.ui.screens.EventDetailScreen
|
||||
import ch.parano.myice.ui.screens.LoginScreen
|
||||
import ch.parano.myice.ui.screens.ScheduleScreen
|
||||
import ch.parano.myice.ui.theme.MyIceTheme
|
||||
import ch.parano.myice.viewmodel.AuthState
|
||||
import ch.parano.myice.viewmodel.AuthViewModel
|
||||
import ch.parano.myice.viewmodel.EventDetailViewModel
|
||||
import ch.parano.myice.viewmodel.ScheduleViewModel
|
||||
import ch.parano.myicek.auth.AuthSettings
|
||||
import ch.parano.myicek.auth.OAuthClient
|
||||
import ch.parano.myicek.cache.ScheduleCache
|
||||
import ch.parano.myicek.network.ApiService
|
||||
import ch.parano.myicek.network.createHttpClient
|
||||
import ch.parano.myicek.ui.screens.EventDetailScreen
|
||||
import ch.parano.myicek.ui.screens.LoginScreen
|
||||
import ch.parano.myicek.ui.screens.ScheduleScreen
|
||||
import ch.parano.myicek.ui.theme.MyIceTheme
|
||||
import ch.parano.myicek.viewmodel.AuthState
|
||||
import ch.parano.myicek.viewmodel.AuthViewModel
|
||||
import ch.parano.myicek.viewmodel.EventDetailViewModel
|
||||
import ch.parano.myicek.viewmodel.ScheduleViewModel
|
||||
import com.russhwolf.settings.Settings
|
||||
|
||||
sealed class Screen {
|
||||
data object Login : Screen()
|
||||
data object Schedule : Screen()
|
||||
data class EventDetail(val gameId: String, val account: String, val eventTitle: String) : Screen()
|
||||
data class EventDetail(
|
||||
val gameId: String,
|
||||
val account: String,
|
||||
val eventTitle: String,
|
||||
val eventStart: String,
|
||||
val eventEnd: String,
|
||||
) : Screen()
|
||||
}
|
||||
|
||||
@Composable
|
||||
@@ -82,14 +88,16 @@ fun App(
|
||||
Screen.Schedule -> ScheduleScreen(
|
||||
scheduleViewModel = scheduleViewModel,
|
||||
authViewModel = authViewModel,
|
||||
onEventClick = { gameId, account, eventTitle ->
|
||||
currentScreen = Screen.EventDetail(gameId, account, eventTitle)
|
||||
onEventClick = { gameId, account, eventTitle, eventStart, eventEnd ->
|
||||
currentScreen = Screen.EventDetail(gameId, account, eventTitle, eventStart, eventEnd)
|
||||
},
|
||||
)
|
||||
is Screen.EventDetail -> EventDetailScreen(
|
||||
gameId = screen.gameId,
|
||||
account = screen.account,
|
||||
eventTitle = screen.eventTitle,
|
||||
eventStart = screen.eventStart,
|
||||
eventEnd = screen.eventEnd,
|
||||
viewModel = eventDetailViewModel,
|
||||
onBack = { currentScreen = Screen.Schedule },
|
||||
)
|
||||
+2
-2
@@ -15,9 +15,9 @@
|
||||
// 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.myice.auth
|
||||
package ch.parano.myicek.auth
|
||||
|
||||
import ch.parano.myice.network.AuthTokenHolder
|
||||
import ch.parano.myicek.network.AuthTokenHolder
|
||||
import com.russhwolf.settings.Settings
|
||||
|
||||
class AuthSettings(private val settings: Settings) {
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
// 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.myice.auth
|
||||
package ch.parano.myicek.auth
|
||||
|
||||
interface OAuthClient {
|
||||
fun redirectUri(): String
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
// 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.myice.auth
|
||||
package ch.parano.myicek.auth
|
||||
|
||||
internal fun parseAccessTokenFromFragment(fragment: String): String? {
|
||||
val params = fragment.split("&").associate { pair ->
|
||||
+2
-2
@@ -15,9 +15,9 @@
|
||||
// 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.myice.cache
|
||||
package ch.parano.myicek.cache
|
||||
|
||||
import ch.parano.myice.models.Event
|
||||
import ch.parano.myicek.models.Event
|
||||
import com.russhwolf.settings.Settings
|
||||
import kotlin.time.Clock
|
||||
import kotlin.time.ExperimentalTime
|
||||
+2
-2
@@ -15,9 +15,9 @@
|
||||
// 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.myice.filter
|
||||
package ch.parano.myicek.filter
|
||||
|
||||
import ch.parano.myice.models.Event
|
||||
import ch.parano.myicek.models.Event
|
||||
|
||||
fun extractSubgroup(name: String): String {
|
||||
val parts = name.split(" - ")
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
// 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.myice.filter
|
||||
package ch.parano.myicek.filter
|
||||
|
||||
enum class TypeFilter {
|
||||
ALL, GAMES, PRACTICES;
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
// 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.myice.models
|
||||
package ch.parano.myicek.models
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
// 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.myice.models
|
||||
package ch.parano.myicek.models
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
// 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.myice.models
|
||||
package ch.parano.myicek.models
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
// 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.myice.models
|
||||
package ch.parano.myicek.models
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
// 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.myice.models
|
||||
package ch.parano.myicek.models
|
||||
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.SerializationException
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
// 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.myice.models
|
||||
package ch.parano.myicek.models
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
// 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.myice.models
|
||||
package ch.parano.myicek.models
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
// 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.myice.models
|
||||
package ch.parano.myicek.models
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
// 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.myice.network
|
||||
package ch.parano.myicek.network
|
||||
|
||||
object ApiConfig {
|
||||
const val baseUrl = "https://myice.parano.ch"
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
// 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.myice.network
|
||||
package ch.parano.myicek.network
|
||||
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.engine.HttpClientEngineConfig
|
||||
+5
-5
@@ -15,12 +15,12 @@
|
||||
// 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.myice.network
|
||||
package ch.parano.myicek.network
|
||||
|
||||
import ch.parano.myice.models.Account
|
||||
import ch.parano.myice.models.Event
|
||||
import ch.parano.myice.models.EventDetail
|
||||
import ch.parano.myice.models.UserInfo
|
||||
import ch.parano.myicek.models.Account
|
||||
import ch.parano.myicek.models.Event
|
||||
import ch.parano.myicek.models.EventDetail
|
||||
import ch.parano.myicek.models.UserInfo
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.call.body
|
||||
import io.ktor.client.request.get
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
// 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.myice.network
|
||||
package ch.parano.myicek.network
|
||||
|
||||
object AuthTokenHolder {
|
||||
var token: String? = null
|
||||
@@ -0,0 +1,23 @@
|
||||
// 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.ui
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
|
||||
@Composable
|
||||
expect fun PlatformBackHandler(enabled: Boolean = true, onBack: () -> Unit)
|
||||
+4
-4
@@ -15,7 +15,7 @@
|
||||
// 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.myice.ui.components
|
||||
package ch.parano.myicek.ui.components
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
@@ -33,8 +33,8 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.unit.dp
|
||||
import ch.parano.myice.models.Event
|
||||
import ch.parano.myice.util.formatEventDateTime
|
||||
import ch.parano.myicek.models.Event
|
||||
import ch.parano.myicek.util.formatEventDateTime
|
||||
|
||||
@Composable
|
||||
fun EventCard(
|
||||
@@ -80,7 +80,7 @@ fun EventCard(
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
)
|
||||
Text(
|
||||
text = formatEventDateTime(event.start, event.end),
|
||||
text = "Date: ${formatEventDateTime(event.start, event.end)}",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
)
|
||||
}
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
// 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.myice.ui.components
|
||||
package ch.parano.myicek.ui.components
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
// 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.myice.ui.components
|
||||
package ch.parano.myicek.ui.components
|
||||
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
+50
-17
@@ -15,15 +15,17 @@
|
||||
// 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.myice.ui.screens
|
||||
package ch.parano.myicek.ui.screens
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material3.Card
|
||||
@@ -43,8 +45,11 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import ch.parano.myice.ui.components.LoadingIndicator
|
||||
import ch.parano.myice.viewmodel.EventDetailViewModel
|
||||
import ch.parano.myicek.ui.PlatformBackHandler
|
||||
import ch.parano.myicek.ui.components.LoadingIndicator
|
||||
import ch.parano.myicek.util.formatEventDate
|
||||
import ch.parano.myicek.util.formatEventTimeRange
|
||||
import ch.parano.myicek.viewmodel.EventDetailViewModel
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
@@ -52,10 +57,14 @@ fun EventDetailScreen(
|
||||
gameId: String,
|
||||
account: String,
|
||||
eventTitle: String,
|
||||
eventStart: String,
|
||||
eventEnd: String,
|
||||
viewModel: EventDetailViewModel,
|
||||
onBack: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
PlatformBackHandler { onBack() }
|
||||
|
||||
LaunchedEffect(gameId, account) {
|
||||
viewModel.loadEventDetail(gameId, account)
|
||||
}
|
||||
@@ -113,7 +122,8 @@ fun EventDetailScreen(
|
||||
) {
|
||||
Text("Type: ${detail.type}")
|
||||
Text("Lieu: ${detail.place}")
|
||||
Text("Heure: ${detail.timeStart} - ${detail.timeEnd}")
|
||||
Text("Date: ${formatEventDate(eventStart)}")
|
||||
Text("Heure: ${formatEventTimeRange(eventStart, eventEnd)}")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -142,18 +152,31 @@ fun EventDetailScreen(
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
)
|
||||
}
|
||||
items(detail.convocation.available) { player ->
|
||||
val position = player.position ?: "N/A"
|
||||
val number = player.number ?: "N/A"
|
||||
item {
|
||||
Card(modifier = Modifier.fillMaxWidth()) {
|
||||
Column(
|
||||
modifier = Modifier.padding(16.dp),
|
||||
Column(modifier = Modifier.padding(16.dp)) {
|
||||
detail.convocation.available.forEach { player ->
|
||||
val position = player.position ?: ""
|
||||
val number = player.number ?: ""
|
||||
val muted = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(6.dp),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
Text("[$position] #$number - ${player.fname} ${player.lname}")
|
||||
Text(
|
||||
text = "DOB: ${player.dob}",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
if (number.isNotEmpty()) "#$number" else "",
|
||||
color = muted,
|
||||
modifier = Modifier.width(40.dp),
|
||||
)
|
||||
Text("${player.fname} ${player.lname}")
|
||||
Spacer(Modifier.weight(1f))
|
||||
if (position.isNotEmpty()) {
|
||||
Text(position, color = muted)
|
||||
}
|
||||
Text("(${player.dob.take(4)})", color = muted)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -166,12 +189,22 @@ fun EventDetailScreen(
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
)
|
||||
}
|
||||
items(detail.convocation.staff) { staff ->
|
||||
item {
|
||||
Card(modifier = Modifier.fillMaxWidth()) {
|
||||
Text(
|
||||
text = "${staff.role}: ${staff.fname} ${staff.lname}",
|
||||
modifier = Modifier.padding(16.dp),
|
||||
)
|
||||
Column(modifier = Modifier.padding(16.dp)) {
|
||||
detail.convocation.staff.forEach { staff ->
|
||||
val muted = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(6.dp),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
) {
|
||||
Text("${staff.fname} ${staff.lname}")
|
||||
Spacer(Modifier.weight(1f))
|
||||
Text(staff.role, color = muted)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+4
-4
@@ -15,7 +15,7 @@
|
||||
// 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.myice.ui.screens
|
||||
package ch.parano.myicek.ui.screens
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
@@ -39,8 +39,8 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import ch.parano.myice.viewmodel.AuthViewModel
|
||||
import ch.parano.myice.viewmodel.AuthState
|
||||
import ch.parano.myicek.viewmodel.AuthViewModel
|
||||
import ch.parano.myicek.viewmodel.AuthState
|
||||
|
||||
@Composable
|
||||
fun LoginScreen(
|
||||
@@ -66,7 +66,7 @@ fun LoginScreen(
|
||||
verticalArrangement = Arrangement.Center,
|
||||
) {
|
||||
Text(
|
||||
text = "MyIce",
|
||||
text = "MyIceK",
|
||||
style = MaterialTheme.typography.headlineLarge,
|
||||
fontWeight = FontWeight.Bold,
|
||||
)
|
||||
+16
-29
@@ -15,7 +15,7 @@
|
||||
// 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.myice.ui.screens
|
||||
package ch.parano.myicek.ui.screens
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.expandVertically
|
||||
@@ -31,7 +31,7 @@ import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
@@ -53,19 +53,19 @@ import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.height
|
||||
import ch.parano.myice.filter.TypeFilter
|
||||
import ch.parano.myice.ui.components.EventCard
|
||||
import ch.parano.myice.ui.components.FilterDropdown
|
||||
import ch.parano.myice.ui.components.LoadingIndicator
|
||||
import ch.parano.myice.viewmodel.AuthViewModel
|
||||
import ch.parano.myice.viewmodel.ScheduleViewModel
|
||||
import ch.parano.myicek.filter.TypeFilter
|
||||
import ch.parano.myicek.ui.components.EventCard
|
||||
import ch.parano.myicek.ui.components.FilterDropdown
|
||||
import ch.parano.myicek.ui.components.LoadingIndicator
|
||||
import ch.parano.myicek.viewmodel.AuthViewModel
|
||||
import ch.parano.myicek.viewmodel.ScheduleViewModel
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun ScheduleScreen(
|
||||
scheduleViewModel: ScheduleViewModel,
|
||||
authViewModel: AuthViewModel,
|
||||
onEventClick: (gameId: String, account: String, eventTitle: String) -> Unit,
|
||||
onEventClick: (gameId: String, account: String, eventTitle: String, eventStart: String, eventEnd: String) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val scheduleState by scheduleViewModel.state.collectAsState()
|
||||
@@ -80,9 +80,9 @@ fun ScheduleScreen(
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = { Text("MyIce") },
|
||||
title = { Text("MyIceK") },
|
||||
actions = {
|
||||
val email = (authState as? ch.parano.myice.viewmodel.AuthState.Authenticated)?.email
|
||||
val email = (authState as? ch.parano.myicek.viewmodel.AuthState.Authenticated)?.email
|
||||
if (email != null) {
|
||||
Text(
|
||||
text = email,
|
||||
@@ -201,7 +201,9 @@ fun ScheduleScreen(
|
||||
}
|
||||
}
|
||||
}
|
||||
Box(
|
||||
PullToRefreshBox(
|
||||
isRefreshing = scheduleState.isRefreshing,
|
||||
onRefresh = { scheduleViewModel.refreshSchedule() },
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
@@ -250,6 +252,8 @@ fun ScheduleScreen(
|
||||
event.idEvent,
|
||||
scheduleState.selectedAccount!!,
|
||||
event.title,
|
||||
event.start,
|
||||
event.end,
|
||||
)
|
||||
},
|
||||
)
|
||||
@@ -257,23 +261,6 @@ fun ScheduleScreen(
|
||||
}
|
||||
}
|
||||
}
|
||||
if (scheduleState.isRefreshing) {
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Card {
|
||||
Column(
|
||||
modifier = Modifier.padding(32.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
CircularProgressIndicator()
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
Text("Chargement...", fontWeight = FontWeight.Medium)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
// 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.myice.ui.theme
|
||||
package ch.parano.myicek.ui.theme
|
||||
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
+30
-6
@@ -15,27 +15,51 @@
|
||||
// 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.myice.util
|
||||
package ch.parano.myicek.util
|
||||
|
||||
import kotlinx.datetime.LocalDateTime
|
||||
|
||||
private val FRENCH_MONTHS = listOf(
|
||||
"janvier", "février", "mars", "avril", "mai", "juin",
|
||||
"juillet", "août", "septembre", "octobre", "novembre", "décembre",
|
||||
)
|
||||
|
||||
private val FRENCH_DAYS = listOf(
|
||||
"Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi", "Dimanche",
|
||||
)
|
||||
|
||||
private fun parseOrNull(iso: String): LocalDateTime? = runCatching {
|
||||
LocalDateTime.parse(iso)
|
||||
LocalDateTime.parse(iso.replace(' ', 'T'))
|
||||
}.getOrNull()
|
||||
|
||||
private fun LocalDateTime.dateStr(): String =
|
||||
"${day.toString().padStart(2, '0')}.${monthNumber.toString().padStart(2, '0')}.${year.toString().padStart(4, '0')}"
|
||||
"${year.toString().padStart(4, '0')}-${monthNumber.toString().padStart(2, '0')}-${day.toString().padStart(2, '0')}"
|
||||
|
||||
private fun LocalDateTime.dateStrLong(): String =
|
||||
"${FRENCH_DAYS[date.dayOfWeek.ordinal]} $day ${FRENCH_MONTHS[monthNumber - 1]} $year"
|
||||
|
||||
private fun LocalDateTime.timeStr(): String =
|
||||
"${hour.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}"
|
||||
if (minute == 0) "${hour}h" else "${hour}h${minute.toString().padStart(2, '0')}"
|
||||
|
||||
fun formatEventDateTime(start: String, end: String): String {
|
||||
val startDt = parseOrNull(start)
|
||||
val endDt = parseOrNull(end)
|
||||
if (startDt == null || endDt == null) return "$start - $end"
|
||||
return if (startDt.date == endDt.date) {
|
||||
"${startDt.dateStr()} ${startDt.timeStr()} - ${endDt.timeStr()}"
|
||||
"${startDt.dateStrLong()} de ${startDt.timeStr()} à ${endDt.timeStr()}"
|
||||
} else {
|
||||
"${startDt.dateStr()} ${startDt.timeStr()} - ${endDt.timeStr()} (${endDt.dateStr()})"
|
||||
"${startDt.dateStrLong()} de ${startDt.timeStr()} à ${endDt.timeStr()} (${endDt.dateStrLong()})"
|
||||
}
|
||||
}
|
||||
|
||||
fun formatEventDate(start: String): String {
|
||||
val dt = parseOrNull(start)
|
||||
return dt?.dateStrLong() ?: start
|
||||
}
|
||||
|
||||
fun formatEventTimeRange(start: String, end: String): String {
|
||||
val startDt = parseOrNull(start)
|
||||
val endDt = parseOrNull(end)
|
||||
if (startDt == null || endDt == null) return "$start - $end"
|
||||
return "${startDt.timeStr()} - ${endDt.timeStr()}"
|
||||
}
|
||||
+6
-6
@@ -15,13 +15,13 @@
|
||||
// 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.myice.viewmodel
|
||||
package ch.parano.myicek.viewmodel
|
||||
|
||||
import ch.parano.myice.auth.AuthSettings
|
||||
import ch.parano.myice.auth.OAuthClient
|
||||
import ch.parano.myice.network.ApiConfig
|
||||
import ch.parano.myice.network.ApiService
|
||||
import ch.parano.myice.network.AuthTokenHolder
|
||||
import ch.parano.myicek.auth.AuthSettings
|
||||
import ch.parano.myicek.auth.OAuthClient
|
||||
import ch.parano.myicek.network.ApiConfig
|
||||
import ch.parano.myicek.network.ApiService
|
||||
import ch.parano.myicek.network.AuthTokenHolder
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
+3
-3
@@ -15,10 +15,10 @@
|
||||
// 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.myice.viewmodel
|
||||
package ch.parano.myicek.viewmodel
|
||||
|
||||
import ch.parano.myice.models.EventDetail
|
||||
import ch.parano.myice.network.ApiService
|
||||
import ch.parano.myicek.models.EventDetail
|
||||
import ch.parano.myicek.network.ApiService
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
+10
-10
@@ -15,17 +15,17 @@
|
||||
// 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.myice.viewmodel
|
||||
package ch.parano.myicek.viewmodel
|
||||
|
||||
import ch.parano.myice.auth.AuthSettings
|
||||
import ch.parano.myice.cache.ScheduleCache
|
||||
import ch.parano.myice.filter.TypeFilter
|
||||
import ch.parano.myice.filter.distinctAgegroups
|
||||
import ch.parano.myice.filter.distinctSubgroups
|
||||
import ch.parano.myice.filter.filterEvents
|
||||
import ch.parano.myice.models.Account
|
||||
import ch.parano.myice.models.Event
|
||||
import ch.parano.myice.network.ApiService
|
||||
import ch.parano.myicek.auth.AuthSettings
|
||||
import ch.parano.myicek.cache.ScheduleCache
|
||||
import ch.parano.myicek.filter.TypeFilter
|
||||
import ch.parano.myicek.filter.distinctAgegroups
|
||||
import ch.parano.myicek.filter.distinctSubgroups
|
||||
import ch.parano.myicek.filter.filterEvents
|
||||
import ch.parano.myicek.models.Account
|
||||
import ch.parano.myicek.models.Event
|
||||
import ch.parano.myicek.network.ApiService
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
+2
-2
@@ -15,9 +15,9 @@
|
||||
// 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.myice.cache
|
||||
package ch.parano.myicek.cache
|
||||
|
||||
import ch.parano.myice.models.Event
|
||||
import ch.parano.myicek.models.Event
|
||||
import com.russhwolf.settings.MapSettings
|
||||
import com.russhwolf.settings.Settings
|
||||
import kotlin.test.Test
|
||||
+2
-2
@@ -15,9 +15,9 @@
|
||||
// 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.myice.filter
|
||||
package ch.parano.myicek.filter
|
||||
|
||||
import ch.parano.myice.models.Event
|
||||
import ch.parano.myicek.models.Event
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
// 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.myice.models
|
||||
package ch.parano.myicek.models
|
||||
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlin.test.Test
|
||||
+2
-2
@@ -15,9 +15,9 @@
|
||||
// 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.myice.network
|
||||
package ch.parano.myicek.network
|
||||
|
||||
import ch.parano.myice.models.EventDetail
|
||||
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
|
||||
+18
-3
@@ -15,7 +15,7 @@
|
||||
// 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.myice.util
|
||||
package ch.parano.myicek.util
|
||||
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
@@ -28,7 +28,16 @@ class EventDateTimeFormatterTest {
|
||||
start = "2024-11-10T14:00:00",
|
||||
end = "2024-11-10T16:15:00",
|
||||
)
|
||||
assertEquals("10.11.2024 14:00 - 16:15", formatted)
|
||||
assertEquals("Dimanche 10 novembre 2024 de 14h à 16h15", formatted)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun formatsSameDayEventWithSpaceSeparator() {
|
||||
val formatted = formatEventDateTime(
|
||||
start = "2026-08-09 10:00:00",
|
||||
end = "2026-08-09 12:00:00",
|
||||
)
|
||||
assertEquals("Dimanche 9 août 2026 de 10h à 12h", formatted)
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -37,7 +46,7 @@ class EventDateTimeFormatterTest {
|
||||
start = "2024-11-10T20:00:00",
|
||||
end = "2024-11-11T01:30:00",
|
||||
)
|
||||
assertEquals("10.11.2024 20:00 - 01:30 (11.11.2024)", formatted)
|
||||
assertEquals("Dimanche 10 novembre 2024 de 20h à 1h30 (Lundi 11 novembre 2024)", formatted)
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -57,4 +66,10 @@ class EventDateTimeFormatterTest {
|
||||
val formatted = formatEventDateTime(start = "s", end = "e")
|
||||
assertEquals("s - e", formatted)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun formatsEventDateWithDayOfWeek() {
|
||||
val formatted = formatEventDate("2026-08-09T10:00:00")
|
||||
assertEquals("Dimanche 9 août 2026", formatted)
|
||||
}
|
||||
}
|
||||
+6
-6
@@ -15,13 +15,13 @@
|
||||
// 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.myice.viewmodel
|
||||
package ch.parano.myicek.viewmodel
|
||||
|
||||
import ch.parano.myice.cache.ScheduleCache
|
||||
import ch.parano.myice.filter.TypeFilter
|
||||
import ch.parano.myice.models.Account
|
||||
import ch.parano.myice.models.Event
|
||||
import ch.parano.myice.network.ApiService
|
||||
import ch.parano.myicek.cache.ScheduleCache
|
||||
import ch.parano.myicek.filter.TypeFilter
|
||||
import ch.parano.myicek.models.Account
|
||||
import ch.parano.myicek.models.Event
|
||||
import ch.parano.myicek.network.ApiService
|
||||
import com.russhwolf.settings.MapSettings
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.engine.mock.MockEngine
|
||||
+3
-3
@@ -15,14 +15,14 @@
|
||||
// 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.myice
|
||||
package ch.parano.myicek
|
||||
|
||||
import androidx.compose.ui.window.Window
|
||||
import androidx.compose.ui.window.application
|
||||
import ch.parano.myice.auth.DesktopOAuthClient
|
||||
import ch.parano.myicek.auth.DesktopOAuthClient
|
||||
|
||||
fun main() = application {
|
||||
Window(onCloseRequest = ::exitApplication, title = "MyIce") {
|
||||
Window(onCloseRequest = ::exitApplication, title = "MyIceK") {
|
||||
App(oauthClient = DesktopOAuthClient())
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
// 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.myice.auth
|
||||
package ch.parano.myicek.auth
|
||||
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
import kotlinx.coroutines.TimeoutCancellationException
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
// 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.myice.network
|
||||
package ch.parano.myicek.network
|
||||
|
||||
import io.ktor.client.engine.HttpClientEngineConfig
|
||||
import io.ktor.client.engine.HttpClientEngineFactory
|
||||
@@ -0,0 +1,24 @@
|
||||
// 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.ui
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
|
||||
@Composable
|
||||
actual fun PlatformBackHandler(enabled: Boolean, onBack: () -> Unit) {
|
||||
}
|
||||
+2
-2
@@ -15,10 +15,10 @@
|
||||
// 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.myice
|
||||
package ch.parano.myicek
|
||||
|
||||
import androidx.compose.ui.window.ComposeUIViewController
|
||||
import ch.parano.myice.auth.IosOAuthClient
|
||||
import ch.parano.myicek.auth.IosOAuthClient
|
||||
|
||||
fun MainViewController() = ComposeUIViewController {
|
||||
App(oauthClient = IosOAuthClient())
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
// 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.myice.auth
|
||||
package ch.parano.myicek.auth
|
||||
|
||||
import kotlinx.cinterop.ExperimentalForeignApi
|
||||
import kotlinx.coroutines.TimeoutCancellationException
|
||||
+1
-1
@@ -15,7 +15,7 @@
|
||||
// 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.myice.network
|
||||
package ch.parano.myicek.network
|
||||
|
||||
import io.ktor.client.engine.HttpClientEngineConfig
|
||||
import io.ktor.client.engine.HttpClientEngineFactory
|
||||
@@ -0,0 +1,24 @@
|
||||
// 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.ui
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
|
||||
@Composable
|
||||
actual fun PlatformBackHandler(enabled: Boolean, onBack: () -> Unit) {
|
||||
}
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
## Context
|
||||
|
||||
The existing [MyIce webapp](https://gitea.parano.ch/herel/myice) is a FastAPI backend at `https://myice.parano.ch` that proxies the upstream MyIce Hockey API (`app.myice.hockey`). It serves a single-page web frontend (`index.html`) and a JSON API consumed by a Flutter mobile app (`myice_mobile/`).
|
||||
The existing [MyIce webapp](https://gitea.parano.ch/herel/myice) is a FastAPI backend at `https://myice.parano.ch` that proxies the upstream MyIce Hockey API (`app.myice.hockey`). It serves a single-page web frontend (`index.html`) and a JSON API.
|
||||
|
||||
This spec describes a **Kotlin Multiplatform (KMP) port** of the Flutter app, targeting **Android, iOS, and Desktop** (macOS/Linux/Windows JVM). It mirrors the Flutter app's clean, simple architecture while using idiomatic KMP tooling, and adds one small feature improvement: showing **practices** alongside games (the Flutter app fetches but discards them).
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleURLName</key>
|
||||
<string>ch.parano.myice</string>
|
||||
<string>ch.parano.myicek</string>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>myice</string>
|
||||
|
||||
Reference in New Issue
Block a user