Initial commit

This commit is contained in:
weishaupt
2026-09-01 12:32:15 -04:00
commit defdefdb28
33 changed files with 3540 additions and 0 deletions
+112
View File
@@ -0,0 +1,112 @@
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.darkColorScheme
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.key
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.window.Window
import androidx.compose.ui.window.application
import androidx.compose.ui.window.rememberWindowState
import data.AliasRepository
import data.BufferKey
import data.ServerRepository
import data.WindowPrefsRepository
import irc.ConnectionManager
import ui.ChannelWindow
import ui.MainScreen
import java.io.File
// Material 3's darkColorScheme() defaults to a purple accent; this keeps everything
// (OutlinedTextField borders, Button/TextButton labels, etc.) neutral black-and-white
// to match the rest of the app's deliberately plain look, same as the Android version.
private val neutralDarkScheme = darkColorScheme(
primary = Color.White,
onPrimary = Color.Black,
secondary = Color.White,
onSecondary = Color.Black,
background = Color.Black,
onBackground = Color.White,
surface = Color.Black,
onSurface = Color.White,
outline = Color.White
)
// All persistence lives under the user's home directory - the desktop analogue of
// Android's app-private filesDir, since a desktop app has no sandboxed storage of its own.
private val appRoot = File(System.getProperty("user.home"), ".wirc")
fun main() = application {
if (!appRoot.exists()) appRoot.mkdirs()
ConnectionManager.init(appRoot)
// Loaded once here (not per-window) so the alias map is truly global - every
// ChannelWindow's command router reads from this same AliasRepository singleton.
AliasRepository.init()
val repo = remember { ServerRepository(appRoot) }
val prefsRepo = remember { WindowPrefsRepository(appRoot) }
var openBuffers by remember { mutableStateOf(setOf<BufferKey>()) }
// Full shutdown: same steps /quit (bare, no args) already runs from a channel window -
// disconnect every server, then end the process. Shared here so the "Exit wIRC" button
// and every channel window's /quit reuse the exact same logic instead of duplicating it.
val shutdownApp: () -> Unit = {
ConnectionManager.disconnectAll()
exitApplication()
}
// Desktop OSes don't kill backgrounded network connections for battery reasons the
// way Android does, so there's no foreground-service equivalent needed here -
// ConnectionManager's sockets just live for as long as this process runs, and this
// listener (which the Android foreground service used to own) can live directly at
// the application level instead.
LaunchedEffect(Unit) {
ConnectionManager.newQueryBuffers.collect { key ->
openBuffers = openBuffers + key
}
}
// There's no persistent tray/notification surface on this build, so closing the main
// server-list window quits the whole app (and every channel window with it) - the
// same "closing the primary window ends the session" convention most desktop chat
// apps without a tray icon follow.
Window(
onCloseRequest = ::exitApplication,
title = "wIRC - Main",
icon = painterResource("icon.png"),
state = rememberWindowState()
) {
MaterialTheme(colorScheme = neutralDarkScheme) {
Surface {
MainScreen(
repo = repo,
colorScheme = neutralDarkScheme,
onOpenChannel = { serverId, channelName ->
openBuffers = openBuffers + BufferKey(serverId, channelName)
},
onExitApp = shutdownApp
)
}
}
}
openBuffers.forEach { bufferKey ->
key(bufferKey) {
ChannelWindow(
bufferKey = bufferKey,
prefsRepo = prefsRepo,
colorScheme = neutralDarkScheme,
onOpenBuffer = { newKey -> openBuffers = openBuffers + newKey },
onCloseSelf = { openBuffers = openBuffers - bufferKey },
onQuitApp = {
exitApplication()
}
)
}
}
}