Skip to content

Commit 694abf8

Browse files
committed
Merge branch 'release/v1.3.2'
2 parents 76297f0 + a3a3e9a commit 694abf8

File tree

42 files changed

+1076
-213
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+1076
-213
lines changed

.github/workflows/develop.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ jobs:
8383
echo "$delimiter" >> $GITHUB_OUTPUT
8484
8585
- name: Upload Release Build to Artifacts
86-
uses: actions/upload-artifact@v5
86+
uses: actions/upload-artifact@v6
8787
with:
8888
name: release-apks
8989
path: ${{ steps.releaseApks.outputs.release_apks }}

.github/workflows/release.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ jobs:
8585
echo "$delimiter" >> $GITHUB_OUTPUT
8686
8787
- name: Upload Release Build to Artifacts
88-
uses: actions/upload-artifact@v5
88+
uses: actions/upload-artifact@v6
8989
with:
9090
name: release-apks
9191
path: ${{ steps.releaseApks.outputs.release_apks }}

app/build.gradle.kts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ android {
1919

2020
defaultConfig {
2121
applicationId = "com.mrl.pixiv"
22-
versionCode = 10301
23-
versionName = "1.3.1"
22+
versionCode = 10302
23+
versionName = "1.3.2"
2424

2525
vectorDrawables {
2626
useSupportLibrary = true
@@ -79,7 +79,9 @@ android {
7979
signingConfig = signingConfigs.getByName("release")
8080
}
8181
}
82-
82+
buildFeatures {
83+
buildConfig = true
84+
}
8385

8486
packaging {
8587
resources {

app/src/main/kotlin/com/mrl/pixiv/App.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import coil3.disk.DiskCache
77
import coil3.memory.MemoryCache
88
import com.mrl.pixiv.common.data.setting.setAppCompatDelegateThemeMode
99
import com.mrl.pixiv.common.repository.SettingRepository
10+
import com.mrl.pixiv.common.repository.VersionManager
1011
import com.mrl.pixiv.common.util.AppUtil
1112
import com.mrl.pixiv.common.util.deleteFiles
1213
import com.mrl.pixiv.common.util.initKotzilla
@@ -39,7 +40,7 @@ class App : Application() {
3940
instance = this
4041
MMKV.initialize(this)
4142
initializeFirebase()
42-
AppUtil.init(this)
43+
AppUtil.init(this, BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE, BuildConfig.FLAVOR)
4344
startKoin {
4445
androidLogger()
4546
androidContext(this@App)
@@ -48,6 +49,7 @@ class App : Application() {
4849
}
4950
migrateDataStoreToMMKV()
5051
setAppCompatDelegateThemeMode(SettingRepository.settingTheme)
52+
VersionManager.checkUpdate()
5153
}
5254

5355
private fun migrateDataStoreToMMKV() {

app/src/main/kotlin/com/mrl/pixiv/navigation/Navigation3MainGraph.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ import com.mrl.pixiv.search.SearchScreen
5858
import com.mrl.pixiv.search.result.SearchResultsScreen
5959
import com.mrl.pixiv.setting.FileNameFormatScreen
6060
import com.mrl.pixiv.setting.SettingScreen
61+
import com.mrl.pixiv.setting.about.AboutScreen
6162
import com.mrl.pixiv.setting.appdata.AppDataScreen
6263
import com.mrl.pixiv.setting.block.BlockSettingsScreen
6364
import com.mrl.pixiv.setting.download.DownloadScreen
@@ -261,6 +262,9 @@ fun Navigation3MainGraph(
261262
entry<Destination.Download> {
262263
DownloadScreen()
263264
}
265+
entry<Destination.About> {
266+
AboutScreen()
267+
}
264268
}
265269
)
266270
}
@@ -390,7 +394,7 @@ private fun LogScreen(
390394

391395
is Destination.Login, Destination.LoginOption, Destination.OAuthLogin,
392396
Destination.Search, Destination.Setting, Destination.NetworkSetting,
393-
Destination.History, Destination.BlockSettings, Destination.AppData, Destination.Download, Destination.FileNameFormat -> Unit
397+
Destination.History, Destination.BlockSettings, Destination.AppData, Destination.Download, Destination.FileNameFormat, Destination.About -> Unit
394398
}
395399
})
396400
}

build-logic/src/main/kotlin/com/mrl/pixiv/buildsrc/KotlinAndroid.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ internal fun Project.configureKotlinAndroid(
7373
// Logger
7474
implementation(libs.findLibrary("kermit").get())
7575
}
76+
77+
configureSortKoinKspGeneration()
7678
}
7779

7880
/**
Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
package com.mrl.pixiv.buildsrc
2+
3+
import com.android.build.gradle.LibraryExtension
4+
import com.android.build.gradle.internal.dsl.BaseAppModuleExtension
5+
import org.gradle.api.DefaultTask
6+
import org.gradle.api.Project
7+
import org.gradle.api.Task
8+
import org.gradle.api.file.DirectoryProperty
9+
import org.gradle.api.tasks.InputDirectory
10+
import org.gradle.api.tasks.TaskAction
11+
import org.gradle.kotlin.dsl.configure
12+
import org.gradle.kotlin.dsl.register
13+
import org.gradle.kotlin.dsl.support.uppercaseFirstChar
14+
import org.gradle.work.DisableCachingByDefault
15+
16+
fun Project.configureSortKoinKspGeneration() {
17+
val taskBlock: (variant: String) -> Unit = { variant ->
18+
val cap = variant.uppercaseFirstChar()
19+
20+
val kspOutputDirPath = layout.buildDirectory.dir(
21+
"generated/ksp/$variant/kotlin/org/koin/ksp/generated"
22+
)
23+
24+
val sortTask = tasks.register<SortKoinModulesTask>("sort${cap}KoinModules") {
25+
group = "koin"
26+
description = "Sort Koin KSP generated modules for variant: $variant"
27+
kspOutputDir.set(kspOutputDirPath)
28+
29+
onlyIf {
30+
val dir = kspOutputDir.orNull?.asFile
31+
dir != null && dir.exists()
32+
}
33+
}
34+
35+
val kspTaskName = "ksp${cap}Kotlin"
36+
val compileTaskName = "compile${cap}Kotlin"
37+
38+
// 关键:平铺式建立依赖,不要嵌套 configure
39+
// sort <- ksp
40+
sortTask.configure {
41+
dependsOn(tasks.named(kspTaskName))
42+
}
43+
44+
// compile <- sort
45+
tasks.named(compileTaskName).configure {
46+
dependsOn(sortTask)
47+
}
48+
49+
logger.quiet("✅ 已链接: $kspTaskName${sortTask.name}$compileTaskName")
50+
}
51+
52+
if (plugins.hasPlugin("com.android.application")) {
53+
extensions.configure<BaseAppModuleExtension> {
54+
applicationVariants.all {
55+
taskBlock(name)
56+
}
57+
}
58+
} else {
59+
extensions.configure<LibraryExtension> {
60+
libraryVariants.all {
61+
taskBlock(name)
62+
}
63+
}
64+
}
65+
}
66+
67+
/**
68+
* ⭐ 自定义任务类:支持配置缓存 + 条件跳过
69+
*/
70+
@DisableCachingByDefault(because = "KSP 生成文件不稳定,不启用任务缓存")
71+
abstract class SortKoinModulesTask : DefaultTask() {
72+
73+
// 📥 可选的输入目录(不存在时自动跳过)
74+
@get:InputDirectory
75+
abstract val kspOutputDir: DirectoryProperty
76+
77+
@TaskAction
78+
fun sortModules() {
79+
// 1️⃣ 检查目录是否存在
80+
val outputDir = kspOutputDir.orNull?.asFile
81+
82+
if (outputDir == null || !outputDir.exists()) {
83+
logger.quiet("⏭️ KSP 输出目录不存在,跳过排序: ${outputDir?.absolutePath ?: "null"}")
84+
return // 直接返回,不执行排序逻辑
85+
}
86+
87+
logger.quiet(" 📂 处理目录: ${outputDir.absolutePath}")
88+
89+
// 2️⃣ 查找 .kt 文件
90+
val moduleFiles = outputDir.listFiles { file ->
91+
file.isFile && file.name.endsWith(".kt")
92+
}?.sortedBy { it.name } ?: run {
93+
logger.quiet(" ⚠️ 目录为空或不可读")
94+
return
95+
}
96+
97+
if (moduleFiles.isEmpty()) {
98+
logger.quiet(" ⚠️ 未找到 .kt 文件,跳过排序")
99+
return
100+
}
101+
102+
logger.quiet(" 📋 发现 ${moduleFiles.size} 个文件: ${moduleFiles.joinToString(", ") { it.name }}")
103+
104+
// 3️⃣ 执行排序
105+
var sortedCount = 0
106+
moduleFiles.forEach { moduleFile ->
107+
val originalContent = moduleFile.readText(Charsets.UTF_8)
108+
val sortedContent = sortKoinModuleContent(originalContent)
109+
110+
if (originalContent != sortedContent) {
111+
moduleFile.writeText(sortedContent, Charsets.UTF_8)
112+
logger.quiet(" ✅ Sorted: ${moduleFile.name}")
113+
sortedCount++
114+
}
115+
}
116+
117+
logger.lifecycle(" 📊 共排序 $sortedCount 个文件")
118+
}
119+
}
120+
121+
/**
122+
* ⭐ 排序核心逻辑
123+
*/
124+
private fun Task.sortKoinModuleContent(content: String): String {
125+
val moduleRegex = Regex(
126+
pattern = """module\s*\{([\s\S]*?)\n\s*\}""",
127+
options = setOf(RegexOption.MULTILINE)
128+
)
129+
130+
var result = moduleRegex.replace(content) { matchResult ->
131+
val blockContent = matchResult.groupValues[1]
132+
val lines = blockContent.split("\n")
133+
.map { it.trim() }
134+
.filter { it.isNotEmpty() }
135+
.sorted()
136+
137+
val rebuiltContent = lines.joinToString("\n ")
138+
"""module {
139+
$rebuiltContent
140+
}"""
141+
}
142+
// 2️⃣ 排序 @ExternalDefinition 函数定义
143+
result = sortDefinitionsByExternalDefinition(result)
144+
145+
return result
146+
}
147+
148+
/**
149+
* ⭐ 对 @ExternalDefinition 函数定义按照 ExternalDefinition 值进行排序
150+
*/
151+
private fun Task.sortDefinitionsByExternalDefinition(content0: String): String {
152+
// 1) 统一换行,避免 CRLF/LF 导致的边界不一致
153+
val content = content0.replace("\r\n", "\n").replace("\r", "\n")
154+
155+
// 2) 抽离固定 footer 注释,避免被上一个 block 吞掉
156+
val footerRegex = Regex("""(?s)\n\s*//DefaultModule generation is disabled\..*""")
157+
val footerMatch = footerRegex.find(content)
158+
val footer = footerMatch?.value.orEmpty()
159+
val contentNoFooter = footerMatch?.let { content.removeRange(it.range) } ?: content
160+
161+
// 3) 仅匹配“行首”的 ExternalDefinition block,block 结束于下一个 ExternalDefinition(行首)或 EOF
162+
val defRegex = Regex(
163+
pattern = """(?ms)^\s*@ExternalDefinition\([^)]*\)\s*public fun Module\..*?(?=^\s*@ExternalDefinition|\Z)"""
164+
)
165+
166+
val matches = defRegex.findAll(contentNoFooter).toList()
167+
if (matches.isEmpty()) return content
168+
169+
val header = contentNoFooter.substring(0, matches.first().range.first)
170+
val tail = contentNoFooter.substring(matches.last().range.last + 1)
171+
172+
val sortedBlocks = matches
173+
.map { it.value }
174+
.sorted()
175+
176+
return buildString {
177+
append(header)
178+
sortedBlocks.forEach { b ->
179+
append(b.trimEnd())
180+
append("\n")
181+
}
182+
append(tail)
183+
if (footer.isNotBlank()) {
184+
if (!endsWith("\n")) append("\n")
185+
append(footer.trimStart('\n'))
186+
}
187+
}.also { rebuiltContent ->
188+
logger.quiet("before:\n$content\n\nafter:\n$rebuiltContent")
189+
}
190+
}

common/core/src/main/kotlin/com/mrl/pixiv/common/router/Navigation.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,9 @@ sealed class Destination : NavKey {
9595

9696
@Serializable
9797
data object Download : Destination()
98+
99+
@Serializable
100+
data object About : Destination()
98101
}
99102

100103
@Serializable

common/core/src/main/kotlin/com/mrl/pixiv/common/router/NavigationManager.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,4 +165,8 @@ class NavigationManager(
165165
fun navigateToDownloadScreen() {
166166
backStack.navigate(route = Destination.Download)
167167
}
168+
169+
fun navigateToAboutScreen() {
170+
backStack.navigate(route = Destination.About)
171+
}
168172
}

common/core/src/main/kotlin/com/mrl/pixiv/common/util/AppUtil.kt

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,31 @@ import androidx.annotation.StringRes
66

77
object AppUtil {
88
lateinit var appContext: Context
9+
private set
910
lateinit var application: Application
11+
private set
1012

11-
fun init(application: Application) {
13+
lateinit var versionName: String
14+
private set
15+
var versionCode: Int = 0
16+
private set
17+
18+
lateinit var flavor: String
19+
20+
fun init(application: Application, versionName: String, versionCode: Int, flavor: String) {
1221
appContext = application
1322
this.application = application
23+
this.versionName = versionName
24+
this.versionCode = versionCode
25+
this.flavor = flavor
1426
}
1527

1628
@Suppress("UNCHECKED_CAST")
1729
fun <T> getSystemService(serviceName: String): T? {
1830
return appContext.getSystemService(serviceName) as? T
1931
}
2032

21-
fun getString(@StringRes resId: Int): String {
22-
return appContext.getString(resId)
33+
fun getString(@StringRes resId: Int, vararg args: Any): String {
34+
return appContext.getString(resId, *args)
2335
}
2436
}

0 commit comments

Comments
 (0)