diff --git a/build-logic/config/src/main/kotlin/Config.kt b/build-logic/config/src/main/kotlin/Config.kt index 1ad1ad9de0..bda6569046 100644 --- a/build-logic/config/src/main/kotlin/Config.kt +++ b/build-logic/config/src/main/kotlin/Config.kt @@ -69,8 +69,8 @@ object Config { } fun getBaseName(project: Project): String { - return project.path.split(Pattern.compile("-|:")).joinToString("") { - it.uppercase() + return project.path.split(Pattern.compile("-|:|_")).joinToString("") { name -> + name.replaceFirstChar { it.uppercaseChar() } } } } diff --git a/build-logic/config/src/main/kotlin/Multiplatform.kt b/build-logic/config/src/main/kotlin/Multiplatform.kt index 40e5e9fb86..e1556002cf 100644 --- a/build-logic/config/src/main/kotlin/Multiplatform.kt +++ b/build-logic/config/src/main/kotlin/Multiplatform.kt @@ -18,4 +18,30 @@ object Multiplatform { fun enableDesktop(project: Project): Boolean { return project.rootProject.properties["cyxbs.multiplatform.desktop"] == "true" } + + // 运行 Android 的任务 + fun runAndroid(project: Project): Boolean { + return project.gradle.startParameter.taskNames.any { + it.contains("assembleRelease") + || it.contains("assembleDebug") + || it == "channelRelease" + || it == "channelDebug" + || it == "cyxbsRelease" + } + } + + // 运行 Desktop 的任务 + fun runDesktop(project: Project): Boolean { + return project.gradle.startParameter.taskNames.any { + it.contains("desktop") + || it.contains("package") + } + } + + // 运行 WasmJs 的任务 + fun runWasmJs(project: Project): Boolean { + return project.gradle.startParameter.taskNames.any { + it.contains("wasmJs") + } + } } \ No newline at end of file diff --git a/build-logic/manager/src/main/kotlin/UsePlugins.kt b/build-logic/manager/src/main/kotlin/UsePlugins.kt index 8d57c89237..6f0797a153 100644 --- a/build-logic/manager/src/main/kotlin/UsePlugins.kt +++ b/build-logic/manager/src/main/kotlin/UsePlugins.kt @@ -1,15 +1,10 @@ -import com.android.build.api.dsl.ApplicationBuildFeatures -import com.android.build.api.dsl.CommonExtension -import com.android.build.api.dsl.LibraryBuildFeatures import com.g985892345.provider.plugin.gradle.extensions.KtProviderExtensions import com.google.devtools.ksp.gradle.KspExtension -import org.gradle.api.NamedDomainObjectContainer import org.gradle.api.Project import org.gradle.kotlin.dsl.apply import org.gradle.kotlin.dsl.configure import org.gradle.kotlin.dsl.dependencies import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension -import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet /** @@ -28,38 +23,8 @@ fun Project.useKtProvider(isNeedKsp: Boolean = !name.startsWith("api")) { kspMultiplatform(ktProvider.ksp) } extensions.configure { - extensions.configure> { - commonMain.dependencies { - implementation(libsEx.`kmp-ktProvider-api`) - } - } - } -} - -/** - * 使用 DataBinding - * @param isNeedKapt 是否只依赖而不开启 DataBinding,默认开启 DataBinding - */ -@Deprecated("不再建议使用 DataBinding,因为强依赖了 kapt,官方也未提供 ksp 支持。使用 Int.view() 或者 findViewById() 代替") -fun Project.useDataBinding(isNeedKapt: Boolean = !name.startsWith("api")) { - if (isNeedKapt) { - // kapt 按需引入 - apply(plugin = "org.jetbrains.kotlin.kapt") - extensions.configure(CommonExtension::class.java) { - buildFeatures { - when (this) { - is LibraryBuildFeatures -> dataBinding = true // com.android.library 插件的配置 - is ApplicationBuildFeatures -> dataBinding = true // com.android.application 插件的配置 - } - } - } - } - extensions.configure { - extensions.configure> { - androidMain.dependencies { - implementation(libsEx.`androidx-databinding`) - implementation(libsEx.`androidx-databinding-ktx`) - } + sourceSets.commonMain.dependencies { + implementation(libsEx.`kmp-ktProvider-api`) } } } @@ -82,16 +47,14 @@ fun Project.useRoom( arg("room.incremental", "true") } extensions.configure { - extensions.configure> { - androidMain.dependencies { - implementation(libsEx.`androidx-room`) - implementation(libsEx.`androidx-room-ktx`) - if (rxjava) { - implementation(libsEx.`androidx-room-rxjava`) - } - if (paging) { - implementation(libsEx.`androidx-room-paging`) - } + sourceSets.androidMain.dependencies { + implementation(libsEx.`androidx-room`) + implementation(libsEx.`androidx-room-ktx`) + if (rxjava) { + implementation(libsEx.`androidx-room-rxjava`) + } + if (paging) { + implementation(libsEx.`androidx-room-paging`) } } } diff --git a/build-logic/manager/src/main/kotlin/manager.app.gradle.kts b/build-logic/manager/src/main/kotlin/manager.app.gradle.kts index 1fa2ffddee..3ea2b7c26d 100644 --- a/build-logic/manager/src/main/kotlin/manager.app.gradle.kts +++ b/build-logic/manager/src/main/kotlin/manager.app.gradle.kts @@ -1,5 +1,6 @@ import org.jetbrains.compose.desktop.application.dsl.TargetFormat import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl +import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackConfig import rule.ModuleNamespaceCheckRule plugins { @@ -74,7 +75,6 @@ android { resources.excludes += Config.resourcesExclude } buildFeatures { - dataBinding = true // application 模块必须开启 databinding,因为编译期需要关联其他模块的 databinding buildConfig = true } } @@ -83,11 +83,6 @@ kotlin { if (Multiplatform.enableWasm(project)) { @OptIn(ExperimentalWasmDsl::class) wasmJs { - browser { - commonWebpackConfig { - outputFileName = "${Config.getBaseName(project)}.js" - } - } binaries.executable() } } @@ -96,7 +91,7 @@ kotlin { if (Multiplatform.enableDesktop(project)) { compose.desktop { application { - mainClass = "com.test.MainKt" // todo 待补充 desktop 的 main Class + mainClass = "CyxbsDesktopAppKt" nativeDistributions { targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb) packageName = Config.getApplicationId(project) diff --git a/build-logic/plugin/kmp/src/main/kotlin/kmp.base.gradle.kts b/build-logic/plugin/kmp/src/main/kotlin/kmp.base.gradle.kts index 5b143d009a..fc5a33a1b9 100644 --- a/build-logic/plugin/kmp/src/main/kotlin/kmp.base.gradle.kts +++ b/build-logic/plugin/kmp/src/main/kotlin/kmp.base.gradle.kts @@ -31,10 +31,12 @@ kotlin { if (Multiplatform.enableWasm(project)) { @OptIn(ExperimentalWasmDsl::class) wasmJs { + moduleName = Config.getBaseName(project) browser { val rootDirPath = project.rootDir.path val projectDirPath = project.projectDir.path commonWebpackConfig { + outputFileName = "${Config.getBaseName(project)}.js" devServer = (devServer ?: KotlinWebpackConfig.DevServer()).apply { static = (static ?: mutableListOf()).apply { // Serve sources to debug inside browser @@ -52,22 +54,19 @@ kotlin { implementation(libsEx.`kotlinx-coroutines`) implementation(libsEx.`kotlinx-collections`) implementation(libsEx.`kotlinx-serialization`) // 想要序列化还需要引入 alias(libs.plugins.kotlinSerialization) 插件 +// implementation(libsEx.`kotlinx-datetime`) implementation(libsEx.`kmp-uri`) - implementation(libsEx.`kmp-settings-core`) - implementation(libsEx.`kmp-settings-serialization`) - implementation(libsEx.`kmp-settings-serialization`) +// implementation(libsEx.`kmp-settings-core`) +// implementation(libsEx.`kmp-settings-serialization`) } - if (Multiplatform.enableDesktop(project)) { - val desktopMain by getting { - dependencies { - implementation(libsEx.`kotlinx-coroutines-swing`) - } - } + androidMain.dependencies { + implementation(libsEx.`kotlinx-coroutines-android`) + implementation(libsEx.`androidx-appcompat`) } - androidMain { - dependencies { - implementation(libsEx.`kotlinx-coroutines-android`) - implementation(libsEx.`androidx-appcompat`) + if (Multiplatform.enableDesktop(project)) { + val desktopMain by getting + desktopMain.dependencies { + implementation(libsEx.`kotlinx-coroutines-swing`) } } } diff --git a/build-logic/plugin/kmp/src/main/kotlin/kmp.compose.gradle.kts b/build-logic/plugin/kmp/src/main/kotlin/kmp.compose.gradle.kts index d980268931..be53b71012 100644 --- a/build-logic/plugin/kmp/src/main/kotlin/kmp.compose.gradle.kts +++ b/build-logic/plugin/kmp/src/main/kotlin/kmp.compose.gradle.kts @@ -1,9 +1,9 @@ import com.android.build.gradle.BaseExtension plugins { + id("kmp.base") id("org.jetbrains.kotlin.plugin.compose") id("org.jetbrains.compose") - id("kmp.base") } kotlin { @@ -11,7 +11,7 @@ kotlin { commonMain.dependencies { implementation(compose.runtime) implementation(compose.foundation) - implementation(compose.material3) + implementation(compose.material) implementation(compose.ui) implementation(compose.components.resources) implementation(compose.components.uiToolingPreview) diff --git a/cyxbs-applications/test/src/desktopMain/kotlin/CyxbsDesktopApp.kt b/cyxbs-applications/test/src/desktopMain/kotlin/CyxbsDesktopApp.kt new file mode 100644 index 0000000000..098758c7c1 --- /dev/null +++ b/cyxbs-applications/test/src/desktopMain/kotlin/CyxbsDesktopApp.kt @@ -0,0 +1,41 @@ +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.Text +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.compose.ui.window.Window +import androidx.compose.ui.window.launchApplication +import androidx.compose.ui.window.rememberWindowState +import com.cyxbs.components.utils.coroutine.runApp +import com.g985892345.provider.cyxbsmobile.cyxbsapplications.test.TestKtProviderInitializer + +/** + * . + * + * @author 985892345 + * @date 2024/12/29 + */ + +fun main() = runApp { + TestKtProviderInitializer.tryInitKtProvider() + launchApplication { + val width = 1000 + val height = 600 + Window( + onCloseRequest = ::exitApplication, + title = "桌上重邮", + state = rememberWindowState(width = width.dp, height = height.dp), +// resizable = false, + ) { + remember { +// this.window.minimumSize = java.awt.Dimension(width, height) + } + Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { + Text(text = "桌上重邮", fontSize = 20.sp) + } + } + } +} \ No newline at end of file diff --git a/cyxbs-applications/test/src/wasmJsMain/composeResources/font/SourceHanSansCN-Bold.woff2 b/cyxbs-applications/test/src/wasmJsMain/composeResources/font/SourceHanSansCN-Bold.woff2 new file mode 100644 index 0000000000..251ed2631b Binary files /dev/null and b/cyxbs-applications/test/src/wasmJsMain/composeResources/font/SourceHanSansCN-Bold.woff2 differ diff --git a/cyxbs-applications/test/src/wasmJsMain/composeResources/font/SourceHanSansCN-ExtraLight.woff2 b/cyxbs-applications/test/src/wasmJsMain/composeResources/font/SourceHanSansCN-ExtraLight.woff2 new file mode 100644 index 0000000000..3e2435290d Binary files /dev/null and b/cyxbs-applications/test/src/wasmJsMain/composeResources/font/SourceHanSansCN-ExtraLight.woff2 differ diff --git a/cyxbs-applications/test/src/wasmJsMain/composeResources/font/SourceHanSansCN-Heavy.woff2 b/cyxbs-applications/test/src/wasmJsMain/composeResources/font/SourceHanSansCN-Heavy.woff2 new file mode 100644 index 0000000000..0562b227b7 Binary files /dev/null and b/cyxbs-applications/test/src/wasmJsMain/composeResources/font/SourceHanSansCN-Heavy.woff2 differ diff --git a/cyxbs-applications/test/src/wasmJsMain/composeResources/font/SourceHanSansCN-Light.woff2 b/cyxbs-applications/test/src/wasmJsMain/composeResources/font/SourceHanSansCN-Light.woff2 new file mode 100644 index 0000000000..ffde6e508a Binary files /dev/null and b/cyxbs-applications/test/src/wasmJsMain/composeResources/font/SourceHanSansCN-Light.woff2 differ diff --git a/cyxbs-applications/test/src/wasmJsMain/composeResources/font/SourceHanSansCN-Medium.woff2 b/cyxbs-applications/test/src/wasmJsMain/composeResources/font/SourceHanSansCN-Medium.woff2 new file mode 100644 index 0000000000..abefae0960 Binary files /dev/null and b/cyxbs-applications/test/src/wasmJsMain/composeResources/font/SourceHanSansCN-Medium.woff2 differ diff --git a/cyxbs-applications/test/src/wasmJsMain/composeResources/font/SourceHanSansCN-Normal.woff2 b/cyxbs-applications/test/src/wasmJsMain/composeResources/font/SourceHanSansCN-Normal.woff2 new file mode 100644 index 0000000000..e7096e4e18 Binary files /dev/null and b/cyxbs-applications/test/src/wasmJsMain/composeResources/font/SourceHanSansCN-Normal.woff2 differ diff --git a/cyxbs-applications/test/src/wasmJsMain/composeResources/font/SourceHanSansCN-Regular.woff2 b/cyxbs-applications/test/src/wasmJsMain/composeResources/font/SourceHanSansCN-Regular.woff2 new file mode 100644 index 0000000000..438bf2075d Binary files /dev/null and b/cyxbs-applications/test/src/wasmJsMain/composeResources/font/SourceHanSansCN-Regular.woff2 differ diff --git a/cyxbs-applications/test/src/wasmJsMain/kotlin/CyxbsWasmJsApp.kt b/cyxbs-applications/test/src/wasmJsMain/kotlin/CyxbsWasmJsApp.kt new file mode 100644 index 0000000000..942e82b428 --- /dev/null +++ b/cyxbs-applications/test/src/wasmJsMain/kotlin/CyxbsWasmJsApp.kt @@ -0,0 +1,34 @@ +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.ui.Alignment +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.sp +import androidx.compose.ui.window.ComposeViewport +import com.g985892345.provider.cyxbsmobile.cyxbsapplications.test.TestKtProviderInitializer +import kotlinx.browser.document + +/** + * . + * + * @author 985892345 + * @date 2024/12/29 + */ + +@OptIn(ExperimentalComposeUiApi::class) +fun main() { + TestKtProviderInitializer.tryInitKtProvider() + ComposeViewport( + viewportContainer = document.getElementById("compose")!!, + ) { + MaterialTheme( + typography = createTypography(), + ) { + Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { + Text(text = "网上重邮", fontSize = 20.sp) + } + } + } +} \ No newline at end of file diff --git a/cyxbs-applications/test/src/wasmJsMain/kotlin/Font.kt b/cyxbs-applications/test/src/wasmJsMain/kotlin/Font.kt new file mode 100644 index 0000000000..a0ceba76f2 --- /dev/null +++ b/cyxbs-applications/test/src/wasmJsMain/kotlin/Font.kt @@ -0,0 +1,66 @@ +import androidx.compose.material.Typography +import androidx.compose.runtime.Composable +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.sp +import cyxbsmobile.cyxbs_applications.test.generated.resources.Res +import cyxbsmobile.cyxbs_applications.test.generated.resources.SourceHanSansCN_Bold +import cyxbsmobile.cyxbs_applications.test.generated.resources.SourceHanSansCN_ExtraLight +import cyxbsmobile.cyxbs_applications.test.generated.resources.SourceHanSansCN_Heavy +import cyxbsmobile.cyxbs_applications.test.generated.resources.SourceHanSansCN_Light +import cyxbsmobile.cyxbs_applications.test.generated.resources.SourceHanSansCN_Medium +import cyxbsmobile.cyxbs_applications.test.generated.resources.SourceHanSansCN_Normal +import cyxbsmobile.cyxbs_applications.test.generated.resources.SourceHanSansCN_Regular +import org.jetbrains.compose.resources.Font + +/** + * wasmJs 端的 skiko 不支持中文字体,只能把字体文件打进去 + * + * @author 985892345 + * @date 2024/12/29 + */ + +@Composable +fun createTypography() : Typography = Typography( + defaultFontFamily = getFontFamily(), + body1 = TextStyle.Default.copy( + fontWeight = FontWeight.Normal, + fontSize = 16.sp, + letterSpacing = 0.5.sp + ) +) + +// 思源黑体 https://github.com/adobe-fonts/source-han-sans/tree/release +// 压缩教程 https://moyuscript.github.io/MoyuScript/2022/10/26/font-compress/ +@Composable +fun getFontFamily(): FontFamily = FontFamily( + Font( + Res.font.SourceHanSansCN_ExtraLight, + FontWeight.ExtraLight, + ), + Font( + Res.font.SourceHanSansCN_Light, + FontWeight.Light, + ), + Font( + Res.font.SourceHanSansCN_Normal, + FontWeight.Normal, + ), + Font( + Res.font.SourceHanSansCN_Regular, + FontWeight.Medium, + ), + Font( + Res.font.SourceHanSansCN_Medium, + FontWeight.SemiBold, + ), + Font( + Res.font.SourceHanSansCN_Bold, + FontWeight.Bold, + ), + Font( + Res.font.SourceHanSansCN_Heavy, + FontWeight.ExtraBold, + ), +) \ No newline at end of file diff --git a/cyxbs-applications/test/src/wasmJsMain/resources/index.html b/cyxbs-applications/test/src/wasmJsMain/resources/index.html new file mode 100644 index 0000000000..3cac513ce5 --- /dev/null +++ b/cyxbs-applications/test/src/wasmJsMain/resources/index.html @@ -0,0 +1,26 @@ + + + + + + 网上重邮 + + + + +
+
+ + + \ No newline at end of file diff --git a/cyxbs-applications/test/src/wasmJsMain/resources/styles.css b/cyxbs-applications/test/src/wasmJsMain/resources/styles.css new file mode 100644 index 0000000000..e69de29bb2 diff --git a/cyxbs-components/account/src/androidMain/kotlin/com/cyxbs/components/account/AccountService.kt b/cyxbs-components/account/src/androidMain/kotlin/com/cyxbs/components/account/AccountService.kt index d491692d53..36c57fc7f7 100644 --- a/cyxbs-components/account/src/androidMain/kotlin/com/cyxbs/components/account/AccountService.kt +++ b/cyxbs-components/account/src/androidMain/kotlin/com/cyxbs/components/account/AccountService.kt @@ -15,7 +15,7 @@ import com.cyxbs.components.account.bean.RefreshParams import com.cyxbs.components.account.bean.TokenWrapper import com.cyxbs.components.account.bean.UserInfo import com.cyxbs.components.config.sp.defaultSp -import com.cyxbs.components.utils.extensions.GsonDefault +import com.cyxbs.components.utils.extensions.defaultGson import com.cyxbs.components.utils.extensions.appContext import com.cyxbs.components.utils.extensions.toast import com.cyxbs.components.utils.network.ApiException @@ -95,7 +95,7 @@ internal object AccountService : IAccountService { defaultSp.edit { putString( SP_KEY_USER_V2, - mUserInfoEncryption.encrypt(GsonDefault.toJson(tokenWrapper)) + mUserInfoEncryption.encrypt(defaultGson.toJson(tokenWrapper)) ) } //每次刷新的时候拿token请求一次个人信息,覆盖原来的 @@ -109,7 +109,7 @@ internal object AccountService : IAccountService { val userInfo = response.body()?.data //如果为空就不更新 userInfo?.let { defaultSp.edit(commit = true) { - putString(SP_KEY_USER_INFO, mUserInfoEncryption.encrypt(GsonDefault.toJson(userInfo))) + putString(SP_KEY_USER_INFO, mUserInfoEncryption.encrypt(defaultGson.toJson(userInfo))) } this@AccountService.user = userInfo // 通知 StuNum 更新 @@ -201,7 +201,7 @@ internal object AccountService : IAccountService { val encryptedTokenJson = defaultSp.getString(SP_KEY_USER_V2, null) ?: "" val userInfo = defaultSp.getString(SP_KEY_USER_INFO, "") userInfo?.let { - user = GsonDefault + user = defaultGson .fromJson(mUserInfoEncryption.decrypt(userInfo), UserInfo::class.java) // 这里是从本地拿取数据,是第一次通知 StuNum 更新 (mUserService as UserService).emitStuNum(user?.stuNum) @@ -290,7 +290,7 @@ internal object AccountService : IAccountService { // 该异常已与下游约定,不可更改!!! //请求失败目前分两种 40004为次数过多,20004为账号密码错误,返回值需json解析 val errorBody = response.errorBody()?.string() - val errorMsg:ErrorMsg? = GsonDefault.fromJson(errorBody, ErrorMsg::class.java) + val errorMsg:ErrorMsg? = defaultGson.fromJson(errorBody, ErrorMsg::class.java) if (errorMsg != null) { when (errorMsg.status) { 40004 -> throw IllegalStateException("tried too many times") @@ -316,7 +316,7 @@ internal object AccountService : IAccountService { defaultSp.edit(commit = true) { putString( SP_KEY_USER_V2, - mUserInfoEncryption.encrypt(GsonDefault.toJson(apiWrapper.data)) + mUserInfoEncryption.encrypt(defaultGson.toJson(apiWrapper.data)) ) putLong( SP_KEY_REFRESH_TOKEN_EXPIRED, diff --git a/cyxbs-components/base/build.gradle.kts b/cyxbs-components/base/build.gradle.kts index d27638c718..f4da44f1e4 100644 --- a/cyxbs-components/base/build.gradle.kts +++ b/cyxbs-components/base/build.gradle.kts @@ -3,20 +3,24 @@ plugins { id("kmp.compose") } -useKtProvider(false) // base 模块不包含实现类,不需要处理注解 -useDataBinding(false) // base 模块只依赖 DataBinding 但不开启 DataBinding +useKtProvider() -dependencies { - implementation(projects.cyxbsComponents.init) - implementation(projects.cyxbsComponents.utils) - implementation(projects.cyxbsComponents.config) - implementation(projects.cyxbsComponents.account.api) - implementation(projects.cyxbsPages.login.api) - - implementation(libs.bundles.projectBase) - implementation(libs.bundles.views) - implementation(libs.bundles.network) - implementation(libs.photoView) - implementation(libs.slideShow) - implementation(libs.glide) -} \ No newline at end of file +kotlin { + sourceSets { + commonMain.dependencies { + implementation(projects.cyxbsComponents.init) + implementation(projects.cyxbsComponents.utils) + implementation(projects.cyxbsComponents.config) + implementation(projects.cyxbsComponents.account.api) + implementation(projects.cyxbsPages.login.api) + } + androidMain.dependencies { + implementation(libs.bundles.projectBase) + implementation(libs.bundles.views) + implementation(libs.bundles.network) + implementation(libs.photoView) + implementation(libs.slideShow) + implementation(libs.glide) + } + } +} diff --git a/cyxbs-components/base/src/androidMain/kotlin/com/cyxbs/components/base/crash/CrashMonitor.kt b/cyxbs-components/base/src/androidMain/kotlin/com/cyxbs/components/base/crash/CrashMonitor.kt index 0e4ec04b24..22943cdd08 100644 --- a/cyxbs-components/base/src/androidMain/kotlin/com/cyxbs/components/base/crash/CrashMonitor.kt +++ b/cyxbs-components/base/src/androidMain/kotlin/com/cyxbs/components/base/crash/CrashMonitor.kt @@ -6,7 +6,9 @@ import android.util.Log import com.cyxbs.components.base.BuildConfig import com.cyxbs.components.base.crash.CrashActivity.Companion.NetworkApiResult import com.cyxbs.components.base.pages.SecretActivity +import com.g985892345.provider.api.annotation.ImplProvider import io.reactivex.rxjava3.plugins.RxJavaPlugins +import java.lang.Thread.UncaughtExceptionHandler /** * . @@ -14,29 +16,31 @@ import io.reactivex.rxjava3.plugins.RxJavaPlugins * @author 985892345 * @date 2024/12/25 */ -object CrashMonitor { +@ImplProvider // 提供给其他模块使用,比如 ApiGenerator 中的 OkHttp Dispatcher +object CrashMonitor : UncaughtExceptionHandler { // 提供给 application 模块向外暴露异常用于上报 var crashReport: ((Throwable) -> Unit)? = null private var lastThrowableTime = 0L + private val mainThread = Looper.getMainLooper().thread + fun install() { installThreadHandler() installRxjavaErrorHandler() } private fun installThreadHandler() { - Thread.setDefaultUncaughtExceptionHandler { t, e -> - if (t === Looper.getMainLooper().thread) { - if (BuildConfig.DEBUG) { - Log.d("crash", e.stackTraceToString()) - } - handleMainThread(t, e) - crashReport?.invoke(e) - } else { - handleOtherThread(t, e) + // 这里的 ExceptionHandler 优先级会高于 DefaultUncaughtExceptionHandler + mainThread.setUncaughtExceptionHandler(this) + Thread.setDefaultUncaughtExceptionHandler(this) + Looper.getMainLooper().queue.addIdleHandler { + // 我们需要确保 DefaultUncaughtExceptionHandler 没有被其他 sdk 覆盖掉 + if (Thread.getDefaultUncaughtExceptionHandler() !== this) { + Thread.setDefaultUncaughtExceptionHandler(this) } + true } } @@ -46,11 +50,14 @@ object CrashMonitor { } } - private fun handleOtherThread(thread: Thread, throwable: Throwable) { + private fun handleOtherThread(throwable: Throwable) { // 其他线程不处理 + if (BuildConfig.DEBUG) { + Log.d("OtherThread", throwable.stackTraceToString()) + } } - private fun handleMainThread(thread: Thread, throwable: Throwable) { + private fun handleMainThread(throwable: Throwable) { CrashDialog.Builder( RuntimeException( "触发了一次来自主线程的异常, ${throwable.message}", @@ -65,7 +72,7 @@ object CrashMonitor { // 主线程崩溃后 loop 会停掉,这里重启 loop try { Looper.loop() - } catch (e: Exception) { + } catch (e: Throwable) { e.printStackTrace() if (SystemClock.elapsedRealtime() - lastThrowableTime < 1000) { // 短时间内再次崩溃,则直接打开 CrashActivity @@ -86,4 +93,16 @@ object CrashMonitor { } } } + + override fun uncaughtException(t: Thread, e: Throwable) { + if (t === mainThread) { + if (BuildConfig.DEBUG) { + Log.d("crash", e.stackTraceToString()) + } + handleMainThread(e) + crashReport?.invoke(e) + } else { + handleOtherThread(e) + } + } } \ No newline at end of file diff --git a/cyxbs-components/base/src/androidMain/kotlin/com/cyxbs/components/base/ui/BaseBindActivity.kt b/cyxbs-components/base/src/androidMain/kotlin/com/cyxbs/components/base/ui/BaseBindActivity.kt deleted file mode 100644 index 517e0154a3..0000000000 --- a/cyxbs-components/base/src/androidMain/kotlin/com/cyxbs/components/base/ui/BaseBindActivity.kt +++ /dev/null @@ -1,91 +0,0 @@ -package com.cyxbs.components.base.ui - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import androidx.annotation.CallSuper -import androidx.databinding.ViewDataBinding -import androidx.viewbinding.ViewBinding -import com.cyxbs.components.utils.extensions.lazyUnlock -import com.cyxbs.components.utils.utils.get.GenericityUtils.getGenericClass -import java.lang.reflect.Method - -/** - * - * 该类封装了 DataBind,可直接使用 [binding] 获得 - * - * ## 零、使用 DataBinding 需要打开开关 - * ``` - * // 在你模块的 build.gradle.kts 中调用 - * useDataBinding() - * - * // 如果你只依赖 DataBinding 而不开启 DataBinding 这个功能,可以给上述函数传入 true 参数 - * ``` - * - * ## 一、获取 ViewModel 的规范写法 - * 请查看该父类 [BaseFragment] - * - * - * - * - * - * - * # 更多封装请往父类和接口查看 - * @author 985892345 - * @email 2767465918@qq.com - * @data 2021/6/2 - */ -abstract class BaseBindActivity : BaseActivity() { - - companion object { - // VB inflate() 缓存。key 为 javaClass,value 为 VB 的 inflate 方法 - private val VB_METHOD_BY_CLASS = hashMapOf>, Method>() - } - - /** - * 用于在调用 [setContentView] 之前的方法, 可用于设置一些主题或窗口的东西, 放这里不会报错 - */ - open fun onSetContentViewBefore() {} - - @Suppress("UNCHECKED_CAST") - protected val binding: VB by lazyUnlock { - val method = VB_METHOD_BY_CLASS.getOrPut(javaClass) { - getGenericClass(javaClass).getMethod( - "inflate", - LayoutInflater::class.java - ) - } - // 正常情况下这里一定不会为 null,DataBinding 会在编译期根据模块依赖关系生成 Bind 类的映射文件, - // 但是如果使用 runtimeOnly 时将因为未加入编译环境而导致这里会出现返回 null 的情况 - val binding = method.invoke(null, layoutInflater) as VB - if (binding is ViewDataBinding) { - // ViewBinding 是 ViewBind 和 DataBind 共有的父类 - binding.lifecycleOwner = getViewLifecycleOwner() - } - binding - } - - @CallSuper - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - onSetContentViewBefore() - super.setContentView(binding.root) - // 注意:这里已经 setContentView(),请不要自己再次调用,否则 ViewBinding 会失效 - } - - @Deprecated( - "打个标记,因为使用了 ViewBinding,防止你忘记删除这个", - level = DeprecationLevel.ERROR, replaceWith = ReplaceWith("") - ) - override fun setContentView(layoutResID: Int) { - super.setContentView(layoutResID) - } - - @Deprecated( - "打个标记,因为使用了 ViewBinding,防止你忘记删除这个", - level = DeprecationLevel.ERROR, replaceWith = ReplaceWith("") - ) - override fun setContentView(view: View?) { - super.setContentView(view) - } -} \ No newline at end of file diff --git a/cyxbs-components/base/src/androidMain/kotlin/com/cyxbs/components/base/ui/BaseBindFragment.kt b/cyxbs-components/base/src/androidMain/kotlin/com/cyxbs/components/base/ui/BaseBindFragment.kt deleted file mode 100644 index 498a55d8e6..0000000000 --- a/cyxbs-components/base/src/androidMain/kotlin/com/cyxbs/components/base/ui/BaseBindFragment.kt +++ /dev/null @@ -1,104 +0,0 @@ -package com.cyxbs.components.base.ui - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.annotation.CallSuper -import androidx.databinding.ViewDataBinding -import androidx.viewbinding.ViewBinding -import com.cyxbs.components.utils.utils.get.GenericityUtils.getGenericClass -import java.lang.reflect.Method - -/** - * - * 该类封装了 DataBind,可直接使用 [binding] 获得 - * - * ## 零、使用 DataBinding 需要打开开关 - * ``` - * // 在你模块的 build.gradle.kts 中调用 - * useDataBinding() - * - * // 如果你只依赖 DataBinding 而不开启 DataBinding 这个功能,可以给上述函数传入 true 参数 - * ``` - * - * ## 一、获取 ViewModel 的规范写法 - * 请查看该父类 [BaseFragment] - * - * - * - * - * - * # 更多封装请往父类和接口查看 - * @author 985892345 - * @email 2767465918@qq.com - * @data 2021/6/2 - */ -abstract class BaseBindFragment : BaseFragment() { - - companion object { - // VB inflate() 缓存。key 为 javaClass,value 为 VB 的 inflate 方法 - private val VB_METHOD_BY_CLASS = hashMapOf>, Method>() - } - - abstract override fun onViewCreated(view: View, savedInstanceState: Bundle?) - - /** - * 由于 View 的生命周期与 Fragment 不匹配, - * 所以在 [onDestroyView] 后需要取消对 [binding] 的引用 - */ - private var _binding: VB? = null - protected val binding: VB - get() = _binding!! - - init { - viewLifecycleOwnerLiveData.observeForever { - // 因为 binding 需要在 onDestroyView() 中置空 - // 但是置空是在父类中操作,会导致比子类先调用 (除非你把 super 写在末尾) - // 所以为了优雅,可以观察 viewLifecycleOwnerLiveData,它是在 onDestroyView() 后回调的 - if (it == null) { - _binding = null - } - } - } - - @CallSuper - @Suppress("UNCHECKED_CAST") - @Deprecated( - "不建议重写该方法,请使用 onCreateViewBefore() 代替", - ReplaceWith("onCreateViewBefore(container, savedInstanceState)"), - DeprecationLevel.WARNING - ) - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View { - val method = VB_METHOD_BY_CLASS.getOrPut(javaClass) { - getGenericClass(javaClass).getMethod( - "inflate", - LayoutInflater::class.java, - ViewGroup::class.java, - Boolean::class.java - ) - } - // 正常情况下这里一定不会为 null,DataBinding 会在编译期根据模块依赖关系生成 Bind 类的映射文件, - // 但是如果使用 runtimeOnly 时将因为未加入编译环境而导致这里会出现返回 null 的情况 - _binding = method.invoke(null, inflater, container, false) as VB - if (_binding is ViewDataBinding) { - // ViewBinding 是 ViewBind 和 DataBind 共有的父类 - (binding as ViewDataBinding).lifecycleOwner = viewLifecycleOwner - } - onCreateViewBefore(container, savedInstanceState) - return binding.root - } - - /** - * 在 [onCreateView] 中返回 View 前回调 - */ - open fun onCreateViewBefore( - container: ViewGroup?, - savedInstanceState: Bundle? - ) { - } -} \ No newline at end of file diff --git a/cyxbs-components/base/src/androidMain/kotlin/com/cyxbs/components/base/ui/BaseUi.kt b/cyxbs-components/base/src/androidMain/kotlin/com/cyxbs/components/base/ui/BaseUi.kt index 6ed7f4d8fb..d406e7733d 100644 --- a/cyxbs-components/base/src/androidMain/kotlin/com/cyxbs/components/base/ui/BaseUi.kt +++ b/cyxbs-components/base/src/androidMain/kotlin/com/cyxbs/components/base/ui/BaseUi.kt @@ -88,7 +88,7 @@ interface BaseUi : ToastUtils, RxjavaLifecycle { fun doOnCreateContentView(action: (rootView: View) -> Any?) /** - * 在简单界面,使用这种方式来得到 View,避免使用 ViewBinding 大材小用 + * 在简单界面,使用这种方式来得到 View,kae 插件 和 DataBinding/ViewBinding 已不被允许使用 * ``` * 使用方法: * val mTvNum: TextView by R.id.xxx.view() diff --git a/cyxbs-components/init/build.gradle.kts b/cyxbs-components/init/build.gradle.kts index 9ff63acfb8..dfb0ae3e18 100644 --- a/cyxbs-components/init/build.gradle.kts +++ b/cyxbs-components/init/build.gradle.kts @@ -1,6 +1,5 @@ plugins { id("manager.lib") - id("kmp.compose") } diff --git a/cyxbs-components/utils/build.gradle.kts b/cyxbs-components/utils/build.gradle.kts index a31536e9ff..829785e64d 100644 --- a/cyxbs-components/utils/build.gradle.kts +++ b/cyxbs-components/utils/build.gradle.kts @@ -11,7 +11,7 @@ kotlin { implementation(projects.cyxbsComponents.init) implementation(projects.cyxbsComponents.config) implementation(projects.cyxbsComponents.account.api) - implementation(libs.kmp.ktProvider.manager) + implementation(libs.kmp.ktProvider.manager) // utils 私有,其他模块通过 ::class.impl() 获取 } androidMain.dependencies { implementation(libs.bundles.projectBase) diff --git a/cyxbs-components/utils/src/androidMain/kotlin/com/cyxbs/components/utils/Utils.android.kt b/cyxbs-components/utils/src/androidMain/kotlin/com/cyxbs/components/utils/Utils.android.kt new file mode 100644 index 0000000000..189b49d504 --- /dev/null +++ b/cyxbs-components/utils/src/androidMain/kotlin/com/cyxbs/components/utils/Utils.android.kt @@ -0,0 +1,12 @@ +package com.cyxbs.components.utils + +/** + * . + * + * @author 985892345 + * @date 2024/12/29 + */ + +actual fun isDebug(): Boolean { + return BuildConfig.DEBUG +} \ No newline at end of file diff --git a/cyxbs-components/utils/src/androidMain/kotlin/com/cyxbs/components/utils/coroutine/Coroutine.android.kt b/cyxbs-components/utils/src/androidMain/kotlin/com/cyxbs/components/utils/coroutine/Coroutine.android.kt new file mode 100644 index 0000000000..754184316b --- /dev/null +++ b/cyxbs-components/utils/src/androidMain/kotlin/com/cyxbs/components/utils/coroutine/Coroutine.android.kt @@ -0,0 +1,14 @@ +package com.cyxbs.components.utils.coroutine + +import androidx.lifecycle.coroutineScope +import com.cyxbs.components.utils.extensions.appLifecycle +import kotlinx.coroutines.CoroutineScope + +/** + * . + * + * @author 985892345 + * @date 2024/12/28 + */ +actual val appCoroutineScope: CoroutineScope + get() = appLifecycle.coroutineScope \ No newline at end of file diff --git a/cyxbs-components/utils/src/androidMain/kotlin/com/cyxbs/components/utils/extensions/Context.kt b/cyxbs-components/utils/src/androidMain/kotlin/com/cyxbs/components/utils/extensions/Context.kt index 77986576a4..dfaa06e364 100644 --- a/cyxbs-components/utils/src/androidMain/kotlin/com/cyxbs/components/utils/extensions/Context.kt +++ b/cyxbs-components/utils/src/androidMain/kotlin/com/cyxbs/components/utils/extensions/Context.kt @@ -2,10 +2,9 @@ package com.cyxbs.components.utils.extensions import android.content.Context import androidx.lifecycle.Lifecycle -import androidx.lifecycle.LifecycleCoroutineScope import androidx.lifecycle.ProcessLifecycleOwner -import androidx.lifecycle.coroutineScope import com.cyxbs.components.init.appApplication +import com.cyxbs.components.utils.coroutine.appCoroutineScope /** * ... @@ -24,12 +23,8 @@ val appContext: Context * - ON_START、ON_RESUME 在应用程序进入前台时回调 * - ON_PAUSE、ON_STOP 在应用程序进入后台时回调 * - ON_DESTROY 永远不会回调 + * + * 如果需要使用对应协程作用域,请直接使用 [appCoroutineScope] */ -val processLifecycle: Lifecycle +val appLifecycle: Lifecycle get() = ProcessLifecycleOwner.get().lifecycle - -/** - * 应用程序的生命周期内的协程,代替 GlobalScope 的最好方式 - */ -val processLifecycleScope: LifecycleCoroutineScope - get() = processLifecycle.coroutineScope \ No newline at end of file diff --git a/cyxbs-components/utils/src/androidMain/kotlin/com/cyxbs/components/utils/extensions/Exception.android.kt b/cyxbs-components/utils/src/androidMain/kotlin/com/cyxbs/components/utils/extensions/Exception.android.kt new file mode 100644 index 0000000000..76fd698359 --- /dev/null +++ b/cyxbs-components/utils/src/androidMain/kotlin/com/cyxbs/components/utils/extensions/Exception.android.kt @@ -0,0 +1,45 @@ +package com.cyxbs.components.utils.extensions + +import java.io.PrintWriter +import java.io.StringWriter + +/** + * 配合 Rxjava 和 Flow 使用 DSL 处理异常的工具类 + * + * @author 985892345 (Guo Xiangrui) + * @email guo985892345@foxmail.com + * @date 2022/8/28 14:31 + */ + +/** + * 收集异常中最有用的信息 + */ +fun Throwable.collectUsefulStackTrace(): String { + val writer = StringWriter() + val printWriter = PrintWriter(writer) + printStackTrace(printWriter) + var firstLine = Int.MAX_VALUE + // 只保留:at 方法名(类名.kt:行号) + val regex = Regex("(?<=at )([a-zA-Z\$]+\\.)+") + val s = writer.buffer.replaceLine { lineIndex, old -> + if (lineIndex < firstLine) { + // 先找到第一行以 .kt 结尾的 + if (old.contains(".kt:")) { + firstLine = lineIndex + } + old.replace(regex, "") + } else { + if (old.contains("\tat ") && !old.contains(".kt:")) { + // 筛除无用信息 + "" + } else old.replace(regex, "") + } + } + printWriter.close() + return s +} + + + + + diff --git a/cyxbs-components/utils/src/androidMain/kotlin/com/cyxbs/components/utils/extensions/Flow.android.kt b/cyxbs-components/utils/src/androidMain/kotlin/com/cyxbs/components/utils/extensions/Flow.android.kt new file mode 100644 index 0000000000..8c5fd2da00 --- /dev/null +++ b/cyxbs-components/utils/src/androidMain/kotlin/com/cyxbs/components/utils/extensions/Flow.android.kt @@ -0,0 +1,20 @@ +package com.cyxbs.components.utils.extensions + +import io.reactivex.rxjava3.core.Maybe +import io.reactivex.rxjava3.core.Observable +import io.reactivex.rxjava3.core.Single +import kotlinx.coroutines.flow.* +import kotlinx.coroutines.rx3.asFlow + + +fun Observable.asFlow(): Flow { + return asFlow() +} + +fun Single.asFlow(): Flow { + return toObservable().asFlow() +} + +fun Maybe.asFlow(): Flow { + return toObservable().asFlow() +} diff --git a/cyxbs-components/utils/src/androidMain/kotlin/com/cyxbs/components/utils/extensions/Json.android.kt b/cyxbs-components/utils/src/androidMain/kotlin/com/cyxbs/components/utils/extensions/Json.android.kt index 03ca87a8aa..03e567161b 100644 --- a/cyxbs-components/utils/src/androidMain/kotlin/com/cyxbs/components/utils/extensions/Json.android.kt +++ b/cyxbs-components/utils/src/androidMain/kotlin/com/cyxbs/components/utils/extensions/Json.android.kt @@ -9,4 +9,4 @@ import com.google.gson.Gson * @date 2024/11/10 */ -val GsonDefault = Gson() \ No newline at end of file +val defaultGson = Gson() \ No newline at end of file diff --git a/cyxbs-components/utils/src/androidMain/kotlin/com/cyxbs/components/utils/extensions/Other.android.kt b/cyxbs-components/utils/src/androidMain/kotlin/com/cyxbs/components/utils/extensions/Other.android.kt new file mode 100644 index 0000000000..31580c4569 --- /dev/null +++ b/cyxbs-components/utils/src/androidMain/kotlin/com/cyxbs/components/utils/extensions/Other.android.kt @@ -0,0 +1,27 @@ +package com.cyxbs.components.utils.extensions + +import android.content.res.Configuration +import android.os.Handler +import android.os.Looper + +/** + * 默认的主线程 Handler + */ +val defaultHandler = Handler(Looper.getMainLooper()) + +/** + * 是否是日间模式,否则为夜间模式 + */ +fun isDaytimeMode(): Boolean { + val uiMode = appContext.resources.configuration.uiMode + return (uiMode and Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_NO +} + +/** + * 是否是夜间模式 + */ +fun isDarkMode() = !isDaytimeMode() + + + + diff --git a/cyxbs-components/utils/src/androidMain/kotlin/com/cyxbs/components/utils/extensions/Other.kt b/cyxbs-components/utils/src/androidMain/kotlin/com/cyxbs/components/utils/extensions/Other.kt deleted file mode 100644 index 9dcc524f3a..0000000000 --- a/cyxbs-components/utils/src/androidMain/kotlin/com/cyxbs/components/utils/extensions/Other.kt +++ /dev/null @@ -1,38 +0,0 @@ -package com.cyxbs.components.utils.extensions - -import android.content.pm.ApplicationInfo -import android.content.res.Configuration - -/** - * ... - * @author 985892345 (Guo Xiangrui) - * @email 2767465918@qq.com - * @date 2022/3/7 17:51 - */ - -/** - * 不带锁的懒加载,建议使用这个代替 lazy,因为 Android 一般情况下不会遇到多线程问题 - */ -fun lazyUnlock(initializer: () -> T) = lazy(LazyThreadSafetyMode.NONE, initializer) - -/** - * 是否是日间模式,否则为夜间模式 - */ -fun isDaytimeMode(): Boolean { - val uiMode = appContext.resources.configuration.uiMode - return (uiMode and Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_NO -} - -/** - * 是否是夜间模式 - */ -fun isDarkMode() = !isDaytimeMode() - -/** - * 是否是 debug 构建 - */ -val isDebuggableBuild: Boolean - get() = (appContext.applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE) != 0 - - - diff --git a/cyxbs-components/utils/src/androidMain/kotlin/com/cyxbs/components/utils/network/ApiGenerator.kt b/cyxbs-components/utils/src/androidMain/kotlin/com/cyxbs/components/utils/network/ApiGenerator.kt index bc134f183d..02e2ab1ed1 100644 --- a/cyxbs-components/utils/src/androidMain/kotlin/com/cyxbs/components/utils/network/ApiGenerator.kt +++ b/cyxbs-components/utils/src/androidMain/kotlin/com/cyxbs/components/utils/network/ApiGenerator.kt @@ -6,15 +6,17 @@ import android.util.Log import android.widget.Toast import com.cyxbs.components.account.api.IAccountService import com.cyxbs.components.utils.BuildConfig -import com.cyxbs.components.utils.extensions.JsonDefault import com.cyxbs.components.utils.extensions.appContext +import com.cyxbs.components.utils.extensions.defaultJson import com.cyxbs.components.utils.service.allImpl import com.cyxbs.components.utils.service.impl +import com.cyxbs.components.utils.service.implOrNull import com.cyxbs.components.utils.utils.LogLocal import com.cyxbs.components.utils.utils.LogUtils import com.google.gson.Gson import com.google.gson.annotations.SerializedName import com.google.gson.reflect.TypeToken +import okhttp3.Dispatcher import okhttp3.HttpUrl import okhttp3.Interceptor import okhttp3.MediaType.Companion.toMediaType @@ -26,6 +28,9 @@ import retrofit2.Retrofit import retrofit2.adapter.rxjava3.RxJava3CallAdapterFactory import retrofit2.converter.gson.GsonConverterFactory import retrofit2.converter.kotlinx.serialization.asConverterFactory +import java.util.concurrent.SynchronousQueue +import java.util.concurrent.ThreadFactory +import java.util.concurrent.ThreadPoolExecutor import java.util.concurrent.TimeUnit import java.util.concurrent.locks.ReentrantLock import kotlin.concurrent.withLock @@ -86,6 +91,22 @@ object ApiGenerator { private val mAccountService = IAccountService::class.impl() + // 手动创建 okhttp 的线程分发器,规避 协程 + Retrofit 在子线程请求被 cancel 后的异常问题 + private val OkHttpDispatcher = Dispatcher( + ThreadPoolExecutor( + 0, 64, 60, TimeUnit.SECONDS, + SynchronousQueue(), + ThreadFactory { + Thread(it, "ApiGenerator OkHttp Dispatcher").apply { + // 这里设置线程的异常处理器,默认给 base 模块的 CrashMonitor + setUncaughtExceptionHandler( + Thread.UncaughtExceptionHandler::class.implOrNull() + ) + } + } + ) + ) + //init对两种公共的retrofit进行配置 init { val networkConfigs = INetworkConfigService::class.allImpl() @@ -224,7 +245,7 @@ object ApiGenerator { })) .addConverterFactory(GsonConverterFactory.create()) // https://github.com/square/retrofit/tree/trunk/retrofit-converters/kotlinx-serialization - .addConverterFactory(JsonDefault.asConverterFactory("application/json; charset=UTF8".toMediaType())) + .addConverterFactory(defaultJson.asConverterFactory("application/json; charset=UTF8".toMediaType())) .addCallAdapterFactory(RxJava3CallAdapterFactory.createSynchronous()) } @@ -237,6 +258,7 @@ object ApiGenerator { private fun OkHttpClient.Builder.defaultConfig() { this.connectTimeout(DEFAULT_TIME_OUT.toLong(), TimeUnit.SECONDS) this.readTimeout(DEFAULT_TIME_OUT.toLong(), TimeUnit.SECONDS) + dispatcher(OkHttpDispatcher) } diff --git a/cyxbs-components/utils/src/androidMain/kotlin/com/cyxbs/components/utils/utils/BindView.kt b/cyxbs-components/utils/src/androidMain/kotlin/com/cyxbs/components/utils/utils/BindView.kt index 0f8e3d5a3d..36697c6e42 100644 --- a/cyxbs-components/utils/src/androidMain/kotlin/com/cyxbs/components/utils/utils/BindView.kt +++ b/cyxbs-components/utils/src/androidMain/kotlin/com/cyxbs/components/utils/utils/BindView.kt @@ -38,7 +38,7 @@ import kotlin.reflect.KProperty * ViewBinding 是给所有布局都默认开启的,大项目会严重拖垮编译速度 * ``` * **NOTE:** kt 直接通过 id 获取 View 的插件已经被废弃,禁止再使用! - * + * **NOTE:** DataBinding 因 Kapt 与多平台问题,禁止再使用! * * * diff --git a/cyxbs-components/utils/src/androidMain/kotlin/com/cyxbs/components/utils/utils/judge/NetworkUtil.kt b/cyxbs-components/utils/src/androidMain/kotlin/com/cyxbs/components/utils/utils/judge/NetworkUtil.kt index 8573551632..354f06efd8 100644 --- a/cyxbs-components/utils/src/androidMain/kotlin/com/cyxbs/components/utils/utils/judge/NetworkUtil.kt +++ b/cyxbs-components/utils/src/androidMain/kotlin/com/cyxbs/components/utils/utils/judge/NetworkUtil.kt @@ -6,7 +6,7 @@ import android.net.Network import android.net.NetworkCapabilities import android.net.wifi.WifiManager import com.cyxbs.components.utils.extensions.appContext -import com.cyxbs.components.utils.extensions.processLifecycleScope +import com.cyxbs.components.utils.coroutine.appCoroutineScope import com.cyxbs.components.utils.network.ApiStatus import com.cyxbs.components.utils.network.IApi import com.cyxbs.components.utils.network.commonApi @@ -15,7 +15,6 @@ import io.reactivex.rxjava3.subjects.BehaviorSubject import kotlinx.coroutines.CancellationException import kotlinx.coroutines.Job import kotlinx.coroutines.TimeoutCancellationException -import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.withTimeout @@ -190,7 +189,7 @@ object NetworkUtil { override fun onAvailable(network: Network) { // 注意:这里回调了 onAvailable 也不代表可用,只是表明连上了网络 - job = processLifecycleScope.launch { + job = appCoroutineScope.launch { when (tryPingNetWork()?.isSuccess) { true -> _state.onNext(true) false -> _state.onNext(false) // 后端服务问题 diff --git a/cyxbs-components/utils/src/commonMain/kotlin/com/cyxbs/components/utils/Utils.kt b/cyxbs-components/utils/src/commonMain/kotlin/com/cyxbs/components/utils/Utils.kt new file mode 100644 index 0000000000..b434cb87b0 --- /dev/null +++ b/cyxbs-components/utils/src/commonMain/kotlin/com/cyxbs/components/utils/Utils.kt @@ -0,0 +1,10 @@ +package com.cyxbs.components.utils + +/** + * . + * + * @author 985892345 + * @date 2024/12/29 + */ + +expect fun isDebug(): Boolean \ No newline at end of file diff --git a/cyxbs-components/utils/src/commonMain/kotlin/com/cyxbs/components/utils/compose/dialog/ChooseDialogCompose.kt b/cyxbs-components/utils/src/commonMain/kotlin/com/cyxbs/components/utils/compose/dialog/ChooseDialogCompose.kt index f793c7c499..1d742dcfd0 100644 --- a/cyxbs-components/utils/src/commonMain/kotlin/com/cyxbs/components/utils/compose/dialog/ChooseDialogCompose.kt +++ b/cyxbs-components/utils/src/commonMain/kotlin/com/cyxbs/components/utils/compose/dialog/ChooseDialogCompose.kt @@ -11,8 +11,8 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.Card -import androidx.compose.material3.Text +import androidx.compose.material.Card +import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.snapshots.StateFactoryMarker import androidx.compose.ui.Alignment diff --git a/cyxbs-components/utils/src/commonMain/kotlin/com/cyxbs/components/utils/extensions/Coroutine.kt b/cyxbs-components/utils/src/commonMain/kotlin/com/cyxbs/components/utils/coroutine/Coroutine.kt similarity index 78% rename from cyxbs-components/utils/src/commonMain/kotlin/com/cyxbs/components/utils/extensions/Coroutine.kt rename to cyxbs-components/utils/src/commonMain/kotlin/com/cyxbs/components/utils/coroutine/Coroutine.kt index cff1899427..14d32981ea 100644 --- a/cyxbs-components/utils/src/commonMain/kotlin/com/cyxbs/components/utils/extensions/Coroutine.kt +++ b/cyxbs-components/utils/src/commonMain/kotlin/com/cyxbs/components/utils/coroutine/Coroutine.kt @@ -1,6 +1,7 @@ -package com.cyxbs.components.utils.extensions +package com.cyxbs.components.utils.coroutine import kotlinx.coroutines.CoroutineExceptionHandler +import kotlinx.coroutines.CoroutineScope import kotlin.coroutines.cancellation.CancellationException /** @@ -10,6 +11,11 @@ import kotlin.coroutines.cancellation.CancellationException * @date 2024/12/28 */ +/** + * 应用级别的协程作用域 + */ +expect val appCoroutineScope: CoroutineScope + /** * 默认协程异常处理 * ``` diff --git a/cyxbs-components/utils/src/androidMain/kotlin/com/cyxbs/components/utils/extensions/Exception.kt b/cyxbs-components/utils/src/commonMain/kotlin/com/cyxbs/components/utils/extensions/Exception.kt similarity index 57% rename from cyxbs-components/utils/src/androidMain/kotlin/com/cyxbs/components/utils/extensions/Exception.kt rename to cyxbs-components/utils/src/commonMain/kotlin/com/cyxbs/components/utils/extensions/Exception.kt index 8f026e5247..ff235c0f01 100644 --- a/cyxbs-components/utils/src/androidMain/kotlin/com/cyxbs/components/utils/extensions/Exception.kt +++ b/cyxbs-components/utils/src/commonMain/kotlin/com/cyxbs/components/utils/extensions/Exception.kt @@ -1,7 +1,5 @@ package com.cyxbs.components.utils.extensions -import java.io.PrintWriter -import java.io.StringWriter import kotlin.reflect.KClass /** @@ -12,52 +10,12 @@ import kotlin.reflect.KClass * @date 2022/8/28 14:31 */ -/** - * 收集异常中最有用的信息 - */ -fun Throwable.collectUsefulStackTrace(): String { - val writer = StringWriter() - val printWriter = PrintWriter(writer) - printStackTrace(printWriter) - var firstLine = Int.MAX_VALUE - // 只保留:at 方法名(类名.kt:行号) - val regex = Regex("(?<=at )([a-zA-Z\$]+\\.)+") - val s = writer.buffer.replaceLine { lineIndex, old -> - if (lineIndex < firstLine) { - // 先找到第一行以 .kt 结尾的 - if (old.contains(".kt:")) { - firstLine = lineIndex - } - old.replace(regex, "") - } else { - if (old.contains("\tat ") && !old.contains(".kt:")) { - // 筛除无用信息 - "" - } else old.replace(regex, "") - } - } - printWriter.close() - return s -} - - - - - - - - - - - - - open class ExceptionResult( val throwable: Throwable, val emitter: Emitter ) { - + /** * 处理 [T] 类型的异常 * ``` @@ -75,7 +33,7 @@ open class ExceptionResult( } return this } - + /** * 抓取除了 [T] 以外的其他异常 */ diff --git a/cyxbs-components/utils/src/androidMain/kotlin/com/cyxbs/components/utils/extensions/Flow.kt b/cyxbs-components/utils/src/commonMain/kotlin/com/cyxbs/components/utils/extensions/Flow.kt similarity index 91% rename from cyxbs-components/utils/src/androidMain/kotlin/com/cyxbs/components/utils/extensions/Flow.kt rename to cyxbs-components/utils/src/commonMain/kotlin/com/cyxbs/components/utils/extensions/Flow.kt index 4664756f6a..96ac353d40 100644 --- a/cyxbs-components/utils/src/androidMain/kotlin/com/cyxbs/components/utils/extensions/Flow.kt +++ b/cyxbs-components/utils/src/commonMain/kotlin/com/cyxbs/components/utils/extensions/Flow.kt @@ -1,10 +1,9 @@ package com.cyxbs.components.utils.extensions -import io.reactivex.rxjava3.core.Maybe -import io.reactivex.rxjava3.core.Observable -import io.reactivex.rxjava3.core.Single -import kotlinx.coroutines.flow.* -import kotlinx.coroutines.rx3.asFlow +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.FlowCollector +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.map /** * ... @@ -104,16 +103,4 @@ fun Flow.interceptException( */ fun Flow.interceptExceptionByResult( action: suspend ExceptionResult>>.(Throwable) -> Unit -) : Flow> = map { Result.success(it) }.interceptException(action) - -fun Observable.asFlow(): Flow { - return asFlow() -} - -fun Single.asFlow(): Flow { - return toObservable().asFlow() -} - -fun Maybe.asFlow(): Flow { - return toObservable().asFlow() -} +) : Flow> = map { Result.success(it) }.interceptException(action) \ No newline at end of file diff --git a/cyxbs-components/utils/src/commonMain/kotlin/com/cyxbs/components/utils/extensions/Json.kt b/cyxbs-components/utils/src/commonMain/kotlin/com/cyxbs/components/utils/extensions/Json.kt index 32a8d3b2ff..16885fabeb 100644 --- a/cyxbs-components/utils/src/commonMain/kotlin/com/cyxbs/components/utils/extensions/Json.kt +++ b/cyxbs-components/utils/src/commonMain/kotlin/com/cyxbs/components/utils/extensions/Json.kt @@ -11,7 +11,7 @@ import kotlinx.serialization.json.Json */ @OptIn(ExperimentalSerializationApi::class) -val JsonDefault = Json { +val defaultJson = Json { encodeDefaults = false // 需要编码默认值 ignoreUnknownKeys = true // 忽略未知键 isLenient = true // 宽松模式,允许键和字符串值不带引号 diff --git a/cyxbs-components/utils/src/commonMain/kotlin/com/cyxbs/components/utils/extensions/Other.kt b/cyxbs-components/utils/src/commonMain/kotlin/com/cyxbs/components/utils/extensions/Other.kt new file mode 100644 index 0000000000..833537e0b5 --- /dev/null +++ b/cyxbs-components/utils/src/commonMain/kotlin/com/cyxbs/components/utils/extensions/Other.kt @@ -0,0 +1,13 @@ +package com.cyxbs.components.utils.extensions + +/** + * ... + * @author 985892345 (Guo Xiangrui) + * @email 2767465918@qq.com + * @date 2022/3/7 17:51 + */ + +/** + * 不带锁的懒加载,建议使用这个代替 lazy,因为 Android 一般情况下不会遇到多线程问题 + */ +fun lazyUnlock(initializer: () -> T) = lazy(LazyThreadSafetyMode.NONE, initializer) \ No newline at end of file diff --git a/cyxbs-components/utils/src/androidMain/kotlin/com/cyxbs/components/utils/network/Api.kt b/cyxbs-components/utils/src/commonMain/kotlin/com/cyxbs/components/utils/network/Api.kt similarity index 86% rename from cyxbs-components/utils/src/androidMain/kotlin/com/cyxbs/components/utils/network/Api.kt rename to cyxbs-components/utils/src/commonMain/kotlin/com/cyxbs/components/utils/network/Api.kt index 0dc688eec5..1e4038539b 100644 --- a/cyxbs-components/utils/src/androidMain/kotlin/com/cyxbs/components/utils/network/Api.kt +++ b/cyxbs-components/utils/src/commonMain/kotlin/com/cyxbs/components/utils/network/Api.kt @@ -1,6 +1,6 @@ package com.cyxbs.components.utils.network -import com.cyxbs.components.utils.BuildConfig +import com.cyxbs.components.utils.isDebug /** * ... @@ -24,5 +24,5 @@ const val BASE_THUMBNAIL_IMG_URL = BASE_NORMAL_IMG_URL + "thumbnail/" const val BASE_NORMAL_BACKUP_GET = "https://be-prod.redrock.team/cloud-manager/check" //获取baseUrl的方法 -fun getBaseUrl() = if (BuildConfig.DEBUG) END_POINT_REDROCK_DEV else END_POINT_REDROCK_PROD +fun getBaseUrl() = if (isDebug()) END_POINT_REDROCK_DEV else END_POINT_REDROCK_PROD //fun getBaseUrl() = END_POINT_REDROCK_PROD \ No newline at end of file diff --git a/cyxbs-components/utils/src/commonMain/kotlin/com/cyxbs/components/utils/service/ServiceManager.kt b/cyxbs-components/utils/src/commonMain/kotlin/com/cyxbs/components/utils/service/ServiceManager.kt index 6f264667eb..743716fe83 100644 --- a/cyxbs-components/utils/src/commonMain/kotlin/com/cyxbs/components/utils/service/ServiceManager.kt +++ b/cyxbs-components/utils/src/commonMain/kotlin/com/cyxbs/components/utils/service/ServiceManager.kt @@ -1,7 +1,7 @@ package com.cyxbs.components.utils.service import com.g985892345.provider.api.init.wrapper.ImplProviderWrapper -import com.g985892345.provider.manager.KtProviderManager +import com.g985892345.provider.manager.KtProvider import kotlin.reflect.KClass /** @@ -81,7 +81,11 @@ import kotlin.reflect.KClass * ``` */ fun KClass.impl(name: String = ""): T { - return KtProviderManager.getImplOrThrow(this, name) + return KtProvider.impl(this, name) +} + +fun KClass.implOrNull(name: String = ""): T? { + return KtProvider.implOrNull(this, name) } /** @@ -91,7 +95,7 @@ fun KClass.impl(name: String = ""): T { * ``` */ fun KClass.allImpl(): Map> { - return KtProviderManager.getAllImpl(this) + return KtProvider.allImpl(this) } /** @@ -108,6 +112,6 @@ fun KClass.allImpl(): Map> { * ``` */ fun KClass.implClass(name: String): KClass { - return KtProviderManager.getKClassOrThrow(this, name) + return KtProvider.clazz(this, name) } diff --git a/cyxbs-components/utils/src/desktopMain/kotlin/com/cyxbs/components/utils/Utils.desktop.kt b/cyxbs-components/utils/src/desktopMain/kotlin/com/cyxbs/components/utils/Utils.desktop.kt new file mode 100644 index 0000000000..2170f21a43 --- /dev/null +++ b/cyxbs-components/utils/src/desktopMain/kotlin/com/cyxbs/components/utils/Utils.desktop.kt @@ -0,0 +1,11 @@ +package com.cyxbs.components.utils + +/** + * . + * + * @author 985892345 + * @date 2024/12/29 + */ +actual fun isDebug(): Boolean { + TODO("Not yet implemented") +} \ No newline at end of file diff --git a/cyxbs-components/utils/src/desktopMain/kotlin/com/cyxbs/components/utils/coroutine/Coroutine.desktop.kt b/cyxbs-components/utils/src/desktopMain/kotlin/com/cyxbs/components/utils/coroutine/Coroutine.desktop.kt new file mode 100644 index 0000000000..66f2d18b54 --- /dev/null +++ b/cyxbs-components/utils/src/desktopMain/kotlin/com/cyxbs/components/utils/coroutine/Coroutine.desktop.kt @@ -0,0 +1,29 @@ +package com.cyxbs.components.utils.coroutine + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.runBlocking +import kotlin.system.exitProcess + +/** + * . + * + * @author 985892345 + * @date 2024/12/28 + */ +actual val appCoroutineScope: CoroutineScope + get() = appCoroutineScopeInternal + +private lateinit var appCoroutineScopeInternal: CoroutineScope + +fun runApp(block: suspend CoroutineScope.() -> Unit) { + runBlocking { + // appCoroutineScopeInternal 使用 SupervisorJob 避免异常传播 + val supervisor = SupervisorJob(coroutineContext[Job]) + val coroutineScope = CoroutineScope(supervisor) + appCoroutineScopeInternal = coroutineScope + block() + } + exitProcess(0) +} \ No newline at end of file diff --git a/cyxbs-components/utils/src/iosMain/kotlin/com/cyxbs/components/utils/Utils.ios.kt b/cyxbs-components/utils/src/iosMain/kotlin/com/cyxbs/components/utils/Utils.ios.kt new file mode 100644 index 0000000000..2170f21a43 --- /dev/null +++ b/cyxbs-components/utils/src/iosMain/kotlin/com/cyxbs/components/utils/Utils.ios.kt @@ -0,0 +1,11 @@ +package com.cyxbs.components.utils + +/** + * . + * + * @author 985892345 + * @date 2024/12/29 + */ +actual fun isDebug(): Boolean { + TODO("Not yet implemented") +} \ No newline at end of file diff --git a/cyxbs-components/utils/src/iosMain/kotlin/com/cyxbs/components/utils/coroutine/Coroutine.ios.kt b/cyxbs-components/utils/src/iosMain/kotlin/com/cyxbs/components/utils/coroutine/Coroutine.ios.kt new file mode 100644 index 0000000000..1a5185a8e1 --- /dev/null +++ b/cyxbs-components/utils/src/iosMain/kotlin/com/cyxbs/components/utils/coroutine/Coroutine.ios.kt @@ -0,0 +1,9 @@ +package com.cyxbs.components.utils.coroutine + +import kotlinx.coroutines.CoroutineScope + +/** + * 应用级别的协程作用域 + */ +actual val appCoroutineScope: CoroutineScope + get() = TODO("Not yet implemented") \ No newline at end of file diff --git a/cyxbs-components/utils/src/wasmJsMain/kotlin/com/cyxbs/components/utils/Utils.wasmJs.kt b/cyxbs-components/utils/src/wasmJsMain/kotlin/com/cyxbs/components/utils/Utils.wasmJs.kt new file mode 100644 index 0000000000..2170f21a43 --- /dev/null +++ b/cyxbs-components/utils/src/wasmJsMain/kotlin/com/cyxbs/components/utils/Utils.wasmJs.kt @@ -0,0 +1,11 @@ +package com.cyxbs.components.utils + +/** + * . + * + * @author 985892345 + * @date 2024/12/29 + */ +actual fun isDebug(): Boolean { + TODO("Not yet implemented") +} \ No newline at end of file diff --git a/cyxbs-components/utils/src/wasmJsMain/kotlin/com/cyxbs/components/utils/coroutine/Coroutine.wasmJs.kt b/cyxbs-components/utils/src/wasmJsMain/kotlin/com/cyxbs/components/utils/coroutine/Coroutine.wasmJs.kt new file mode 100644 index 0000000000..1a5185a8e1 --- /dev/null +++ b/cyxbs-components/utils/src/wasmJsMain/kotlin/com/cyxbs/components/utils/coroutine/Coroutine.wasmJs.kt @@ -0,0 +1,9 @@ +package com.cyxbs.components.utils.coroutine + +import kotlinx.coroutines.CoroutineScope + +/** + * 应用级别的协程作用域 + */ +actual val appCoroutineScope: CoroutineScope + get() = TODO("Not yet implemented") \ No newline at end of file diff --git a/cyxbs-functions/debug/build.gradle.kts b/cyxbs-functions/debug/build.gradle.kts index f378200491..0856d70944 100644 --- a/cyxbs-functions/debug/build.gradle.kts +++ b/cyxbs-functions/debug/build.gradle.kts @@ -6,13 +6,14 @@ useKtProvider() kotlin { sourceSets { - androidMain.dependencies { + commonMain.dependencies { implementation(projects.cyxbsComponents.init) implementation(projects.cyxbsComponents.base) implementation(projects.cyxbsComponents.utils) implementation(projects.cyxbsComponents.config) implementation(projects.cyxbsComponents.account.api) - + } + androidMain.dependencies { // 依赖 LeakCanary,检查内存泄漏 https://github.com/square/leakcanary implementation(libs.leakcanary) diff --git a/cyxbs-pages/declare/build.gradle.kts b/cyxbs-pages/declare/build.gradle.kts index 4ee5bba95e..b8ba011da9 100644 --- a/cyxbs-pages/declare/build.gradle.kts +++ b/cyxbs-pages/declare/build.gradle.kts @@ -4,7 +4,6 @@ plugins { } useKtProvider() -useDataBinding() kotlin { sourceSets { diff --git a/cyxbs-pages/declare/src/androidMain/kotlin/com/cyxbs/pages/declare/detail/page/activity/DetailActivity.kt b/cyxbs-pages/declare/src/androidMain/kotlin/com/cyxbs/pages/declare/detail/page/activity/DetailActivity.kt index 901fc97735..cffeff7886 100644 --- a/cyxbs-pages/declare/src/androidMain/kotlin/com/cyxbs/pages/declare/detail/page/activity/DetailActivity.kt +++ b/cyxbs-pages/declare/src/androidMain/kotlin/com/cyxbs/pages/declare/detail/page/activity/DetailActivity.kt @@ -3,19 +3,22 @@ package com.cyxbs.pages.declare.detail.page.activity import android.content.Context import android.content.Intent import android.os.Bundle +import android.view.View +import android.widget.TextView import androidx.activity.viewModels import androidx.recyclerview.widget.LinearLayoutManager -import com.cyxbs.components.base.ui.BaseBindActivity +import androidx.recyclerview.widget.RecyclerView +import com.cyxbs.components.base.ui.BaseActivity import com.cyxbs.components.utils.extensions.gone import com.cyxbs.components.utils.extensions.visible import com.cyxbs.components.utils.service.impl -import com.cyxbs.pages.declare.databinding.DeclareActivityDetailBinding +import com.cyxbs.pages.declare.R import com.cyxbs.pages.declare.detail.bean.VoteData import com.cyxbs.pages.declare.detail.page.adapter.DetailRvAdapter import com.cyxbs.pages.declare.detail.page.viewmodel.DetailViewModel import com.cyxbs.pages.store.api.IStoreService -class DetailActivity : BaseBindActivity() { +class DetailActivity : BaseActivity() { companion object { /** * 启动投票详情页面 @@ -31,26 +34,33 @@ class DetailActivity : BaseBindActivity() { private val mViewModel by viewModels() + private val declareDetailRecyclerview by R.id.declare_detail_recyclerview.view() + private val declareDetailIvToolbarArrowLeft by R.id.declare_detail_iv_toolbar_arrow_left.view() + private val declareDetailTitle by R.id.declare_detail_title.view() + private val declareDetailCl by R.id.declare_detail_cl.view() + private val declareDetailNoNet by R.id.declare_detail_no_net.view() + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + setContentView(R.layout.declare_activity_detail) val id = intent.getIntExtra("id", -1) //因为投票后返回的是个map,map是无序的,所以这里用个list记下未投票之前的选项排布顺序 val voteDataList = mutableListOf() val declareDetailRvAdapter = DetailRvAdapter() - binding.declareDetailRecyclerview.run { + declareDetailRecyclerview.run { layoutManager = LinearLayoutManager(this@DetailActivity) adapter = declareDetailRvAdapter } - binding.declareDetailIvToolbarArrowLeft.setOnClickListener { + declareDetailIvToolbarArrowLeft.setOnClickListener { finish() } mViewModel.detailLiveData.observe { voteDataList.clear() - binding.declareDetailTitle.text = it.title + declareDetailTitle.text = it.title val votedList = mutableListOf()//差分刷新要求 源数据集和新数据集 不是同一个对象才能生效 if (it.choices != null) {//防止后端返回个没有选项的投票 @@ -104,11 +114,11 @@ class DetailActivity : BaseBindActivity() { mViewModel.errorLiveData.observe { if (it) { - binding.declareDetailCl.gone() - binding.declareDetailNoNet.visible() + declareDetailCl.gone() + declareDetailNoNet.visible() } else { - binding.declareDetailCl.visible() - binding.declareDetailNoNet.gone() + declareDetailCl.visible() + declareDetailNoNet.gone() } } diff --git a/cyxbs-pages/declare/src/androidMain/kotlin/com/cyxbs/pages/declare/main/page/activity/HomeActivity.kt b/cyxbs-pages/declare/src/androidMain/kotlin/com/cyxbs/pages/declare/main/page/activity/HomeActivity.kt index dc8f0cc37c..e7730c5ea3 100644 --- a/cyxbs-pages/declare/src/androidMain/kotlin/com/cyxbs/pages/declare/main/page/activity/HomeActivity.kt +++ b/cyxbs-pages/declare/src/androidMain/kotlin/com/cyxbs/pages/declare/main/page/activity/HomeActivity.kt @@ -4,21 +4,24 @@ import android.app.Activity import android.content.Context import android.content.Intent import android.os.Bundle +import android.view.View +import android.widget.TextView import androidx.activity.viewModels import androidx.recyclerview.widget.LinearLayoutManager -import com.cyxbs.components.base.ui.BaseBindActivity +import androidx.recyclerview.widget.RecyclerView +import com.cyxbs.components.base.ui.BaseActivity import com.cyxbs.components.config.route.DECLARE_ENTRY import com.cyxbs.components.utils.extensions.gone import com.cyxbs.components.utils.extensions.setOnDoubleClickListener import com.cyxbs.components.utils.extensions.visible -import com.cyxbs.pages.declare.databinding.DeclareActivityHomeBinding +import com.cyxbs.pages.declare.R import com.cyxbs.pages.declare.detail.page.activity.DetailActivity import com.cyxbs.pages.declare.main.page.adapter.HomeRvAdapter import com.cyxbs.pages.declare.main.page.viewmodel.HomeViewModel import com.g985892345.provider.api.annotation.KClassProvider @KClassProvider(clazz = Activity::class, name = DECLARE_ENTRY) -class HomeActivity : BaseBindActivity() { +class HomeActivity : BaseActivity() { companion object { /** * 启动表态主页 @@ -29,47 +32,55 @@ class HomeActivity : BaseBindActivity() { } private val mViewModel by viewModels() + + private val declareHomeRecyclerview by R.id.declare_home_recyclerview.view() + private val declareHomeToolbarTv by R.id.declare_home_toolbar_tv.view() + private val declareHomeToolbarPost by R.id.declare_home_toolbar_post.view() + private val declareIvToolbarArrowLeft by R.id.declare_iv_toolbar_arrow_left.view() + private val declareHomeNoData by R.id.declare_home_no_data.view() + private val declareHomeCl by R.id.declare_home_cl.view() + private val declareHomeNoNet by R.id.declare_home_no_net.view() + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + setContentView(R.layout.declare_activity_home) val declareHomeRvAdapter = HomeRvAdapter() - binding.run { - declareHomeRecyclerview.run { - layoutManager = LinearLayoutManager(this@HomeActivity) - adapter = declareHomeRvAdapter - declareHomeToolbarTv.setOnDoubleClickListener { - smoothScrollToPosition(0) - } + declareHomeRecyclerview.run { + layoutManager = LinearLayoutManager(this@HomeActivity) + adapter = declareHomeRvAdapter + declareHomeToolbarTv.setOnDoubleClickListener { + smoothScrollToPosition(0) } } declareHomeRvAdapter.setOnItemClickedListener { DetailActivity.startActivity(this, it) } - binding.declareHomeToolbarPost.setOnClickListener { + declareHomeToolbarPost.setOnClickListener { //跳至自己发布过的话题页面 PostedActivity.startActivity(this) } - binding.declareIvToolbarArrowLeft.setOnClickListener { + declareIvToolbarArrowLeft.setOnClickListener { finish() } mViewModel.homeLiveData.observe { if (it.isEmpty()) { - binding.declareHomeNoData.visible() + declareHomeNoData.visible() } else { - binding.declareHomeNoData.gone() + declareHomeNoData.gone() declareHomeRvAdapter.submitList(it) } } mViewModel.homeErrorLiveData.observe { if (it) { - binding.declareHomeCl.gone() - binding.declareHomeNoNet.visible() + declareHomeCl.gone() + declareHomeNoNet.visible() } else { - binding.declareHomeCl.visible() - binding.declareHomeNoNet.gone() + declareHomeCl.visible() + declareHomeNoNet.gone() } } mViewModel.permLiveData.observe { - binding.declareHomeToolbarPost.run { + declareHomeToolbarPost.run { if (it.isPerm) visible() else gone() } } diff --git a/cyxbs-pages/declare/src/androidMain/kotlin/com/cyxbs/pages/declare/main/page/activity/PostedActivity.kt b/cyxbs-pages/declare/src/androidMain/kotlin/com/cyxbs/pages/declare/main/page/activity/PostedActivity.kt index 66a490601b..3065d2cb8b 100644 --- a/cyxbs-pages/declare/src/androidMain/kotlin/com/cyxbs/pages/declare/main/page/activity/PostedActivity.kt +++ b/cyxbs-pages/declare/src/androidMain/kotlin/com/cyxbs/pages/declare/main/page/activity/PostedActivity.kt @@ -3,15 +3,18 @@ package com.cyxbs.pages.declare.main.page.activity import android.content.Context import android.content.Intent import android.os.Bundle +import android.view.View +import android.widget.ImageView +import android.widget.TextView import androidx.activity.viewModels import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.cyxbs.components.base.ui.BaseActivity import com.cyxbs.pages.declare.R -import com.cyxbs.pages.declare.databinding.DeclareActivityHomeBinding import com.cyxbs.pages.declare.detail.page.activity.DetailActivity import com.cyxbs.pages.declare.main.page.adapter.HomeRvAdapter import com.cyxbs.pages.declare.main.page.viewmodel.PostedViewModel import com.cyxbs.pages.declare.post.PostActivity -import com.cyxbs.components.base.ui.BaseBindActivity import com.cyxbs.components.utils.extensions.gone import com.cyxbs.components.utils.extensions.setOnDoubleClickListener import com.cyxbs.components.utils.extensions.visible @@ -19,7 +22,7 @@ import com.cyxbs.components.utils.extensions.visible /** * 因为发布过投票的页面和主页面差不多,所以这里就共用了主页面的xml */ -class PostedActivity : BaseBindActivity() { +class PostedActivity : BaseActivity() { /** * 启动表态详情页面 */ @@ -31,48 +34,57 @@ class PostedActivity : BaseBindActivity() { private val mViewModel by viewModels() + private val declareHomeToolbarPost by R.id.declare_home_toolbar_post.view() + private val declareHomeRecyclerview by R.id.declare_home_recyclerview.view() + private val declareHomeToolbarTv by R.id.declare_home_toolbar_tv.view() + private val declareHomeNoDataPic by R.id.declare_home_no_data_pic.view() + private val declareHomeNoDataTv by R.id.declare_home_no_data_tv.view() + private val declareIvToolbarArrowLeft by R.id.declare_iv_toolbar_arrow_left.view() + private val declareHomeNoData by R.id.declare_home_no_data.view() + private val declareHomeCl by R.id.declare_home_cl.view() + private val declareHomeNoNet by R.id.declare_home_no_net.view() + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + setContentView(R.layout.declare_activity_home) val declareHomeRvAdapter = HomeRvAdapter() - binding.declareHomeToolbarPost.visible() - binding.run { - declareHomeRecyclerview.run { - layoutManager = LinearLayoutManager(this@PostedActivity) - adapter = declareHomeRvAdapter - declareHomeToolbarTv.text = resources.getString(R.string.declare_posted_title) - declareHomeToolbarTv.setOnDoubleClickListener { - smoothScrollToPosition(0) - } + declareHomeToolbarPost.visible() + declareHomeRecyclerview.run { + layoutManager = LinearLayoutManager(this@PostedActivity) + adapter = declareHomeRvAdapter + declareHomeToolbarTv.text = resources.getString(R.string.declare_posted_title) + declareHomeToolbarTv.setOnDoubleClickListener { + smoothScrollToPosition(0) } - declareHomeNoDataPic.setImageResource(R.drawable.declare_ic_posted_no_data) - declareHomeNoDataTv.text = resources.getString(R.string.declare_posted_no_data) - declareHomeToolbarPost.setBackgroundResource(R.drawable.declare_ic_mine_post) } + declareHomeNoDataPic.setImageResource(R.drawable.declare_ic_posted_no_data) + declareHomeNoDataTv.text = resources.getString(R.string.declare_posted_no_data) + declareHomeToolbarPost.setBackgroundResource(R.drawable.declare_ic_mine_post) declareHomeRvAdapter.setOnItemClickedListener { DetailActivity.startActivity(this, it) } - binding.declareHomeToolbarPost.setOnClickListener { + declareHomeToolbarPost.setOnClickListener { //跳至自己发布话题页面 PostActivity.start(this) } - binding.declareIvToolbarArrowLeft.setOnClickListener { + declareIvToolbarArrowLeft.setOnClickListener { finish() } mViewModel.postedLiveData.observe { if (it.isEmpty()) { - binding.declareHomeNoData.visible() + declareHomeNoData.visible() } else { - binding.declareHomeNoData.gone() + declareHomeNoData.gone() declareHomeRvAdapter.submitList(it) } } mViewModel.postedErrorLiveData.observe { if (it) { - binding.declareHomeCl.gone() - binding.declareHomeNoNet.visible() + declareHomeCl.gone() + declareHomeNoNet.visible() } else { - binding.declareHomeCl.visible() - binding.declareHomeNoNet.gone() + declareHomeCl.visible() + declareHomeNoNet.gone() } } } diff --git a/cyxbs-pages/declare/src/androidMain/kotlin/com/cyxbs/pages/declare/post/PostActivity.kt b/cyxbs-pages/declare/src/androidMain/kotlin/com/cyxbs/pages/declare/post/PostActivity.kt index 1cde9305d1..d42685d2f0 100644 --- a/cyxbs-pages/declare/src/androidMain/kotlin/com/cyxbs/pages/declare/post/PostActivity.kt +++ b/cyxbs-pages/declare/src/androidMain/kotlin/com/cyxbs/pages/declare/post/PostActivity.kt @@ -5,85 +5,91 @@ import android.content.Context import android.content.Intent import android.os.Bundle import android.text.InputFilter.LengthFilter +import android.view.LayoutInflater +import android.view.View +import android.widget.EditText import androidx.activity.viewModels import androidx.appcompat.widget.AppCompatButton import androidx.core.widget.addTextChangedListener import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView import com.afollestad.materialdialogs.MaterialDialog import com.afollestad.materialdialogs.customview.customView +import com.cyxbs.components.base.ui.BaseActivity +import com.cyxbs.components.utils.extensions.dp2px import com.cyxbs.pages.declare.R -import com.cyxbs.pages.declare.databinding.DeclareActivityPostBinding -import com.cyxbs.pages.declare.databinding.DeclareLayoutDialogEditBinding -import com.cyxbs.pages.declare.databinding.DeclareLayoutDialogSubmitBinding import com.cyxbs.pages.declare.post.adapter.PostSectionRvAdapter -import com.cyxbs.components.base.ui.BaseBindActivity -import com.cyxbs.components.utils.extensions.dp2px +import com.google.android.material.textfield.TextInputEditText +import com.google.android.material.textfield.TextInputLayout import kotlinx.coroutines.launch import kotlinx.coroutines.suspendCancellableCoroutine -import java.util.Optional import kotlin.coroutines.resume -class PostActivity : BaseBindActivity() { +class PostActivity : BaseActivity() { override val isCancelStatusBar: Boolean get() = false private val viewModel by viewModels() - private lateinit var submitDialogLayoutBinding: DeclareLayoutDialogSubmitBinding - private lateinit var editDialogLayoutBinding: DeclareLayoutDialogEditBinding - private lateinit var submitDialog: MaterialDialog - private lateinit var editDialog: MaterialDialog + private lateinit var submitDialogManager: SubmitDialogManager + private lateinit var editDialogManager: EditDialogManager private lateinit var sectionAdapter: PostSectionRvAdapter + private val rvTopic by R.id.rv_topic.view() + private val pageBtnSubmit by R.id.btn_submit.view() + private val etTopic by R.id.et_topic.view() + private val declareIvToolbarArrowLeft by R.id.declare_iv_toolbar_arrow_left.view() + @SuppressLint("ClickableViewAccessibility") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setSupportActionBar(binding.toolbar) + setContentView(R.layout.declare_activity_post) + setSupportActionBar(findViewById(R.id.toolbar)) supportActionBar?.title = null initDialog() - binding.rvTopic.apply { + rvTopic.apply { layoutManager = LinearLayoutManager(this@PostActivity) adapter = PostSectionRvAdapter( onItemTouch = { list, position, et -> lifecycleScope.launch { // 长度限制15 - openEdit(15, list[position]).ifPresent { + openEdit(15, list[position])?.let { if (list.any { s -> it == s }) { toast("不允许存在多个相同的选项哦") - return@ifPresent + return@let } et.setText(it) list[position] = it // 更新主页按钮状态 - binding.btnSubmit.active() + pageBtnSubmit.active() } } }, - onItemUpdate = { binding.btnSubmit.active() } + onItemUpdate = { pageBtnSubmit.active() } ).also { sectionAdapter = it } } - binding.btnSubmit.setOnClickListener { + pageBtnSubmit.setOnClickListener { if (isPublishable()) { // 弹出Dialog - submitDialog.show() + submitDialogManager.dialog.show() } } - binding.etTopic.setOnClickListener { + etTopic.setOnClickListener { lifecycleScope.launch { - openEdit(30, binding.etTopic.text.toString()).ifPresent { - binding.etTopic.setText(it) - binding.btnSubmit.active() + openEdit(30, etTopic.text.toString())?.let { + etTopic.setText(it) + pageBtnSubmit.active() } } } - binding.etTopic.isFocusable = false - binding.declareIvToolbarArrowLeft.setOnClickListener { + etTopic.isFocusable = false + declareIvToolbarArrowLeft.setOnClickListener { finish() } lifecycleScope.launch { viewModel.postResultFlow.collectLaunch { - if (it.isSuccess()) { + if (it == null) { toast("发布成功") finish() } else { @@ -94,30 +100,23 @@ class PostActivity : BaseBindActivity() { } private fun initDialog() { - submitDialogLayoutBinding = DeclareLayoutDialogSubmitBinding.inflate(layoutInflater) - editDialogLayoutBinding = DeclareLayoutDialogEditBinding.inflate(layoutInflater) - submitDialog = MaterialDialog(this) - .customView(view = submitDialogLayoutBinding.root) - .cornerRadius(literalDp = 8f) - .maxWidth(literal = 300.dp2px) - editDialog = MaterialDialog(this) - .customView(view = editDialogLayoutBinding.root) - .cornerRadius(literalDp = 8f) - .maxWidth(literal = 300.dp2px) - submitDialogLayoutBinding.btnCancel.setOnClickListener { - submitDialog.hide() + submitDialogManager = SubmitDialogManager() + editDialogManager = EditDialogManager() + + submitDialogManager.btnCancel.setOnClickListener { + submitDialogManager.dialog.hide() } - submitDialogLayoutBinding.btnSubmit.setOnClickListener { + submitDialogManager.btnSubmit.setOnClickListener { lifecycleScope.launch { // 空白选项不能发 - viewModel.post(binding.etTopic.text.toString(), sectionAdapter.list) + viewModel.post(etTopic.text.toString(), sectionAdapter.list) } - submitDialog.hide() + submitDialogManager.dialog.hide() } } - private suspend fun openEdit(maxLen: Int, originText: String = ""): Optional = suspendCancellableCoroutine { co -> - editDialogLayoutBinding.apply { + private suspend fun openEdit(maxLen: Int, originText: String = ""): String? = suspendCancellableCoroutine { co -> + editDialogManager.apply { // 重置edittext状态 et.setText(originText) et.filters = arrayOf(LengthFilter(maxLen)) @@ -132,33 +131,34 @@ class PostActivity : BaseBindActivity() { et.removeTextChangedListener(textWatcher) } btnCancel.setOnClickListener { - editDialog.cancel() + dialog.cancel() resetListeners() - co.resume(Optional.empty()) + co.resume(null) } btnSubmit.setOnClickListener { val str = et.text.toString().replace("\n"," ") if (str.isNotBlank()) { - editDialog.hide() + dialog.hide() resetListeners() - co.resume(Optional.of(str)) + co.resume(str) } } co.invokeOnCancellation { - editDialog.cancel() + dialog.cancel() resetListeners() } - } - editDialog.apply { - setOnCancelListener { co.cancel() } - show() + dialog.apply { + setOnCancelListener { co.cancel() } + show() + et.requestFocus() // 弹起键盘 + } } } // 发布前的预检 private fun isPublishable(): Boolean { return sectionAdapter.list.all { s -> s.isNotBlank() } - && !binding.etTopic.text?.toString().isNullOrBlank() + && !etTopic.text?.toString().isNullOrBlank() && sectionAdapter.list.size >= 2 && sectionAdapter.list.distinct().size == sectionAdapter.list.size } @@ -171,6 +171,29 @@ class PostActivity : BaseBindActivity() { } } + inner class EditDialogManager { + val layout = LayoutInflater.from(this@PostActivity).inflate(R.layout.declare_layout_dialog_edit, null) + val dialog = MaterialDialog(this@PostActivity) + .customView(view = layout) + .cornerRadius(literalDp = 8f) + .maxWidth(literal = 300.dp2px) + val textInputLayout = layout.findViewById(R.id.text_input_layout) + val et = layout.findViewById(R.id.et) + val btnCancel = layout.findViewById(R.id.btn_cancel) + val btnSubmit = layout.findViewById(R.id.btn_submit) + } + + inner class SubmitDialogManager { + val layout = LayoutInflater.from(this@PostActivity).inflate(R.layout.declare_layout_dialog_submit, null) + val dialog = MaterialDialog(this@PostActivity) + .customView(view = layout) + .cornerRadius(literalDp = 8f) + .maxWidth(literal = 300.dp2px) + val btnCancel = layout.findViewById(R.id.btn_cancel) + val btnSubmit = layout.findViewById(R.id.btn_submit) + + } + companion object { @JvmStatic fun start(context: Context) { diff --git a/cyxbs-pages/declare/src/androidMain/kotlin/com/cyxbs/pages/declare/post/PostViewModel.kt b/cyxbs-pages/declare/src/androidMain/kotlin/com/cyxbs/pages/declare/post/PostViewModel.kt index 43438c267e..062e5af16d 100644 --- a/cyxbs-pages/declare/src/androidMain/kotlin/com/cyxbs/pages/declare/post/PostViewModel.kt +++ b/cyxbs-pages/declare/src/androidMain/kotlin/com/cyxbs/pages/declare/post/PostViewModel.kt @@ -3,7 +3,8 @@ package com.cyxbs.pages.declare.post import com.cyxbs.pages.declare.post.net.PostApiService import com.cyxbs.components.base.ui.BaseViewModel import com.cyxbs.components.utils.extensions.asFlow -import com.cyxbs.components.utils.network.ApiStatus +import com.cyxbs.components.utils.extensions.interceptException +import com.cyxbs.components.utils.network.throwApiExceptionIfFail import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.schedulers.Schedulers import kotlinx.coroutines.flow.MutableSharedFlow @@ -18,8 +19,8 @@ import kotlinx.coroutines.flow.SharedFlow */ class PostViewModel : BaseViewModel() { // livedata 是粘性事件,不适合在这里使用,所以直接使用 SharedFlow - private val _postResultFlow: MutableSharedFlow = MutableSharedFlow() - val postResultFlow: SharedFlow + private val _postResultFlow: MutableSharedFlow = MutableSharedFlow() + val postResultFlow: SharedFlow get() = _postResultFlow fun post(title: String, choices: List) { @@ -27,8 +28,12 @@ class PostViewModel : BaseViewModel() { .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .asFlow() + .throwApiExceptionIfFail() + .interceptException { + _postResultFlow.emit(it.message) + } .collectLaunch { - _postResultFlow.emit(it) + _postResultFlow.emit(null) } } } \ No newline at end of file diff --git a/cyxbs-pages/declare/src/androidMain/kotlin/com/cyxbs/pages/declare/post/adapter/PostSectionRvAdapter.kt b/cyxbs-pages/declare/src/androidMain/kotlin/com/cyxbs/pages/declare/post/adapter/PostSectionRvAdapter.kt index 64a6558729..0a4e9c251f 100644 --- a/cyxbs-pages/declare/src/androidMain/kotlin/com/cyxbs/pages/declare/post/adapter/PostSectionRvAdapter.kt +++ b/cyxbs-pages/declare/src/androidMain/kotlin/com/cyxbs/pages/declare/post/adapter/PostSectionRvAdapter.kt @@ -2,13 +2,12 @@ package com.cyxbs.pages.declare.post.adapter import android.annotation.SuppressLint import android.view.LayoutInflater +import android.view.View import android.view.ViewGroup import android.widget.EditText import androidx.recyclerview.widget.RecyclerView -import androidx.viewbinding.ViewBinding -import com.cyxbs.pages.declare.databinding.DeclareItemAddSectionBinding -import com.cyxbs.pages.declare.databinding.DeclareItemSectionBinding import com.cyxbs.components.utils.extensions.toast +import com.cyxbs.pages.declare.R /** @@ -28,63 +27,64 @@ class PostSectionRvAdapter( ) @SuppressLint("ClickableViewAccessibility") - inner class Holder(val binding: ViewBinding) : RecyclerView.ViewHolder(binding.root) { + sealed class Holder(itemView: View) : RecyclerView.ViewHolder(itemView) + + inner class DeclareItemSectionHolder(parent: ViewGroup) : Holder( + LayoutInflater.from(parent.context).inflate(R.layout.declare_item_section, parent, false) + ) { + val sivRm = itemView.findViewById(R.id.siv_rm) + val et = itemView.findViewById(R.id.et) init { - if (binding is DeclareItemSectionBinding) { - binding.sivRm.setOnClickListener { - list.removeAt(bindingAdapterPosition) - notifyItemRemoved(bindingAdapterPosition) - // 刷新下面所有的item,让选项号保持顺序 - (bindingAdapterPosition..list.size).forEach { - notifyItemChanged(it) - } - onItemUpdate(list) - } - binding.et.isFocusable = false - binding.et.setOnClickListener { - onItemTouch(list, bindingAdapterPosition, binding.et) + sivRm.setOnClickListener { + list.removeAt(bindingAdapterPosition) + notifyItemRemoved(bindingAdapterPosition) + // 刷新下面所有的item,让选项号保持顺序 + (bindingAdapterPosition..list.size).forEach { + notifyItemChanged(it) } - } else if (binding is DeclareItemAddSectionBinding) { - binding.sivAdd.setOnClickListener { - if (list.size == 10) { - toast("最多仅可以添加10个选项") - return@setOnClickListener - } - list.add("") - notifyItemInserted(list.size - 1) - onItemUpdate(list) + onItemUpdate(list) + } + et.isFocusable = false + et.setOnClickListener { + onItemTouch(list, bindingAdapterPosition, et) + } + } + } + + inner class DeclareItemAddSectionHolder(parent: ViewGroup) : Holder( + LayoutInflater.from(parent.context).inflate(R.layout.declare_item_add_section, parent, false) + ) { + val sivAdd = itemView.findViewById(R.id.siv_add) + init { + sivAdd.setOnClickListener { + if (list.size == 10) { + toast("最多仅可以添加10个选项") + return@setOnClickListener } + list.add("") + notifyItemInserted(list.size - 1) + onItemUpdate(list) } } } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder { - return Holder( - if (viewType == TYPE_TAIL) { - DeclareItemAddSectionBinding.inflate( - LayoutInflater.from(parent.context), - parent, - false - ) - } else { - DeclareItemSectionBinding.inflate( - LayoutInflater.from(parent.context), - parent, - false - ) - } - ) + return if (viewType == TYPE_TAIL) { + DeclareItemAddSectionHolder(parent) + } else { + DeclareItemSectionHolder(parent) + } } override fun onBindViewHolder(holder: Holder, position: Int) { when (getItemViewType(position)) { TYPE_NORMAL -> { - val binding = holder.binding as DeclareItemSectionBinding + holder as DeclareItemSectionHolder val content = list[position] - if (binding.et.hint != "选项${position + 1}") { - binding.root.post { - binding.et.hint = "选项${position + 1}" - binding.et.setText(content) + if (holder.et.hint != "选项${position + 1}") { + holder.itemView.post { + holder.et.hint = "选项${position + 1}" + holder.et.setText(content) } } } diff --git a/cyxbs-pages/declare/src/androidMain/res/layout/declare_activity_detail.xml b/cyxbs-pages/declare/src/androidMain/res/layout/declare_activity_detail.xml index 507e5f5b7b..d4e22270c5 100644 --- a/cyxbs-pages/declare/src/androidMain/res/layout/declare_activity_detail.xml +++ b/cyxbs-pages/declare/src/androidMain/res/layout/declare_activity_detail.xml @@ -1,122 +1,115 @@ - - - - - + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent"> + android:layout_height="match_parent"> + android:layout_height="200dp" + android:background="@drawable/declare_ic_detail_header_bg" + android:fitsSystemWindows="true" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent"> - + app:layout_constraintTop_toTopOf="@+id/declare_detail_bar" + app:layout_constraintVertical_bias="0.545454545" + tools:ignore="ContentDescription,SpeakableTextPresentCheck,SpeakableTextPresentCheck,TouchTargetSizeCheck" /> - - - - - - + - - - - - + tools:text="哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈" /> - + app:layout_constraintTop_toBottomOf="@+id/declare_detail_toolbar" + app:layout_constraintVertical_bias="0.0"> - + + - - - \ No newline at end of file + + + + + + + + diff --git a/cyxbs-pages/declare/src/androidMain/res/layout/declare_activity_home.xml b/cyxbs-pages/declare/src/androidMain/res/layout/declare_activity_home.xml index 49fe5a7237..410a7db3a1 100644 --- a/cyxbs-pages/declare/src/androidMain/res/layout/declare_activity_home.xml +++ b/cyxbs-pages/declare/src/androidMain/res/layout/declare_activity_home.xml @@ -1,133 +1,84 @@ - - - - - + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="@color/config_white_black" + android:fitsSystemWindows="true"> + android:background="@color/config_window_background"> - - - - - - - - - - - - - - + android:layout_height="80dp" + android:background="@color/config_window_background" + app:cardElevation="0.3dp" + app:cardCornerRadius="10dp" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent"> - - + + + - - - - + android:id="@+id/declare_home_toolbar_tv" + android:layout_width="wrap_content" + android:layout_height="40dp" + android:gravity="center_vertical" + android:text="@string/declare_home_toolbar" + android:textColor="@color/config_level_three_font_color" + android:textSize="22sp" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0.036" + app:layout_constraintStart_toEndOf="@+id/declare_iv_toolbar_arrow_left" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintVertical_bias="0.4818" /> + + + + + android:src="@drawable/declare_ic_home_no_data" /> + + - \ No newline at end of file + + + + + + + + diff --git a/cyxbs-pages/declare/src/androidMain/res/layout/declare_activity_post.xml b/cyxbs-pages/declare/src/androidMain/res/layout/declare_activity_post.xml index e4f03dc670..8d282e3592 100644 --- a/cyxbs-pages/declare/src/androidMain/res/layout/declare_activity_post.xml +++ b/cyxbs-pages/declare/src/androidMain/res/layout/declare_activity_post.xml @@ -1,113 +1,106 @@ - - - - - - - + + + + + + + + + + + android:layout_height="wrap_content" + android:layout_marginHorizontal="12dp" + android:layout_marginVertical="8dp" + app:cardElevation="0dp" + app:cardCornerRadius="6dp"> - - - - - - - - - + android:background="@color/config_common_background_color" + android:layout_height="wrap_content"> - + android:layout_height="wrap_content" + android:layout_marginHorizontal="26dp" + android:layout_marginVertical="36dp" + android:orientation="vertical"> - + + + + + + + android:layout_marginVertical="16dp" /> - - - - - - - - - - - - - - - - - - \ No newline at end of file + + + + + + + + + diff --git a/cyxbs-pages/declare/src/androidMain/res/layout/declare_item_add_section.xml b/cyxbs-pages/declare/src/androidMain/res/layout/declare_item_add_section.xml index 6a17c2b55b..96eaee5dc9 100644 --- a/cyxbs-pages/declare/src/androidMain/res/layout/declare_item_add_section.xml +++ b/cyxbs-pages/declare/src/androidMain/res/layout/declare_item_add_section.xml @@ -1,36 +1,30 @@ - + - + - - - - - - - + android:textColor="#4A44E4" + android:text="@string/declare_add_section" /> - + - diff --git a/cyxbs-pages/declare/src/androidMain/res/layout/declare_item_section.xml b/cyxbs-pages/declare/src/androidMain/res/layout/declare_item_section.xml index 4afaf780e4..27ce1c88b1 100644 --- a/cyxbs-pages/declare/src/androidMain/res/layout/declare_item_section.xml +++ b/cyxbs-pages/declare/src/androidMain/res/layout/declare_item_section.xml @@ -1,43 +1,37 @@ - + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingVertical="10dp"> - + - - - - - - - + android:layout_height="38dp" + android:background="@drawable/declare_input_bg_2" + android:ellipsize="end" + android:focusable="false" + android:gravity="center" + android:layout_gravity="center_vertical" + android:inputType="textNoSuggestions" + android:maxLines="1" + android:padding="0dp" + android:textColor="#CC15315B" + android:textColorHint="#8015315B" + android:textSize="14sp" + tools:hint="选项" /> - - \ No newline at end of file + diff --git a/cyxbs-pages/declare/src/androidMain/res/layout/declare_layout_dialog_edit.xml b/cyxbs-pages/declare/src/androidMain/res/layout/declare_layout_dialog_edit.xml index df5b083cc8..888e50bb0c 100644 --- a/cyxbs-pages/declare/src/androidMain/res/layout/declare_layout_dialog_edit.xml +++ b/cyxbs-pages/declare/src/androidMain/res/layout/declare_layout_dialog_edit.xml @@ -1,86 +1,80 @@ - + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="300dp" + android:layout_height="wrap_content" + android:orientation="vertical" + android:padding="24dp"> - - - - - + app:cardElevation="0dp" + app:cardCornerRadius="5dp"> - + android:background="#80E8F1FC" + android:paddingBottom="10dp" + android:theme="@style/Theme.Material3.DayNight" + app:boxStrokeWidth="0dp" + app:boxStrokeWidthFocused="0dp" + app:counterEnabled="true" + app:counterMaxLength="30" + app:counterTextColor="@color/declare_post_text_light"> - - - + android:gravity="start|top" + android:maxLength="30" + android:minHeight="80dp" + android:paddingTop="5dp" + android:textColor="#CC15315B" + android:textColorHint="#8015315B" + tools:hint="aaaa" /> - - + + - + - + - - + + - - \ No newline at end of file + diff --git a/cyxbs-pages/declare/src/androidMain/res/layout/declare_layout_dialog_submit.xml b/cyxbs-pages/declare/src/androidMain/res/layout/declare_layout_dialog_submit.xml index 855346b6c1..1f1322fa33 100644 --- a/cyxbs-pages/declare/src/androidMain/res/layout/declare_layout_dialog_submit.xml +++ b/cyxbs-pages/declare/src/androidMain/res/layout/declare_layout_dialog_submit.xml @@ -1,77 +1,72 @@ - + - + - + - - - + - - - + + android:text="@string/declare_cancel_submit" + android:gravity="center" + android:layout_width="82dp" + android:layout_height="36dp" /> - - + + - - + - - diff --git a/cyxbs-pages/discover/src/androidMain/AndroidManifest.xml b/cyxbs-pages/discover/src/androidMain/AndroidManifest.xml index f096b8140e..01d188c326 100644 --- a/cyxbs-pages/discover/src/androidMain/AndroidManifest.xml +++ b/cyxbs-pages/discover/src/androidMain/AndroidManifest.xml @@ -3,6 +3,7 @@ + \ No newline at end of file diff --git a/cyxbs-pages/discover/src/androidMain/kotlin/com/cyxbs/pages/discover/pages/discover/DiscoverHomeFragment.kt b/cyxbs-pages/discover/src/androidMain/kotlin/com/cyxbs/pages/discover/pages/discover/DiscoverHomeFragment.kt index 74eab3b26d..8517cdba2a 100644 --- a/cyxbs-pages/discover/src/androidMain/kotlin/com/cyxbs/pages/discover/pages/discover/DiscoverHomeFragment.kt +++ b/cyxbs-pages/discover/src/androidMain/kotlin/com/cyxbs/pages/discover/pages/discover/DiscoverHomeFragment.kt @@ -31,7 +31,7 @@ import com.cyxbs.components.config.route.MINE_CHECK_IN import com.cyxbs.components.config.route.NOTIFICATION_HOME import com.cyxbs.components.utils.extensions.dp2px import com.cyxbs.components.utils.extensions.gone -import com.cyxbs.components.utils.extensions.processLifecycleScope +import com.cyxbs.components.utils.coroutine.appCoroutineScope import com.cyxbs.components.utils.extensions.setOnSingleClickListener import com.cyxbs.components.utils.extensions.visible import com.cyxbs.components.utils.logger.TrackingUtils @@ -247,7 +247,7 @@ class DiscoverHomeFragment : BaseFragment() { if (IAccountService::class.impl().getVerifyService().isLogin()) { // 发现首页横排按钮点击埋点 functions[it].clickEvent?.let { clickEvent -> - processLifecycleScope.launch { + appCoroutineScope.launch { TrackingUtils.trackClickEvent(clickEvent) } } diff --git a/cyxbs-pages/discover/src/androidMain/kotlin/com/cyxbs/pages/discover/pages/discover/DiscoverHomeViewModel.kt b/cyxbs-pages/discover/src/androidMain/kotlin/com/cyxbs/pages/discover/pages/discover/DiscoverHomeViewModel.kt index 64091930ca..185cefa23b 100644 --- a/cyxbs-pages/discover/src/androidMain/kotlin/com/cyxbs/pages/discover/pages/discover/DiscoverHomeViewModel.kt +++ b/cyxbs-pages/discover/src/androidMain/kotlin/com/cyxbs/pages/discover/pages/discover/DiscoverHomeViewModel.kt @@ -4,7 +4,7 @@ import android.os.Parcelable import androidx.lifecycle.MutableLiveData import androidx.lifecycle.viewModelScope import com.cyxbs.components.base.ui.BaseViewModel -import com.cyxbs.components.utils.extensions.EmptyCoroutineExceptionHandler +import com.cyxbs.components.utils.coroutine.EmptyCoroutineExceptionHandler import com.cyxbs.components.utils.extensions.setSchedulers import com.cyxbs.components.utils.network.ApiGenerator import com.cyxbs.components.utils.network.mapOrThrowApiException diff --git a/cyxbs-pages/discover/src/androidMain/kotlin/com/cyxbs/pages/discover/pages/discover/adapter/RollerViewInfoAdapter.kt b/cyxbs-pages/discover/src/androidMain/kotlin/com/cyxbs/pages/discover/pages/discover/adapter/RollerViewInfoAdapter.kt index d48b9f5554..f488849514 100644 --- a/cyxbs-pages/discover/src/androidMain/kotlin/com/cyxbs/pages/discover/pages/discover/adapter/RollerViewInfoAdapter.kt +++ b/cyxbs-pages/discover/src/androidMain/kotlin/com/cyxbs/pages/discover/pages/discover/adapter/RollerViewInfoAdapter.kt @@ -6,7 +6,7 @@ import androidx.recyclerview.widget.RecyclerView import com.bumptech.glide.Glide import com.cyxbs.components.account.api.IAccountService import com.cyxbs.components.utils.extensions.dp2pxF -import com.cyxbs.components.utils.extensions.processLifecycleScope +import com.cyxbs.components.utils.coroutine.appCoroutineScope import com.cyxbs.components.utils.extensions.setOnSingleClickListener import com.cyxbs.components.utils.logger.TrackingUtils import com.cyxbs.components.utils.logger.event.ClickEvent @@ -32,7 +32,7 @@ class RollerViewInfoAdapter( iv.setOnSingleClickListener { if (IAccountService::class.impl().getVerifyService().isLogin()) { // banner位的点击埋点 - processLifecycleScope.launch { + appCoroutineScope.launch { TrackingUtils.trackClickEvent(ClickEvent.CLICK_YLC_BANNER_ENTRY) } } diff --git a/cyxbs-pages/home/src/androidMain/kotlin/com/cyxbs/pages/home/ui/course/HomeCourseCompose.kt b/cyxbs-pages/home/src/androidMain/kotlin/com/cyxbs/pages/home/ui/course/HomeCourseCompose.kt index 09b202739f..7152962d63 100644 --- a/cyxbs-pages/home/src/androidMain/kotlin/com/cyxbs/pages/home/ui/course/HomeCourseCompose.kt +++ b/cyxbs-pages/home/src/androidMain/kotlin/com/cyxbs/pages/home/ui/course/HomeCourseCompose.kt @@ -15,7 +15,7 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.systemBarsPadding -import androidx.compose.material3.Text +import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope diff --git a/cyxbs-pages/home/src/androidMain/kotlin/com/cyxbs/pages/home/ui/course/utils/CourseHeaderHelper.kt b/cyxbs-pages/home/src/androidMain/kotlin/com/cyxbs/pages/home/ui/course/utils/CourseHeaderHelper.kt index 299f994a37..4479a8f3af 100644 --- a/cyxbs-pages/home/src/androidMain/kotlin/com/cyxbs/pages/home/ui/course/utils/CourseHeaderHelper.kt +++ b/cyxbs-pages/home/src/androidMain/kotlin/com/cyxbs/pages/home/ui/course/utils/CourseHeaderHelper.kt @@ -3,8 +3,8 @@ package com.cyxbs.pages.home.ui.course.utils import android.util.Log import com.cyxbs.components.account.api.IAccountService import com.cyxbs.components.config.config.SchoolCalendar -import com.cyxbs.components.utils.extensions.isDebuggableBuild import com.cyxbs.components.utils.extensions.toast +import com.cyxbs.components.utils.isDebug import com.cyxbs.components.utils.service.impl import com.cyxbs.components.utils.utils.judge.NetworkUtil import com.cyxbs.pages.affair.api.IAffairService @@ -80,7 +80,7 @@ object CourseHeaderHelper { .toObservable() .flatMap { Observable.empty
() } .doOnError { - if (isDebuggableBuild) { + if (isDebug()) { toast("课表崩了,长按课表头显示") } }.onErrorReturn { throwable -> diff --git a/cyxbs-pages/home/src/androidMain/kotlin/com/cyxbs/pages/home/ui/main/MainActivity.kt b/cyxbs-pages/home/src/androidMain/kotlin/com/cyxbs/pages/home/ui/main/MainActivity.kt index 6544b21192..930d427096 100644 --- a/cyxbs-pages/home/src/androidMain/kotlin/com/cyxbs/pages/home/ui/main/MainActivity.kt +++ b/cyxbs-pages/home/src/androidMain/kotlin/com/cyxbs/pages/home/ui/main/MainActivity.kt @@ -14,7 +14,7 @@ import com.cyxbs.components.config.route.DISCOVER_SCHOOL_CAR import com.cyxbs.components.config.sp.SP_COURSE_SHOW_STATE import com.cyxbs.components.config.sp.defaultSp import com.cyxbs.components.utils.extensions.launch -import com.cyxbs.components.utils.extensions.processLifecycleScope +import com.cyxbs.components.utils.coroutine.appCoroutineScope import com.cyxbs.components.utils.logger.TrackingUtils import com.cyxbs.components.utils.logger.event.ClickEvent import com.cyxbs.components.utils.service.impl @@ -143,7 +143,7 @@ class MainActivity : BaseActivity() { mViewModel.courseBottomSheetExpand.value = null if (mIsLogin) { // “邮乐园” 按钮点击事件埋点 - processLifecycleScope.launch { + appCoroutineScope.launch { TrackingUtils.trackClickEvent(ClickEvent.CLICK_YLC_ENTRY) } } diff --git a/cyxbs-pages/home/src/androidMain/kotlin/com/cyxbs/pages/home/ui/main/MainCompose.kt b/cyxbs-pages/home/src/androidMain/kotlin/com/cyxbs/pages/home/ui/main/MainCompose.kt index 3ee76ed094..8ee1d0e9ef 100644 --- a/cyxbs-pages/home/src/androidMain/kotlin/com/cyxbs/pages/home/ui/main/MainCompose.kt +++ b/cyxbs-pages/home/src/androidMain/kotlin/com/cyxbs/pages/home/ui/main/MainCompose.kt @@ -18,7 +18,7 @@ import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.statusBarsPadding -import androidx.compose.material3.Text +import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableFloatState diff --git a/cyxbs-pages/home/src/androidMain/kotlin/com/cyxbs/pages/home/viewmodel/MainViewModel.kt b/cyxbs-pages/home/src/androidMain/kotlin/com/cyxbs/pages/home/viewmodel/MainViewModel.kt index 817cbc5ea7..79ba7b6cb3 100644 --- a/cyxbs-pages/home/src/androidMain/kotlin/com/cyxbs/pages/home/viewmodel/MainViewModel.kt +++ b/cyxbs-pages/home/src/androidMain/kotlin/com/cyxbs/pages/home/viewmodel/MainViewModel.kt @@ -4,7 +4,7 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.viewModelScope import com.cyxbs.components.base.ui.BaseViewModel -import com.cyxbs.components.utils.extensions.EmptyCoroutineExceptionHandler +import com.cyxbs.components.utils.coroutine.EmptyCoroutineExceptionHandler import com.cyxbs.components.utils.network.api import com.cyxbs.pages.home.network.NotificationApiService import kotlinx.coroutines.Dispatchers diff --git a/cyxbs-pages/home/src/androidMain/res/layout/home_activity_debug.xml b/cyxbs-pages/home/src/androidMain/res/layout/home_activity_debug.xml deleted file mode 100644 index bb17dde011..0000000000 --- a/cyxbs-pages/home/src/androidMain/res/layout/home_activity_debug.xml +++ /dev/null @@ -1,96 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - -