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