Skip to content

Commit

Permalink
wip kotlinx.datetime
Browse files Browse the repository at this point in the history
  • Loading branch information
zsmb13 committed Feb 11, 2025
1 parent f94c932 commit 4e46b1f
Show file tree
Hide file tree
Showing 12 changed files with 212 additions and 191 deletions.
61 changes: 23 additions & 38 deletions shared/src/commonMain/kotlin/org/jetbrains/kotlinconf/APIClient.kt
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@ import io.ktor.http.isSuccess
import io.ktor.http.path
import io.ktor.http.takeFrom
import io.ktor.serialization.kotlinx.json.json
import io.ktor.util.date.GMTDate
import io.ktor.util.date.Month
import io.ktor.utils.io.core.Closeable
import kotlinx.datetime.LocalDateTime
import org.jetbrains.kotlinconf.utils.appLogger

val HTTP_CLIENT = HttpClient()
private const val MAX_RETRIES = 3
private const val RETRY_DELAY_MS = 10000L

/**
* Adapter to handle backend API and manage auth information.
Expand All @@ -38,7 +38,7 @@ class APIClient(
) : Closeable {
var userId: String? = null

private val client = HTTP_CLIENT.config {
private val client = HttpClient {
install(ContentNegotiation) {
json()
}
Expand All @@ -55,17 +55,23 @@ class APIClient(
TOO_LATE_STATUS -> throw TooLateVote()
HttpStatusCode.Conflict -> return@validateResponse
HttpStatusCode.Unauthorized -> throw Unauthorized()
HttpStatusCode.BadRequest -> throw IllegalArgumentException("Invalid request parameters")
HttpStatusCode.NotFound -> throw NoSuchElementException("Resource not found")
else -> {
if (!it.status.isSuccess()) {
throw IllegalStateException("Request failed with status: ${it.status}")
}
}
}
}
}

install(HttpRequestRetry) {
maxRetries = Int.MAX_VALUE
delay {
kotlinx.coroutines.delay(it)
}
constantDelay(10 * 1000L)
maxRetries = MAX_RETRIES
retryIf { _, response -> !response.status.isSuccess() }
constantDelay(RETRY_DELAY_MS)
retryOnException(retryOnTimeout = true)
retryOnServerErrors()
}

install(DefaultRequest) {
Expand Down Expand Up @@ -136,11 +142,11 @@ class APIClient(
}

/**
* Get server time.
* Get server time in milliseconds since epoch.
*/
suspend fun getServerTime(): GMTDate = client.get {
suspend fun getServerTime(): Long = client.get {
apiUrl("time")
}.bodyAsText().let { response -> GMTDate(response.toLong()) }
}.bodyAsText().toLong()

// TODO real api call https://github.com/JetBrains/kotlinconf-app/issues/268
suspend fun getNews(): List<NewsItem> = EXAMPLE_NEWS_ITEMS
Expand Down Expand Up @@ -171,56 +177,35 @@ private val EXAMPLE_NEWS_ITEMS = listOf(
id = "0",
title = "Kotlin 1.9 Released",
content = "**Exciting news for Kotlin developers!** The latest version of Kotlin brings significant improvements and new features to enhance your development experience.\n\nSome highlights include:\n- *K2 compiler* improvements for faster compilation\n- Enhanced type inference system\n- New stdlib functions\n\nCheck out the detailed release notes at [kotlinlang.org](https://kotlinlang.org) and start exploring these amazing features today! The Kotlin team has been working hard to make this release even more **powerful** and *developer-friendly*.",
date = GMTDate(
year = 2024,
month = Month.APRIL,
dayOfMonth = 23,
hours = 10,
minutes = 24,
seconds = 2
),
date = LocalDateTime.parse("2024-04-23T10:24:02"),
photoUrl = "https://picsum.photos/1800/900"
),
NewsItem(
id = "1",
title = "KotlinConf 2024 Announced",
content = "Get ready for the most anticipated Kotlin event of the year! **KotlinConf 2024** brings together developers from around the world for an unforgettable experience.\n\n*What to expect:*\n- Inspiring keynotes from Kotlin leaders\n- In-depth technical sessions\n- Hands-on workshops\n- Networking opportunities\n\nDon't miss the chance to meet fellow Kotlin enthusiasts and learn from industry experts. Early bird tickets are now available at [kotlinconf.com/2024](https://kotlinconf.com/2024).\n\n**Pro tip:** Check out the *conference app* to plan your schedule and connect with other attendees!",
date = GMTDate(year = 2024, month = Month.MAY, dayOfMonth = 22, hours = 10, minutes = 24, seconds = 2),
date = LocalDateTime.parse("2024-05-22T10:24:02"),
photoUrl = null
),
NewsItem(
id = "2",
title = "Jetpack Compose Updates",
content = "The world of **Jetpack Compose** continues to evolve with exciting new features for both Android and Desktop development!\n\n*Latest improvements include:*\n- Enhanced performance optimizations\n- New material design components\n- Improved animation APIs\n- Better desktop window management\n\nRead the comprehensive guide on the [Android Developers Blog](https://android-developers.googleblog.com) and explore the [Compose Multiplatform documentation](https://www.jetbrains.com/compose-multiplatform/).\n\n**Did you know?** You can now easily share up to *90% of your UI code* between Android and Desktop applications using Compose Multiplatform!",
date = GMTDate(
year = 2024,
month = Month.JANUARY,
dayOfMonth = 22,
hours = 10,
minutes = 24,
seconds = 2
),
date = LocalDateTime.parse("2024-01-22T10:24:02"),
photoUrl = null
),
NewsItem(
id = "3",
title = "New Kotlin Multiplatform Features",
content = "**Kotlin Multiplatform** technology reaches new heights with groundbreaking features and improvements!\n\n*Key highlights of the latest release:*\n- Simplified project setup and configuration\n- Enhanced iOS integration with new Kotlin/Native features\n- Improved dependency management\n- Extended WebAssembly support\n\nStart building your next cross-platform project with [KMP](https://kotlinlang.org/docs/multiplatform.html) today!\n\n**Success Story:** *Philips* recently shared how they achieved a **75% code sharing rate** across platforms using Kotlin Multiplatform. Read their detailed case study on the [Kotlin Blog](https://blog.jetbrains.com/kotlin/).\n\nExplore the [official documentation](https://kotlinlang.org/docs/multiplatform-get-started.html) to learn more about these exciting developments!",
date = GMTDate(year = 2024, month = Month.MAY, dayOfMonth = 23, hours = 10, minutes = 24, seconds = 2),
date = LocalDateTime.parse("2024-05-23T10:24:02"),
photoUrl = "https://picsum.photos/1800/900"
),
NewsItem(
id = "4",
title = "Kotlin Community Highlights",
content = "The **Kotlin community** continues to innovate and inspire! Let's celebrate some remarkable community contributions.\n\n*Featured Projects:*\n- **Ktor 2.0**: A powerful framework for building asynchronous servers and clients\n- *Kotlin Native Bridge*: Seamless integration between Kotlin and native platforms\n- **KMP-NativeCoroutines**: Simplified concurrency for multiplatform projects\n\nJoin the community on [Kotlin Slack](https://kotlinlang.slack.com) with over *100,000 members* and share your own projects!\n\n**Want to contribute?** Check out the [Kotlin Contributing Guidelines](https://github.com/JetBrains/kotlin) and help shape the future of Kotlin. The community has already contributed more than *500 patches* to the latest release!\n\nExplore more community projects on [Kotlin Weekly](https://kotlinweekly.net) and get inspired for your next project.",
date = GMTDate(
year = 2024,
month = Month.APRIL,
dayOfMonth = 20,
hours = 10,
minutes = 24,
seconds = 2
),
date = LocalDateTime.parse("2024-04-20T10:24:02"),
photoUrl = "https://picsum.photos/1800/900"
)
)
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package org.jetbrains.kotlinconf

import io.ktor.util.date.GMTDate
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
Expand All @@ -14,6 +13,7 @@ import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import kotlinx.datetime.LocalDateTime
import org.jetbrains.kotlinconf.storage.ApplicationStorage
import org.jetbrains.kotlinconf.utils.time

Expand All @@ -22,8 +22,8 @@ val UNKNOWN_SESSION_CARD: SessionCardView = SessionCardView(
title = "unknown",
speakerLine = "unknown",
locationLine = "unknown",
startsAt = GMTDate.START,
endsAt = GMTDate.START,
startsAt = LocalDateTime.parse("2020-01-01T00:00:00"),
endsAt = LocalDateTime.parse("2020-01-01T00:00:00"),
speakerIds = emptyList(),
isFavorite = false,
description = "unknown",
Expand Down Expand Up @@ -199,41 +199,41 @@ class ConferenceService(
return PARTNER_DESCRIPTIONS[name] ?: ""
}

private fun scheduleNotification(session: SessionCardView) {
scope.launch {
val notificationsAllowed = storage.getNotificationsAllowed().first()
if (!notificationsAllowed) return@launch

val startTimestamp = session.startsAt.timestamp
val reminderTimestamp = startTimestamp - 5 * 60 * 1000
val nowTimestamp = timeProvider.now().timestamp
val delay = reminderTimestamp - nowTimestamp
val voteTimeStamp = session.endsAt.timestamp

when {
delay >= 0 -> {
notificationManager.schedule(delay, session.title, "Starts in 5 minutes.")
}

nowTimestamp in reminderTimestamp..<startTimestamp -> {
notificationManager.schedule(0, session.title, "The session is about to start.")
}

nowTimestamp in startTimestamp..<voteTimeStamp -> {
notificationManager.schedule(0, session.title, "Hurry up! The session has already started!")
}
}

if (nowTimestamp > voteTimeStamp) return@launch

val voteDelay = voteTimeStamp - nowTimestamp
notificationManager.schedule(
voteDelay,
"${session.title} finished",
"How was the talk?"
)
}
}
// private fun scheduleNotification(session: SessionCardView) {
// scope.launch {
// val notificationsAllowed = storage.getNotificationsAllowed().first()
// if (!notificationsAllowed) return@launch
//
// val startTimestamp = session.startsAt
// val reminderTimestamp = startTimestamp - 5 * 60 * 1000
// val nowTimestamp = timeProvider.now()
// val delay = reminderTimestamp - nowTimestamp
// val voteTimeStamp = session.endsAt
//
// when {
// delay >= 0 -> {
// notificationManager.schedule(delay, session.title, "Starts in 5 minutes.")
// }
//
// nowTimestamp in reminderTimestamp..<startTimestamp -> {
// notificationManager.schedule(0, session.title, "The session is about to start.")
// }
//
// nowTimestamp in startTimestamp..<voteTimeStamp -> {
// notificationManager.schedule(0, session.title, "Hurry up! The session has already started!")
// }
// }
//
// if (nowTimestamp > voteTimeStamp) return@launch
//
// val voteDelay = voteTimeStamp - nowTimestamp
// notificationManager.schedule(
// voteDelay,
// "${session.title} finished",
// "How was the talk?"
// )
// }
// }

private fun cancelNotification(session: SessionCardView) {
scope.launch {
Expand All @@ -247,7 +247,7 @@ class ConferenceService(

private fun mapNewsItemToDisplayItem(
item: NewsItem,
now: GMTDate,
now: LocalDateTime,
): NewsDisplayItem {
return NewsDisplayItem(
id = item.id,
Expand All @@ -258,13 +258,13 @@ class ConferenceService(
)
}

private fun GMTDate.toNewsDisplayTime(now: GMTDate): String {
private fun LocalDateTime.toNewsDisplayTime(now: LocalDateTime): String {
return if (year == now.year && dayOfYear == now.dayOfYear) {
return time()
} else if (year == now.year) {
"${month.value} $dayOfMonth"
"${month} $dayOfMonth"
} else {
"${month.value} $dayOfMonth, $year"
"${month} $dayOfMonth, $year"
}
}

Expand Down
17 changes: 6 additions & 11 deletions shared/src/commonMain/kotlin/org/jetbrains/kotlinconf/Model.kt
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
package org.jetbrains.kotlinconf

import io.ktor.util.date.*
import kotlinx.serialization.*
import org.jetbrains.kotlinconf.utils.*
import kotlinx.datetime.LocalDateTime
import kotlinx.serialization.Serializable
import kotlin.jvm.JvmInline

typealias GMTDateSerializable = @Serializable(GMTDateSerializer::class) GMTDate

@Serializable
@JvmInline
value class SpeakerId(val id: String) {
Expand Down Expand Up @@ -46,12 +43,10 @@ class Session(
val description: String,
val speakerIds: List<SpeakerId>,
val location: String,
val startsAt: GMTDateSerializable,
val endsAt: GMTDateSerializable,
val startsAt: LocalDateTime,
val endsAt: LocalDateTime,
val tags: List<String>? = null
) {
val timeLine get() = startsAt.time() + " - " + endsAt.time()
}
)

@Serializable
class VoteInfo(
Expand Down Expand Up @@ -92,7 +87,7 @@ enum class Theme {
class NewsItem(
val id: String,
val photoUrl: String?,
val date: GMTDateSerializable,
val date: LocalDateTime,
val title: String,
val content: String,
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,20 @@
package org.jetbrains.kotlinconf

import io.ktor.util.date.GMTDate
import kotlinx.datetime.LocalDateTime
import kotlinx.datetime.TimeZone
import kotlinx.datetime.toInstant
import org.jetbrains.kotlinconf.utils.dayAndMonth
import org.jetbrains.kotlinconf.utils.time
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.minutes

data class SessionCardView(
val id: SessionId,
val title: String,
val speakerLine: String,
val locationLine: String,
val startsAt: GMTDate,
val endsAt: GMTDate,
val startsAt: LocalDateTime,
val endsAt: LocalDateTime,
val state: SessionState,
val speakerIds: List<SpeakerId>,
val vote: Score?,
Expand All @@ -29,12 +33,15 @@ data class SessionCardView(
append("-")
append(endsAt.time())
},
val isLightning: Boolean = endsAt.timestamp - startsAt.timestamp <= 15 * 60 * 1000,
val isLightning: Boolean = (endsAt.toInstant(TimeZone.UTC).toEpochMilliseconds() - startsAt.toInstant(TimeZone.UTC)
.toEpochMilliseconds()).milliseconds <= 15.minutes,
val startsInMinutes: Int?,
)

val SessionCardView.isLive get() = state == SessionState.Live
val SessionCardView.isUpcoming get() = state == SessionState.Upcoming
val SessionCardView.isPast get() = state == SessionState.Past

val Session.isLightning: Boolean get() = endsAt.timestamp - startsAt.timestamp <= 15 * 60 * 1000
val Session.isLightning: Boolean
get() = (endsAt.toInstant(TimeZone.UTC).toEpochMilliseconds() - startsAt.toInstant(TimeZone.UTC)
.toEpochMilliseconds()).milliseconds <= 15.minutes
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package org.jetbrains.kotlinconf

import io.ktor.util.date.GMTDate
import kotlinx.datetime.LocalDateTime

enum class SessionState {
Live,
Expand All @@ -9,7 +9,7 @@ enum class SessionState {
;

companion object {
fun from(startsAt: GMTDate, endsAt: GMTDate, now: GMTDate): SessionState = when {
fun from(startsAt: LocalDateTime, endsAt: LocalDateTime, now: LocalDateTime): SessionState = when {
startsAt <= now && now < endsAt -> Live
endsAt <= now -> Past
else -> Upcoming
Expand Down
Loading

0 comments on commit 4e46b1f

Please sign in to comment.