Skip to content

Commit ec56813

Browse files
authored
Merge pull request #1202 from DimensionDev/feature/desktop_storage
add storage settings for desktop
2 parents 734fb2a + 35b28ff commit ec56813

File tree

10 files changed

+456
-20
lines changed

10 files changed

+456
-20
lines changed

.github/workflows/desktop.yml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@ on:
77
- release
88
- develop
99
tags:
10-
- '**'
10+
- "**"
1111
paths-ignore:
12-
- '**.md'
13-
- '**.yml'
12+
- "**.md"
13+
- "**.yml"
1414
pull_request:
1515
branches:
1616
- master
@@ -19,7 +19,7 @@ on:
1919

2020
jobs:
2121
build:
22-
runs-on: [ubuntu-latest]
22+
runs-on: [macos-latest]
2323
timeout-minutes: 30
2424

2525
steps:
@@ -31,7 +31,7 @@ jobs:
3131
- name: Set up JDK
3232
uses: actions/setup-java@v3
3333
with:
34-
distribution: 'zulu'
34+
distribution: "zulu"
3535
java-version: 21
3636

3737
# Build with Gradle

desktopApp/build-swift.gradle.kts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,7 @@ tasks.register<Exec>("compileWebviewBridgeArm64") {
2222
"-configuration", "Release",
2323
)
2424

25-
// copy buildOutput to targetLib
26-
if (isMac) {
25+
doLast {
2726
Files.copy(
2827
buildOutput.asFile.toPath(),
2928
targetLib.toPath(),

desktopApp/install-native-libs.gradle.kts

Lines changed: 85 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -81,16 +81,50 @@ fun Project.registerExtractFromJarTask(
8181
}
8282
}
8383

84-
val nativeDestDirPath = (findProperty("nativeDestDir") as String?) ?: "resources/macos-arm64"
85-
val nativeDestDir = file(nativeDestDirPath)
84+
val currentArch = System.getProperty("os.arch")
85+
val isArm = currentArch == "aarch64" || currentArch == "arm64"
86+
val isX64 = currentArch == "x86_64" || currentArch == "amd64"
87+
val currentOs =
88+
org.gradle.internal.os.OperatingSystem
89+
.current()
90+
91+
val resourceDirName =
92+
when {
93+
currentOs.isMacOsX && isArm -> "macos-arm64"
94+
currentOs.isMacOsX && isX64 -> "macos-x64"
95+
currentOs.isLinux && isArm -> "linux-arm64"
96+
currentOs.isLinux && isX64 -> "linux-x64"
97+
currentOs.isWindows && isX64 -> "windows-x64"
98+
currentOs.isWindows && isArm -> "windows-arm64"
99+
else -> throw GradleException("Unsupported OS or architecture: ${currentOs.name} $currentArch")
100+
}
86101

102+
val nativeDestDirPath = "resources/$resourceDirName"
103+
val nativeDestDir = file(nativeDestDirPath)
87104
val sqliteVersion = (findProperty("sqliteVersion") as String?) ?: "2.5.2"
88-
val sqliteOsArch = (findProperty("sqliteOsArch") as String?) ?: "osx_arm64"
105+
val sqliteOsArch =
106+
when {
107+
currentOs.isMacOsX && isArm -> "osx_arm64"
108+
currentOs.isMacOsX && isX64 -> "osx_x64"
109+
currentOs.isLinux && isArm -> "linux_arm64"
110+
currentOs.isLinux && isX64 -> "linux_x64"
111+
currentOs.isWindows && isX64 -> "windows_x64"
112+
currentOs.isWindows && isArm -> "windows_arm64"
113+
else -> throw GradleException("Unsupported OS or architecture: ${currentOs.name} $currentArch")
114+
}
115+
116+
val sqliteFileName =
117+
when {
118+
currentOs.isMacOsX -> "libsqliteJni.dylib"
119+
currentOs.isLinux -> "libsqliteJni.so"
120+
currentOs.isWindows -> "sqliteJni.dll"
121+
else -> throw GradleException("Unsupported OS: ${currentOs.name}")
122+
}
89123

90124
val sqliteJarName = "sqlite-bundled-jvm-$sqliteVersion.jar"
91125
val sqliteJarUrl =
92126
"https://dl.google.com/android/maven2/androidx/sqlite/sqlite-bundled-jvm/$sqliteVersion/$sqliteJarName"
93-
val sqliteEntry = "natives/$sqliteOsArch/libsqliteJni.dylib"
127+
val sqliteEntry = "natives/$sqliteOsArch/$sqliteFileName"
94128

95129
val sqliteTask =
96130
registerExtractFromJarTask(
@@ -99,14 +133,34 @@ val sqliteTask =
99133
cacheSubDir = "sqliteJni/$sqliteVersion",
100134
jarFileName = sqliteJarName,
101135
entryPathInJar = sqliteEntry,
102-
destFile = nativeDestDir.resolve("libsqliteJni.dylib"),
136+
destFile = nativeDestDir.resolve(sqliteFileName),
103137
)
104138

105139
val cmpVersion = (findProperty("composeMediaPlayerVersion") as String?) ?: "0.8.1"
106140
val cmpJarName = "composemediaplayer-jvm-$cmpVersion.jar"
107141
val cmpJarUrl =
108142
"https://repo1.maven.org/maven2/io/github/kdroidfilter/composemediaplayer-jvm/$cmpVersion/$cmpJarName"
109-
val cmpEntry = "darwin-aarch64/libNativeVideoPlayer.dylib"
143+
144+
val cmpDirName =
145+
when {
146+
currentOs.isMacOsX && isArm -> "darwin-aarch64"
147+
currentOs.isMacOsX && isX64 -> "darwin-x86-64"
148+
currentOs.isLinux && isArm -> "linux-aarch64"
149+
currentOs.isLinux && isX64 -> "linux-x86-64"
150+
currentOs.isWindows && isX64 -> "win32-x86-64"
151+
currentOs.isWindows && isArm -> "win32-arm64"
152+
else -> throw GradleException("Unsupported OS or architecture: ${currentOs.name} $currentArch")
153+
}
154+
155+
val cmpLibName =
156+
when {
157+
currentOs.isMacOsX -> "libNativeVideoPlayer.dylib"
158+
currentOs.isLinux -> "libNativeVideoPlayer.so"
159+
currentOs.isWindows -> "NativeVideoPlayer.dll"
160+
else -> throw GradleException("Unsupported OS: ${currentOs.name}")
161+
}
162+
163+
val cmpEntry = "$cmpDirName/$cmpLibName"
110164

111165
val cmpTask =
112166
registerExtractFromJarTask(
@@ -115,13 +169,35 @@ val cmpTask =
115169
cacheSubDir = "composeMediaPlayer/$cmpVersion",
116170
jarFileName = cmpJarName,
117171
entryPathInJar = cmpEntry,
118-
destFile = nativeDestDir.resolve("libNativeVideoPlayer.dylib"),
172+
destFile = nativeDestDir.resolve(cmpLibName),
119173
)
120174

121175
val jnaVersion = (findProperty("jnaVersion") as String?) ?: "5.17.0"
122176
val jnaJarName = "jna-$jnaVersion.jar"
123177
val jnaJarUrl = "https://repo1.maven.org/maven2/net/java/dev/jna/jna/$jnaVersion/$jnaJarName"
124-
val jnaEntry = "com/sun/jna/darwin-aarch64/libjnidispatch.jnilib"
178+
val jnaDirName =
179+
when {
180+
currentOs.isMacOsX && isArm -> "darwin-aarch64"
181+
currentOs.isMacOsX && isX64 -> "darwin-x86-64"
182+
currentOs.isLinux && isArm -> "linux-aarch64"
183+
currentOs.isLinux && isX64 -> "linux-x86-64"
184+
currentOs.isWindows && isX64 -> "win32-x86-64"
185+
currentOs.isWindows && isArm -> "win32-arm64"
186+
else -> throw GradleException("Unsupported OS or architecture: ${currentOs.name} $currentArch")
187+
}
188+
val jnaLibName =
189+
when {
190+
currentOs.isMacOsX -> "libjnidispatch.jnilib"
191+
currentOs.isLinux -> "libjnidispatch.so"
192+
currentOs.isWindows -> "jnidispatch.dll"
193+
else -> throw GradleException("Unsupported OS: ${currentOs.name}")
194+
}
195+
val jnaEntry = "com/sun/jna/$jnaDirName/$jnaLibName"
196+
val targetJnaLibName =
197+
when {
198+
currentOs.isMacOsX -> "libjnidispatch.dylib"
199+
else -> jnaLibName
200+
}
125201

126202
val jnaTask =
127203
registerExtractFromJarTask(
@@ -130,7 +206,7 @@ val jnaTask =
130206
cacheSubDir = "jna/$jnaVersion",
131207
jarFileName = jnaJarName,
132208
entryPathInJar = jnaEntry,
133-
destFile = nativeDestDir.resolve("libjnidispatch.dylib"),
209+
destFile = nativeDestDir.resolve(targetJnaLibName),
134210
)
135211

136212
afterEvaluate {

desktopApp/src/main/kotlin/dev/dimension/flare/ui/route/Route.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,12 @@ internal sealed interface Route {
222222
@Serializable
223223
data object TabSetting : ScreenRoute
224224

225+
@Serializable
226+
data object LocalCache : ScreenRoute
227+
228+
@Serializable
229+
data object StorageUsage : ScreenRoute
230+
225231
companion object {
226232
public fun parse(url: String): Route? {
227233
val data = Url(url)

desktopApp/src/main/kotlin/dev/dimension/flare/ui/route/Router.kt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,9 @@ import dev.dimension.flare.ui.screen.misskey.AntennasListScreen
4444
import dev.dimension.flare.ui.screen.rss.EditRssSourceScreen
4545
import dev.dimension.flare.ui.screen.rss.RssListScreen
4646
import dev.dimension.flare.ui.screen.serviceselect.ServiceSelectScreen
47+
import dev.dimension.flare.ui.screen.settings.LocalCacheScreen
4748
import dev.dimension.flare.ui.screen.settings.SettingsScreen
49+
import dev.dimension.flare.ui.screen.settings.StorageScreen
4850
import dev.dimension.flare.ui.screen.status.StatusScreen
4951
import dev.dimension.flare.ui.screen.status.VVOCommentScreen
5052
import dev.dimension.flare.ui.screen.status.VVOStatusScreen
@@ -353,6 +355,12 @@ internal fun WindowScope.RouteContent(
353355
toLogin = {
354356
navigate(Route.ServiceSelect)
355357
},
358+
toLocalCache = {
359+
navigate(Route.LocalCache)
360+
},
361+
toStorage = {
362+
navigate(Route.StorageUsage)
363+
},
356364
)
357365
}
358366

@@ -536,5 +544,11 @@ internal fun WindowScope.RouteContent(
536544
)
537545
},
538546
)
547+
548+
Route.LocalCache ->
549+
LocalCacheScreen()
550+
551+
Route.StorageUsage ->
552+
StorageScreen()
539553
}
540554
}
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
package dev.dimension.flare.ui.screen.settings
2+
3+
import androidx.compose.foundation.layout.Box
4+
import androidx.compose.foundation.layout.fillMaxSize
5+
import androidx.compose.foundation.layout.widthIn
6+
import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridItemSpan
7+
import androidx.compose.foundation.lazy.staggeredgrid.rememberLazyStaggeredGridState
8+
import androidx.compose.foundation.text.input.TextFieldLineLimits
9+
import androidx.compose.foundation.text.input.rememberTextFieldState
10+
import androidx.compose.runtime.Composable
11+
import androidx.compose.runtime.LaunchedEffect
12+
import androidx.compose.runtime.getValue
13+
import androidx.compose.runtime.mutableStateOf
14+
import androidx.compose.runtime.remember
15+
import androidx.compose.runtime.setValue
16+
import androidx.compose.runtime.snapshotFlow
17+
import androidx.compose.ui.Alignment
18+
import androidx.compose.ui.Modifier
19+
import androidx.compose.ui.platform.LocalUriHandler
20+
import androidx.compose.ui.unit.dp
21+
import dev.dimension.flare.LocalWindowPadding
22+
import dev.dimension.flare.Res
23+
import dev.dimension.flare.common.isEmpty
24+
import dev.dimension.flare.common.isSuccess
25+
import dev.dimension.flare.local_history_search_status_title
26+
import dev.dimension.flare.local_history_search_user_title
27+
import dev.dimension.flare.ui.common.itemsIndexed
28+
import dev.dimension.flare.ui.component.AccountItem
29+
import dev.dimension.flare.ui.component.status.LazyStatusVerticalStaggeredGrid
30+
import dev.dimension.flare.ui.component.status.status
31+
import dev.dimension.flare.ui.model.ClickContext
32+
import dev.dimension.flare.ui.model.UiState
33+
import dev.dimension.flare.ui.presenter.invoke
34+
import dev.dimension.flare.ui.presenter.settings.LocalCacheSearchPresenter
35+
import io.github.composefluent.component.LiteFilter
36+
import io.github.composefluent.component.PillButton
37+
import io.github.composefluent.component.Text
38+
import io.github.composefluent.component.TextField
39+
import kotlinx.collections.immutable.toImmutableList
40+
import kotlinx.coroutines.FlowPreview
41+
import kotlinx.coroutines.flow.debounce
42+
import kotlinx.coroutines.flow.distinctUntilChanged
43+
import moe.tlaster.precompose.molecule.producePresenter
44+
import org.jetbrains.compose.resources.StringResource
45+
import org.jetbrains.compose.resources.stringResource
46+
import kotlin.time.Duration.Companion.seconds
47+
48+
@Composable
49+
internal fun LocalCacheScreen() {
50+
val uriHandler = LocalUriHandler.current
51+
val state by producePresenter {
52+
presenter()
53+
}
54+
val lazyListState = rememberLazyStaggeredGridState()
55+
LazyStatusVerticalStaggeredGrid(
56+
state = lazyListState,
57+
contentPadding = LocalWindowPadding.current,
58+
modifier =
59+
Modifier
60+
.fillMaxSize(),
61+
) {
62+
item(
63+
span = StaggeredGridItemSpan.FullLine,
64+
) {
65+
Box(
66+
modifier =
67+
Modifier
68+
.fillMaxSize(),
69+
) {
70+
TextField(
71+
state = state.searchTextState,
72+
modifier =
73+
Modifier
74+
.widthIn(min = 200.dp)
75+
.align(Alignment.CenterEnd),
76+
lineLimits = TextFieldLineLimits.SingleLine,
77+
)
78+
}
79+
}
80+
item(
81+
span = StaggeredGridItemSpan.FullLine,
82+
) {
83+
LiteFilter {
84+
state.allSearchTypes.forEach { searchType ->
85+
PillButton(
86+
selected = state.selectedSearchType == searchType,
87+
onSelectedChanged = {
88+
if (it) {
89+
state.setSearchType(searchType)
90+
}
91+
},
92+
) {
93+
Text(stringResource(searchType.title))
94+
}
95+
}
96+
}
97+
}
98+
when (state.selectedSearchType) {
99+
SearchType.Status ->
100+
status(
101+
if (state.data.isEmpty || !state.data.isSuccess()) {
102+
state.history
103+
} else {
104+
state.data
105+
},
106+
)
107+
SearchType.User ->
108+
itemsIndexed(
109+
if (state.searchUser.isEmpty || !state.searchUser.isSuccess()) {
110+
state.userHistory
111+
} else {
112+
state.searchUser
113+
},
114+
) { index, itemsCount, user ->
115+
AccountItem(
116+
userState = UiState.Success(user),
117+
onClick = { user.onClicked.invoke(ClickContext(uriHandler::openUri)) },
118+
toLogin = {},
119+
)
120+
}
121+
}
122+
}
123+
}
124+
125+
@OptIn(FlowPreview::class)
126+
@Composable
127+
private fun presenter() =
128+
run {
129+
val searchTextState = rememberTextFieldState()
130+
val state = remember { LocalCacheSearchPresenter() }.invoke()
131+
var selectedSearchType by remember { mutableStateOf(SearchType.Status) }
132+
LaunchedEffect(Unit) {
133+
snapshotFlow { searchTextState.text }
134+
.distinctUntilChanged()
135+
.debounce(1.seconds)
136+
.collect {
137+
state.setQuery(it.toString())
138+
}
139+
}
140+
object : LocalCacheSearchPresenter.State by state {
141+
val searchTextState = searchTextState
142+
143+
val selectedSearchType = selectedSearchType
144+
val allSearchTypes = SearchType.entries.toImmutableList()
145+
146+
fun setSearchType(value: SearchType) {
147+
selectedSearchType = value
148+
}
149+
}
150+
}
151+
152+
private enum class SearchType(
153+
val title: StringResource,
154+
) {
155+
Status(Res.string.local_history_search_status_title),
156+
User(Res.string.local_history_search_user_title),
157+
}

0 commit comments

Comments
 (0)