Minimize hides only that window, Show wIRC main tray item, remember hidden main, bump to 0.1.15
- Minimizing a window hides only that window to the tray. The other windows stay on the desktop. Win+D still hides everything. - Tray left click shows every hidden window at its old position, or hides them all if none are hidden. - The tray menu has a new Show wIRC main item that brings back just wIRC main. - If wIRC main was hidden when wIRC quit, it starts hidden next time and the servers that were connected reconnect by themselves, so their channel windows open without wIRC main. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
24f54574ec
commit
78bd235a48
+2
-2
@@ -6,7 +6,7 @@ plugins {
|
|||||||
}
|
}
|
||||||
|
|
||||||
group = "com.tpuahsiew.wirc"
|
group = "com.tpuahsiew.wirc"
|
||||||
version = "0.1.14"
|
version = "0.1.15"
|
||||||
|
|
||||||
// No project-level repositories{} block here: settings.gradle.kts sets
|
// No project-level repositories{} block here: settings.gradle.kts sets
|
||||||
// dependencyResolutionManagement.repositoriesMode = FAIL_ON_PROJECT_REPOS and already
|
// dependencyResolutionManagement.repositoriesMode = FAIL_ON_PROJECT_REPOS and already
|
||||||
@@ -40,7 +40,7 @@ compose.desktop {
|
|||||||
appResourcesRootDir.set(project.file("installerResources"))
|
appResourcesRootDir.set(project.file("installerResources"))
|
||||||
targetFormats(TargetFormat.Msi, TargetFormat.Exe)
|
targetFormats(TargetFormat.Msi, TargetFormat.Exe)
|
||||||
packageName = "wIRC"
|
packageName = "wIRC"
|
||||||
packageVersion = "0.1.14"
|
packageVersion = "0.1.15"
|
||||||
windows {
|
windows {
|
||||||
menuGroup = "wIRC"
|
menuGroup = "wIRC"
|
||||||
upgradeUuid = "8f14e45f-ceea-4a5e-9c3b-5b1f0e8c2b7a"
|
upgradeUuid = "8f14e45f-ceea-4a5e-9c3b-5b1f0e8c2b7a"
|
||||||
|
|||||||
+40
-14
@@ -1,6 +1,7 @@
|
|||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.darkColorScheme
|
import androidx.compose.material3.darkColorScheme
|
||||||
|
import androidx.compose.runtime.DisposableEffect
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
@@ -18,6 +19,8 @@ import data.AliasRepository
|
|||||||
import data.BufferKey
|
import data.BufferKey
|
||||||
import data.MAIN_BUFFER_KEY
|
import data.MAIN_BUFFER_KEY
|
||||||
import data.ServerRepository
|
import data.ServerRepository
|
||||||
|
import data.SessionState
|
||||||
|
import data.SessionStateRepository
|
||||||
import data.WindowPrefsRepository
|
import data.WindowPrefsRepository
|
||||||
import data.storageId
|
import data.storageId
|
||||||
import irc.ConnectionManager
|
import irc.ConnectionManager
|
||||||
@@ -61,10 +64,34 @@ fun main() = application {
|
|||||||
var openBuffers by remember { mutableStateOf(setOf<BufferKey>()) }
|
var openBuffers by remember { mutableStateOf(setOf<BufferKey>()) }
|
||||||
var mainUnread by remember { mutableStateOf(0) }
|
var mainUnread by remember { mutableStateOf(0) }
|
||||||
|
|
||||||
|
// wIRC lives in the tray: no window is on the taskbar, and minimizing a window hides just
|
||||||
|
// that window (see TrayWindowBehavior). Hidden windows stay composed and connected, so
|
||||||
|
// nothing is lost while they're away; showing them again puts each back where it was.
|
||||||
|
// wIRC main starts hidden if it was hidden when wIRC last quit.
|
||||||
|
val sessionRepo = remember { SessionStateRepository(appRoot) }
|
||||||
|
val startSession = remember { sessionRepo.load() }
|
||||||
|
var mainHidden by remember { mutableStateOf(startSession.mainHidden) }
|
||||||
|
var hiddenBuffers by remember { mutableStateOf(setOf<BufferKey>()) }
|
||||||
|
val anyHidden = mainHidden || openBuffers.any { it in hiddenBuffers }
|
||||||
|
val showAll: () -> Unit = { mainHidden = false; hiddenBuffers = emptySet() }
|
||||||
|
// Windows opened after this (e.g. a new PM) still show up
|
||||||
|
val hideAll: () -> Unit = { mainHidden = true; hiddenBuffers = openBuffers }
|
||||||
|
|
||||||
|
val saveSession: () -> Unit = {
|
||||||
|
sessionRepo.saveOnce(SessionState(mainHidden, ConnectionManager.connectedIds.value))
|
||||||
|
}
|
||||||
|
// Also saves when Windows ends the process (sign-out, shutdown) instead of the user quitting
|
||||||
|
DisposableEffect(Unit) {
|
||||||
|
val hook = Thread { saveSession() }
|
||||||
|
Runtime.getRuntime().addShutdownHook(hook)
|
||||||
|
onDispose { }
|
||||||
|
}
|
||||||
|
|
||||||
// Full shutdown for the "Exit wIRC" button: disconnect every server (which also writes
|
// Full shutdown for the "Exit wIRC" button: disconnect every server (which also writes
|
||||||
// all scrollback to disk), then end the process. A bare /quit in a channel window only
|
// all scrollback to disk), then end the process. A bare /quit in a channel window only
|
||||||
// disconnects that window's server - see ConnectionManager.disconnect.
|
// disconnects that window's server - see ConnectionManager.disconnect.
|
||||||
val shutdownApp: () -> Unit = {
|
val shutdownApp: () -> Unit = {
|
||||||
|
saveSession()
|
||||||
ConnectionManager.disconnectAll()
|
ConnectionManager.disconnectAll()
|
||||||
exitApplication()
|
exitApplication()
|
||||||
}
|
}
|
||||||
@@ -92,20 +119,15 @@ fun main() = application {
|
|||||||
val mainLineGap = ui.rememberLineGap()
|
val mainLineGap = ui.rememberLineGap()
|
||||||
ui.WindowSnapping(mainWindowState, mainLineGap, osFrame = true)
|
ui.WindowSnapping(mainWindowState, mainLineGap, osFrame = true)
|
||||||
|
|
||||||
// wIRC lives in the tray: no window is on the taskbar, and minimizing any window hides
|
|
||||||
// all of them (see TrayWindowBehavior). Hidden windows stay composed and connected, so
|
|
||||||
// nothing is lost while they're away; showing them again puts each back where it was.
|
|
||||||
// A window opened while hidden (e.g. a new PM) stays hidden until they're shown.
|
|
||||||
var windowsShown by remember { mutableStateOf(true) }
|
|
||||||
val hideAll: () -> Unit = { windowsShown = false }
|
|
||||||
val mainTitle = if (mainUnread > 0) "wIRC - Main ($mainUnread new)" else "wIRC - Main"
|
val mainTitle = if (mainUnread > 0) "wIRC - Main ($mainUnread new)" else "wIRC - Main"
|
||||||
|
|
||||||
ui.AppTray(
|
ui.AppTray(
|
||||||
// The unread count shows on the tray icon's tooltip too, so new server messages
|
// The unread count shows on the tray icon's tooltip too, so new server messages
|
||||||
// are visible even when the windows are hidden.
|
// are visible even when the windows are hidden.
|
||||||
tooltip = if (mainUnread > 0) "wIRC ($mainUnread new)" else "wIRC",
|
tooltip = if (mainUnread > 0) "wIRC ($mainUnread new)" else "wIRC",
|
||||||
onToggle = { windowsShown = !windowsShown },
|
onToggle = { if (anyHidden) showAll() else hideAll() },
|
||||||
onShow = { windowsShown = true },
|
onShow = showAll,
|
||||||
|
onShowMain = { mainHidden = false },
|
||||||
onHide = hideAll,
|
onHide = hideAll,
|
||||||
onExit = shutdownApp
|
onExit = shutdownApp
|
||||||
)
|
)
|
||||||
@@ -113,13 +135,13 @@ fun main() = application {
|
|||||||
// Closing the main server-list window quits the whole app (and every channel window
|
// Closing the main server-list window quits the whole app (and every channel window
|
||||||
// with it) - the tray's Exit does the same, and also disconnects first.
|
// with it) - the tray's Exit does the same, and also disconnects first.
|
||||||
Window(
|
Window(
|
||||||
onCloseRequest = ::exitApplication,
|
onCloseRequest = { saveSession(); exitApplication() },
|
||||||
visible = windowsShown,
|
visible = !mainHidden,
|
||||||
title = mainTitle,
|
title = mainTitle,
|
||||||
icon = painterResource("icon.png"),
|
icon = painterResource("icon.png"),
|
||||||
state = mainWindowState
|
state = mainWindowState
|
||||||
) {
|
) {
|
||||||
TrayWindowBehavior(mainWindowState, hideAll)
|
TrayWindowBehavior(mainWindowState) { mainHidden = true }
|
||||||
androidx.compose.runtime.CompositionLocalProvider(ui.LocalLineGap provides mainLineGap) {
|
androidx.compose.runtime.CompositionLocalProvider(ui.LocalLineGap provides mainLineGap) {
|
||||||
MaterialTheme(colorScheme = neutralDarkScheme) {
|
MaterialTheme(colorScheme = neutralDarkScheme) {
|
||||||
Surface {
|
Surface {
|
||||||
@@ -127,6 +149,9 @@ fun main() = application {
|
|||||||
repo = repo,
|
repo = repo,
|
||||||
colorScheme = neutralDarkScheme,
|
colorScheme = neutralDarkScheme,
|
||||||
displaySettings = mainDisplaySettings,
|
displaySettings = mainDisplaySettings,
|
||||||
|
// Started hidden: reconnect what was connected at quit, so the channel
|
||||||
|
// windows open without wIRC main having to be shown to click Connect.
|
||||||
|
autoConnectServerIds = if (startSession.mainHidden) startSession.connectedServerIds else emptySet(),
|
||||||
onOpenChannel = { serverId, channelName ->
|
onOpenChannel = { serverId, channelName ->
|
||||||
openBuffers = openBuffers + BufferKey(serverId, channelName)
|
openBuffers = openBuffers + BufferKey(serverId, channelName)
|
||||||
},
|
},
|
||||||
@@ -145,10 +170,11 @@ fun main() = application {
|
|||||||
bufferKey = bufferKey,
|
bufferKey = bufferKey,
|
||||||
prefsRepo = prefsRepo,
|
prefsRepo = prefsRepo,
|
||||||
colorScheme = neutralDarkScheme,
|
colorScheme = neutralDarkScheme,
|
||||||
visible = windowsShown,
|
visible = bufferKey !in hiddenBuffers,
|
||||||
onHideAll = hideAll,
|
onHide = { hiddenBuffers = hiddenBuffers + bufferKey },
|
||||||
onOpenBuffer = { newKey -> openBuffers = openBuffers + newKey },
|
onOpenBuffer = { newKey -> openBuffers = openBuffers + newKey },
|
||||||
onCloseSelf = { openBuffers = openBuffers - bufferKey },
|
// Also forgets it was hidden, so reopening it later shows it
|
||||||
|
onCloseSelf = { openBuffers = openBuffers - bufferKey; hiddenBuffers = hiddenBuffers - bufferKey },
|
||||||
// openBuffers is read when /quit runs, so every window open at that moment
|
// openBuffers is read when /quit runs, so every window open at that moment
|
||||||
// gets its "Disconnected" line, even one that hasn't had a line yet.
|
// gets its "Disconnected" line, even one that hasn't had a line yet.
|
||||||
onDisconnectServer = {
|
onDisconnectServer = {
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
package data
|
||||||
|
|
||||||
|
import org.json.JSONArray
|
||||||
|
import org.json.JSONObject
|
||||||
|
import java.io.File
|
||||||
|
import java.nio.file.AtomicMoveNotSupportedException
|
||||||
|
import java.nio.file.Files
|
||||||
|
import java.nio.file.StandardCopyOption
|
||||||
|
|
||||||
|
/**
|
||||||
|
* What wIRC looked like when it last quit: whether wIRC main was hidden to the tray, and
|
||||||
|
* which servers were connected. If main was hidden, the next start keeps it hidden and
|
||||||
|
* reconnects those servers so their channel windows open by themselves (see Main.kt).
|
||||||
|
*/
|
||||||
|
data class SessionState(
|
||||||
|
val mainHidden: Boolean = false,
|
||||||
|
val connectedServerIds: Set<String> = emptySet()
|
||||||
|
)
|
||||||
|
|
||||||
|
/** rootDir/session_state.json, read once at startup and written once at quit. */
|
||||||
|
class SessionStateRepository(rootDir: File) {
|
||||||
|
|
||||||
|
private val file = File(rootDir, "session_state.json")
|
||||||
|
@Volatile private var saved = false
|
||||||
|
|
||||||
|
fun load(): SessionState = try {
|
||||||
|
if (!file.exists()) SessionState() else {
|
||||||
|
val obj = JSONObject(file.readText())
|
||||||
|
val ids = obj.optJSONArray("connectedServerIds") ?: JSONArray()
|
||||||
|
SessionState(
|
||||||
|
mainHidden = obj.optBoolean("mainHidden", false),
|
||||||
|
connectedServerIds = (0 until ids.length()).map { ids.getString(it) }.toSet()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
SessionState()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Only the first call counts: quitting can reach this from more than one place (the
|
||||||
|
* Exit/close handlers and the JVM shutdown hook), and a later call could come after the
|
||||||
|
* servers were already disconnected.
|
||||||
|
*/
|
||||||
|
@Synchronized
|
||||||
|
fun saveOnce(state: SessionState) {
|
||||||
|
if (saved) return
|
||||||
|
saved = true
|
||||||
|
try {
|
||||||
|
val obj = JSONObject().apply {
|
||||||
|
put("mainHidden", state.mainHidden)
|
||||||
|
put("connectedServerIds", JSONArray(state.connectedServerIds.toList()))
|
||||||
|
}
|
||||||
|
file.parentFile?.mkdirs()
|
||||||
|
// Temp file and move, same as WindowPrefsRepository, so a crash can't truncate it
|
||||||
|
val tmp = File(file.parentFile, "${file.name}.tmp")
|
||||||
|
tmp.writeText(obj.toString())
|
||||||
|
try {
|
||||||
|
Files.move(tmp.toPath(), file.toPath(), StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE)
|
||||||
|
} catch (e: AtomicMoveNotSupportedException) {
|
||||||
|
Files.move(tmp.toPath(), file.toPath(), StandardCopyOption.REPLACE_EXISTING)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
// Next start just opens normally
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -27,7 +27,8 @@ import javax.imageio.ImageIO
|
|||||||
/**
|
/**
|
||||||
* wIRC's icon in the Windows notification area, there for as long as the app runs. wIRC
|
* wIRC's icon in the Windows notification area, there for as long as the app runs. wIRC
|
||||||
* windows never show on the taskbar (see [hideFromTaskbar]), so this is how they come back
|
* windows never show on the taskbar (see [hideFromTaskbar]), so this is how they come back
|
||||||
* after being hidden. Left click toggles all windows; right click opens Show / Hide / Exit.
|
* after being hidden. Left click shows every hidden window, or hides them all if none are
|
||||||
|
* hidden; right click opens Show wIRC / Show wIRC main / Hide wIRC / Exit.
|
||||||
* Plain AWT instead of Compose's Tray, because Compose's Tray only reports a double click.
|
* Plain AWT instead of Compose's Tray, because Compose's Tray only reports a double click.
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
@@ -35,11 +36,13 @@ fun AppTray(
|
|||||||
tooltip: String,
|
tooltip: String,
|
||||||
onToggle: () -> Unit,
|
onToggle: () -> Unit,
|
||||||
onShow: () -> Unit,
|
onShow: () -> Unit,
|
||||||
|
onShowMain: () -> Unit,
|
||||||
onHide: () -> Unit,
|
onHide: () -> Unit,
|
||||||
onExit: () -> Unit
|
onExit: () -> Unit
|
||||||
) {
|
) {
|
||||||
val currentToggle by rememberUpdatedState(onToggle)
|
val currentToggle by rememberUpdatedState(onToggle)
|
||||||
val currentShow by rememberUpdatedState(onShow)
|
val currentShow by rememberUpdatedState(onShow)
|
||||||
|
val currentShowMain by rememberUpdatedState(onShowMain)
|
||||||
val currentHide by rememberUpdatedState(onHide)
|
val currentHide by rememberUpdatedState(onHide)
|
||||||
val currentExit by rememberUpdatedState(onExit)
|
val currentExit by rememberUpdatedState(onExit)
|
||||||
|
|
||||||
@@ -49,6 +52,7 @@ fun AppTray(
|
|||||||
?: return@remember null
|
?: return@remember null
|
||||||
val menu = PopupMenu().apply {
|
val menu = PopupMenu().apply {
|
||||||
add(MenuItem("Show wIRC").apply { addActionListener { currentShow() } })
|
add(MenuItem("Show wIRC").apply { addActionListener { currentShow() } })
|
||||||
|
add(MenuItem("Show wIRC main").apply { addActionListener { currentShowMain() } })
|
||||||
add(MenuItem("Hide wIRC").apply { addActionListener { currentHide() } })
|
add(MenuItem("Hide wIRC").apply { addActionListener { currentHide() } })
|
||||||
addSeparator()
|
addSeparator()
|
||||||
add(MenuItem("Exit").apply { addActionListener { currentExit() } })
|
add(MenuItem("Exit").apply { addActionListener { currentExit() } })
|
||||||
@@ -73,17 +77,18 @@ fun AppTray(
|
|||||||
/**
|
/**
|
||||||
* Called first thing inside every wIRC Window's content:
|
* Called first thing inside every wIRC Window's content:
|
||||||
* - Keeps the window off the taskbar (see [hideFromTaskbar]).
|
* - Keeps the window off the taskbar (see [hideFromTaskbar]).
|
||||||
* - Turns a minimize (the title-bar button, Win+D, etc.) into hiding every wIRC window to
|
* - Turns a minimize of this window into hiding just this window to the tray ([onHide]):
|
||||||
* the tray: with no taskbar button, a minimized window would have no way back.
|
* with no taskbar button, a minimized window would have no way back. Win+D minimizes
|
||||||
|
* every window, so it still hides them all.
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun FrameWindowScope.TrayWindowBehavior(state: WindowState, onHideAll: () -> Unit) {
|
fun FrameWindowScope.TrayWindowBehavior(state: WindowState, onHide: () -> Unit) {
|
||||||
remember(window) { hideFromTaskbar(window) }
|
remember(window) { hideFromTaskbar(window) }
|
||||||
val currentHideAll by rememberUpdatedState(onHideAll)
|
val currentHide by rememberUpdatedState(onHide)
|
||||||
LaunchedEffect(state) {
|
LaunchedEffect(state) {
|
||||||
snapshotFlow { state.isMinimized }.collect { minimized ->
|
snapshotFlow { state.isMinimized }.collect { minimized ->
|
||||||
if (minimized) {
|
if (minimized) {
|
||||||
currentHideAll()
|
currentHide()
|
||||||
// Hidden before it's un-minimized, so it doesn't flash back up on the way
|
// Hidden before it's un-minimized, so it doesn't flash back up on the way
|
||||||
window.isVisible = false
|
window.isVisible = false
|
||||||
state.isMinimized = false
|
state.isMinimized = false
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ fun ChannelWindow(
|
|||||||
prefsRepo: WindowPrefsRepository,
|
prefsRepo: WindowPrefsRepository,
|
||||||
colorScheme: ColorScheme,
|
colorScheme: ColorScheme,
|
||||||
visible: Boolean,
|
visible: Boolean,
|
||||||
onHideAll: () -> Unit,
|
onHide: () -> Unit,
|
||||||
onOpenBuffer: (BufferKey) -> Unit,
|
onOpenBuffer: (BufferKey) -> Unit,
|
||||||
onCloseSelf: () -> Unit,
|
onCloseSelf: () -> Unit,
|
||||||
onDisconnectServer: () -> Unit
|
onDisconnectServer: () -> Unit
|
||||||
@@ -86,8 +86,8 @@ fun ChannelWindow(
|
|||||||
// supplies its own title bar with minimize/close/drag/resize to make up for what the
|
// supplies its own title bar with minimize/close/drag/resize to make up for what the
|
||||||
// OS chrome would otherwise provide. No Surface wrapper here (M3 Surface paints an
|
// OS chrome would otherwise provide. No Surface wrapper here (M3 Surface paints an
|
||||||
// opaque background) - ChannelScreen already paints its own alpha-aware background.
|
// opaque background) - ChannelScreen already paints its own alpha-aware background.
|
||||||
// Hidden to the tray along with every other wIRC window when [visible] is false - the
|
// Hidden to the tray when [visible] is false - the window and its composition stay
|
||||||
// window and its composition stay alive, so its buffer keeps filling while hidden.
|
// alive, so its buffer keeps filling while hidden.
|
||||||
Window(
|
Window(
|
||||||
onCloseRequest = onCloseSelf,
|
onCloseRequest = onCloseSelf,
|
||||||
visible = visible,
|
visible = visible,
|
||||||
@@ -98,11 +98,11 @@ fun ChannelWindow(
|
|||||||
transparent = true,
|
transparent = true,
|
||||||
resizable = true
|
resizable = true
|
||||||
) {
|
) {
|
||||||
TrayWindowBehavior(windowState, onHideAll)
|
TrayWindowBehavior(windowState, onHide)
|
||||||
CompositionLocalProvider(LocalLineGap provides lineGap) {
|
CompositionLocalProvider(LocalLineGap provides lineGap) {
|
||||||
MaterialTheme(colorScheme = colorScheme) {
|
MaterialTheme(colorScheme = colorScheme) {
|
||||||
ChannelScreen(
|
ChannelScreen(
|
||||||
onMinimize = onHideAll,
|
onMinimize = onHide,
|
||||||
onCloseWindow = onCloseSelf,
|
onCloseWindow = onCloseSelf,
|
||||||
channelName = windowTitle,
|
channelName = windowTitle,
|
||||||
buffer = buffer,
|
buffer = buffer,
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ fun MainScreen(
|
|||||||
repo: ServerRepository,
|
repo: ServerRepository,
|
||||||
colorScheme: ColorScheme,
|
colorScheme: ColorScheme,
|
||||||
displaySettings: WindowDisplaySettings,
|
displaySettings: WindowDisplaySettings,
|
||||||
|
autoConnectServerIds: Set<String>,
|
||||||
onOpenChannel: (serverId: String, channelName: String) -> Unit,
|
onOpenChannel: (serverId: String, channelName: String) -> Unit,
|
||||||
onExitApp: () -> Unit,
|
onExitApp: () -> Unit,
|
||||||
onUnreadChange: (Int) -> Unit,
|
onUnreadChange: (Int) -> Unit,
|
||||||
@@ -57,13 +58,38 @@ fun MainScreen(
|
|||||||
var openedChannels by remember { mutableStateOf(mapOf<String, List<String>>()) }
|
var openedChannels by remember { mutableStateOf(mapOf<String, List<String>>()) }
|
||||||
val coroutineScope = rememberCoroutineScope()
|
val coroutineScope = rememberCoroutineScope()
|
||||||
|
|
||||||
|
fun connectServer(server: ServerConfig) {
|
||||||
|
// Also rejoins the channels this server was in before a /quit - their
|
||||||
|
// windows are still open, and opening one that already is is a no-op.
|
||||||
|
ConnectionManager.connect(server)
|
||||||
|
if (server.autoJoinChannels.isNotEmpty()) {
|
||||||
|
// Auto-join sends JOIN correctly on connect (see IrcConnection),
|
||||||
|
// but nothing else opens a window for it like the manual
|
||||||
|
// "Join + Open" flow does - so give the server a moment to
|
||||||
|
// actually join us before opening windows for them.
|
||||||
|
coroutineScope.launch {
|
||||||
|
delay(500)
|
||||||
|
server.autoJoinChannels.forEach { ch ->
|
||||||
|
val current = openedChannels[server.id] ?: emptyList()
|
||||||
|
if (ch !in current) openedChannels = openedChannels + (server.id to (current + ch))
|
||||||
|
onOpenChannel(server.id, ch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Servers to connect at startup, as if Connect had been clicked (see Main.kt)
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
servers.filter { it.id in autoConnectServerIds }.forEach { connectServer(it) }
|
||||||
|
}
|
||||||
|
|
||||||
val buffers by ConnectionManager.buffers.collectAsState()
|
val buffers by ConnectionManager.buffers.collectAsState()
|
||||||
val mainMessages by remember { derivedStateOf { buffers[MAIN_BUFFER_KEY]?.messages ?: emptyList() } }
|
val mainMessages by remember { derivedStateOf { buffers[MAIN_BUFFER_KEY]?.messages ?: emptyList() } }
|
||||||
val textColor = mircPalette[displaySettings.textColorMircIndex]
|
val textColor = mircPalette[displaySettings.textColorMircIndex]
|
||||||
|
|
||||||
// Unread indicator: lines that arrived in wIRC main while this window didn't have focus
|
// Unread indicator: lines that arrived in wIRC main while this window didn't have focus
|
||||||
// (e.g. while typing in a channel window). Shown next to the "Server messages" label and,
|
// (e.g. while typing in a channel window). Shown next to the "Server messages" label and,
|
||||||
// via onUnreadChange, in the window title / taskbar. Cleared as soon as the window is
|
// via onUnreadChange, in the window title and the tray tooltip. Cleared as soon as the window is
|
||||||
// focused. Compared by timestamp rather than count so history loaded at startup never
|
// focused. Compared by timestamp rather than count so history loaded at startup never
|
||||||
// counts as new.
|
// counts as new.
|
||||||
val windowFocused = LocalWindowInfo.current.isWindowFocused
|
val windowFocused = LocalWindowInfo.current.isWindowFocused
|
||||||
@@ -116,25 +142,7 @@ fun MainScreen(
|
|||||||
channelInput = channelInputs[server.id] ?: "#",
|
channelInput = channelInputs[server.id] ?: "#",
|
||||||
openedChannels = openedChannels[server.id] ?: emptyList(),
|
openedChannels = openedChannels[server.id] ?: emptyList(),
|
||||||
onChannelInputChange = { text -> channelInputs = channelInputs + (server.id to text) },
|
onChannelInputChange = { text -> channelInputs = channelInputs + (server.id to text) },
|
||||||
onConnect = {
|
onConnect = { connectServer(server) },
|
||||||
// Also rejoins the channels this server was in before a /quit - their
|
|
||||||
// windows are still open, and opening one that already is is a no-op.
|
|
||||||
ConnectionManager.connect(server)
|
|
||||||
if (server.autoJoinChannels.isNotEmpty()) {
|
|
||||||
// Auto-join sends JOIN correctly on connect (see IrcConnection),
|
|
||||||
// but nothing else opens a window for it like the manual
|
|
||||||
// "Join + Open" flow does - so give the server a moment to
|
|
||||||
// actually join us before opening windows for them.
|
|
||||||
coroutineScope.launch {
|
|
||||||
delay(500)
|
|
||||||
server.autoJoinChannels.forEach { ch ->
|
|
||||||
val current = openedChannels[server.id] ?: emptyList()
|
|
||||||
if (ch !in current) openedChannels = openedChannels + (server.id to (current + ch))
|
|
||||||
onOpenChannel(server.id, ch)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onEdit = { editingServer = server; showAddDialog = true },
|
onEdit = { editingServer = server; showAddDialog = true },
|
||||||
onDelete = {
|
onDelete = {
|
||||||
repo.remove(server.id)
|
repo.remove(server.id)
|
||||||
|
|||||||
Reference in New Issue
Block a user