mirror of
https://github.com/fromchat-messenger/app.git
synced 2026-09-22 19:15:05 +03:00
Fix attachments and tray menu on Windows
This commit is contained in:
Generated
+1
-1
@@ -2,6 +2,6 @@
|
|||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="KotlinJpsPluginSettings">
|
<component name="KotlinJpsPluginSettings">
|
||||||
<option name="externalSystemId" value="Gradle" />
|
<option name="externalSystemId" value="Gradle" />
|
||||||
<option name="version" value="2.4.10" />
|
<option name="version" value="2.4.20" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
@@ -56,7 +56,11 @@ val fixComposeResourcesStructure = tasks.register<FixComposeResTask>("fixCompose
|
|||||||
|
|
||||||
extensions.configure<ApplicationExtension> {
|
extensions.configure<ApplicationExtension> {
|
||||||
namespace = "ru.fromchat"
|
namespace = "ru.fromchat"
|
||||||
compileSdk = 37
|
compileSdk {
|
||||||
|
version = release(37) {
|
||||||
|
minorApiLevel = 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId = "ru.fromchat"
|
applicationId = "ru.fromchat"
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
|
import java.nio.ByteBuffer
|
||||||
|
import java.nio.ByteOrder
|
||||||
|
import java.util.zip.ZipFile
|
||||||
|
|
||||||
plugins {
|
plugins {
|
||||||
kotlin("jvm")
|
kotlin("jvm")
|
||||||
alias(libs.plugins.compose.multiplatform)
|
alias(libs.plugins.compose.multiplatform)
|
||||||
alias(libs.plugins.compose.compiler)
|
alias(libs.plugins.compose.compiler)
|
||||||
}
|
}
|
||||||
|
|
||||||
import java.nio.ByteBuffer
|
|
||||||
import java.nio.ByteOrder
|
|
||||||
import java.util.zip.ZipFile
|
|
||||||
|
|
||||||
val javafxVersion = libs.versions.openjfx.get()
|
val javafxVersion = libs.versions.openjfx.get()
|
||||||
val javafxClassifier = openjfxClassifier()
|
val javafxClassifier = openjfxClassifier()
|
||||||
val javafxModules = listOf("base", "graphics", "controls", "media", "web", "swing")
|
val javafxModules = listOf("base", "graphics", "controls", "media", "web", "swing")
|
||||||
@@ -21,13 +21,13 @@ dependencies {
|
|||||||
} else {
|
} else {
|
||||||
implementation(compose.desktop.currentOs)
|
implementation(compose.desktop.currentOs)
|
||||||
}
|
}
|
||||||
implementation(compose.material3)
|
implementation(libs.compose.material3)
|
||||||
implementation(compose.materialIconsExtended)
|
implementation(libs.compose.materialIconsExtended)
|
||||||
implementation(libs.compose.components.resources)
|
implementation(libs.compose.components.resources)
|
||||||
implementation(project(":app:shared"))
|
implementation(project(":app:shared"))
|
||||||
implementation(project(":utils:shared"))
|
implementation(project(":utils:shared"))
|
||||||
implementation(libs.kotlinx.coroutines.swing)
|
implementation(libs.kotlinx.coroutines.swing)
|
||||||
implementation("net.java.dev.jna:jna-platform:5.15.0")
|
implementation(libs.jna.platform)
|
||||||
// OpenJFX publishes empty jars without a classifier; declare every module
|
// OpenJFX publishes empty jars without a classifier; declare every module
|
||||||
// with the host classifier and exclude transitive stubs.
|
// with the host classifier and exclude transitive stubs.
|
||||||
javafxModules.forEach { module ->
|
javafxModules.forEach { module ->
|
||||||
@@ -618,7 +618,7 @@ val exportDmgBackground = tasks.register<Exec>("exportDmgBackground") {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun org.gradle.api.tasks.TaskContainer.registerPackageDmgTask(
|
fun TaskContainer.registerPackageDmgTask(
|
||||||
name: String,
|
name: String,
|
||||||
appBundle: Provider<Directory>,
|
appBundle: Provider<Directory>,
|
||||||
dmgOutput: Provider<RegularFile>,
|
dmgOutput: Provider<RegularFile>,
|
||||||
@@ -644,37 +644,37 @@ fun org.gradle.api.tasks.TaskContainer.registerPackageDmgTask(
|
|||||||
val script = dmgScript.get().asFile
|
val script = dmgScript.get().asFile
|
||||||
script.parentFile.mkdirs()
|
script.parentFile.mkdirs()
|
||||||
script.writeText(
|
script.writeText(
|
||||||
"""
|
$$"""
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
POSITIONS='${positions.absolutePath}'
|
POSITIONS='$${positions.absolutePath}'
|
||||||
APP='${app.absolutePath}'
|
APP='$${app.absolutePath}'
|
||||||
OUT='${dmgOutput.get().asFile.absolutePath}'
|
OUT='$${dmgOutput.get().asFile.absolutePath}'
|
||||||
DIST='${dmgDistDir.get().asFile.absolutePath}'
|
DIST='$${dmgDistDir.get().asFile.absolutePath}'
|
||||||
STAGING="${'$'}{TMPDIR:-/tmp}/fromchat-dmg-staging-${'$'}RANDOM"
|
STAGING="${TMPDIR:-/tmp}/fromchat-dmg-staging-$RANDOM"
|
||||||
rm -rf "${'$'}OUT" "${'$'}STAGING"
|
rm -rf "$OUT" "$STAGING"
|
||||||
mkdir -p "${'$'}STAGING"
|
mkdir -p "$STAGING"
|
||||||
cp -R "${'$'}APP" "${'$'}STAGING/"
|
cp -R "$APP" "$STAGING/"
|
||||||
WINDOW_SIZE=($(node -e "const c=require('${'$'}POSITIONS').createDmg; console.log(c.windowSize.join(' '))"))
|
WINDOW_SIZE=($(node -e "const c=require('$POSITIONS').createDmg; console.log(c.windowSize.join(' '))"))
|
||||||
ICON_SIZE=$(node -e "console.log(require('${'$'}POSITIONS').createDmg.iconSize)")
|
ICON_SIZE=$(node -e "console.log(require('$POSITIONS').createDmg.iconSize)")
|
||||||
APP_POS=($(node -e "const i=require('${'$'}POSITIONS').createDmg.icons.find(x=>x[0]==='FromChat.app'); console.log(i[1], i[2])"))
|
APP_POS=($(node -e "const i=require('$POSITIONS').createDmg.icons.find(x=>x[0]==='FromChat.app'); console.log(i[1], i[2])"))
|
||||||
APPS_POS=($(node -e "const i=require('${'$'}POSITIONS').createDmg.icons.find(x=>x[0]==='Applications'); console.log(i[1], i[2])"))
|
APPS_POS=($(node -e "const i=require('$POSITIONS').createDmg.icons.find(x=>x[0]==='Applications'); console.log(i[1], i[2])"))
|
||||||
(
|
(
|
||||||
cd "${'$'}DIST"
|
cd "$DIST"
|
||||||
create-dmg \
|
create-dmg \
|
||||||
--volname "FromChat" \
|
--volname "FromChat" \
|
||||||
--window-size "${'$'}{WINDOW_SIZE[0]}" "${'$'}{WINDOW_SIZE[1]}" \
|
--window-size "${WINDOW_SIZE[0]}" "${WINDOW_SIZE[1]}" \
|
||||||
--icon-size "${'$'}ICON_SIZE" \
|
--icon-size "$ICON_SIZE" \
|
||||||
--icon "FromChat.app" "${'$'}{APP_POS[0]}" "${'$'}{APP_POS[1]}" \
|
--icon "FromChat.app" "${APP_POS[0]}" "${APP_POS[1]}" \
|
||||||
--hide-extension "FromChat.app" \
|
--hide-extension "FromChat.app" \
|
||||||
--app-drop-link "${'$'}{APPS_POS[0]}" "${'$'}{APPS_POS[1]}" \
|
--app-drop-link "${APPS_POS[0]}" "${APPS_POS[1]}" \
|
||||||
--app-drop-link-name "Программы" \
|
--app-drop-link-name "Программы" \
|
||||||
--text-size 14 \
|
--text-size 14 \
|
||||||
--background "dmg-background@2x.png" \
|
--background "dmg-background@2x.png" \
|
||||||
"${'$'}OUT" \
|
"$OUT" \
|
||||||
"${'$'}STAGING"
|
"$STAGING"
|
||||||
)
|
)
|
||||||
rm -rf "${'$'}STAGING"
|
rm -rf "$STAGING"
|
||||||
""".trimIndent() + "\n",
|
""".trimIndent() + "\n",
|
||||||
)
|
)
|
||||||
script.setExecutable(true)
|
script.setExecutable(true)
|
||||||
@@ -770,10 +770,10 @@ val packageLinuxAppImage = tasks.register<Exec>("packageLinuxAppImage") {
|
|||||||
val appRun = stage.resolve("AppRun")
|
val appRun = stage.resolve("AppRun")
|
||||||
val rel = stage.toPath().relativize(nestedExe.toPath()).toString()
|
val rel = stage.toPath().relativize(nestedExe.toPath()).toString()
|
||||||
appRun.writeText(
|
appRun.writeText(
|
||||||
"""
|
$$"""
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
SELF="${'$'}(dirname "${'$'}(readlink -f "${'$'}0")")"
|
SELF="$(dirname "$(readlink -f "$0")")"
|
||||||
exec "${'$'}SELF/$rel" "${'$'}@"
|
exec "$SELF/$$rel" "$@"
|
||||||
""".trimIndent() + "\n",
|
""".trimIndent() + "\n",
|
||||||
)
|
)
|
||||||
appRun.setExecutable(true)
|
appRun.setExecutable(true)
|
||||||
@@ -795,16 +795,16 @@ val packageLinuxAppImage = tasks.register<Exec>("packageLinuxAppImage") {
|
|||||||
commandLine(
|
commandLine(
|
||||||
"bash",
|
"bash",
|
||||||
"-lc",
|
"-lc",
|
||||||
"""
|
$$"""
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
TOOL="${'$'}{APPIMAGETOOL:-appimagetool}"
|
TOOL="${APPIMAGETOOL:-appimagetool}"
|
||||||
if ! command -v "${'$'}TOOL" >/dev/null 2>&1; then
|
if ! command -v "$TOOL" >/dev/null 2>&1; then
|
||||||
echo "appimagetool not found. Install it or set APPIMAGETOOL." >&2
|
echo "appimagetool not found. Install it or set APPIMAGETOOL." >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
ARCH="${'$'}(uname -m)"
|
ARCH="$(uname -m)"
|
||||||
export ARCH
|
export ARCH
|
||||||
"${'$'}TOOL" "${stage.absolutePath}" "${out.absolutePath}"
|
"$TOOL" "$${stage.absolutePath}" "$${out.absolutePath}"
|
||||||
""".trimIndent(),
|
""".trimIndent(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -925,8 +925,8 @@ val windowsPrebuiltX64AppImage = layout.buildDirectory.dir("prebuilt/windows-x64
|
|||||||
val windowsPrebuiltArm64AppImage = layout.buildDirectory.dir("prebuilt/windows-arm64/app/FromChat")
|
val windowsPrebuiltArm64AppImage = layout.buildDirectory.dir("prebuilt/windows-arm64/app/FromChat")
|
||||||
|
|
||||||
fun Exec.configureWindowsPackTask(
|
fun Exec.configureWindowsPackTask(
|
||||||
appImageDir: org.gradle.api.provider.Provider<org.gradle.api.file.Directory>?,
|
appImageDir: Provider<Directory>?,
|
||||||
setupOutput: org.gradle.api.provider.Provider<org.gradle.api.file.RegularFile>,
|
setupOutput: Provider<RegularFile>,
|
||||||
registrationId: String = "FromChat",
|
registrationId: String = "FromChat",
|
||||||
prebuiltOnly: Boolean = false,
|
prebuiltOnly: Boolean = false,
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -1,20 +1,35 @@
|
|||||||
package ru.fromchat.desktop
|
package ru.fromchat.desktop
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.border
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.hoverable
|
import androidx.compose.foundation.hoverable
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.interaction.collectIsHoveredAsState
|
import androidx.compose.foundation.interaction.collectIsHoveredAsState
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.IntrinsicSize
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.widthIn
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.layout.wrapContentSize
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.automirrored.filled.Logout
|
||||||
|
import androidx.compose.material.icons.filled.CloudDone
|
||||||
|
import androidx.compose.material.icons.filled.CloudOff
|
||||||
|
import androidx.compose.material.icons.filled.CloudSync
|
||||||
|
import androidx.compose.material.icons.filled.DesktopWindows
|
||||||
|
import androidx.compose.material.icons.filled.Info
|
||||||
import androidx.compose.material3.HorizontalDivider
|
import androidx.compose.material3.HorizontalDivider
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.DisposableEffect
|
import androidx.compose.runtime.DisposableEffect
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.SideEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
@@ -22,40 +37,58 @@ import androidx.compose.runtime.rememberUpdatedState
|
|||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.draw.shadow
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
|
import androidx.compose.ui.input.key.Key
|
||||||
|
import androidx.compose.ui.input.key.KeyEventType
|
||||||
|
import androidx.compose.ui.input.key.key
|
||||||
|
import androidx.compose.ui.input.key.type
|
||||||
|
import androidx.compose.ui.layout.onSizeChanged
|
||||||
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
|
import androidx.compose.ui.unit.DpSize
|
||||||
|
import androidx.compose.ui.unit.IntSize
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.window.Window
|
import androidx.compose.ui.window.Window
|
||||||
import androidx.compose.ui.window.WindowPosition
|
import androidx.compose.ui.window.WindowPosition
|
||||||
import androidx.compose.ui.window.rememberWindowState
|
import androidx.compose.ui.window.rememberWindowState
|
||||||
import java.awt.AWTEvent
|
import java.awt.AWTEvent
|
||||||
|
import java.awt.Point
|
||||||
import java.awt.SystemTray
|
import java.awt.SystemTray
|
||||||
import java.awt.Toolkit
|
import java.awt.Toolkit
|
||||||
import java.awt.TrayIcon
|
import java.awt.TrayIcon
|
||||||
import java.awt.event.AWTEventListener
|
import java.awt.event.AWTEventListener
|
||||||
import java.awt.event.MouseAdapter
|
import java.awt.event.MouseAdapter
|
||||||
import java.awt.event.MouseEvent
|
import java.awt.event.MouseEvent
|
||||||
|
import java.awt.event.WindowEvent
|
||||||
import java.awt.image.BufferedImage
|
import java.awt.image.BufferedImage
|
||||||
|
import javax.swing.JFrame
|
||||||
import javax.swing.SwingUtilities
|
import javax.swing.SwingUtilities
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
import ru.fromchat.ui.FromChatTheme
|
||||||
import ru.fromchat.ui.components.Text
|
import ru.fromchat.ui.components.Text
|
||||||
import ru.fromchat.ui.getColorScheme
|
|
||||||
import ru.fromchat.ui.isInsideWindow
|
import ru.fromchat.ui.isInsideWindow
|
||||||
|
|
||||||
|
internal enum class TrayConnectionStatus {
|
||||||
|
Connected,
|
||||||
|
Connecting,
|
||||||
|
Disconnected,
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
internal fun DesktopTrayHost(
|
internal fun DesktopTrayHost(
|
||||||
trayImage: BufferedImage,
|
trayImage: BufferedImage,
|
||||||
tooltip: String,
|
tooltip: String,
|
||||||
statusLabel: String,
|
statusLabel: String,
|
||||||
|
connectionStatus: TrayConnectionStatus,
|
||||||
showLabel: String,
|
showLabel: String,
|
||||||
aboutLabel: String,
|
aboutLabel: String,
|
||||||
quitLabel: String,
|
quitLabel: String,
|
||||||
darkTheme: Boolean,
|
|
||||||
onShow: () -> Unit,
|
onShow: () -> Unit,
|
||||||
onAbout: () -> Unit,
|
onAbout: () -> Unit,
|
||||||
onQuit: () -> Unit,
|
onQuit: () -> Unit,
|
||||||
) {
|
) {
|
||||||
var menuOpen by remember { mutableStateOf(false) }
|
var menuOpen by remember { mutableStateOf(false) }
|
||||||
var menuPosition by remember { mutableStateOf(WindowPosition(0.dp, 0.dp)) }
|
var menuAnchor by remember { mutableStateOf<Point?>(null) }
|
||||||
val closeMenu = rememberUpdatedState { menuOpen = false }
|
val closeMenu = rememberUpdatedState { menuOpen = false }
|
||||||
val onShowState = rememberUpdatedState(onShow)
|
val onShowState = rememberUpdatedState(onShow)
|
||||||
|
|
||||||
@@ -68,10 +101,7 @@ internal fun DesktopTrayHost(
|
|||||||
fun openMenu(event: MouseEvent) {
|
fun openMenu(event: MouseEvent) {
|
||||||
val screen = event.locationOnScreen
|
val screen = event.locationOnScreen
|
||||||
SwingUtilities.invokeLater {
|
SwingUtilities.invokeLater {
|
||||||
menuPosition = WindowPosition(
|
menuAnchor = screen
|
||||||
x = (screen.x - 220).coerceAtLeast(8).dp,
|
|
||||||
y = (screen.y - 180).coerceAtLeast(8).dp,
|
|
||||||
)
|
|
||||||
menuOpen = true
|
menuOpen = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -105,11 +135,15 @@ internal fun DesktopTrayHost(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (menuOpen) {
|
if (menuOpen) {
|
||||||
|
val density = LocalDensity.current
|
||||||
|
var contentSizePx by remember { mutableStateOf(IntSize.Zero) }
|
||||||
|
var showWindow by remember { mutableStateOf(false) }
|
||||||
val windowState = rememberWindowState(
|
val windowState = rememberWindowState(
|
||||||
position = menuPosition,
|
width = 220.dp,
|
||||||
width = 240.dp,
|
height = 260.dp,
|
||||||
height = 220.dp,
|
position = WindowPosition((-20_000).dp, (-20_000).dp),
|
||||||
)
|
)
|
||||||
|
|
||||||
Window(
|
Window(
|
||||||
onCloseRequest = { menuOpen = false },
|
onCloseRequest = { menuOpen = false },
|
||||||
state = windowState,
|
state = windowState,
|
||||||
@@ -119,9 +153,29 @@ internal fun DesktopTrayHost(
|
|||||||
resizable = false,
|
resizable = false,
|
||||||
alwaysOnTop = true,
|
alwaysOnTop = true,
|
||||||
focusable = true,
|
focusable = true,
|
||||||
|
onPreviewKeyEvent = { event ->
|
||||||
|
if (event.type == KeyEventType.KeyDown && event.key == Key.Escape) {
|
||||||
|
menuOpen = false
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
},
|
||||||
) {
|
) {
|
||||||
MaterialTheme(colorScheme = getColorScheme(darkTheme, dynamicColor = false)) {
|
val awtWindow = window
|
||||||
DisposableEffect(window) {
|
|
||||||
|
SideEffect {
|
||||||
|
if (!showWindow) {
|
||||||
|
awtWindow.isVisible = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DisposableEffect(awtWindow) {
|
||||||
|
showWindow = false
|
||||||
|
awtWindow.isVisible = false
|
||||||
|
awtWindow.setLocation(-20_000, -20_000)
|
||||||
|
|
||||||
|
val dismiss = { SwingUtilities.invokeLater { closeMenu.value() } }
|
||||||
val toolkit = Toolkit.getDefaultToolkit()
|
val toolkit = Toolkit.getDefaultToolkit()
|
||||||
var armed = false
|
var armed = false
|
||||||
val armTimer = javax.swing.Timer(200) { armed = true }.apply {
|
val armTimer = javax.swing.Timer(200) { armed = true }.apply {
|
||||||
@@ -135,77 +189,204 @@ internal fun DesktopTrayHost(
|
|||||||
if (!armed) return@AWTEventListener
|
if (!armed) return@AWTEventListener
|
||||||
val mouse = event as MouseEvent
|
val mouse = event as MouseEvent
|
||||||
if (mouse.button != MouseEvent.BUTTON1) return@AWTEventListener
|
if (mouse.button != MouseEvent.BUTTON1) return@AWTEventListener
|
||||||
if (mouse.isInsideWindow(window)) return@AWTEventListener
|
if (mouse.isInsideWindow(awtWindow)) return@AWTEventListener
|
||||||
SwingUtilities.invokeLater { closeMenu.value() }
|
dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var focusDismissArmed = false
|
||||||
|
val focusArmTimer = javax.swing.Timer(250) { focusDismissArmed = true }.apply {
|
||||||
|
isRepeats = false
|
||||||
|
start()
|
||||||
|
}
|
||||||
|
val focusListener = object : java.awt.event.WindowFocusListener {
|
||||||
|
override fun windowGainedFocus(event: WindowEvent) = Unit
|
||||||
|
|
||||||
|
override fun windowLostFocus(event: WindowEvent) {
|
||||||
|
if (focusDismissArmed) {
|
||||||
|
dismiss()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
toolkit.addAWTEventListener(dismissListener, AWTEvent.MOUSE_EVENT_MASK)
|
toolkit.addAWTEventListener(dismissListener, AWTEvent.MOUSE_EVENT_MASK)
|
||||||
|
awtWindow.addWindowFocusListener(focusListener)
|
||||||
onDispose {
|
onDispose {
|
||||||
armTimer.stop()
|
armTimer.stop()
|
||||||
|
focusArmTimer.stop()
|
||||||
toolkit.removeAWTEventListener(dismissListener)
|
toolkit.removeAWTEventListener(dismissListener)
|
||||||
|
awtWindow.removeWindowFocusListener(focusListener)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Column(
|
|
||||||
|
LaunchedEffect(contentSizePx, menuAnchor) {
|
||||||
|
val anchor = menuAnchor
|
||||||
|
if (anchor == null || contentSizePx.width < 80 || contentSizePx.height < 40) {
|
||||||
|
showWindow = false
|
||||||
|
awtWindow.isVisible = false
|
||||||
|
return@LaunchedEffect
|
||||||
|
}
|
||||||
|
val scale = density.density
|
||||||
|
val awtWidth = (contentSizePx.width / scale).roundToInt().coerceAtLeast(1)
|
||||||
|
val awtHeight = (contentSizePx.height / scale).roundToInt().coerceAtLeast(1)
|
||||||
|
val screen = awtWindow.graphicsConfiguration.bounds
|
||||||
|
val x = (anchor.x - awtWidth).coerceIn(
|
||||||
|
screen.x,
|
||||||
|
(screen.x + screen.width - awtWidth).coerceAtLeast(screen.x),
|
||||||
|
)
|
||||||
|
val y = (anchor.y - awtHeight).coerceIn(
|
||||||
|
screen.y,
|
||||||
|
(screen.y + screen.height - awtHeight).coerceAtLeast(screen.y),
|
||||||
|
)
|
||||||
|
awtWindow.setSize(awtWidth, awtHeight)
|
||||||
|
awtWindow.setLocation(x, y)
|
||||||
|
with(density) {
|
||||||
|
windowState.position = WindowPosition(
|
||||||
|
x = (x / density.density).dp,
|
||||||
|
y = (y / density.density).dp,
|
||||||
|
)
|
||||||
|
windowState.size = DpSize(
|
||||||
|
width = contentSizePx.width.toDp(),
|
||||||
|
height = contentSizePx.height.toDp(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
excludeFromWindowsTaskbar(awtWindow)
|
||||||
|
showWindow = true
|
||||||
|
awtWindow.isVisible = true
|
||||||
|
awtWindow.toFront()
|
||||||
|
awtWindow.requestFocus()
|
||||||
|
}
|
||||||
|
|
||||||
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.shadow(12.dp, RoundedCornerShape(12.dp))
|
.wrapContentSize(unbounded = true)
|
||||||
.clip(RoundedCornerShape(12.dp))
|
.onSizeChanged { size ->
|
||||||
.background(MaterialTheme.colorScheme.surfaceContainer)
|
if (size.width > 0 && size.height > 0) {
|
||||||
.padding(vertical = 6.dp)
|
contentSizePx = size
|
||||||
.widthIn(min = 220.dp),
|
}
|
||||||
|
},
|
||||||
) {
|
) {
|
||||||
DesktopTrayMenuRow(
|
FromChatTheme(darkTheme = desktopAppDarkTheme(), dynamicColor = false) {
|
||||||
text = statusLabel,
|
val surfaceColor = MaterialTheme.colorScheme.surfaceContainerHigh
|
||||||
enabled = false,
|
SideEffect {
|
||||||
onClick = {},
|
val awtColor = java.awt.Color(
|
||||||
|
surfaceColor.red,
|
||||||
|
surfaceColor.green,
|
||||||
|
surfaceColor.blue,
|
||||||
|
surfaceColor.alpha,
|
||||||
)
|
)
|
||||||
HorizontalDivider(
|
awtWindow.background = awtColor
|
||||||
modifier = Modifier.padding(vertical = 4.dp),
|
(awtWindow as? JFrame)?.contentPane?.background = awtColor
|
||||||
color = MaterialTheme.colorScheme.outline.copy(alpha = 0.25f),
|
}
|
||||||
)
|
DesktopTrayMenuContent(
|
||||||
DesktopTrayMenuRow(text = showLabel, onClick = {
|
statusLabel = statusLabel,
|
||||||
|
connectionStatus = connectionStatus,
|
||||||
|
showLabel = showLabel,
|
||||||
|
aboutLabel = aboutLabel,
|
||||||
|
quitLabel = quitLabel,
|
||||||
|
onShow = {
|
||||||
menuOpen = false
|
menuOpen = false
|
||||||
onShow()
|
onShow()
|
||||||
})
|
},
|
||||||
DesktopTrayMenuRow(text = aboutLabel, onClick = {
|
onAbout = {
|
||||||
menuOpen = false
|
menuOpen = false
|
||||||
onAbout()
|
onAbout()
|
||||||
})
|
},
|
||||||
HorizontalDivider(
|
onQuit = {
|
||||||
modifier = Modifier.padding(vertical = 4.dp),
|
|
||||||
color = MaterialTheme.colorScheme.outline.copy(alpha = 0.25f),
|
|
||||||
)
|
|
||||||
DesktopTrayMenuRow(text = quitLabel, onClick = {
|
|
||||||
menuOpen = false
|
menuOpen = false
|
||||||
onQuit()
|
onQuit()
|
||||||
})
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun DesktopTrayMenuContent(
|
||||||
|
statusLabel: String,
|
||||||
|
connectionStatus: TrayConnectionStatus,
|
||||||
|
showLabel: String,
|
||||||
|
aboutLabel: String,
|
||||||
|
quitLabel: String,
|
||||||
|
onShow: () -> Unit,
|
||||||
|
onAbout: () -> Unit,
|
||||||
|
onQuit: () -> Unit,
|
||||||
|
) {
|
||||||
|
val statusIcon = when (connectionStatus) {
|
||||||
|
TrayConnectionStatus.Connected -> Icons.Filled.CloudDone
|
||||||
|
TrayConnectionStatus.Connecting -> Icons.Filled.CloudSync
|
||||||
|
TrayConnectionStatus.Disconnected -> Icons.Filled.CloudOff
|
||||||
|
}
|
||||||
|
val statusTint = when (connectionStatus) {
|
||||||
|
TrayConnectionStatus.Connected -> MaterialTheme.colorScheme.primary
|
||||||
|
TrayConnectionStatus.Connecting -> MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
TrayConnectionStatus.Disconnected -> MaterialTheme.colorScheme.error
|
||||||
|
}
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.width(IntrinsicSize.Max)
|
||||||
|
.background(MaterialTheme.colorScheme.surfaceContainerHigh)
|
||||||
|
.border(1.dp, MaterialTheme.colorScheme.outlineVariant)
|
||||||
|
.padding(vertical = 2.dp),
|
||||||
|
) {
|
||||||
|
DesktopTrayMenuRow(
|
||||||
|
text = statusLabel,
|
||||||
|
icon = statusIcon,
|
||||||
|
iconTint = statusTint,
|
||||||
|
enabled = false,
|
||||||
|
onClick = {},
|
||||||
|
)
|
||||||
|
HorizontalDivider(color = MaterialTheme.colorScheme.outlineVariant)
|
||||||
|
DesktopTrayMenuRow(
|
||||||
|
text = showLabel,
|
||||||
|
icon = Icons.Filled.DesktopWindows,
|
||||||
|
onClick = onShow,
|
||||||
|
)
|
||||||
|
DesktopTrayMenuRow(
|
||||||
|
text = aboutLabel,
|
||||||
|
icon = Icons.Filled.Info,
|
||||||
|
onClick = onAbout,
|
||||||
|
)
|
||||||
|
HorizontalDivider(color = MaterialTheme.colorScheme.outlineVariant)
|
||||||
|
DesktopTrayMenuRow(
|
||||||
|
text = quitLabel,
|
||||||
|
icon = Icons.AutoMirrored.Filled.Logout,
|
||||||
|
textColor = MaterialTheme.colorScheme.error,
|
||||||
|
iconTint = MaterialTheme.colorScheme.error,
|
||||||
|
onClick = onQuit,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun DesktopTrayMenuRow(
|
private fun DesktopTrayMenuRow(
|
||||||
text: String,
|
text: String,
|
||||||
|
icon: ImageVector,
|
||||||
enabled: Boolean = true,
|
enabled: Boolean = true,
|
||||||
|
textColor: Color = MaterialTheme.colorScheme.onSurface,
|
||||||
|
iconTint: Color = textColor,
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
) {
|
) {
|
||||||
val interactionSource = remember { MutableInteractionSource() }
|
val interactionSource = remember { MutableInteractionSource() }
|
||||||
val hovered by interactionSource.collectIsHoveredAsState()
|
val hovered by interactionSource.collectIsHoveredAsState()
|
||||||
val textColor = when {
|
val resolvedTextColor = when {
|
||||||
!enabled -> MaterialTheme.colorScheme.onSurface.copy(alpha = 0.45f)
|
!enabled -> MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
else -> MaterialTheme.colorScheme.onSurface
|
else -> textColor
|
||||||
|
}
|
||||||
|
val resolvedIconTint = when {
|
||||||
|
!enabled -> iconTint.copy(alpha = 0.7f)
|
||||||
|
else -> iconTint
|
||||||
}
|
}
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.clip(RoundedCornerShape(8.dp))
|
|
||||||
.background(
|
.background(
|
||||||
if (enabled && hovered) {
|
if (enabled && hovered) {
|
||||||
MaterialTheme.colorScheme.onSurface.copy(alpha = 0.08f)
|
MaterialTheme.colorScheme.onSurface.copy(alpha = 0.08f)
|
||||||
} else {
|
} else {
|
||||||
androidx.compose.ui.graphics.Color.Transparent
|
Color.Transparent
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.hoverable(interactionSource, enabled = enabled)
|
.hoverable(interactionSource, enabled = enabled)
|
||||||
@@ -215,13 +396,20 @@ private fun DesktopTrayMenuRow(
|
|||||||
enabled = enabled,
|
enabled = enabled,
|
||||||
onClick = onClick,
|
onClick = onClick,
|
||||||
)
|
)
|
||||||
.padding(horizontal = 14.dp, vertical = 10.dp),
|
.padding(horizontal = 10.dp, vertical = 7.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(10.dp),
|
||||||
) {
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = icon,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(18.dp),
|
||||||
|
tint = resolvedIconTint,
|
||||||
|
)
|
||||||
Text(
|
Text(
|
||||||
text = text,
|
text = text,
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
color = textColor,
|
color = resolvedTextColor,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package ru.fromchat.desktop
|
package ru.fromchat.desktop
|
||||||
|
|
||||||
|
import ru.fromchat.config.Settings
|
||||||
|
import ru.fromchat.ui.Theme
|
||||||
import javax.swing.JRootPane
|
import javax.swing.JRootPane
|
||||||
|
|
||||||
internal fun isMacOs(): Boolean =
|
internal fun isMacOs(): Boolean =
|
||||||
@@ -14,6 +16,48 @@ internal fun isWindowsArm64(): Boolean {
|
|||||||
return arch.contains("aarch64") || arch.contains("arm64")
|
return arch.contains("aarch64") || arch.contains("arm64")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal fun desktopAppDarkTheme(): Boolean =
|
||||||
|
when (runCatching { Settings.theme }.getOrDefault(Theme.AsSystem)) {
|
||||||
|
Theme.Dark -> true
|
||||||
|
Theme.Light -> false
|
||||||
|
Theme.AsSystem -> desktopSystemDarkTheme()
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun desktopSystemDarkTheme(): Boolean = runCatching {
|
||||||
|
when {
|
||||||
|
isMacOs() ->
|
||||||
|
ProcessBuilder("defaults", "read", "-g", "AppleInterfaceStyle")
|
||||||
|
.redirectErrorStream(true)
|
||||||
|
.start()
|
||||||
|
.inputStream
|
||||||
|
.bufferedReader()
|
||||||
|
.readText()
|
||||||
|
.trim()
|
||||||
|
.equals("Dark", ignoreCase = true)
|
||||||
|
isWindowsOs() -> isWindowsAppsDarkTheme()
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
}.getOrDefault(false)
|
||||||
|
|
||||||
|
private fun isWindowsAppsDarkTheme(): Boolean {
|
||||||
|
val process = ProcessBuilder(
|
||||||
|
"reg",
|
||||||
|
"query",
|
||||||
|
"HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize",
|
||||||
|
"/v",
|
||||||
|
"AppsUseLightTheme",
|
||||||
|
)
|
||||||
|
.redirectErrorStream(true)
|
||||||
|
.start()
|
||||||
|
val output = process.inputStream.bufferedReader().readText()
|
||||||
|
process.waitFor()
|
||||||
|
return when {
|
||||||
|
"0x0" in output -> true
|
||||||
|
"0x1" in output -> false
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** Enables drawing under the system title bar on macOS. */
|
/** Enables drawing under the system title bar on macOS. */
|
||||||
internal fun applyDesktopEdgeToEdgeChrome(rootPane: JRootPane) {
|
internal fun applyDesktopEdgeToEdgeChrome(rootPane: JRootPane) {
|
||||||
if (!isMacOs()) return
|
if (!isMacOs()) return
|
||||||
|
|||||||
@@ -357,12 +357,17 @@ fun main(args: Array<String>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Mirror ConnectionStateStore + WebSocketManager.isConnected (+ logged-out → disconnected).
|
// Mirror ConnectionStateStore + WebSocketManager.isConnected (+ logged-out → disconnected).
|
||||||
val wsStatusLabel = when {
|
val trayConnectionStatus = when {
|
||||||
connectionStatus == ConnectionStatus.CONNECTED ||
|
connectionStatus == ConnectionStatus.CONNECTED ||
|
||||||
connectionStatus == ConnectionStatus.UPDATING ||
|
connectionStatus == ConnectionStatus.UPDATING ||
|
||||||
wsLinked -> stringResource(Res.string.status_connected)
|
wsLinked -> TrayConnectionStatus.Connected
|
||||||
ApiClient.token.isNullOrEmpty() -> stringResource(Res.string.status_disconnected)
|
ApiClient.token.isNullOrEmpty() -> TrayConnectionStatus.Disconnected
|
||||||
else -> stringResource(Res.string.status_connecting)
|
else -> TrayConnectionStatus.Connecting
|
||||||
|
}
|
||||||
|
val wsStatusLabel = when (trayConnectionStatus) {
|
||||||
|
TrayConnectionStatus.Connected -> stringResource(Res.string.status_connected)
|
||||||
|
TrayConnectionStatus.Disconnected -> stringResource(Res.string.status_disconnected)
|
||||||
|
TrayConnectionStatus.Connecting -> stringResource(Res.string.status_connecting)
|
||||||
}
|
}
|
||||||
|
|
||||||
val windowChrome = remember { desktopThemeBackgroundCompose() }
|
val windowChrome = remember { desktopThemeBackgroundCompose() }
|
||||||
@@ -498,11 +503,6 @@ fun main(args: Array<String>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (traySupported) {
|
if (traySupported) {
|
||||||
val darkTheme = when (runCatching { Settings.theme }.getOrDefault(Theme.AsSystem)) {
|
|
||||||
Theme.Dark -> true
|
|
||||||
Theme.Light -> false
|
|
||||||
Theme.AsSystem -> isSystemAppearanceDark()
|
|
||||||
}
|
|
||||||
if (mac) {
|
if (mac) {
|
||||||
Tray(
|
Tray(
|
||||||
icon = trayIcon,
|
icon = trayIcon,
|
||||||
@@ -523,10 +523,10 @@ fun main(args: Array<String>) {
|
|||||||
trayImage = trayIconImage,
|
trayImage = trayIconImage,
|
||||||
tooltip = appName,
|
tooltip = appName,
|
||||||
statusLabel = wsStatusLabel,
|
statusLabel = wsStatusLabel,
|
||||||
|
connectionStatus = trayConnectionStatus,
|
||||||
showLabel = trayShow,
|
showLabel = trayShow,
|
||||||
aboutLabel = aboutApp,
|
aboutLabel = aboutApp,
|
||||||
quitLabel = quit,
|
quitLabel = quit,
|
||||||
darkTheme = darkTheme,
|
|
||||||
onShow = { showMainWindow() },
|
onShow = { showMainWindow() },
|
||||||
onAbout = { openAbout() },
|
onAbout = { openAbout() },
|
||||||
onQuit = { exitApplication() },
|
onQuit = { exitApplication() },
|
||||||
@@ -690,12 +690,7 @@ fun main(args: Array<String>) {
|
|||||||
) {
|
) {
|
||||||
App(onContentReady = { contentReady = true })
|
App(onContentReady = { contentReady = true })
|
||||||
if (windows) {
|
if (windows) {
|
||||||
val darkTheme = when (runCatching { Settings.theme }.getOrDefault(Theme.AsSystem)) {
|
MaterialTheme(colorScheme = getColorScheme(desktopAppDarkTheme(), dynamicColor = false)) {
|
||||||
Theme.Dark -> true
|
|
||||||
Theme.Light -> false
|
|
||||||
Theme.AsSystem -> isSystemAppearanceDark()
|
|
||||||
}
|
|
||||||
MaterialTheme(colorScheme = getColorScheme(darkTheme, dynamicColor = false)) {
|
|
||||||
WindowsDesktopTitleBar(
|
WindowsDesktopTitleBar(
|
||||||
title = appName,
|
title = appName,
|
||||||
windowIcon = windowIcon,
|
windowIcon = windowIcon,
|
||||||
@@ -841,29 +836,12 @@ private fun applyDesktopWindowChromeBackground() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun desktopThemeBackgroundCompose() =
|
private fun desktopThemeBackgroundCompose() =
|
||||||
if (
|
if (desktopAppDarkTheme()) Color(0xFF1C1B1F) else Color(0xFFFFFBFE)
|
||||||
when (runCatching { Settings.theme }.getOrDefault(Theme.AsSystem)) {
|
|
||||||
Theme.Dark -> true
|
|
||||||
Theme.Light -> false
|
|
||||||
Theme.AsSystem -> isSystemAppearanceDark()
|
|
||||||
}
|
|
||||||
) Color(0xFF1C1B1F) else Color(0xFFFFFBFE)
|
|
||||||
|
|
||||||
private fun Color.toAwtColor() =
|
private fun Color.toAwtColor() =
|
||||||
java.awt.Color(red, green, blue, alpha)
|
java.awt.Color(red, green, blue, alpha)
|
||||||
|
|
||||||
private fun isSystemAppearanceDark() = runCatching {
|
private fun isSystemAppearanceDark() = desktopSystemDarkTheme()
|
||||||
if (isMacOs()) {
|
|
||||||
ProcessBuilder("defaults", "read", "-g", "AppleInterfaceStyle")
|
|
||||||
.redirectErrorStream(true)
|
|
||||||
.start()
|
|
||||||
.inputStream
|
|
||||||
.bufferedReader()
|
|
||||||
.readText()
|
|
||||||
.trim()
|
|
||||||
.equals("Dark", ignoreCase = true)
|
|
||||||
} else false
|
|
||||||
}.getOrDefault(false)
|
|
||||||
|
|
||||||
private fun applyDockIcon(image: BufferedImage?) {
|
private fun applyDockIcon(image: BufferedImage?) {
|
||||||
if (image == null) {
|
if (image == null) {
|
||||||
|
|||||||
@@ -51,6 +51,36 @@ internal fun updateWindowsNativeCaptionBackground(window: Window, background: Co
|
|||||||
(frame.rootPane.getClientProperty(ChromeKey) as? WindowsCaptionWndProc)?.background = background
|
(frame.rootPane.getClientProperty(ChromeKey) as? WindowsCaptionWndProc)?.background = background
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private const val GwlExStyle = -20
|
||||||
|
private const val WsExToolWindow = 0x00000080
|
||||||
|
private const val WsExAppWindow = 0x00040000
|
||||||
|
|
||||||
|
/** Keeps transient popups (tray menu, etc.) out of the Windows taskbar and Alt+Tab. */
|
||||||
|
internal fun excludeFromWindowsTaskbar(window: Window) {
|
||||||
|
if (!isWindowsOs()) return
|
||||||
|
val hwnd = window.windowsHwnd()
|
||||||
|
if (hwnd.pointer == null) return
|
||||||
|
val exStyle = User32.INSTANCE.GetWindowLong(hwnd, GwlExStyle)
|
||||||
|
User32.INSTANCE.SetWindowLong(
|
||||||
|
hwnd,
|
||||||
|
GwlExStyle,
|
||||||
|
(exStyle or WsExToolWindow) and WsExAppWindow.inv(),
|
||||||
|
)
|
||||||
|
User32.INSTANCE.SetWindowPos(
|
||||||
|
hwnd,
|
||||||
|
null,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
WinUser.SWP_NOMOVE or
|
||||||
|
WinUser.SWP_NOSIZE or
|
||||||
|
WinUser.SWP_NOZORDER or
|
||||||
|
WinUser.SWP_NOACTIVATE or
|
||||||
|
WinUser.SWP_FRAMECHANGED,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
internal fun Window.windowsHwnd(): HWND {
|
internal fun Window.windowsHwnd(): HWND {
|
||||||
val handle = (this as? ComposeWindow)?.windowHandle ?: 0L
|
val handle = (this as? ComposeWindow)?.windowHandle ?: 0L
|
||||||
return if (handle != 0L) {
|
return if (handle != 0L) {
|
||||||
|
|||||||
@@ -68,7 +68,11 @@ kotlin {
|
|||||||
android {
|
android {
|
||||||
namespace = "ru.fromchat.shared"
|
namespace = "ru.fromchat.shared"
|
||||||
minSdk = 24
|
minSdk = 24
|
||||||
compileSdk = 37
|
compileSdk {
|
||||||
|
version = release(37) {
|
||||||
|
minorApiLevel = 2
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
jvmToolchain(17)
|
jvmToolchain(17)
|
||||||
|
|||||||
@@ -92,10 +92,10 @@ import androidx.compose.ui.unit.dp
|
|||||||
import androidx.compose.ui.zIndex
|
import androidx.compose.ui.zIndex
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
|
import dev.chrisbanes.haze.HazeInput
|
||||||
import dev.chrisbanes.haze.HazeState
|
import dev.chrisbanes.haze.HazeState
|
||||||
import dev.chrisbanes.haze.blur.blurEffect
|
import dev.chrisbanes.haze.blur.hazeBlur
|
||||||
import dev.chrisbanes.haze.blur.materials.HazeMaterials
|
import dev.chrisbanes.haze.blur.materials.HazeMaterials
|
||||||
import dev.chrisbanes.haze.hazeEffect
|
|
||||||
import dev.chrisbanes.haze.hazeSource
|
import dev.chrisbanes.haze.hazeSource
|
||||||
import dev.chrisbanes.haze.rememberHazeState
|
import dev.chrisbanes.haze.rememberHazeState
|
||||||
import io.livekit.android.RoomOptions
|
import io.livekit.android.RoomOptions
|
||||||
@@ -1221,16 +1221,11 @@ private fun PreviewTile(
|
|||||||
innerClipShape = tileShape,
|
innerClipShape = tileShape,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
val placeholderHazeStyle = HazeMaterials.thick()
|
|
||||||
Box(
|
Box(
|
||||||
Modifier
|
Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
// Placeholder blur only for missing/disabled track.
|
// Placeholder blur only for missing/disabled track.
|
||||||
.hazeEffect(state = hazeState) {
|
.hazeBlur(input = HazeInput.Backdrop(hazeState), style = HazeMaterials.thick())
|
||||||
blurEffect {
|
|
||||||
style = placeholderHazeStyle
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.background(MaterialTheme.colorScheme.surfaceContainerLow.copy(alpha = 0.55f)),
|
.background(MaterialTheme.colorScheme.surfaceContainerLow.copy(alpha = 0.55f)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -1329,16 +1324,11 @@ private fun CallInlineControlBar(
|
|||||||
modifier = modifier.fillMaxWidth(),
|
modifier = modifier.fillMaxWidth(),
|
||||||
contentAlignment = Alignment.Center,
|
contentAlignment = Alignment.Center,
|
||||||
) {
|
) {
|
||||||
val controlsHazeStyle = HazeMaterials.thick()
|
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth(0.86f)
|
.fillMaxWidth(0.86f)
|
||||||
.clip(RoundedCornerShape(28.dp))
|
.clip(RoundedCornerShape(28.dp))
|
||||||
.hazeEffect(state = hazeState) {
|
.hazeBlur(input = HazeInput.Backdrop(hazeState), style = HazeMaterials.thick())
|
||||||
blurEffect {
|
|
||||||
style = controlsHazeStyle
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.background(MaterialTheme.colorScheme.surfaceContainerHigh.copy(alpha = 0.48f)),
|
.background(MaterialTheme.colorScheme.surfaceContainerHigh.copy(alpha = 0.48f)),
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
|
|||||||
@@ -54,8 +54,8 @@ import androidx.compose.ui.text.style.TextOverflow
|
|||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.pr0gramm3r101.utils.ToggleNavScrimEffect
|
import com.pr0gramm3r101.utils.ToggleNavScrimEffect
|
||||||
import dev.chrisbanes.haze.blur.blurEffect
|
import dev.chrisbanes.haze.HazeInput
|
||||||
import dev.chrisbanes.haze.hazeEffect
|
import dev.chrisbanes.haze.blur.hazeBlur
|
||||||
import dev.chrisbanes.haze.hazeSource
|
import dev.chrisbanes.haze.hazeSource
|
||||||
import dev.chrisbanes.haze.rememberHazeState
|
import dev.chrisbanes.haze.rememberHazeState
|
||||||
import org.jetbrains.compose.resources.stringResource
|
import org.jetbrains.compose.resources.stringResource
|
||||||
@@ -118,7 +118,6 @@ fun DocumentScreen(
|
|||||||
contentColor = MaterialTheme.colorScheme.onSurface,
|
contentColor = MaterialTheme.colorScheme.onSurface,
|
||||||
contentWindowInsets = WindowInsets.navigationBars,
|
contentWindowInsets = WindowInsets.navigationBars,
|
||||||
topBar = {
|
topBar = {
|
||||||
val topBarHazeStyle = rememberChatSurfaceContainerHazeStyle()
|
|
||||||
MediumTopAppBar(
|
MediumTopAppBar(
|
||||||
windowInsets = WindowInsets.extraStatusBars,
|
windowInsets = WindowInsets.extraStatusBars,
|
||||||
title = {
|
title = {
|
||||||
@@ -145,11 +144,10 @@ fun DocumentScreen(
|
|||||||
),
|
),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.background(MaterialTheme.colorScheme.surfaceContainer.copy(alpha = 0.91f))
|
.background(MaterialTheme.colorScheme.surfaceContainer.copy(alpha = 0.91f))
|
||||||
.hazeEffect(state = hazeState) {
|
.hazeBlur(
|
||||||
blurEffect {
|
input = HazeInput.Backdrop(hazeState),
|
||||||
style = topBarHazeStyle
|
style = rememberChatSurfaceContainerHazeStyle(),
|
||||||
}
|
),
|
||||||
},
|
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
) { innerPadding ->
|
) { innerPadding ->
|
||||||
|
|||||||
@@ -66,9 +66,9 @@ import androidx.compose.ui.unit.IntSize
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import coil3.compose.AsyncImage
|
import coil3.compose.AsyncImage
|
||||||
import com.pr0gramm3r101.utils.crypto.Base64
|
import com.pr0gramm3r101.utils.crypto.Base64
|
||||||
import dev.chrisbanes.haze.blur.blurEffect
|
import dev.chrisbanes.haze.HazeInput
|
||||||
|
import dev.chrisbanes.haze.blur.hazeBlur
|
||||||
import dev.chrisbanes.haze.blur.materials.HazeMaterials
|
import dev.chrisbanes.haze.blur.materials.HazeMaterials
|
||||||
import dev.chrisbanes.haze.hazeEffect
|
|
||||||
import dev.chrisbanes.haze.hazeSource
|
import dev.chrisbanes.haze.hazeSource
|
||||||
import dev.chrisbanes.haze.rememberHazeState
|
import dev.chrisbanes.haze.rememberHazeState
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
@@ -784,11 +784,10 @@ private fun ChatImageTileContent(
|
|||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.hazeEffect(state = thumbHazeState) {
|
.hazeBlur(
|
||||||
blurEffect {
|
input = HazeInput.Backdrop(thumbHazeState),
|
||||||
style = thumbOverlayHazeStyle
|
style = thumbOverlayHazeStyle,
|
||||||
}
|
),
|
||||||
},
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -917,14 +916,9 @@ private fun DownloadCancelledImageOverlay(
|
|||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val scrim = MaterialTheme.colorScheme.scrim.copy(alpha = 0.38f)
|
val scrim = MaterialTheme.colorScheme.scrim.copy(alpha = 0.38f)
|
||||||
val overlayHazeStyle = HazeMaterials.thin()
|
|
||||||
Box(
|
Box(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.hazeEffect {
|
.hazeBlur(input = HazeInput.Content, style = HazeMaterials.thin())
|
||||||
blurEffect {
|
|
||||||
style = overlayHazeStyle
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.background(MaterialTheme.colorScheme.scrim.copy(alpha = 0.12f)),
|
.background(MaterialTheme.colorScheme.scrim.copy(alpha = 0.12f)),
|
||||||
contentAlignment = Alignment.Center,
|
contentAlignment = Alignment.Center,
|
||||||
) {
|
) {
|
||||||
@@ -964,17 +958,12 @@ private fun UploadingImageOverlay(
|
|||||||
onCancelUpload: (() -> Unit)? = null,
|
onCancelUpload: (() -> Unit)? = null,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val overlayHazeStyle = HazeMaterials.thin()
|
|
||||||
Box(modifier = modifier) {
|
Box(modifier = modifier) {
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.matchParentSize()
|
.matchParentSize()
|
||||||
.clip(clipShape)
|
.clip(clipShape)
|
||||||
.hazeEffect {
|
.hazeBlur(input = HazeInput.Content, style = HazeMaterials.thin())
|
||||||
blurEffect {
|
|
||||||
style = overlayHazeStyle
|
|
||||||
}
|
|
||||||
}
|
|
||||||
) {
|
) {
|
||||||
when {
|
when {
|
||||||
previewBitmap != null -> {
|
previewBitmap != null -> {
|
||||||
@@ -1259,15 +1248,10 @@ private fun CorruptedImagePlaceholder(
|
|||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
clipShape: RoundedCornerShape = attachmentImageCornerShape(isAuthor = false),
|
clipShape: RoundedCornerShape = attachmentImageCornerShape(isAuthor = false),
|
||||||
) {
|
) {
|
||||||
val overlayHazeStyle = HazeMaterials.thin()
|
|
||||||
Box(
|
Box(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.clip(clipShape)
|
.clip(clipShape)
|
||||||
.hazeEffect {
|
.hazeBlur(input = HazeInput.Content, style = HazeMaterials.thin()),
|
||||||
blurEffect {
|
|
||||||
style = overlayHazeStyle
|
|
||||||
}
|
|
||||||
},
|
|
||||||
contentAlignment = Alignment.Center,
|
contentAlignment = Alignment.Center,
|
||||||
) {
|
) {
|
||||||
Icon(
|
Icon(
|
||||||
|
|||||||
@@ -93,9 +93,9 @@ import androidx.compose.ui.unit.dp
|
|||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import coil3.compose.AsyncImage
|
import coil3.compose.AsyncImage
|
||||||
import com.pr0gramm3r101.utils.conditional
|
import com.pr0gramm3r101.utils.conditional
|
||||||
|
import dev.chrisbanes.haze.HazeInput
|
||||||
import dev.chrisbanes.haze.HazeState
|
import dev.chrisbanes.haze.HazeState
|
||||||
import dev.chrisbanes.haze.blur.blurEffect
|
import dev.chrisbanes.haze.blur.hazeBlur
|
||||||
import dev.chrisbanes.haze.hazeEffect
|
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.jetbrains.compose.resources.stringResource
|
import org.jetbrains.compose.resources.stringResource
|
||||||
@@ -323,7 +323,7 @@ fun ChatInput(
|
|||||||
attachmentDropBridge: AttachmentDropBridge,
|
attachmentDropBridge: AttachmentDropBridge,
|
||||||
pendingDropUris: List<String> = emptyList(),
|
pendingDropUris: List<String> = emptyList(),
|
||||||
) {
|
) {
|
||||||
val composerHazeStyle = rememberChatSurfaceContainerHazeStyle()
|
val composerHazeStyle = rememberChatSurfaceContainerHazeStyle().then { blurEnabled(hazeBlurEnabled) }
|
||||||
val dropScrimColor = lerp(MaterialTheme.colorScheme.primary, Color.Black, 0.62f)
|
val dropScrimColor = lerp(MaterialTheme.colorScheme.primary, Color.Black, 0.62f)
|
||||||
val attachmentDropEnabled = supportsAttachments && !isReadOnly
|
val attachmentDropEnabled = supportsAttachments && !isReadOnly
|
||||||
val dropHighlightActive =
|
val dropHighlightActive =
|
||||||
@@ -450,12 +450,7 @@ fun ChatInput(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.clip(pillShape)
|
.clip(pillShape)
|
||||||
.hazeEffect(state = hazeState) {
|
.hazeBlur(input = HazeInput.Backdrop(hazeState), style = composerHazeStyle)
|
||||||
blurEffect {
|
|
||||||
blurEnabled = hazeBlurEnabled
|
|
||||||
style = composerHazeStyle
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.clickable(enabled = true) { onReadOnlyMessageClick() },
|
.clickable(enabled = true) { onReadOnlyMessageClick() },
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
@@ -499,12 +494,7 @@ fun ChatInput(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.background(MaterialTheme.colorScheme.surfaceContainer, pillShape)
|
.background(MaterialTheme.colorScheme.surfaceContainer, pillShape)
|
||||||
.hazeEffect(state = hazeState) {
|
.hazeBlur(input = HazeInput.Backdrop(hazeState), style = composerHazeStyle)
|
||||||
blurEffect {
|
|
||||||
blurEnabled = hazeBlurEnabled
|
|
||||||
style = composerHazeStyle
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.conditional(dropBlurRadius > 0.dp) {
|
.conditional(dropBlurRadius > 0.dp) {
|
||||||
blur(dropBlurRadius, BlurredEdgeTreatment.Unbounded)
|
blur(dropBlurRadius, BlurredEdgeTreatment.Unbounded)
|
||||||
},
|
},
|
||||||
@@ -753,15 +743,12 @@ fun ChatInput(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.background(actionBackground)
|
.background(actionBackground)
|
||||||
.hazeEffect(state = hazeState) {
|
.hazeBlur(
|
||||||
blurEffect {
|
input = HazeInput.Backdrop(hazeState),
|
||||||
// Keep the node in the tree in both directions; fading alpha avoids the
|
// Keep the node in the tree in both directions; fading alpha avoids the
|
||||||
// frosted layer popping on over the color when send -> voice.
|
// frosted layer popping on over the color when send -> voice.
|
||||||
blurEnabled = hazeBlurEnabled
|
style = composerHazeStyle.then { alpha(hazeOverlayAlpha) },
|
||||||
style = composerHazeStyle
|
),
|
||||||
alpha = hazeOverlayAlpha
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
AnimatedContent(
|
AnimatedContent(
|
||||||
targetState = canSend,
|
targetState = canSend,
|
||||||
|
|||||||
@@ -81,11 +81,11 @@ import ru.fromchat.chat_date_yesterday
|
|||||||
import ru.fromchat.utils.rememberRegistrationDateFormatStrings
|
import ru.fromchat.utils.rememberRegistrationDateFormatStrings
|
||||||
import com.pr0gramm3r101.utils.resetFocus
|
import com.pr0gramm3r101.utils.resetFocus
|
||||||
import com.pr0gramm3r101.utils.supportClipboardManagerImpl
|
import com.pr0gramm3r101.utils.supportClipboardManagerImpl
|
||||||
|
import dev.chrisbanes.haze.HazeInput
|
||||||
|
import dev.chrisbanes.haze.HazeProgressive
|
||||||
import dev.chrisbanes.haze.HazeState
|
import dev.chrisbanes.haze.HazeState
|
||||||
import dev.chrisbanes.haze.blur.HazeProgressive
|
import dev.chrisbanes.haze.blur.hazeBlur
|
||||||
import dev.chrisbanes.haze.blur.blurEffect
|
|
||||||
import dev.chrisbanes.haze.blur.materials.HazeMaterials
|
import dev.chrisbanes.haze.blur.materials.HazeMaterials
|
||||||
import dev.chrisbanes.haze.hazeEffect
|
|
||||||
import dev.chrisbanes.haze.hazeSource
|
import dev.chrisbanes.haze.hazeSource
|
||||||
import dev.chrisbanes.haze.rememberHazeState
|
import dev.chrisbanes.haze.rememberHazeState
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
@@ -876,22 +876,23 @@ fun ChatScreen(
|
|||||||
// floating header Box only; avoids extra top inset on the fade / list and duplicate “padding”.
|
// floating header Box only; avoids extra top inset on the fade / list and duplicate “padding”.
|
||||||
contentWindowInsets = WindowInsets.navigationBars,
|
contentWindowInsets = WindowInsets.navigationBars,
|
||||||
bottomBar = {
|
bottomBar = {
|
||||||
val bottomBarHazeStyle = HazeMaterials.thin()
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.windowInsetsPadding(WindowInsets.ime)
|
.windowInsetsPadding(WindowInsets.ime)
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.background(MaterialTheme.colorScheme.surfaceContainer)
|
.background(MaterialTheme.colorScheme.surfaceContainer)
|
||||||
.hazeEffect(state = hazeState) {
|
.hazeBlur(
|
||||||
blurEffect {
|
input = HazeInput.Backdrop(hazeState),
|
||||||
blurEnabled = hazeBlurEnabled
|
style = HazeMaterials.thin().then {
|
||||||
style = bottomBarHazeStyle
|
blurEnabled(hazeBlurEnabled)
|
||||||
progressive = HazeProgressive.verticalGradient(
|
progressive(
|
||||||
|
HazeProgressive.verticalGradient(
|
||||||
startIntensity = 0f,
|
startIntensity = 0f,
|
||||||
endIntensity = 1f,
|
endIntensity = 1f,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
|
||||||
) {
|
) {
|
||||||
if (peerDeleted && dmRecipientId != null) {
|
if (peerDeleted && dmRecipientId != null) {
|
||||||
Box(
|
Box(
|
||||||
@@ -1726,7 +1727,6 @@ private fun ChatScrollToBottomButton(
|
|||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
hazeBlurEnabled: Boolean = true,
|
hazeBlurEnabled: Boolean = true,
|
||||||
) {
|
) {
|
||||||
val hazeStyle = rememberChatSurfaceContainerHazeStyle()
|
|
||||||
Box(
|
Box(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.size(ChatScrollToBottomButtonSize)
|
.size(ChatScrollToBottomButtonSize)
|
||||||
@@ -1740,12 +1740,10 @@ private fun ChatScrollToBottomButton(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.background(MaterialTheme.colorScheme.surfaceContainer.copy(alpha = 0.91f))
|
.background(MaterialTheme.colorScheme.surfaceContainer.copy(alpha = 0.91f))
|
||||||
.hazeEffect(state = hazeState) {
|
.hazeBlur(
|
||||||
blurEffect {
|
input = HazeInput.Backdrop(hazeState),
|
||||||
blurEnabled = hazeBlurEnabled
|
style = rememberChatSurfaceContainerHazeStyle().then { blurEnabled(hazeBlurEnabled) },
|
||||||
style = hazeStyle
|
),
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Rounded.KeyboardArrowDown,
|
imageVector = Icons.Rounded.KeyboardArrowDown,
|
||||||
|
|||||||
@@ -62,13 +62,13 @@ import androidx.compose.ui.unit.Dp
|
|||||||
import androidx.compose.ui.unit.LayoutDirection
|
import androidx.compose.ui.unit.LayoutDirection
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.pr0gramm3r101.utils.conditional
|
import com.pr0gramm3r101.utils.conditional
|
||||||
|
import dev.chrisbanes.haze.HazeInput
|
||||||
|
import dev.chrisbanes.haze.HazeProgressive
|
||||||
import dev.chrisbanes.haze.HazeState
|
import dev.chrisbanes.haze.HazeState
|
||||||
import dev.chrisbanes.haze.blur.HazeBlurStyle
|
import dev.chrisbanes.haze.blur.HazeBlurStyle
|
||||||
import dev.chrisbanes.haze.blur.HazeColorEffect
|
import dev.chrisbanes.haze.blur.HazeColorEffect
|
||||||
import dev.chrisbanes.haze.blur.HazeProgressive
|
import dev.chrisbanes.haze.blur.hazeBlur
|
||||||
import dev.chrisbanes.haze.blur.blurEffect
|
|
||||||
import dev.chrisbanes.haze.blur.materials.HazeMaterials
|
import dev.chrisbanes.haze.blur.materials.HazeMaterials
|
||||||
import dev.chrisbanes.haze.hazeEffect
|
|
||||||
import org.jetbrains.compose.resources.stringResource
|
import org.jetbrains.compose.resources.stringResource
|
||||||
import ru.fromchat.Res
|
import ru.fromchat.Res
|
||||||
import ru.fromchat.chat_members_count
|
import ru.fromchat.chat_members_count
|
||||||
@@ -397,7 +397,6 @@ fun ChatTopBar(
|
|||||||
val layoutHeight = topBarPlaceable.height + arcExtentPx
|
val layoutHeight = topBarPlaceable.height + arcExtentPx
|
||||||
|
|
||||||
val bgPlaceable = subcompose("background") {
|
val bgPlaceable = subcompose("background") {
|
||||||
val hazeStyle = rememberChatSurfaceContainerHazeStyle()
|
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
@@ -407,12 +406,10 @@ fun ChatTopBar(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
.background(MaterialTheme.colorScheme.surfaceContainer)
|
.background(MaterialTheme.colorScheme.surfaceContainer)
|
||||||
.hazeEffect(state = hazeState) {
|
.hazeBlur(
|
||||||
blurEffect {
|
input = HazeInput.Backdrop(hazeState),
|
||||||
blurEnabled = hazeBlurEnabled
|
style = rememberChatSurfaceContainerHazeStyle().then { blurEnabled(hazeBlurEnabled) },
|
||||||
style = hazeStyle
|
),
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
}.first().measure(Constraints.fixed(layoutWidth, layoutHeight))
|
}.first().measure(Constraints.fixed(layoutWidth, layoutHeight))
|
||||||
|
|
||||||
@@ -443,8 +440,7 @@ private fun ChatTopBarPill(
|
|||||||
hazeBlurEnabled: Boolean = true,
|
hazeBlurEnabled: Boolean = true,
|
||||||
showBackButton: Boolean = true,
|
showBackButton: Boolean = true,
|
||||||
) {
|
) {
|
||||||
val hazeStyle = rememberChatSurfaceContainerHazeStyle()
|
val hazeStyle = rememberChatSurfaceContainerHazeStyle().then { blurEnabled(hazeBlurEnabled) }
|
||||||
val progressiveStripHazeStyle = HazeMaterials.thin()
|
|
||||||
val chromeColor = MaterialTheme.colorScheme.surfaceContainer
|
val chromeColor = MaterialTheme.colorScheme.surfaceContainer
|
||||||
val pillShape = RoundedCornerShape(28.dp)
|
val pillShape = RoundedCornerShape(28.dp)
|
||||||
|
|
||||||
@@ -453,16 +449,18 @@ private fun ChatTopBarPill(
|
|||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.matchParentSize()
|
.matchParentSize()
|
||||||
.hazeEffect(state = hazeState) {
|
.hazeBlur(
|
||||||
blurEffect {
|
input = HazeInput.Backdrop(hazeState),
|
||||||
blurEnabled = hazeBlurEnabled
|
style = HazeMaterials.thin().then {
|
||||||
style = progressiveStripHazeStyle
|
blurEnabled(hazeBlurEnabled)
|
||||||
progressive = HazeProgressive.verticalGradient(
|
progressive(
|
||||||
|
HazeProgressive.verticalGradient(
|
||||||
startIntensity = 1f,
|
startIntensity = 1f,
|
||||||
endIntensity = 0f,
|
endIntensity = 0f,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
),
|
||||||
)
|
)
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@@ -478,12 +476,7 @@ private fun ChatTopBarPill(
|
|||||||
.size(48.dp)
|
.size(48.dp)
|
||||||
.clip(CircleShape)
|
.clip(CircleShape)
|
||||||
.background(chromeColor)
|
.background(chromeColor)
|
||||||
.hazeEffect(state = hazeState) {
|
.hazeBlur(input = HazeInput.Backdrop(hazeState), style = hazeStyle),
|
||||||
blurEffect {
|
|
||||||
blurEnabled = hazeBlurEnabled
|
|
||||||
style = hazeStyle
|
|
||||||
}
|
|
||||||
},
|
|
||||||
contentAlignment = Alignment.Center,
|
contentAlignment = Alignment.Center,
|
||||||
) {
|
) {
|
||||||
IconButton(onClick = onBack) {
|
IconButton(onClick = onBack) {
|
||||||
@@ -499,12 +492,7 @@ private fun ChatTopBarPill(
|
|||||||
.weight(1f)
|
.weight(1f)
|
||||||
.background(chromeColor, pillShape)
|
.background(chromeColor, pillShape)
|
||||||
.clip(pillShape)
|
.clip(pillShape)
|
||||||
.hazeEffect(state = hazeState) {
|
.hazeBlur(input = HazeInput.Backdrop(hazeState), style = hazeStyle)
|
||||||
blurEffect {
|
|
||||||
blurEnabled = hazeBlurEnabled
|
|
||||||
style = hazeStyle
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.padding(start = 12.dp, end = 4.dp, top = 4.dp, bottom = 4.dp),
|
.padding(start = 12.dp, end = 4.dp, top = 4.dp, bottom = 4.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
) {
|
) {
|
||||||
@@ -592,15 +580,19 @@ fun rememberChatSurfaceContainerHazeStyle(): HazeBlurStyle {
|
|||||||
val surface = MaterialTheme.colorScheme.surfaceContainer
|
val surface = MaterialTheme.colorScheme.surfaceContainer
|
||||||
|
|
||||||
return remember(surface) {
|
return remember(surface) {
|
||||||
HazeBlurStyle(
|
HazeBlurStyle {
|
||||||
blurRadius = 24.dp,
|
blurRadius(24.dp)
|
||||||
backgroundColor = surface,
|
backgroundColor(surface)
|
||||||
colorEffect = HazeColorEffect.tint(
|
colorEffects(
|
||||||
|
listOf(
|
||||||
|
HazeColorEffect.tint(
|
||||||
surface.copy(alpha = if (surface.luminance() >= 0.5f) 0.74f else 0.79f),
|
surface.copy(alpha = if (surface.luminance() >= 0.5f) 0.74f else 0.79f),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Single-flight gate for chat title/avatar navigate vs back during shared-element transitions.
|
* Single-flight gate for chat title/avatar navigate vs back during shared-element transitions.
|
||||||
|
|||||||
@@ -83,11 +83,11 @@ import androidx.compose.ui.zIndex
|
|||||||
import androidx.graphics.shapes.Morph
|
import androidx.graphics.shapes.Morph
|
||||||
import androidx.graphics.shapes.RoundedPolygon
|
import androidx.graphics.shapes.RoundedPolygon
|
||||||
import com.pr0gramm3r101.utils.LastAnchoredBottomArrangement
|
import com.pr0gramm3r101.utils.LastAnchoredBottomArrangement
|
||||||
|
import dev.chrisbanes.haze.HazeInput
|
||||||
|
import dev.chrisbanes.haze.HazeProgressive
|
||||||
import dev.chrisbanes.haze.HazeState
|
import dev.chrisbanes.haze.HazeState
|
||||||
import dev.chrisbanes.haze.blur.HazeProgressive
|
import dev.chrisbanes.haze.blur.hazeBlur
|
||||||
import dev.chrisbanes.haze.blur.blurEffect
|
|
||||||
import dev.chrisbanes.haze.blur.materials.HazeMaterials
|
import dev.chrisbanes.haze.blur.materials.HazeMaterials
|
||||||
import dev.chrisbanes.haze.hazeEffect
|
|
||||||
import dev.chrisbanes.haze.hazeSource
|
import dev.chrisbanes.haze.hazeSource
|
||||||
import dev.chrisbanes.haze.rememberHazeState
|
import dev.chrisbanes.haze.rememberHazeState
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
@@ -551,7 +551,6 @@ fun ExpressiveStepFlowScaffold(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun HazeTopBar(hazeState: HazeState) {
|
fun HazeTopBar(hazeState: HazeState) {
|
||||||
val topBarHazeStyle = HazeMaterials.thin()
|
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
windowInsets = topBarWindowInsets,
|
windowInsets = topBarWindowInsets,
|
||||||
title = {},
|
title = {},
|
||||||
@@ -570,15 +569,17 @@ fun ExpressiveStepFlowScaffold(
|
|||||||
containerColor = Color.Transparent,
|
containerColor = Color.Transparent,
|
||||||
scrolledContainerColor = Color.Transparent,
|
scrolledContainerColor = Color.Transparent,
|
||||||
),
|
),
|
||||||
modifier = Modifier.hazeEffect(state = hazeState) {
|
modifier = Modifier.hazeBlur(
|
||||||
blurEffect {
|
input = HazeInput.Backdrop(hazeState),
|
||||||
style = topBarHazeStyle
|
style = HazeMaterials.thin().then {
|
||||||
progressive = HazeProgressive.verticalGradient(
|
progressive(
|
||||||
|
HazeProgressive.verticalGradient(
|
||||||
startIntensity = 1f,
|
startIntensity = 1f,
|
||||||
endIntensity = 0f,
|
endIntensity = 0f,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,12 +15,12 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import dev.chrisbanes.haze.HazeInput
|
||||||
|
import dev.chrisbanes.haze.HazeProgressive
|
||||||
import dev.chrisbanes.haze.HazeState
|
import dev.chrisbanes.haze.HazeState
|
||||||
import dev.chrisbanes.haze.blur.HazeBlurStyle
|
import dev.chrisbanes.haze.blur.HazeBlurStyle
|
||||||
import dev.chrisbanes.haze.blur.HazeProgressive
|
import dev.chrisbanes.haze.blur.hazeBlur
|
||||||
import dev.chrisbanes.haze.blur.blurEffect
|
|
||||||
import dev.chrisbanes.haze.blur.materials.HazeMaterials
|
import dev.chrisbanes.haze.blur.materials.HazeMaterials
|
||||||
import dev.chrisbanes.haze.hazeEffect
|
|
||||||
import ru.fromchat.ui.main.settings.SettingsStepHorizontalPadding
|
import ru.fromchat.ui.main.settings.SettingsStepHorizontalPadding
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -31,28 +31,17 @@ fun HazeBottomBar(
|
|||||||
baseColor: Color? = null,
|
baseColor: Color? = null,
|
||||||
content: @Composable () -> Unit,
|
content: @Composable () -> Unit,
|
||||||
) {
|
) {
|
||||||
val resolvedStyle = hazeStyle ?: HazeMaterials.thin()
|
val effectModifier = Modifier.hazeBlur(
|
||||||
val effectModifier = if (hazeStyle != null) {
|
input = HazeInput.Backdrop(hazeState),
|
||||||
Modifier.hazeEffect(state = hazeState) {
|
style = (hazeStyle ?: HazeMaterials.thin()).then {
|
||||||
blurEffect {
|
progressive(
|
||||||
style = resolvedStyle
|
HazeProgressive.verticalGradient(
|
||||||
progressive = HazeProgressive.verticalGradient(
|
|
||||||
startIntensity = 0f,
|
startIntensity = 0f,
|
||||||
endIntensity = 1f,
|
endIntensity = 1f,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
},
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Modifier.hazeEffect(state = hazeState) {
|
|
||||||
blurEffect {
|
|
||||||
style = resolvedStyle
|
|
||||||
progressive = HazeProgressive.verticalGradient(
|
|
||||||
startIntensity = 0f,
|
|
||||||
endIntensity = 1f,
|
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.windowInsetsPadding(WindowInsets.ime)
|
.windowInsetsPadding(WindowInsets.ime)
|
||||||
|
|||||||
@@ -51,11 +51,11 @@ import androidx.navigation.NavController
|
|||||||
import com.pr0gramm3r101.utils.WindowWidthSizeClass
|
import com.pr0gramm3r101.utils.WindowWidthSizeClass
|
||||||
import com.pr0gramm3r101.utils.currentWindowAdaptiveInfo
|
import com.pr0gramm3r101.utils.currentWindowAdaptiveInfo
|
||||||
import com.pr0gramm3r101.utils.widthSizeClass
|
import com.pr0gramm3r101.utils.widthSizeClass
|
||||||
|
import dev.chrisbanes.haze.HazeInput
|
||||||
import dev.chrisbanes.haze.HazeState
|
import dev.chrisbanes.haze.HazeState
|
||||||
import dev.chrisbanes.haze.blur.HazeBlurStyle
|
import dev.chrisbanes.haze.blur.HazeBlurStyle
|
||||||
import dev.chrisbanes.haze.blur.HazeColorEffect
|
import dev.chrisbanes.haze.blur.HazeColorEffect
|
||||||
import dev.chrisbanes.haze.blur.blurEffect
|
import dev.chrisbanes.haze.blur.hazeBlur
|
||||||
import dev.chrisbanes.haze.hazeEffect
|
|
||||||
import dev.chrisbanes.haze.hazeSource
|
import dev.chrisbanes.haze.hazeSource
|
||||||
import dev.chrisbanes.haze.rememberHazeState
|
import dev.chrisbanes.haze.rememberHazeState
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
@@ -343,11 +343,7 @@ fun MainScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.background(MaterialTheme.colorScheme.surfaceContainer)
|
.background(MaterialTheme.colorScheme.surfaceContainer)
|
||||||
.hazeEffect(state = navBarHazeState) {
|
.hazeBlur(input = HazeInput.Backdrop(navBarHazeState), style = navBarHazeStyle),
|
||||||
blurEffect {
|
|
||||||
style = navBarHazeStyle
|
|
||||||
}
|
|
||||||
},
|
|
||||||
) {
|
) {
|
||||||
NavigationBar(
|
NavigationBar(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
@@ -492,21 +488,19 @@ private fun ChatContextMenuBlurLayer(
|
|||||||
// In-tree under the overlay (zIndex). Never use a Popup here: platform popups stack above
|
// In-tree under the overlay (zIndex). Never use a Popup here: platform popups stack above
|
||||||
// the sharp row/menu, blur them, and intercept dismiss taps.
|
// the sharp row/menu, blur them, and intercept dismiss taps.
|
||||||
val surface = MaterialTheme.colorScheme.surfaceContainerLowest
|
val surface = MaterialTheme.colorScheme.surfaceContainerLowest
|
||||||
|
val tint = HazeColorEffect.tint(surface.copy(alpha = 0.4f * progress))
|
||||||
Box(
|
Box(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.hazeEffect(state = hazeState) {
|
.hazeBlur(
|
||||||
blurEffect {
|
input = HazeInput.Backdrop(hazeState),
|
||||||
style = HazeBlurStyle(
|
style = HazeBlurStyle {
|
||||||
blurRadius = blurRadius,
|
blurRadius(blurRadius)
|
||||||
backgroundColor = surface,
|
backgroundColor(surface)
|
||||||
colorEffects = listOf(
|
colorEffects(listOf(tint))
|
||||||
HazeColorEffect.tint(surface.copy(alpha = 0.4f * progress)),
|
noiseFactor(0f)
|
||||||
),
|
fallbackColorEffect(tint)
|
||||||
noiseFactor = 0f,
|
|
||||||
fallbackColorEffect = HazeColorEffect.tint(surface.copy(alpha = 0.4f * progress)),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,10 +58,10 @@ import androidx.compose.material3.TopAppBarDefaults
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.derivedStateOf
|
import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import dev.chrisbanes.haze.HazeInput
|
||||||
import dev.chrisbanes.haze.HazeState
|
import dev.chrisbanes.haze.HazeState
|
||||||
import dev.chrisbanes.haze.blur.blurEffect
|
import dev.chrisbanes.haze.blur.hazeBlur
|
||||||
import dev.chrisbanes.haze.blur.materials.HazeMaterials
|
import dev.chrisbanes.haze.blur.materials.HazeMaterials
|
||||||
import dev.chrisbanes.haze.hazeEffect
|
|
||||||
import dev.chrisbanes.haze.hazeSource
|
import dev.chrisbanes.haze.hazeSource
|
||||||
import dev.chrisbanes.haze.rememberHazeState
|
import dev.chrisbanes.haze.rememberHazeState
|
||||||
import androidx.compose.runtime.DisposableEffect
|
import androidx.compose.runtime.DisposableEffect
|
||||||
@@ -360,16 +360,13 @@ private fun ChatsTopBarHazeBackdrop(
|
|||||||
blurEnabled: Boolean,
|
blurEnabled: Boolean,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val topBarHazeStyle = HazeMaterials.thin()
|
|
||||||
Box(
|
Box(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.graphicsLayer { alpha = blurReveal.coerceIn(0f, 1f) }
|
.graphicsLayer { alpha = blurReveal.coerceIn(0f, 1f) }
|
||||||
.hazeEffect(state = hazeState) {
|
.hazeBlur(
|
||||||
blurEffect {
|
input = HazeInput.Backdrop(hazeState),
|
||||||
this.blurEnabled = blurEnabled
|
style = HazeMaterials.thin().then { blurEnabled(blurEnabled) },
|
||||||
style = topBarHazeStyle
|
),
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -70,10 +70,10 @@ import org.jetbrains.compose.resources.stringResource
|
|||||||
import com.pr0gramm3r101.components.Category
|
import com.pr0gramm3r101.components.Category
|
||||||
import com.pr0gramm3r101.components.ListItem
|
import com.pr0gramm3r101.components.ListItem
|
||||||
import com.pr0gramm3r101.utils.currentDeviceInfo
|
import com.pr0gramm3r101.utils.currentDeviceInfo
|
||||||
import dev.chrisbanes.haze.blur.HazeProgressive
|
import dev.chrisbanes.haze.HazeInput
|
||||||
import dev.chrisbanes.haze.blur.blurEffect
|
import dev.chrisbanes.haze.HazeProgressive
|
||||||
|
import dev.chrisbanes.haze.blur.hazeBlur
|
||||||
import dev.chrisbanes.haze.blur.materials.HazeMaterials
|
import dev.chrisbanes.haze.blur.materials.HazeMaterials
|
||||||
import dev.chrisbanes.haze.hazeEffect
|
|
||||||
import dev.chrisbanes.haze.hazeSource
|
import dev.chrisbanes.haze.hazeSource
|
||||||
import dev.chrisbanes.haze.rememberHazeState
|
import dev.chrisbanes.haze.rememberHazeState
|
||||||
import io.ktor.client.network.sockets.ConnectTimeoutException
|
import io.ktor.client.network.sockets.ConnectTimeoutException
|
||||||
@@ -435,7 +435,6 @@ fun DevicesScreen(onBack: () -> Unit) {
|
|||||||
contentWindowInsets = WindowInsets.navigationBars,
|
contentWindowInsets = WindowInsets.navigationBars,
|
||||||
snackbarHost = { FromChatSnackbarHost(hostState = snackbarHostState) },
|
snackbarHost = { FromChatSnackbarHost(hostState = snackbarHostState) },
|
||||||
topBar = {
|
topBar = {
|
||||||
val topBarHazeStyle = HazeMaterials.thin()
|
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
windowInsets = settingsDetailWindowInsets(),
|
windowInsets = settingsDetailWindowInsets(),
|
||||||
title = {},
|
title = {},
|
||||||
@@ -450,15 +449,17 @@ fun DevicesScreen(onBack: () -> Unit) {
|
|||||||
containerColor = Color.Transparent,
|
containerColor = Color.Transparent,
|
||||||
scrolledContainerColor = Color.Transparent
|
scrolledContainerColor = Color.Transparent
|
||||||
),
|
),
|
||||||
modifier = Modifier.hazeEffect(state = hazeState) {
|
modifier = Modifier.hazeBlur(
|
||||||
blurEffect {
|
input = HazeInput.Backdrop(hazeState),
|
||||||
style = topBarHazeStyle
|
style = HazeMaterials.thin().then {
|
||||||
progressive = HazeProgressive.verticalGradient(
|
progressive(
|
||||||
|
HazeProgressive.verticalGradient(
|
||||||
startIntensity = 1f,
|
startIntensity = 1f,
|
||||||
endIntensity = 0f,
|
endIntensity = 0f,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
},
|
||||||
}
|
),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
bottomBar = {
|
bottomBar = {
|
||||||
|
|||||||
+10
-9
@@ -67,10 +67,10 @@ import androidx.compose.ui.text.style.TextAlign
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.pr0gramm3r101.utils.navigateAndWipeBackStack
|
import com.pr0gramm3r101.utils.navigateAndWipeBackStack
|
||||||
import com.pr0gramm3r101.utils.toDp
|
import com.pr0gramm3r101.utils.toDp
|
||||||
import dev.chrisbanes.haze.blur.HazeProgressive
|
import dev.chrisbanes.haze.HazeInput
|
||||||
import dev.chrisbanes.haze.blur.blurEffect
|
import dev.chrisbanes.haze.HazeProgressive
|
||||||
|
import dev.chrisbanes.haze.blur.hazeBlur
|
||||||
import dev.chrisbanes.haze.blur.materials.HazeMaterials
|
import dev.chrisbanes.haze.blur.materials.HazeMaterials
|
||||||
import dev.chrisbanes.haze.hazeEffect
|
|
||||||
import dev.chrisbanes.haze.hazeSource
|
import dev.chrisbanes.haze.hazeSource
|
||||||
import dev.chrisbanes.haze.rememberHazeState
|
import dev.chrisbanes.haze.rememberHazeState
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
@@ -339,7 +339,6 @@ fun ServerConfigScreen() {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
topBar = {
|
topBar = {
|
||||||
val topBarHazeStyle = HazeMaterials.thin()
|
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
windowInsets = settingsDetailWindowInsets(),
|
windowInsets = settingsDetailWindowInsets(),
|
||||||
title = {},
|
title = {},
|
||||||
@@ -357,15 +356,17 @@ fun ServerConfigScreen() {
|
|||||||
containerColor = Color.Transparent,
|
containerColor = Color.Transparent,
|
||||||
scrolledContainerColor = Color.Transparent,
|
scrolledContainerColor = Color.Transparent,
|
||||||
),
|
),
|
||||||
modifier = Modifier.hazeEffect(state = actionHazeState) {
|
modifier = Modifier.hazeBlur(
|
||||||
blurEffect {
|
input = HazeInput.Backdrop(actionHazeState),
|
||||||
style = topBarHazeStyle
|
style = HazeMaterials.thin().then {
|
||||||
progressive = HazeProgressive.verticalGradient(
|
progressive(
|
||||||
|
HazeProgressive.verticalGradient(
|
||||||
startIntensity = 1f,
|
startIntensity = 1f,
|
||||||
endIntensity = 0f,
|
endIntensity = 0f,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
) { innerPadding ->
|
) { innerPadding ->
|
||||||
|
|||||||
@@ -47,10 +47,10 @@ import androidx.compose.ui.text.TextRange
|
|||||||
import androidx.compose.ui.layout.onGloballyPositioned
|
import androidx.compose.ui.layout.onGloballyPositioned
|
||||||
import androidx.compose.ui.text.input.TextFieldValue
|
import androidx.compose.ui.text.input.TextFieldValue
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import dev.chrisbanes.haze.blur.HazeProgressive
|
import dev.chrisbanes.haze.HazeInput
|
||||||
import dev.chrisbanes.haze.blur.blurEffect
|
import dev.chrisbanes.haze.HazeProgressive
|
||||||
|
import dev.chrisbanes.haze.blur.hazeBlur
|
||||||
import dev.chrisbanes.haze.blur.materials.HazeMaterials
|
import dev.chrisbanes.haze.blur.materials.HazeMaterials
|
||||||
import dev.chrisbanes.haze.hazeEffect
|
|
||||||
import dev.chrisbanes.haze.hazeSource
|
import dev.chrisbanes.haze.hazeSource
|
||||||
import dev.chrisbanes.haze.rememberHazeState
|
import dev.chrisbanes.haze.rememberHazeState
|
||||||
import io.ktor.client.call.body
|
import io.ktor.client.call.body
|
||||||
@@ -339,7 +339,6 @@ fun EditProfileScreen(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
topBar = {
|
topBar = {
|
||||||
val topBarHazeStyle = HazeMaterials.thin()
|
|
||||||
TopAppBar(
|
TopAppBar(
|
||||||
title = { Text(stringResource(Res.string.profile_edit_title)) },
|
title = { Text(stringResource(Res.string.profile_edit_title)) },
|
||||||
navigationIcon = {
|
navigationIcon = {
|
||||||
@@ -367,15 +366,17 @@ fun EditProfileScreen(
|
|||||||
containerColor = Color.Transparent,
|
containerColor = Color.Transparent,
|
||||||
scrolledContainerColor = Color.Transparent,
|
scrolledContainerColor = Color.Transparent,
|
||||||
),
|
),
|
||||||
modifier = Modifier.hazeEffect(state = hazeState) {
|
modifier = Modifier.hazeBlur(
|
||||||
blurEffect {
|
input = HazeInput.Backdrop(hazeState),
|
||||||
style = topBarHazeStyle
|
style = HazeMaterials.thin().then {
|
||||||
progressive = HazeProgressive.verticalGradient(
|
progressive(
|
||||||
|
HazeProgressive.verticalGradient(
|
||||||
startIntensity = 1f,
|
startIntensity = 1f,
|
||||||
endIntensity = 0f,
|
endIntensity = 0f,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
) { innerPadding ->
|
) { innerPadding ->
|
||||||
|
|||||||
@@ -37,11 +37,11 @@ import androidx.compose.ui.unit.Dp
|
|||||||
import androidx.navigation.NavController
|
import androidx.navigation.NavController
|
||||||
import com.pr0gramm3r101.components.ListItemPosition
|
import com.pr0gramm3r101.components.ListItemPosition
|
||||||
import com.pr0gramm3r101.utils.SupportClipboardManager
|
import com.pr0gramm3r101.utils.SupportClipboardManager
|
||||||
|
import dev.chrisbanes.haze.HazeInput
|
||||||
|
import dev.chrisbanes.haze.HazeProgressive
|
||||||
import dev.chrisbanes.haze.HazeState
|
import dev.chrisbanes.haze.HazeState
|
||||||
import dev.chrisbanes.haze.blur.HazeProgressive
|
import dev.chrisbanes.haze.blur.hazeBlur
|
||||||
import dev.chrisbanes.haze.blur.blurEffect
|
|
||||||
import dev.chrisbanes.haze.blur.materials.HazeMaterials
|
import dev.chrisbanes.haze.blur.materials.HazeMaterials
|
||||||
import dev.chrisbanes.haze.hazeEffect
|
|
||||||
import dev.chrisbanes.haze.hazeSource
|
import dev.chrisbanes.haze.hazeSource
|
||||||
import dev.chrisbanes.haze.rememberHazeState
|
import dev.chrisbanes.haze.rememberHazeState
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
@@ -2058,21 +2058,22 @@ private fun ProfileFloatingBackBar(
|
|||||||
) {
|
) {
|
||||||
if (!visible) return
|
if (!visible) return
|
||||||
|
|
||||||
val backBarHazeStyle = HazeMaterials.thin()
|
|
||||||
Box(modifier = modifier.fillMaxWidth()) {
|
Box(modifier = modifier.fillMaxWidth()) {
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.height(blurHeight)
|
.height(blurHeight)
|
||||||
.hazeEffect(state = hazeState) {
|
.hazeBlur(
|
||||||
blurEffect {
|
input = HazeInput.Backdrop(hazeState),
|
||||||
style = backBarHazeStyle
|
style = HazeMaterials.thin().then {
|
||||||
progressive = HazeProgressive.verticalGradient(
|
progressive(
|
||||||
|
HazeProgressive.verticalGradient(
|
||||||
startIntensity = 1f,
|
startIntensity = 1f,
|
||||||
endIntensity = 0f,
|
endIntensity = 0f,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
),
|
||||||
)
|
)
|
||||||
IconButton(
|
IconButton(
|
||||||
onClick = onBack,
|
onClick = onBack,
|
||||||
|
|||||||
+25
-20
@@ -1,19 +1,20 @@
|
|||||||
[versions]
|
[versions]
|
||||||
agp = "9.3.1"
|
agp = "9.4.0"
|
||||||
androidx-activityCompose = "1.13.0"
|
androidx-activityCompose = "1.13.0"
|
||||||
androidx-appcompat = "1.7.1"
|
androidx-appcompat = "1.8.0"
|
||||||
androidx-core-ktx = "1.19.0"
|
androidx-core-ktx = "1.19.0"
|
||||||
androidx-exifinterface = "1.4.2"
|
androidx-exifinterface = "1.4.2"
|
||||||
coilCompose = "3.5.0"
|
coilCompose = "3.6.2"
|
||||||
compose-multiplatform = "1.12.0-beta02"
|
compose-multiplatform = "1.12.0"
|
||||||
#noinspection NewerVersionAvailable
|
#noinspection NewerVersionAvailable
|
||||||
constraintlayout = "0.8.0-shaded"
|
constraintlayout = "0.8.0-shaded"
|
||||||
coreSplashscreen = "1.2.0"
|
coreSplashscreen = "1.2.0"
|
||||||
firebaseMessaging = "25.1.1"
|
firebaseMessaging = "25.1.3"
|
||||||
googleServices = "4.5.0"
|
googleServices = "4.5.0"
|
||||||
haze = "2.0.0-alpha03"
|
haze = "2.0.0-beta03"
|
||||||
kotlin = "2.4.10"
|
jnaPlatform = "5.19.1"
|
||||||
adaptiveAndroid = "1.2.0"
|
kotlin = "2.4.20"
|
||||||
|
adaptiveAndroid = "1.3.0"
|
||||||
biometric = "1.4.0-alpha07"
|
biometric = "1.4.0-alpha07"
|
||||||
gson = "2.14.0"
|
gson = "2.14.0"
|
||||||
kotlinxIoBytestring = "0.9.1"
|
kotlinxIoBytestring = "0.9.1"
|
||||||
@@ -21,33 +22,34 @@ kotlinxCoroutinesCore = "1.11.0"
|
|||||||
kotlinxIoCore = "0.9.1"
|
kotlinxIoCore = "0.9.1"
|
||||||
multiplatformCryptoLibsodiumBindings = "0.9.5"
|
multiplatformCryptoLibsodiumBindings = "0.9.5"
|
||||||
multiplatformSettings = "1.3.0"
|
multiplatformSettings = "1.3.0"
|
||||||
serialization = "2.4.10"
|
serialization = "2.4.20"
|
||||||
serialization-json = "1.11.0"
|
serialization-json = "1.11.0"
|
||||||
material = "1.14.0"
|
material = "1.14.0"
|
||||||
activityKtx = "1.13.0"
|
activityKtx = "1.13.0"
|
||||||
navigationCompose = "2.9.2"
|
navigationCompose = "2.9.2"
|
||||||
datastore = "1.2.1"
|
datastore = "1.2.1"
|
||||||
security-crypto = "1.1.0"
|
security-crypto = "1.1.0"
|
||||||
ktor = "3.5.1"
|
ktor = "3.5.2"
|
||||||
slf4j = "1.7.36"
|
slf4j = "1.7.36"
|
||||||
kotlinxDatetime = "0.8.0"
|
kotlinxDatetime = "0.8.0"
|
||||||
lifecycleRuntimeKtx = "2.11.0"
|
lifecycleRuntimeKtx = "2.11.0"
|
||||||
composeBom = "2026.06.01"
|
composeBom = "2026.09.00"
|
||||||
composeMaterialIconsExtended = "1.7.3"
|
composeMaterialIconsExtended = "1.7.3"
|
||||||
composeMaterial3 = "1.12.0-alpha03"
|
composeMaterial3 = "1.12.0-alpha03"
|
||||||
composeComponents = "1.12.0-beta02"
|
composeComponents = "1.12.0"
|
||||||
playServicesBase = "18.10.0"
|
playServicesBase = "18.10.1"
|
||||||
tweetnaclJava = "1.1.3"
|
tweetnaclJava = "1.1.3"
|
||||||
androidxWork = "2.11.2"
|
androidxWork = "2.11.2"
|
||||||
cryptography-kotlin = "0.6.0"
|
cryptography-kotlin = "0.6.0"
|
||||||
krypto = "4.0.10"
|
krypto = "4.0.10"
|
||||||
sqldelight = "2.3.2"
|
sqldelight = "2.3.2"
|
||||||
livekitAndroid = "2.27.0"
|
livekitAndroid = "2.28.2"
|
||||||
livekitAndroidComposeComponents = "2.4.0"
|
livekitAndroidComposeComponents = "2.4.2"
|
||||||
markdownRendererM3 = "0.43.0"
|
markdownRendererM3 = "0.45.0"
|
||||||
bouncycastle = "1.85"
|
bouncycastle = "1.85.2"
|
||||||
webkit = "1.16.0"
|
webkit = "1.17.0"
|
||||||
openjfx = "21.0.6"
|
openjfx = "22.0.2"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
androidx-core-splashscreen = { module = "androidx.core:core-splashscreen", version.ref = "coreSplashscreen" }
|
androidx-core-splashscreen = { module = "androidx.core:core-splashscreen", version.ref = "coreSplashscreen" }
|
||||||
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "androidx-core-ktx" }
|
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "androidx-core-ktx" }
|
||||||
@@ -65,6 +67,7 @@ haze = { module = "dev.chrisbanes.haze:haze", version.ref = "haze" }
|
|||||||
haze-blur = { module = "dev.chrisbanes.haze:haze-blur", version.ref = "haze" }
|
haze-blur = { module = "dev.chrisbanes.haze:haze-blur", version.ref = "haze" }
|
||||||
haze-blur-materials = { module = "dev.chrisbanes.haze:haze-blur-materials", version.ref = "haze" }
|
haze-blur-materials = { module = "dev.chrisbanes.haze:haze-blur-materials", version.ref = "haze" }
|
||||||
jetbrains-kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinxCoroutinesCore" }
|
jetbrains-kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinxCoroutinesCore" }
|
||||||
|
jna-platform = { module = "net.java.dev.jna:jna-platform", version.ref = "jnaPlatform" }
|
||||||
kotlinx-coroutines-swing = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-swing", version.ref = "kotlinxCoroutinesCore" }
|
kotlinx-coroutines-swing = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-swing", version.ref = "kotlinxCoroutinesCore" }
|
||||||
jetbrains-kotlinx-io-bytestring = { module = "org.jetbrains.kotlinx:kotlinx-io-bytestring", version.ref = "kotlinxIoBytestring" }
|
jetbrains-kotlinx-io-bytestring = { module = "org.jetbrains.kotlinx:kotlinx-io-bytestring", version.ref = "kotlinxIoBytestring" }
|
||||||
androidx-adaptive-android = { group = "androidx.compose.material3.adaptive", name = "adaptive-android", version.ref = "adaptiveAndroid" }
|
androidx-adaptive-android = { group = "androidx.compose.material3.adaptive", name = "adaptive-android", version.ref = "adaptiveAndroid" }
|
||||||
@@ -91,7 +94,6 @@ ktor-client-logging = { module = "io.ktor:ktor-client-logging", version.ref = "k
|
|||||||
ktor-client-okhttp = { module = "io.ktor:ktor-client-okhttp", version.ref = "ktor" }
|
ktor-client-okhttp = { module = "io.ktor:ktor-client-okhttp", version.ref = "ktor" }
|
||||||
ktor-client-darwin = { module = "io.ktor:ktor-client-darwin", version.ref = "ktor" }
|
ktor-client-darwin = { module = "io.ktor:ktor-client-darwin", version.ref = "ktor" }
|
||||||
ktor-client-cio = { module = "io.ktor:ktor-client-cio", version.ref = "ktor" }
|
ktor-client-cio = { module = "io.ktor:ktor-client-cio", version.ref = "ktor" }
|
||||||
ktor-client-java = { module = "io.ktor:ktor-client-java", version.ref = "ktor" }
|
|
||||||
slf4j-android = { module = "org.slf4j:slf4j-android", version.ref = "slf4j" }
|
slf4j-android = { module = "org.slf4j:slf4j-android", version.ref = "slf4j" }
|
||||||
kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinxDatetime" }
|
kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinxDatetime" }
|
||||||
androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" }
|
androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" }
|
||||||
@@ -114,8 +116,11 @@ cryptography-core = { module = "dev.whyoleg.cryptography:cryptography-core", ver
|
|||||||
cryptography-provider-optimal = { module = "dev.whyoleg.cryptography:cryptography-provider-optimal", version.ref = "cryptography-kotlin" }
|
cryptography-provider-optimal = { module = "dev.whyoleg.cryptography:cryptography-provider-optimal", version.ref = "cryptography-kotlin" }
|
||||||
androidx-work-runtime-ktx = { module = "androidx.work:work-runtime-ktx", version.ref = "androidxWork" }
|
androidx-work-runtime-ktx = { module = "androidx.work:work-runtime-ktx", version.ref = "androidxWork" }
|
||||||
androidx-webkit = { module = "androidx.webkit:webkit", version.ref = "webkit" }
|
androidx-webkit = { module = "androidx.webkit:webkit", version.ref = "webkit" }
|
||||||
|
#noinspection UnusedVersionCatalogEntry
|
||||||
openjfx-controls = { module = "org.openjfx:javafx-controls", version.ref = "openjfx" }
|
openjfx-controls = { module = "org.openjfx:javafx-controls", version.ref = "openjfx" }
|
||||||
|
#noinspection UnusedVersionCatalogEntry
|
||||||
openjfx-web = { module = "org.openjfx:javafx-web", version.ref = "openjfx" }
|
openjfx-web = { module = "org.openjfx:javafx-web", version.ref = "openjfx" }
|
||||||
|
#noinspection UnusedVersionCatalogEntry
|
||||||
openjfx-swing = { module = "org.openjfx:javafx-swing", version.ref = "openjfx" }
|
openjfx-swing = { module = "org.openjfx:javafx-swing", version.ref = "openjfx" }
|
||||||
sqldelight-runtime = { module = "app.cash.sqldelight:runtime", version.ref = "sqldelight" }
|
sqldelight-runtime = { module = "app.cash.sqldelight:runtime", version.ref = "sqldelight" }
|
||||||
sqldelight-coroutines-extensions = { module = "app.cash.sqldelight:coroutines-extensions", version.ref = "sqldelight" }
|
sqldelight-coroutines-extensions = { module = "app.cash.sqldelight:coroutines-extensions", version.ref = "sqldelight" }
|
||||||
|
|||||||
@@ -14,7 +14,11 @@ kotlin {
|
|||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "com.pr0gramm3r101.utils"
|
namespace = "com.pr0gramm3r101.utils"
|
||||||
compileSdk = 37
|
compileSdk {
|
||||||
|
version = release(37) {
|
||||||
|
minorApiLevel = 2
|
||||||
|
}
|
||||||
|
}
|
||||||
minSdk = 24
|
minSdk = 24
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user