Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

version 1.1.0 #10

Merged
merged 26 commits into from
Jun 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
cc760e7
feat: update version
Ayagikei Feb 19, 2023
af27139
fix: shop page title issue, fixed #8
Ayagikei May 21, 2023
1dcb0c7
chore: update dependencies
Ayagikei May 21, 2023
caaaf56
feat: better window size calculate
Ayagikei May 21, 2023
8c0c16a
feat: mdns service discovery
Ayagikei May 22, 2023
c57a421
feat: select service ips dialog
Ayagikei May 30, 2023
fe459dc
feat: service discovery parse the port
Ayagikei May 31, 2023
4103113
chore: update version and package config
Ayagikei May 31, 2023
91495d2
chore: enable Windows perUserInstall to avoid extra permission request
Ayagikei May 31, 2023
c633dcd
fix: minor UI fixes
Ayagikei Jun 3, 2023
7db1cb3
Merge pull request #9 from Ayagikei/mdns
Ayagikei Jun 3, 2023
d143470
feat: update versions
Ayagikei Jun 3, 2023
66c3b86
feat: support export feelings as markdown
Ayagikei Jun 3, 2023
e0b8239
fix: compile issue
Ayagikei Jun 3, 2023
5582462
feat: add tasks
Ayagikei Jun 4, 2023
c5cdcc9
Merge pull request #11 from Ayagikei/export_markdown_feelings
Ayagikei Jun 4, 2023
419fa55
feat: achievement subcategory ui display
Ayagikei Jun 5, 2023
69eee75
feat: achievement subcategory ui display
Ayagikei Jun 5, 2023
a93e907
feat: check update logic wip
Ayagikei Jun 15, 2023
93c1dce
feat: add check updates logic
Ayagikei Jun 24, 2023
8d68e17
feat: add zh-hant
Ayagikei Jun 24, 2023
b84c8ac
fix: minor fixes
Ayagikei Jun 24, 2023
61ceb69
feat: update target lifeup cloud version
Ayagikei Jun 24, 2023
14d7a0f
docs: remove done FIXME
Ayagikei Jun 24, 2023
ac7ecd8
feat: update text
Ayagikei Jun 24, 2023
4aca439
Merge pull request #12 from Ayagikei/add_tasks
Ayagikei Jun 24, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,19 @@ kotlin {
dependencies {
implementation(compose.desktop.currentOs)
implementation(compose.materialIconsExtended)
implementation(compose.uiTooling)
// debugImplementation(compose.preview)

// kotlinx-coroutines-swing
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-swing:1.5.2")

// TODO: md3 migration
// implementation(compose.material3)
// define a BOM and its version
implementation("com.squareup.okhttp3:okhttp:4.10.0")
implementation("com.squareup.okhttp3:logging-interceptor:4.10.0")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.1")
implementation("org.jmdns:jmdns:3.5.8")
}
}
val jvmTest by getting
Expand All @@ -49,14 +55,17 @@ compose.desktop {
modules("java.instrument", "java.prefs", "jdk.unsupported")
targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb)
packageName = "LifeUp Desktop"
packageVersion = "1.0.2"
packageVersion = "1.1.0"
macOS {
iconFile.set(project.file("icon.icns"))
}
windows {
iconFile.set(project.file("icon.ico"))
dirChooser = true // enables customizing the installation path during installation
// console = true
shortcut = true
perUserInstall = true
upgradeUuid = "6400cdde-3cb6-4bad-b238-70b02cc8d210"
menuGroup = "LifeUp Desktop"
}
linux {
Expand Down
10 changes: 9 additions & 1 deletion compose-desktop.pro
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,12 @@
}
-keepclassmembers class <1>.<2> {
<1>.<2>$Companion Companion;
}
}

-dontwarn kotlinx.datetime.**
-dontwarn org.slf4j.**
-keep class org.slf4j.**{ *; }
-keep class com.sun.jna.* { *; }
-keep class * implements com.sun.jna.* { *; }

-keep class org.jmdns.** { *; }
4 changes: 2 additions & 2 deletions gradle.properties
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
kotlin.code.style=official
kotlin.version=1.7.20
kotlin.version=1.8.20
agp.version=7.3.0
compose.version=1.3.0-rc05
compose.version=1.4.0
14 changes: 13 additions & 1 deletion src/jvmMain/kotlin/Main.kt
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,10 @@ import ui.page.list.TasksScreen
import ui.page.status.StatusScreen
import ui.theme.AppTheme
import ui.view.fakeDialog
import java.awt.Toolkit
import java.awt.event.WindowEvent
import java.util.logging.Logger
import javax.swing.UIManager
import kotlin.system.exitProcess

@ExperimentalUnitApi
Expand Down Expand Up @@ -180,22 +182,32 @@ fun main() {
application(exitProcessOnExit = false) {
// To fix the window crash issue: https://github.com/JetBrains/compose-jb/issues/610
System.setProperty("skiko.renderApi", "OPENGL")
// get native dialog UI
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName())

CompositionLocalProvider(
LocalWindowExceptionHandlerFactory provides WindowExceptionHandlerFactory { window ->
WindowExceptionHandler {
lastError = it
window.dispatchEvent(WindowEvent(window, WindowEvent.WINDOW_CLOSING))
// throw it
throw it
}
}
) {
// sum better window size
val screenSize = Toolkit.getDefaultToolkit().screenSize
val width = screenSize.width
val height = screenSize.height
val windowWidth = if (width > 1920) 1200 else 800
val windowHeight = if (height > 1080) 900 else 600

Window(
onCloseRequest = ::exitApplication,
title = "LifeUp",
state = rememberWindowState(
position = WindowPosition(alignment = Alignment.Center),
size = DpSize(1200.dp, 900.dp)
size = DpSize(windowWidth.dp, windowHeight.dp)
),
icon = painterResource("icons/svg/icon.svg")
) {
Expand Down
1 change: 1 addition & 0 deletions src/jvmMain/kotlin/base/JsonSerializer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ import kotlinx.serialization.json.Json

val json = Json {
ignoreUnknownKeys = true
explicitNulls = false
}
21 changes: 17 additions & 4 deletions src/jvmMain/kotlin/base/Val.kt
Original file line number Diff line number Diff line change
@@ -1,10 +1,23 @@
package base

object Val {
// FIXME: get version from gradle
val version: String = "1.0.2"
val version: String
get() = System.getProperty("jpackage.app-version") ?: "UNKNOWN"

val targetLifeUpCloudVersion = "1.1.2+"
val versionCode: Int by lazy {
val version = version.split(".")
assert(version.size == 3) { "Invalid version format" }
if (version.size != 3) {
return@lazy 0
}
val major = version[0].padStart(2, '0').toIntOrNull() ?: 0
val minor = version[1].padStart(2, '0').toIntOrNull() ?: 0
val patch = version[2].padStart(2, '0').toIntOrNull() ?: 0
assert(major <= 99 && minor <= 99 && patch <= 99) { "Version number is too large" }
(major * 100000 + minor * 1000 + patch)
}

val targetLifeUpAndroidVersion = "1.91.0+"
const val targetLifeUpCloudVersion = "1.3.0+"

const val targetLifeUpAndroidVersion = "1.91.3+"
}
6 changes: 6 additions & 0 deletions src/jvmMain/kotlin/datasource/ApiService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package datasource

import datasource.data.*
import datasource.net.HttpResponse
import kotlinx.serialization.json.JsonElement

interface ApiService {

Expand Down Expand Up @@ -38,5 +39,10 @@ interface ApiService {

suspend fun getInfo(): Info

suspend fun rawCall(api: String): JsonElement?

suspend fun purchaseItem(id: Long?, price: Long, desc: String)

suspend fun checkUpdate(): ApiServiceImpl.LocalizedUpdateInfo?

}
67 changes: 67 additions & 0 deletions src/jvmMain/kotlin/datasource/ApiServiceImpl.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,15 @@ import datasource.data.*
import datasource.net.HttpResponse
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.serialization.Serializable
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.jsonPrimitive
import kotlinx.serialization.json.long
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Request
import java.util.*

object ApiServiceImpl : ApiService {

Expand Down Expand Up @@ -130,6 +133,17 @@ object ApiServiceImpl : ApiService {
}
}

override suspend fun rawCall(api: String): JsonElement? {
return withContext(Dispatchers.IO) {
val url = (OkHttpClientHolder.host + "/api/contentprovider").toHttpUrl().newBuilder()
.addQueryParameter("url", api)
.build()
val request = Request.Builder().url(url).build()
val response = okHttpClient.newCall(request).execute()
json.decodeFromString<HttpResponse<JsonElement>>(response.body?.string() ?: "").successOrThrow()
}
}

override fun getIconUrl(icon: String): String {
if (icon.isEmpty()) {
return ""
Expand All @@ -141,4 +155,57 @@ object ApiServiceImpl : ApiService {
.build()
return url.toString()
}

@Serializable
data class UpdateInfoMap(
val versionCode: Int,
val downloadUrl: String?,
val localeInfo: Map<String, UpdateInfo>
)

@Serializable
data class UpdateInfo(
val versionName: String?,
val downloadUrl: String?,
val releaseNotes: String?,
val downloadWebsite: String?
)

data class LocalizedUpdateInfo(
val versionCode: Int,
val versionName: String?,
val downloadUrl: String?,
val releaseNotes: String?,
val downloadWebsite: String?
)

private const val UPDATE_URL = "http://cdn.lifeupapp.fun/version/version.json"


override suspend fun checkUpdate(): LocalizedUpdateInfo? {
return withContext(Dispatchers.IO) {
val request = Request.Builder().url(UPDATE_URL).build()
val response = okHttpClient.newCall(request).execute()
if (response.isSuccessful) {
val jsonText = response.body?.string()
jsonText?.let {
val updateInfo = json.decodeFromString<UpdateInfoMap>(it)
val locale = Locale.getDefault()
val bestMatchedUpdateInfo =
updateInfo.localeInfo["${locale.language.lowercase()}_${locale.country.lowercase()}"]
?: updateInfo.localeInfo[locale.language.lowercase()] ?: updateInfo.localeInfo["en"]

return@let LocalizedUpdateInfo(
versionCode = updateInfo.versionCode,
downloadUrl = updateInfo.downloadUrl,
versionName = bestMatchedUpdateInfo?.versionName ?: "",
releaseNotes = bestMatchedUpdateInfo?.releaseNotes ?: "",
downloadWebsite = bestMatchedUpdateInfo?.downloadWebsite ?: ""
)
}
} else {
null
}
}
}
}
9 changes: 9 additions & 0 deletions src/jvmMain/kotlin/datasource/data/Achievement.kt
Original file line number Diff line number Diff line change
Expand Up @@ -82,5 +82,14 @@ data class Achievement(
fun builder(block: Builder.() -> Unit): Achievement {
return Builder().apply(block).build()
}

private const val TYPE_NORMAL = 0
private const val TYPE_SUBCATEGORY = 1
}

fun isNormalAchievement() = type == TYPE_NORMAL

fun isSubcategory() = type == TYPE_SUBCATEGORY


}
8 changes: 8 additions & 0 deletions src/jvmMain/kotlin/datasource/data/TaskCategory.kt
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,14 @@ data class TaskCategory(
}
}

fun isNormalList(): Boolean {
return (id ?: 0 > 0L) && type == 0
}

fun isNotArchived(): Boolean {
return status == 0
}

companion object {
fun builder(block: Builder.() -> Unit): TaskCategory {
return Builder().apply(block).build()
Expand Down
8 changes: 8 additions & 0 deletions src/jvmMain/kotlin/datasource/net/HttpResponse.kt
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,14 @@ data class HttpResponse<T>(
return data
}


fun successOrThrow(): T? {
if (code != SUCCESS) {
throw HttpException(this)
}
return data
}

fun onSuccess(block: (T?) -> Unit): HttpResponse<T> {
if (code == SUCCESS) {
block(data)
Expand Down
65 changes: 65 additions & 0 deletions src/jvmMain/kotlin/service/MdnsServiceDiscovery.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package service

import logger
import java.net.InetAddress
import java.util.logging.Level
import javax.jmdns.JmDNS
import javax.jmdns.ServiceEvent
import javax.jmdns.ServiceListener

/**
* Service to discover the lifeup cloud server
*/
class MdnsServiceDiscovery {

data class IpAndPort(val ip: String, val port: String) {
override fun toString(): String {
return "$ip:$port"
}
}

val ipAndPorts = HashMap<String, IpAndPort?>()

private val listener = object : ServiceListener {
override fun serviceAdded(event: ServiceEvent?) {
logger.log(Level.INFO, "Service added: ${event?.info}")
}

override fun serviceRemoved(event: ServiceEvent?) {
logger.log(Level.INFO, "Service removed: ${event?.info}")
event?.info?.inetAddresses?.firstOrNull()?.hostAddress?.let {
ipAndPorts.remove(it)
}
}

override fun serviceResolved(event: ServiceEvent?) {
logger.log(Level.INFO, "Service resolved: ${event?.info}")
runCatching {
if (event?.name?.contains("lifeup_cloud") == true) {
logger.log(Level.INFO, "Service resolved, address: ${event.info.inetAddresses}")

val port = event.info.getPropertyString("port")
if (port.isNullOrEmpty()) {
logger.log(Level.INFO, "Service resolved, but data has not port")
return@runCatching
}

val ip = event.info.inetAddresses.first().hostAddress
logger.log(Level.INFO, "Service resolved, ip: $ip")
ipAndPorts[ip] = IpAndPort(ip, port)
}
}.onFailure {
it.printStackTrace()
}
}
}


fun register() = runCatching {
// Create a JmDNS instance
val jmdns = JmDNS.create(InetAddress.getLocalHost())

// Add a service listener
jmdns.addServiceListener("_lifeup._tcp.local.", listener)
}
}
Loading