Compare commits

..

2 Commits

Author SHA1 Message Date
Kingkor Roy Tirtho
95e9d563ec fix: add mavenLocal() to plugin and dependency resolution management 2026-08-15 22:01:56 +06:00
Kingkor Roy Tirtho
e826052691 fix(webview): linux webview crashing the app 2026-08-15 21:59:15 +06:00
5 changed files with 115 additions and 75 deletions

View File

@ -19,6 +19,7 @@ package dev.krtirtho.spotube
import androidx.compose.animation.ExperimentalSharedTransitionApi import androidx.compose.animation.ExperimentalSharedTransitionApi
import androidx.compose.animation.SharedTransitionLayout import androidx.compose.animation.SharedTransitionLayout
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@ -41,6 +42,7 @@ import dev.krtirtho.spotube.core.ui.theming.SpotubeTheme
import dev.krtirtho.spotube.modules.settings.SettingsRepository import dev.krtirtho.spotube.modules.settings.SettingsRepository
import dev.krtirtho.spotube.modules.settings.UserSettings import dev.krtirtho.spotube.modules.settings.UserSettings
import dev.krtirtho.spotube.modules.shell.AppShell import dev.krtirtho.spotube.modules.shell.AppShell
import dev.krtirtho.spotube.modules.webview.WebViewScreen
import dev.krtirtho.spotube.resources.iconsax.Iconsax import dev.krtirtho.spotube.resources.iconsax.Iconsax
import dev.krtirtho.spotube.resources.iconsax.IconsaxHome import dev.krtirtho.spotube.resources.iconsax.IconsaxHome
import dev.krtirtho.spotube.resources.iconsax.IconsaxHomeBroken import dev.krtirtho.spotube.resources.iconsax.IconsaxHomeBroken
@ -89,7 +91,11 @@ val tabs = listOf(
) )
) )
@OptIn(KoinExperimentalAPI::class, ExperimentalCoroutinesApi::class, ExperimentalSharedTransitionApi::class) @OptIn(
KoinExperimentalAPI::class,
ExperimentalCoroutinesApi::class,
ExperimentalSharedTransitionApi::class
)
@Composable @Composable
fun App( fun App(
content: @Composable () -> Unit = {} content: @Composable () -> Unit = {}
@ -108,19 +114,29 @@ fun App(
SpotubeTheme(settings = userSettings) { SpotubeTheme(settings = userSettings) {
val baseUITheme = rememberBaseUITheme() val baseUITheme = rememberBaseUITheme()
CompositionLocalProvider(LocalBaseUITheme provides baseUITheme) { CompositionLocalProvider(LocalBaseUITheme provides baseUITheme) {
AppShell(navigator, navigationState) { Box(modifier = Modifier.fillMaxSize()) {
SharedTransitionLayout { val currentRoute = navigationState.backStacks[navigationState.topLevelRoute]?.last()
CompositionLocalProvider(
LocalSharedTransitionScope provides this@SharedTransitionLayout, Box(Modifier.fillMaxSize()) {
) { AppShell(navigator, navigationState) {
Column { SharedTransitionLayout {
NavDisplay( CompositionLocalProvider(
modifier = Modifier.fillMaxSize(), LocalSharedTransitionScope provides this@SharedTransitionLayout,
onBack = navigator::pop, ) {
entries = navigationState.toEntries(koinEntryProvider()) Column {
) NavDisplay(
modifier = Modifier.fillMaxSize(),
onBack = navigator::pop,
entries = navigationState.toEntries(koinEntryProvider())
)
}
}
} }
} }
when (currentRoute) {
Routes.WebView -> WebViewScreen(koinInject())
else -> {}
}
} }
} }
content() content()

View File

@ -18,9 +18,8 @@
package dev.krtirtho.spotube.core.navigation package dev.krtirtho.spotube.core.navigation
import androidx.navigation3.runtime.NavKey import androidx.navigation3.runtime.NavKey
import dev.krtirtho.spotube.modules.artist.ArtistScreen
import dev.krtirtho.spotube.modules.album.AlbumScreen import dev.krtirtho.spotube.modules.album.AlbumScreen
import dev.krtirtho.spotube.modules.artist.ArtistViewModel import dev.krtirtho.spotube.modules.artist.ArtistScreen
import dev.krtirtho.spotube.modules.blacklist.BlacklistScreen import dev.krtirtho.spotube.modules.blacklist.BlacklistScreen
import dev.krtirtho.spotube.modules.home.HomeScreen import dev.krtirtho.spotube.modules.home.HomeScreen
import dev.krtirtho.spotube.modules.library.LibraryScreen import dev.krtirtho.spotube.modules.library.LibraryScreen
@ -31,7 +30,6 @@ import dev.krtirtho.spotube.modules.saved_tracks.SAVED_TRACKS_COLLECTION_ID
import dev.krtirtho.spotube.modules.saved_tracks.SavedTracksScreen import dev.krtirtho.spotube.modules.saved_tracks.SavedTracksScreen
import dev.krtirtho.spotube.modules.search.SearchScreen import dev.krtirtho.spotube.modules.search.SearchScreen
import dev.krtirtho.spotube.modules.settings.SettingsScreen import dev.krtirtho.spotube.modules.settings.SettingsScreen
import dev.krtirtho.spotube.modules.webview.WebViewScreen
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import org.koin.compose.viewmodel.koinViewModel import org.koin.compose.viewmodel.koinViewModel
import org.koin.core.annotation.KoinExperimentalAPI import org.koin.core.annotation.KoinExperimentalAPI
@ -97,9 +95,7 @@ val navigationModule = module {
navigation<Routes.Plugins> { navigation<Routes.Plugins> {
PluginScreen(pluginManager = get()) PluginScreen(pluginManager = get())
} }
navigation<Routes.WebView> { navigation<Routes.WebView> {}
WebViewScreen(get())
}
navigation<Routes.Playlist> { navigation<Routes.Playlist> {
PlaylistScreen( PlaylistScreen(
playlistId = it.playlistId, playlistId = it.playlistId,

View File

@ -19,6 +19,7 @@ package dev.krtirtho.spotube.core.webview
import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
@ -51,6 +52,7 @@ import compose.icons.feathericons.ChevronLeft
import compose.icons.feathericons.ChevronRight import compose.icons.feathericons.ChevronRight
import compose.icons.feathericons.X import compose.icons.feathericons.X
import dev.krtirtho.spotube.core.tools.user_agents.UserAgents import dev.krtirtho.spotube.core.tools.user_agents.UserAgents
import dev.krtirtho.spotube.core.ui.component.ApplicationMainBar
import dev.nucleusframework.webview.jsbridge.IJsMessageHandler import dev.nucleusframework.webview.jsbridge.IJsMessageHandler
import dev.nucleusframework.webview.jsbridge.JsMessage import dev.nucleusframework.webview.jsbridge.JsMessage
import dev.nucleusframework.webview.jsbridge.rememberWebViewJsBridge import dev.nucleusframework.webview.jsbridge.rememberWebViewJsBridge
@ -149,64 +151,63 @@ fun PlatformWebViewScreen(webViewController: WebViewController) {
Scaffold( Scaffold(
contentWindowInsets = WindowInsets.statusBars, contentWindowInsets = WindowInsets.statusBars,
topBar = { topBar = {
Row( ApplicationMainBar(
modifier = Modifier.fillMaxWidth().statusBarsPadding().height(56.dp), title = {
horizontalArrangement = Arrangement.SpaceBetween, Row(
verticalAlignment = Alignment.CenterVertically modifier = Modifier.fillMaxWidth().statusBarsPadding().height(56.dp),
) { horizontalArrangement = Arrangement.SpaceBetween,
Row( verticalAlignment = Alignment.CenterVertically
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Start,
modifier = Modifier.height(56.dp)
) {
IconButton(
onClick = {
navigator.navigateBack()
}, enabled = navigator.canGoBack
) { ) {
Icon( Row(
FeatherIcons.ChevronLeft, verticalAlignment = Alignment.CenterVertically,
contentDescription = "Go back to browser history" horizontalArrangement = Arrangement.Start,
) modifier = Modifier.height(56.dp)
} ) {
IconButton( IconButton(
onClick = { onClick = {
navigator.navigateForward() navigator.navigateBack()
}, enabled = navigator.canGoForward }, enabled = navigator.canGoBack
) { ) {
Icon( Icon(
FeatherIcons.ChevronRight, FeatherIcons.ChevronLeft,
contentDescription = "Go forward to browser history" contentDescription = "Go back to browser history"
) )
}
IconButton(
onClick = {
navigator.navigateForward()
}, enabled = navigator.canGoForward
) {
Icon(
FeatherIcons.ChevronRight,
contentDescription = "Go forward to browser history"
)
}
}
Surface(
modifier = Modifier.weight(1f).height(36.dp).padding(horizontal = 4.dp),
shape = RoundedCornerShape(18.dp),
color = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.5f),
border = BorderStroke(1.dp, Color.Gray.copy(alpha = 0.5f))
) {
BasicTextField(
value = state.lastLoadedUrl ?: "",
onValueChange = {}, // Read-only
readOnly = true,
singleLine = true,
textStyle = MaterialTheme.typography.bodyMedium.copy(
color = MaterialTheme.colorScheme.onSurface,
textAlign = TextAlign.Start
),
modifier = Modifier.fillMaxWidth().padding(horizontal = 12.dp)
.wrapContentHeight(Alignment.CenterVertically)
)
}
} }
} }
Surface( )
modifier = Modifier.weight(1f).height(36.dp).padding(horizontal = 4.dp), })
shape = RoundedCornerShape(18.dp), { innerPadding ->
color = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.5f),
border = BorderStroke(1.dp, Color.Gray.copy(alpha = 0.5f))
) {
BasicTextField(
value = state.lastLoadedUrl ?: "",
onValueChange = {}, // Read-only
readOnly = true,
singleLine = true,
textStyle = MaterialTheme.typography.bodyMedium.copy(
color = MaterialTheme.colorScheme.onSurface,
textAlign = TextAlign.Start
),
modifier = Modifier.fillMaxWidth().padding(horizontal = 12.dp)
.wrapContentHeight(Alignment.CenterVertically)
)
}
IconButton(
onClick = {
webViewController.closeWebview()
}) {
Icon(FeatherIcons.X, contentDescription = "Close WebView")
}
}
}) { innerPadding ->
WebView( WebView(
state = state, state = state,
modifier = Modifier.padding(innerPadding).fillMaxSize(), modifier = Modifier.padding(innerPadding).fillMaxSize(),

View File

@ -26,6 +26,8 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.rememberWindowState import androidx.compose.ui.window.rememberWindowState
import com.sun.jna.Library
import com.sun.jna.Native
import dev.krtirtho.spotube.core.di.initKoin import dev.krtirtho.spotube.core.di.initKoin
import dev.krtirtho.spotube.core.newpipe.NewPipeDownloader import dev.krtirtho.spotube.core.newpipe.NewPipeDownloader
import dev.krtirtho.spotube.core.paths.Paths import dev.krtirtho.spotube.core.paths.Paths
@ -55,8 +57,33 @@ private object KoinServicesProvider : KoinComponent {
val settingsProvider: SettingsProvider get() = get() val settingsProvider: SettingsProvider get() = get()
} }
private interface LibC : Library {
fun setenv(name: String, value: String, overwrite: Int): Int
companion object {
val INSTANCE: LibC = Native.load("c", LibC::class.java)
}
}
/**
* WebKitGTK is only used on Linux (Windows = WebView2, macOS = WKWebView).
* The webview is created *after* the window is already mapped and the Tao
* render/swap loop is running. WebKitGTK's accelerated-compositing path then
* initialises its own GL context in-process, racing Tao's swap thread on the
* same Mesa display, which deterministically segfaults `libgallium` on the
* next Compose flush. Disabling WebKit's hardware-accelerated compositing
* (and its DMABUF renderer) removes that GL context entirely login pages
* render fine in software. Must run before libwebkit2gtk is loaded.
*/
private fun disableWebKitGpuCompositing() {
if (!System.getProperty("os.name").lowercase().contains("linux")) return
LibC.INSTANCE.setenv("WEBKIT_DISABLE_COMPOSITING_MODE", "1", 1)
LibC.INSTANCE.setenv("WEBKIT_DISABLE_DMABUF_RENDERER", "1", 1)
}
@OptIn(ExperimentalComposeUiApi::class) @OptIn(ExperimentalComposeUiApi::class)
fun main() { fun main() {
disableWebKitGpuCompositing()
FileKit.init(appId = "dev.krtirtho.spotube") FileKit.init(appId = "dev.krtirtho.spotube")
initKoin() initKoin()
NewPipeDownloader.init(KoinPathsProvider.paths) NewPipeDownloader.init(KoinPathsProvider.paths)

View File

@ -20,6 +20,7 @@ enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
pluginManagement { pluginManagement {
repositories { repositories {
mavenLocal()
google { google {
mavenContent { mavenContent {
includeGroupAndSubgroups("androidx") includeGroupAndSubgroups("androidx")
@ -29,13 +30,13 @@ pluginManagement {
} }
mavenCentral() mavenCentral()
gradlePluginPortal() gradlePluginPortal()
mavenLocal()
} }
} }
dependencyResolutionManagement { dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories { repositories {
mavenLocal()
google { google {
mavenContent { mavenContent {
includeGroupAndSubgroups("androidx") includeGroupAndSubgroups("androidx")
@ -45,7 +46,6 @@ dependencyResolutionManagement {
} }
mavenCentral() mavenCentral()
maven("https://jitpack.io") maven("https://jitpack.io")
mavenLocal()
} }
} }