* feat(chat): add animated GIF rendering with tap-to-play animation Closes #721 * fix(#721): add missing GifImageThumbnail component and format ktlint * fix(analytics): resolve ktlint indentation error in itemsIndexed * feat(chat): add interactive play/pause toggle for animated GIFs * fix(chat): control GIF playback via Animatable start and stop
This commit is contained in:
32 files changed
+370
-118
No files matched your search
@@ -105,10 +105,12 @@ class ServerStoreMigration(
|
||||
profile.copy(
|
||||
baseUrl =
|
||||
profile.baseUrl
|
||||
?: ServerEndpoint.fromLegacy(
|
||||
profile.host,
|
||||
profile.port,
|
||||
).baseUrl.toString(),
|
||||
?: ServerEndpoint
|
||||
.fromLegacy(
|
||||
profile.host,
|
||||
profile.port,
|
||||
).baseUrl
|
||||
.toString(),
|
||||
)
|
||||
},
|
||||
selectedProfileId = selectedProfileId,
|
||||
|
||||
@@ -19,19 +19,23 @@ class ServerUrlMigration : DataMigration<ServerStoreState> {
|
||||
state.copy(
|
||||
baseUrl =
|
||||
normalizeExisting(state.baseUrl)
|
||||
?: ServerEndpoint.fromLegacy(
|
||||
state.host,
|
||||
state.port,
|
||||
).baseUrl.toString(),
|
||||
?: ServerEndpoint
|
||||
.fromLegacy(
|
||||
state.host,
|
||||
state.port,
|
||||
).baseUrl
|
||||
.toString(),
|
||||
connectionProfiles =
|
||||
state.connectionProfiles.map { profile ->
|
||||
profile.copy(
|
||||
baseUrl =
|
||||
normalizeExisting(profile.baseUrl)
|
||||
?: ServerEndpoint.fromLegacy(
|
||||
profile.host,
|
||||
profile.port,
|
||||
).baseUrl.toString(),
|
||||
?: ServerEndpoint
|
||||
.fromLegacy(
|
||||
profile.host,
|
||||
profile.port,
|
||||
).baseUrl
|
||||
.toString(),
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
@@ -427,10 +427,12 @@ object AuthManager {
|
||||
|
||||
fun setBaseUrl(baseUrl: String) {
|
||||
val normalized =
|
||||
ServerEndpoint.parse(
|
||||
baseUrl,
|
||||
CleartextPolicy.ALLOW_WITH_WARNING,
|
||||
).baseUrl.toString()
|
||||
ServerEndpoint
|
||||
.parse(
|
||||
baseUrl,
|
||||
CleartextPolicy.ALLOW_WITH_WARNING,
|
||||
).baseUrl
|
||||
.toString()
|
||||
val selectedId =
|
||||
getSelectedProfileId() ?: run {
|
||||
ensureDefaultSelected()
|
||||
|
||||
@@ -28,6 +28,13 @@ data class Attachment(
|
||||
val isImage: Boolean
|
||||
get() = mimeType.startsWith("image/")
|
||||
|
||||
val isGif: Boolean
|
||||
get() =
|
||||
mimeType.equals("image/gif", ignoreCase = true) ||
|
||||
fileExtension == "gif" ||
|
||||
uri.contains(".gif", ignoreCase = true) ||
|
||||
uri.startsWith("data:image/gif", ignoreCase = true)
|
||||
|
||||
val isGateway: Boolean
|
||||
get() = source == AttachmentSource.GATEWAY
|
||||
|
||||
|
||||
@@ -81,8 +81,7 @@ object CookieManager {
|
||||
.expiresAt(
|
||||
System.currentTimeMillis() +
|
||||
10L * 365 * 24 * 60 * 60 * 1000,
|
||||
)
|
||||
.hostOnlyDomain(endpoint.baseUrl.host)
|
||||
).hostOnlyDomain(endpoint.baseUrl.host)
|
||||
.path(endpoint.baseUrl.encodedPath)
|
||||
.httpOnly()
|
||||
if (endpoint.baseUrl.isHttps) builder.secure()
|
||||
|
||||
@@ -149,7 +149,12 @@ class ServerEndpoint private constructor(
|
||||
val converted =
|
||||
replaceScheme(rawUrl, httpScheme).toHttpUrlOrNull()
|
||||
?: return "<invalid-websocket-url>"
|
||||
val redacted = converted.newBuilder().query(null).build().toString()
|
||||
val redacted =
|
||||
converted
|
||||
.newBuilder()
|
||||
.query(null)
|
||||
.build()
|
||||
.toString()
|
||||
return replaceScheme(redacted, socketScheme)
|
||||
}
|
||||
|
||||
|
||||
@@ -54,17 +54,39 @@ object BillingRepository {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
private fun anyToJsonElement(value: Any?): JsonElement =
|
||||
when (value) {
|
||||
null -> JsonNull
|
||||
is JsonElement -> value
|
||||
is Map<*, *> ->
|
||||
null -> {
|
||||
JsonNull
|
||||
}
|
||||
|
||||
is JsonElement -> {
|
||||
value
|
||||
}
|
||||
|
||||
is Map<*, *> -> {
|
||||
JsonObject(
|
||||
(value as Map<String, Any?>).mapValues { (_, v) -> anyToJsonElement(v) },
|
||||
)
|
||||
is List<*> -> JsonArray(value.map { anyToJsonElement(it) })
|
||||
is String -> JsonPrimitive(value)
|
||||
is Boolean -> JsonPrimitive(value)
|
||||
is Number -> JsonPrimitive(value)
|
||||
else -> JsonPrimitive(value.toString())
|
||||
}
|
||||
|
||||
is List<*> -> {
|
||||
JsonArray(value.map { anyToJsonElement(it) })
|
||||
}
|
||||
|
||||
is String -> {
|
||||
JsonPrimitive(value)
|
||||
}
|
||||
|
||||
is Boolean -> {
|
||||
JsonPrimitive(value)
|
||||
}
|
||||
|
||||
is Number -> {
|
||||
JsonPrimitive(value)
|
||||
}
|
||||
|
||||
else -> {
|
||||
JsonPrimitive(value.toString())
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getSubscriptionState(): SubscriptionStateResponse? {
|
||||
|
||||
Reference in new issue
Block a user