|
|
@ -0,0 +1,15 @@
|
|||
*.iml
|
||||
.gradle
|
||||
/local.properties
|
||||
/.idea/caches
|
||||
/.idea/libraries
|
||||
/.idea/modules.xml
|
||||
/.idea/workspace.xml
|
||||
/.idea/navEditor.xml
|
||||
/.idea/assetWizardSettings.xml
|
||||
.DS_Store
|
||||
/build
|
||||
/captures
|
||||
.externalNativeBuild
|
||||
.cxx
|
||||
local.properties
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
|
|
@ -0,0 +1 @@
|
|||
rabbit
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="AndroidProjectSystem">
|
||||
<option name="providerId" value="com.android.tools.idea.GradleProjectSystem" />
|
||||
</component>
|
||||
</project>
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="com.codeverse.userSettings.MarscodeWorkspaceAppSettingsState">
|
||||
<option name="CKGUserStarteByUser" value="true" />
|
||||
<option name="chatAppRouterInfo" value="builder/698af709d073d0c19b43196d" />
|
||||
<option name="ckgOperationStatus" value="CONFIRM" />
|
||||
<option name="lastCKGNotifyTime" value="1770886315206" />
|
||||
</component>
|
||||
</project>
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CompilerConfiguration">
|
||||
<bytecodeTargetLevel target="21" />
|
||||
</component>
|
||||
</project>
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="deploymentTargetSelector">
|
||||
<selectionStates>
|
||||
<SelectionState runConfigName="app">
|
||||
<option name="selectionMode" value="DROPDOWN" />
|
||||
</SelectionState>
|
||||
</selectionStates>
|
||||
</component>
|
||||
</project>
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="DeviceTable">
|
||||
<option name="columnSorters">
|
||||
<list>
|
||||
<ColumnSorterState>
|
||||
<option name="column" value="Name" />
|
||||
<option name="order" value="ASCENDING" />
|
||||
</ColumnSorterState>
|
||||
</list>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="GradleMigrationSettings" migrationVersion="1" />
|
||||
<component name="GradleSettings">
|
||||
<option name="linkedExternalProjectsSettings">
|
||||
<GradleProjectSettings>
|
||||
<option name="testRunner" value="CHOOSE_PER_TEST" />
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" />
|
||||
<option name="modules">
|
||||
<set>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
<option value="$PROJECT_DIR$/app" />
|
||||
</set>
|
||||
</option>
|
||||
</GradleProjectSettings>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="ComposePreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
<option name="previewFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="ComposePreviewMustBeTopLevelFunction" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
<option name="previewFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="ComposePreviewNeedsComposableAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
<option name="previewFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="ComposePreviewNotSupportedInUnitTestFiles" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
<option name="previewFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="GlancePreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="GlancePreviewMustBeTopLevelFunction" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="GlancePreviewNeedsComposableAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="GlancePreviewNotSupportedInUnitTestFiles" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PreviewAnnotationInFunctionWithParameters" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
<option name="previewFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PreviewApiLevelMustBeValid" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
<option name="previewFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PreviewDeviceShouldUseNewSpec" enabled="true" level="WEAK WARNING" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
<option name="previewFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PreviewFontScaleMustBeGreaterThanZero" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
<option name="previewFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PreviewMultipleParameterProviders" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
<option name="previewFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PreviewParameterProviderOnFirstParameter" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
<option name="previewFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PreviewPickerAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
<option name="previewFile" value="true" />
|
||||
</inspection_tool>
|
||||
</profile>
|
||||
</component>
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectMigrations">
|
||||
<option name="MigrateToGradleLocalJavaHome">
|
||||
<set>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
</set>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
<project version="4">
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="jbr-21" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||
</component>
|
||||
<component name="ProjectType">
|
||||
<option name="id" value="Android" />
|
||||
</component>
|
||||
</project>
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="RunConfigurationProducerService">
|
||||
<option name="ignoredProducers">
|
||||
<set>
|
||||
<option value="com.intellij.execution.junit.AbstractAllInDirectoryConfigurationProducer" />
|
||||
<option value="com.intellij.execution.junit.AllInPackageConfigurationProducer" />
|
||||
<option value="com.intellij.execution.junit.PatternConfigurationProducer" />
|
||||
<option value="com.intellij.execution.junit.TestInClassConfigurationProducer" />
|
||||
<option value="com.intellij.execution.junit.UniqueIdConfigurationProducer" />
|
||||
<option value="com.intellij.execution.junit.testDiscovery.JUnitTestDiscoveryConfigurationProducer" />
|
||||
<option value="org.jetbrains.kotlin.idea.junit.KotlinJUnitRunConfigurationProducer" />
|
||||
<option value="org.jetbrains.kotlin.idea.junit.KotlinPatternConfigurationProducer" />
|
||||
</set>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
|
|
@ -0,0 +1 @@
|
|||
/build
|
||||
|
|
@ -0,0 +1,155 @@
|
|||
plugins {
|
||||
id("com.android.application")
|
||||
alias(libs.plugins.compose.compiler)
|
||||
kotlin("plugin.serialization") version "2.3.0"
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.img.rabbit"
|
||||
compileSdk = 36
|
||||
|
||||
buildFeatures {
|
||||
compose = true
|
||||
buildConfig = true
|
||||
viewBinding = true
|
||||
}
|
||||
|
||||
|
||||
composeOptions {
|
||||
kotlinCompilerExtensionVersion = "1.4.8"
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "com.img.rabbit"
|
||||
minSdk = 24
|
||||
targetSdk = 36
|
||||
versionCode = 2
|
||||
versionName = "1.1"
|
||||
|
||||
|
||||
setManifestPlaceholders(mapOf(
|
||||
"GETUI_APPID" to (project.findProperty("GETUI_APPID") as? String ?: ""),
|
||||
"GT_INSTALL_CHANNEL" to (project.findProperty("GT_INSTALL_CHANNEL") as? String ?: "GT_INSTALL_CHANNEL")
|
||||
))
|
||||
ndk {
|
||||
abiFilters.addAll(listOf("arm64-v8a", "x86_64"))
|
||||
}
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
|
||||
vectorDrawables {
|
||||
useSupportLibrary = true
|
||||
}
|
||||
multiDexEnabled = true
|
||||
|
||||
resConfigs("en", "zh-rCN")
|
||||
|
||||
flavorDimensions.addAll(listOf("channel"))
|
||||
|
||||
productFlavors {
|
||||
create("general") {
|
||||
dimension = "channel"
|
||||
manifestPlaceholders["UMENG_CHANNEL"] = "general"
|
||||
}
|
||||
}
|
||||
|
||||
productFlavors.configureEach {
|
||||
dimension = "channel"
|
||||
manifestPlaceholders.putAll(mapOf("UMENG_CHANNEL" to name))
|
||||
}
|
||||
manifestPlaceholders.putAll(mapOf(
|
||||
"GETUI_APPID" to "40qbPjPkYs7TnVAYCX0Ig6",
|
||||
"GT_INSTALL_CHANNEL" to "general",
|
||||
))
|
||||
}
|
||||
|
||||
|
||||
// 配置签名信息
|
||||
signingConfigs {
|
||||
create("config") {
|
||||
storeFile = file(project.findProperty("RELEASE_STORE_FILE") ?: "bidinfo.keystore")
|
||||
storePassword = project.findProperty("RELEASE_STORE_PASSWORD") as? String ?: ""
|
||||
keyAlias = project.findProperty("RELEASE_KEY_ALIAS") as? String ?: ""
|
||||
keyPassword = project.findProperty("RELEASE_KEY_PASSWORD") as? String ?: ""
|
||||
enableV1Signing = true
|
||||
enableV2Signing = true
|
||||
enableV3Signing = true
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
getByName("debug") {
|
||||
isMinifyEnabled = false
|
||||
isShrinkResources = false
|
||||
signingConfig = signingConfigs.getByName("config")
|
||||
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
|
||||
}
|
||||
getByName("release") {
|
||||
isMinifyEnabled = false
|
||||
isShrinkResources = false
|
||||
signingConfig = signingConfigs.getByName("config")
|
||||
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// 基础依赖
|
||||
implementation(libs.androidx.core.ktx)
|
||||
implementation(libs.androidx.appcompat)
|
||||
implementation(libs.androidx.material)
|
||||
implementation(libs.androidx.activity)
|
||||
implementation(libs.androidx.constraintlayout)
|
||||
implementation(libs.androidx.core.splashscreen)
|
||||
|
||||
// Compose 依赖
|
||||
implementation(platform(libs.androidx.compose.bom))
|
||||
implementation(libs.androidx.compose.ui)
|
||||
implementation(libs.androidx.compose.ui.tooling)
|
||||
implementation(libs.androidx.compose.ui.tooling.preview)
|
||||
implementation(libs.androidx.compose.material3)
|
||||
implementation(libs.androidx.compose.activity.compose)
|
||||
implementation(libs.androidx.compose.foundation)
|
||||
implementation(libs.androidx.navigation.runtime.ktx)
|
||||
|
||||
// ViewModel 和 Lifecycle 依赖
|
||||
implementation(libs.androidx.compose.runtime.livedata)
|
||||
implementation(libs.androidx.compose.lifecycle.viewmodel)
|
||||
implementation(libs.androidx.compose.lifecycle.runtime.compose)
|
||||
implementation(libs.androidx.compose.runtime.livedata)
|
||||
|
||||
// Kotlinx Serialization 依赖
|
||||
implementation(libs.kotlinx.serialization.json)
|
||||
implementation(libs.androidx.navigation.compose)
|
||||
implementation(libs.androidx.datastore.core)
|
||||
implementation(libs.androidx.datastore.preferences)
|
||||
implementation(libs.androidx.foundation)
|
||||
|
||||
// 测试依赖
|
||||
testImplementation(libs.junit)
|
||||
androidTestImplementation(libs.androidx.test.ext.junit)
|
||||
androidTestImplementation(libs.androidx.test.espresso.core)
|
||||
|
||||
//个推一键认证
|
||||
implementation(libs.gysdk)
|
||||
implementation(libs.gson)
|
||||
implementation(libs.mmkv)
|
||||
implementation(libs.coil.compose)
|
||||
implementation(libs.coil.network.okhttp)
|
||||
implementation(libs.segmentation.selfie)
|
||||
implementation(libs.face.detection)
|
||||
implementation(libs.android.gif.drawable)
|
||||
implementation(libs.gif.encoder)
|
||||
implementation("com.caverock:androidsvg:1.4")
|
||||
implementation("io.github.lucksiege:pictureselector:v3.11.2")
|
||||
// 压缩库 (可选,建议长图拼接前先压缩防止OOM)
|
||||
implementation("io.github.lucksiege:compress:v3.11.2")
|
||||
implementation("io.github.leavesczy:matisse:2.3.0")
|
||||
implementation("com.github.moyuruaizawa:cropify:0.5.2")
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
|
||||
# 友盟号码认证
|
||||
-keep class com.umeng.**{*;}
|
||||
|
||||
-keepclassmembers class*{
|
||||
public <init> (org.json.JSONObject);
|
||||
}
|
||||
|
||||
-keepclassmembers enum*{
|
||||
public static **[] values();
|
||||
public static ** valueOf(java.lang.String);
|
||||
}
|
||||
# 友盟号码认证 结束
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
{
|
||||
"version": 3,
|
||||
"artifactType": {
|
||||
"type": "APK",
|
||||
"kind": "Directory"
|
||||
},
|
||||
"applicationId": "com.img.rabbit",
|
||||
"variantName": "release",
|
||||
"elements": [
|
||||
{
|
||||
"type": "SINGLE",
|
||||
"filters": [],
|
||||
"attributes": [],
|
||||
"versionCode": 2,
|
||||
"versionName": "1.1",
|
||||
"outputFile": "app-release.apk"
|
||||
}
|
||||
],
|
||||
"elementType": "File",
|
||||
"baselineProfiles": [
|
||||
{
|
||||
"minApi": 28,
|
||||
"maxApi": 30,
|
||||
"baselineProfiles": [
|
||||
"baselineProfiles/1/app-release.dm"
|
||||
]
|
||||
},
|
||||
{
|
||||
"minApi": 31,
|
||||
"maxApi": 2147483647,
|
||||
"baselineProfiles": [
|
||||
"baselineProfiles/0/app-release.dm"
|
||||
]
|
||||
}
|
||||
],
|
||||
"minSdkVersionForDexing": 24
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package com.img.rabbit
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ExampleInstrumentedTest {
|
||||
@Test
|
||||
fun useAppContext() {
|
||||
// Context of the app under test.
|
||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
assertEquals("com.img.rabbit", appContext.packageName)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
<!-- 读取相册权限-Android 12及以下版本的权限 -->
|
||||
<uses-feature
|
||||
android:name="android.hardware.camera"
|
||||
android:required="false" />
|
||||
<!--相机权限,用于打开相机和拍照:如用户反馈等-->
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission android:name = "android.permission.WRITE_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="32"
|
||||
tools:ignore="SelectedPhotoAccess" />
|
||||
<uses-permission android:name = "android.permission.READ_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="32"
|
||||
tools:ignore="SelectedPhotoAccess" />
|
||||
<!-- 读取相册图片权限-Android 13及以上版本的权限 -->
|
||||
<uses-permission android:name = "android.permission.READ_MEDIA_IMAGES"
|
||||
tools:ignore="PhotoAndVideoPolicy,SelectedPhotoAccess" />
|
||||
<!--检测联网方式,在网络异常状态避免数据发送,节省流量和电量-->
|
||||
<uses-permission android:name = "android.permission.ACCESS_NETWORK_STATE" />
|
||||
<!--查看WIFI网络状态-->
|
||||
<uses-permission android:name = "android.permission.ACCESS_WIFI_STATE" />
|
||||
<!--网络访问,允许SDK联网和发送数据的最基础权限-->
|
||||
<uses-permission android:name = "android.permission.INTERNET" />
|
||||
<!--切换网络通道-->
|
||||
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/>
|
||||
<!--开关wifi状态,解决国内机型移动网络权限问题需要-->
|
||||
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
|
||||
|
||||
<application
|
||||
android:name=".BaseApplication"
|
||||
android:allowBackup="true"
|
||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||
android:fullBackupContent="@xml/backup_rules"
|
||||
android:icon="@mipmap/ic_launcher_logo"
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_logo"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.rabbit"
|
||||
android:enableOnBackInvokedCallback="true"
|
||||
android:hardwareAccelerated="true"
|
||||
android:requestLegacyExternalStorage="true"
|
||||
android:resizeableActivity="true"
|
||||
android:usesCleartextTraffic="true"
|
||||
android:networkSecurityConfig="@xml/network_security_config"
|
||||
tools:replace="android:allowBackup,android:supportsRtl"
|
||||
tools:targetApi="33">
|
||||
<activity
|
||||
android:name="com.img.rabbit.MainActivity"
|
||||
android:theme="@style/SplashTheme"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="${applicationId}.fileprovider"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true">
|
||||
<meta-data
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/filepath_data" />
|
||||
</provider>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
package com.img.rabbit
|
||||
|
||||
import android.app.Application
|
||||
import android.util.Log
|
||||
import com.img.rabbit.utils.NetworkMonitor
|
||||
import com.g.gysdk.GYManager
|
||||
import com.tencent.mmkv.MMKV
|
||||
|
||||
|
||||
class BaseApplication : Application() {
|
||||
private val TAG = "BaseApplication"
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
// 初始化网络状态监控
|
||||
NetworkMonitor.initialize(this)
|
||||
// 初始化MMKV
|
||||
initMMKV()
|
||||
// 初始化个推SDK
|
||||
initGeTuiOneKeyLogin()
|
||||
}
|
||||
|
||||
private fun initGeTuiOneKeyLogin() {
|
||||
GYManager.getInstance().preInit(this.applicationContext)
|
||||
}
|
||||
|
||||
private fun initMMKV() {
|
||||
val rootDir = MMKV.initialize(this)
|
||||
Log.i(TAG, "MMKV root dir: $rootDir")
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,138 @@
|
|||
package com.img.rabbit
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.compose.animation.core.Animatable
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.wrapContentHeight
|
||||
import androidx.compose.foundation.layout.wrapContentSize
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.scale
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.img.rabbit.pages.LoginScreen
|
||||
import com.img.rabbit.pages.MainScreen
|
||||
import com.img.rabbit.viewmodel.GeneralViewModel
|
||||
import com.img.rabbit.viewmodel.LoginViewModel
|
||||
import com.img.rabbit.viewmodel.SplashViewModel
|
||||
import kotlinx.coroutines.delay
|
||||
|
||||
class MainActivity : ComponentActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
// 必须在 super.onCreate 之前调用
|
||||
val splashScreen = installSplashScreen()
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
// 启用Edge-to-Edge模式(沉浸模式)
|
||||
enableEdgeToEdge()
|
||||
|
||||
setContent {
|
||||
val splashViewModel: SplashViewModel = viewModel()
|
||||
val generalViewModel: GeneralViewModel = viewModel()
|
||||
val loginViewModel: LoginViewModel = viewModel()
|
||||
|
||||
// 设置启动页显示条件
|
||||
splashScreen.setKeepOnScreenCondition {
|
||||
splashViewModel.isLoading.value // 当为 true 时,启动页不消失
|
||||
}
|
||||
|
||||
AppTheme {
|
||||
SplashScreenContent {
|
||||
val token = generalViewModel.kv.decodeString("token")
|
||||
// 未登录,显示登录页
|
||||
if (token?.isNotEmpty() == false && !loginViewModel.isLogin.value) {
|
||||
// 显示登录页
|
||||
LoginScreen(generalViewModel = generalViewModel, loginViewModel = loginViewModel)
|
||||
} else {
|
||||
//已登录,显示主页面
|
||||
MainScreen(generalViewModel = generalViewModel, loginViewModel = loginViewModel)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 模拟加载过程,2秒后关闭启动页
|
||||
LaunchedEffect(Unit) {
|
||||
delay(100L)
|
||||
splashViewModel.setLoading(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Composable
|
||||
fun AppTheme(content: @Composable () -> Unit) {
|
||||
// 使用Material3主题
|
||||
androidx.compose.material3.MaterialTheme {
|
||||
content()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Composable
|
||||
fun SplashScreenContent(
|
||||
onAnimationFinished: @Composable () -> Unit // 改为Composable函数类型
|
||||
) {
|
||||
val scale = remember { Animatable(0f) }
|
||||
var animationFinished by remember { mutableStateOf(false) }
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
scale.animateTo(
|
||||
targetValue = 1f,
|
||||
animationSpec = tween(durationMillis = 800)
|
||||
)
|
||||
animationFinished = true
|
||||
}
|
||||
|
||||
Box(modifier = Modifier.fillMaxSize()) {
|
||||
if (!animationFinished) {
|
||||
// 显示启动页动画
|
||||
Image(
|
||||
painter = painterResource(id = R.mipmap.ic_splash_mask),
|
||||
contentDescription = null,
|
||||
contentScale = ContentScale.FillWidth,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight()
|
||||
.scale(scale.value)
|
||||
)
|
||||
Image(
|
||||
painter = painterResource(id = R.mipmap.ic_splash_logo),
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.align(Alignment.Center)
|
||||
.wrapContentSize()
|
||||
.scale(scale.value)
|
||||
)
|
||||
} else {
|
||||
// 动画完成后显示主界面
|
||||
onAnimationFinished()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
fun MainScreenPreview() {
|
||||
AppTheme {
|
||||
MainScreen(generalViewModel = viewModel(), loginViewModel = viewModel())
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.img.rabbit.bean
|
||||
|
||||
data class ClothingBean(
|
||||
//衣服索引(区分男女)
|
||||
val index: Int,
|
||||
//衣服id(不区分男女类别)
|
||||
val id: Int,
|
||||
val icon: Int,
|
||||
val clothing: Int? =null,
|
||||
val title: String
|
||||
)
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package com.img.rabbit.bean
|
||||
|
||||
data class FormatBean(
|
||||
//格式id
|
||||
val id: Int,
|
||||
val title: String
|
||||
)
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.img.rabbit.bean
|
||||
|
||||
data class HairstyleBean(
|
||||
//发型索引(区分男女)
|
||||
val index: Int,
|
||||
//发型id(不区分男女类别)
|
||||
val id: Int,
|
||||
val icon: Int,
|
||||
val hairstyle: Int? = null,
|
||||
val title: String
|
||||
)
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package com.img.rabbit.bean
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.net.Uri
|
||||
|
||||
data class LongImageBean(
|
||||
val uri: Uri,
|
||||
val bitmap: Bitmap,
|
||||
var cropTop: Float = 0f, // 裁剪起始点(像素)
|
||||
var displayHeight: Float = bitmap.height.toFloat() // 显示高度(像素)
|
||||
)
|
||||
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package com.img.rabbit.bean
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* 预登录数据模型
|
||||
* {"process_id":"982ec252c9bd82d06886fafc8a01b068","operatorType":"2","clienttype":"1","accessCode":"2ed00faebc5ceb5c55d5623e52672ae6e806d2ba3ef0a2018960165154037812","number":"186****0253","expiredTime":1770196189245,"errorCode":0,"errorDesc":"gysdk success!","costTime":3}
|
||||
*/
|
||||
@Serializable
|
||||
data class OnekeyPreLogin(
|
||||
val process_id: String,
|
||||
val operatorType: String,
|
||||
val clienttype: String,
|
||||
val accessCode: String,
|
||||
val number: String,
|
||||
val expiredTime: Long,
|
||||
val errorCode: Int,
|
||||
val errorDesc: String,
|
||||
val costTime: Int
|
||||
)
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package com.img.rabbit.bean
|
||||
|
||||
data class ResizeBean(
|
||||
//尺寸id
|
||||
val id: Int,
|
||||
val size: String,
|
||||
val title: String,
|
||||
val width: Float,
|
||||
val height: Float
|
||||
)
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package com.img.rabbit.bean
|
||||
|
||||
data class UserInfo(
|
||||
val id: Int,
|
||||
val name: String,
|
||||
val login: Boolean,
|
||||
)
|
||||
|
|
@ -0,0 +1,443 @@
|
|||
package com.img.rabbit.components
|
||||
|
||||
import android.Manifest
|
||||
import android.annotation.SuppressLint
|
||||
import android.net.Uri
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.gestures.detectTransformGestures
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.BoxWithConstraints
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.aspectRatio
|
||||
import androidx.compose.foundation.layout.defaultMinSize
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.lazy.LazyRow
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableFloatStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
import androidx.core.content.FileProvider
|
||||
import java.io.File
|
||||
|
||||
// 首先添加ImageUtils的导入
|
||||
import android.graphics.Bitmap.CompressFormat
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import coil3.compose.AsyncImage
|
||||
import com.img.rabbit.R
|
||||
import com.img.rabbit.utils.ImageUtils
|
||||
import kotlin.apply
|
||||
import kotlin.collections.all
|
||||
import kotlin.collections.minus
|
||||
import kotlin.collections.plus
|
||||
import kotlin.collections.toMutableList
|
||||
import kotlin.let
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun ImagePicker(
|
||||
modifier: Modifier = Modifier,
|
||||
imageHeight: Dp = 100.dp,
|
||||
aspectRatio: Float = 100f / 100f,
|
||||
maxCount: Int = 3,
|
||||
addButtonName: String = "添加图片",
|
||||
currentImageUris: List<Uri> = emptyList(),
|
||||
currentImagePaths: List<String> = emptyList(), // 新增参数
|
||||
onImagesUpdated: (uris: List<Uri>, paths: List<String>) -> Unit
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
// 添加临时文件管理
|
||||
val tempImageUri = remember { mutableStateOf<Uri?>(null) }
|
||||
|
||||
// 记录当前操作场景
|
||||
var currentScene by remember { mutableStateOf<PickerScene?>(null) }
|
||||
// 新增选择对话框
|
||||
var showChoiceDialog by remember { mutableStateOf(false) }
|
||||
|
||||
// 新增大图预览状态
|
||||
var previewImageUri by remember { mutableStateOf<Uri?>(null) }
|
||||
|
||||
// 图片路径转Uri的辅助函数
|
||||
fun getDisplayImages(): List<Uri> {
|
||||
return currentImageUris.ifEmpty {
|
||||
currentImagePaths.mapNotNull { path ->
|
||||
try {
|
||||
Uri.fromFile(File(path))
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
val launcher = rememberLauncherForActivityResult(
|
||||
contract = ActivityResultContracts.StartActivityForResult()
|
||||
) { result ->
|
||||
result.data?.data?.let { uri ->
|
||||
if (currentImages.size < maxCount) {
|
||||
onImagesUpdated(currentImages + uri)
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
// 相册选择逻辑
|
||||
val galleryLauncher = rememberLauncherForActivityResult(
|
||||
contract = ActivityResultContracts.GetContent()
|
||||
) { uri ->
|
||||
uri?.let {
|
||||
if (currentImageUris.size < maxCount) {
|
||||
// 保存图片到存储并获取路径
|
||||
val savedPath = ImageUtils.saveUriToStorage(
|
||||
context = context,
|
||||
uri = it,
|
||||
format = CompressFormat.JPEG,
|
||||
quality = 90
|
||||
)
|
||||
// 更新图片列表和路径列表
|
||||
onImagesUpdated(
|
||||
currentImageUris + it,
|
||||
if (savedPath != null) currentImagePaths + savedPath else currentImagePaths
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 拍照逻辑
|
||||
val cameraLauncher = rememberLauncherForActivityResult(
|
||||
ActivityResultContracts.TakePicture()
|
||||
) { success ->
|
||||
if (success) {
|
||||
tempImageUri.value?.let { uri ->
|
||||
if (currentImageUris.size < maxCount) {
|
||||
// 保存图片到存储并获取路径
|
||||
val savedPath = ImageUtils.saveUriToStorage(
|
||||
context = context,
|
||||
uri = uri,
|
||||
format = CompressFormat.JPEG,
|
||||
quality = 90
|
||||
)
|
||||
// 更新图片列表和路径列表
|
||||
onImagesUpdated(
|
||||
currentImageUris + uri,
|
||||
if (savedPath != null) currentImagePaths + savedPath else currentImagePaths
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 权限检查
|
||||
val permissionLauncher = rememberLauncherForActivityResult(
|
||||
ActivityResultContracts.RequestMultiplePermissions()
|
||||
) { permissions ->
|
||||
// 修改权限回调逻辑
|
||||
if (permissions.all { it.value }) {
|
||||
when (currentScene) {
|
||||
PickerScene.GALLERY -> galleryLauncher.launch("image/*")
|
||||
PickerScene.CAMERA -> {
|
||||
val uri = FileProvider.getUriForFile(
|
||||
context,
|
||||
"${context.packageName}.fileprovider",
|
||||
File.createTempFile("IMG_", ".jpg", context.externalCacheDir)
|
||||
)
|
||||
tempImageUri.value = uri
|
||||
cameraLauncher.launch(uri)
|
||||
}
|
||||
null -> { /* 无操作 */ }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (showChoiceDialog) {
|
||||
AlertDialog(
|
||||
onDismissRequest = { showChoiceDialog = false },
|
||||
title = { Text(text = "选择图片来源", fontSize = 14.sp) },
|
||||
text = { Text(text = "请选择拍照或从相册选取, 若无权限点击相册或者拍照后会弹出权限请求", fontSize = 12.sp) },
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
onClick = {
|
||||
showChoiceDialog = false
|
||||
|
||||
currentScene = PickerScene.CAMERA
|
||||
permissionLauncher.launch(
|
||||
arrayOf(
|
||||
Manifest.permission.CAMERA,
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||
)
|
||||
)
|
||||
}
|
||||
) { Text(text = "拍照", fontSize = 14.sp) }
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(
|
||||
onClick = {
|
||||
showChoiceDialog = false
|
||||
|
||||
currentScene = PickerScene.GALLERY
|
||||
permissionLauncher.launch(
|
||||
arrayOf(
|
||||
Manifest.permission.READ_EXTERNAL_STORAGE
|
||||
)
|
||||
)
|
||||
}
|
||||
) { Text(text = "相册", fontSize = 14.sp) }
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// 全屏大图对话框
|
||||
previewImageUri?.let { uri ->
|
||||
FullScreenDialog(
|
||||
onDismiss = { previewImageUri = null }
|
||||
) {
|
||||
ZoomableImage(
|
||||
uri = uri,
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
LazyRow(modifier = modifier.defaultMinSize(minHeight = (imageHeight + 8.dp))) {
|
||||
items(getDisplayImages()) { uri ->
|
||||
Box(
|
||||
Modifier.padding(end = 6.dp, top = 6.dp).background(color = Color(0x80E5E5E7), shape = RoundedCornerShape(8.dp))
|
||||
) {
|
||||
AsyncImage(
|
||||
model = uri,
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.height(imageHeight)
|
||||
.aspectRatio(aspectRatio)
|
||||
.clip(RoundedCornerShape(8.dp))
|
||||
.clickable(
|
||||
indication = null,
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
) {
|
||||
previewImageUri = uri
|
||||
}
|
||||
)
|
||||
val size = if(imageHeight < 150.dp){
|
||||
24.dp
|
||||
}else{
|
||||
32.dp
|
||||
}
|
||||
Image(
|
||||
painter = painterResource(id = R.mipmap.ic_picture_del),
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.size(size)
|
||||
.aspectRatio(1f) // 设置宽高比为1:1
|
||||
.align(Alignment.TopEnd)
|
||||
.padding(top = 4.dp, end = 4.dp)
|
||||
.clickable(
|
||||
indication = null,
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
){
|
||||
// 找到当前uri在列表中的索引,同时从paths列表中移除对应的路径
|
||||
val uriIndex = currentImageUris.indexOf(uri)
|
||||
val newPaths = if (uriIndex >= 0 && uriIndex < currentImagePaths.size) {
|
||||
currentImagePaths.toMutableList().apply { removeAt(uriIndex) }
|
||||
} else {
|
||||
// 如果uri不在currentImageUris中,可能是从currentImagePaths转换过来的
|
||||
// 尝试通过路径匹配找到对应的索引
|
||||
val pathIndex = currentImagePaths.indexOf(uri.path)
|
||||
if (pathIndex >= 0) {
|
||||
currentImagePaths.toMutableList().apply { removeAt(pathIndex) }
|
||||
} else {
|
||||
currentImagePaths
|
||||
}
|
||||
}
|
||||
onImagesUpdated(currentImageUris - uri, newPaths)
|
||||
}
|
||||
)
|
||||
/*
|
||||
IconButton(
|
||||
onClick = {
|
||||
// 找到当前uri在列表中的索引,同时从paths列表中移除对应的路径
|
||||
val uriIndex = currentImageUris.indexOf(uri)
|
||||
val newPaths = if (uriIndex >= 0 && uriIndex < currentImagePaths.size) {
|
||||
currentImagePaths.toMutableList().apply { removeAt(uriIndex) }
|
||||
} else {
|
||||
// 如果uri不在currentImageUris中,可能是从currentImagePaths转换过来的
|
||||
// 尝试通过路径匹配找到对应的索引
|
||||
val pathIndex = currentImagePaths.indexOf(uri.path)
|
||||
if (pathIndex >= 0) {
|
||||
currentImagePaths.toMutableList().apply { removeAt(pathIndex) }
|
||||
} else {
|
||||
currentImagePaths
|
||||
}
|
||||
}
|
||||
onImagesUpdated(currentImageUris - uri, newPaths)
|
||||
},
|
||||
modifier = Modifier.align(Alignment.TopEnd)
|
||||
) {
|
||||
Icon(Icons.Default.Close, "删除")
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
item {
|
||||
if (currentImageUris.size < maxCount && currentImagePaths.size < maxCount) {
|
||||
// 在触发按钮中添加
|
||||
if(maxCount==1){
|
||||
AddSingleButton(
|
||||
imageHeight = imageHeight,
|
||||
aspectRatio = aspectRatio,
|
||||
onClick = { showChoiceDialog = true },
|
||||
label = addButtonName,
|
||||
)
|
||||
}else{
|
||||
Box(
|
||||
Modifier.defaultMinSize(minHeight = (imageHeight + 8.dp)),
|
||||
contentAlignment = Alignment.BottomCenter
|
||||
) {
|
||||
AddButton(
|
||||
onClick = { showChoiceDialog = true },
|
||||
label = addButtonName,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AddButton(
|
||||
onClick: () -> Unit,
|
||||
label: String = "添加图片",
|
||||
imageHeight: Dp = 100.dp,
|
||||
aspectRatio: Float = 100f / 100f
|
||||
) {
|
||||
TextButton(
|
||||
onClick = onClick,
|
||||
modifier = Modifier
|
||||
.height(imageHeight)
|
||||
.aspectRatio(aspectRatio)
|
||||
.border(1.dp, Color(0xFFA5A5A5), RoundedCornerShape(8.dp))
|
||||
) {
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
Icon(Icons.Default.Add, contentDescription = label)
|
||||
Text(text = label)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AddSingleButton(
|
||||
imageHeight: Dp = 100.dp,
|
||||
aspectRatio: Float = 100f / 100f,
|
||||
onClick: () -> Unit,
|
||||
label: String = "添加图片"
|
||||
) {
|
||||
TextButton(
|
||||
onClick = onClick,
|
||||
modifier = Modifier
|
||||
.height(imageHeight + 8.dp)
|
||||
.aspectRatio(aspectRatio)
|
||||
.border(1.dp, Color(0xFFA5A5A5), RoundedCornerShape(8.dp))
|
||||
) {
|
||||
if(label == "上传头像"){
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center) {
|
||||
Text(text = label)
|
||||
}
|
||||
}else{
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.Center) {
|
||||
Icon(Icons.Default.Add, contentDescription = label)
|
||||
Text(text = label)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Composable
|
||||
private fun FullScreenDialog(
|
||||
onDismiss: () -> Unit,
|
||||
content: @Composable () -> Unit
|
||||
) {
|
||||
Dialog(
|
||||
onDismissRequest = onDismiss,
|
||||
properties = DialogProperties(
|
||||
usePlatformDefaultWidth = false,
|
||||
decorFitsSystemWindows = false
|
||||
)
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(Color.Black.copy(alpha = 0.95f))
|
||||
.clickable(onClick = onDismiss)
|
||||
) {
|
||||
content()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("UnusedBoxWithConstraintsScope", "AutoboxingStateValueProperty")
|
||||
@Composable
|
||||
private fun ZoomableImage(uri: Uri, modifier: Modifier = Modifier) {
|
||||
val scale = remember { mutableFloatStateOf(1f) }
|
||||
val offset = remember { mutableStateOf(Offset.Zero) }
|
||||
|
||||
BoxWithConstraints(modifier = modifier.pointerInput(Unit) {
|
||||
detectTransformGestures { _, pan, zoom, _ ->
|
||||
scale.floatValue *= zoom
|
||||
offset.value += pan
|
||||
}
|
||||
}) {
|
||||
AsyncImage(
|
||||
model = uri,
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.graphicsLayer {
|
||||
scaleX = scale.value
|
||||
scaleY = scale.value
|
||||
translationX = offset.value.x
|
||||
translationY = offset.value.y
|
||||
}
|
||||
.align(Alignment.Center)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 添加场景枚举
|
||||
enum class PickerScene { GALLERY, CAMERA }
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
package com.img.rabbit.config
|
||||
|
||||
object Common {
|
||||
const val privacyUrl = "https://www.baidu.com"
|
||||
const val agreementUrl = "https://www.baidu.com"
|
||||
}
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
package com.img.rabbit.config
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import com.img.rabbit.R
|
||||
import com.img.rabbit.bean.ClothingBean
|
||||
import com.img.rabbit.bean.FormatBean
|
||||
import com.img.rabbit.bean.HairstyleBean
|
||||
import com.img.rabbit.bean.ResizeBean
|
||||
|
||||
object CommonData {
|
||||
//背景颜色
|
||||
val colors = listOf(
|
||||
Color.Transparent, // 第一个:带边框的透明
|
||||
Color.Red, // 第二个:红色
|
||||
Color(0xFFC62828), // 第三个:深红色
|
||||
Color(0xFFB71C1C), // 第四个:暗红色
|
||||
Color(0xFF2196F3), // 第五个:蓝色
|
||||
Color(0xFF1565C0), // 第六个:深蓝色
|
||||
Color(0xFF0277BD), // 第七个:亮蓝色
|
||||
Color.Transparent // 最后一个:带边框的透明
|
||||
)
|
||||
// 女装
|
||||
val clothingForFemales = mutableListOf<ClothingBean>().apply {
|
||||
add(ClothingBean(index = 0, id = 0, icon = R.mipmap.ic_cutout_color_less, clothing = null,title = "无"))
|
||||
add(ClothingBean(index = 1, id = 1, icon = R.mipmap.ic_clothing_female_1, clothing = R.mipmap.ic_clothing_female_1, title = "青春"))
|
||||
add(ClothingBean(index = 2, id = 2, icon = R.mipmap.ic_clothing_female_2, clothing = R.mipmap.ic_clothing_female_2, title = "学生"))
|
||||
add(ClothingBean(index = 3, id = 3, icon = R.mipmap.ic_clothing_female_3, clothing = R.mipmap.ic_clothing_female_3, title = "职场"))
|
||||
add(ClothingBean(index = 4, id = 4, icon = R.mipmap.ic_clothing_female_4, clothing = R.mipmap.ic_clothing_female_4, title = "休闲"))
|
||||
add(ClothingBean(index = 5, id = 5, icon = R.mipmap.ic_clothing_female_5, clothing = R.mipmap.ic_clothing_female_5, title = "日韩"))
|
||||
add(ClothingBean(index = 6, id = 6, icon = R.mipmap.ic_clothing_female_6, clothing = R.mipmap.ic_clothing_female_6, title = "艺术"))
|
||||
}
|
||||
|
||||
// 男装
|
||||
val clothingForMans = mutableListOf<ClothingBean>().apply {
|
||||
add(ClothingBean(index = 0, id = 7, icon = R.mipmap.ic_cutout_color_less, clothing = null, title = "无"))
|
||||
add(ClothingBean(index = 1, id = 8, icon = R.mipmap.ic_clothing_man_2, clothing = R.mipmap.ic_clothing_man_2, title = "青春"))
|
||||
add(ClothingBean(index = 2, id = 9, icon = R.mipmap.ic_clothing_man_3, clothing = R.mipmap.ic_clothing_man_3, title = "学生"))
|
||||
add(ClothingBean(index = 3, id = 10, icon = R.mipmap.ic_clothing_man_4, clothing = R.mipmap.ic_clothing_man_4, title = "职场"))
|
||||
add(ClothingBean(index = 4, id = 11, icon = R.mipmap.ic_clothing_man_5, clothing = R.mipmap.ic_clothing_man_5, title = "休闲"))
|
||||
add(ClothingBean(index = 5, id = 12, icon = R.mipmap.ic_clothing_man_6, clothing = R.mipmap.ic_clothing_man_6, title = "日韩"))
|
||||
add(ClothingBean(index = 6, id = 13, icon = R.mipmap.ic_clothing_man_7, clothing = R.mipmap.ic_clothing_man_7, title = "艺术"))
|
||||
}
|
||||
|
||||
|
||||
// 定义发型列表-女生
|
||||
val hairstyleForFemales = mutableListOf<HairstyleBean>().apply {
|
||||
add(HairstyleBean(index = 0, id = 0, icon = R.mipmap.ic_cutout_color_less, hairstyle = null, title = "无"))
|
||||
add(HairstyleBean(index = 1, id = 1, icon = R.mipmap.ic_hairstyle_female_1, hairstyle = R.mipmap.ic_hairstyle_female_1, title = "青春"))
|
||||
add(HairstyleBean(index = 2, id = 2, icon = R.mipmap.ic_hairstyle_female_2, hairstyle = R.mipmap.ic_hairstyle_female_2, title = "学生"))
|
||||
add(HairstyleBean(index = 3, id = 3, icon = R.mipmap.ic_hairstyle_female_3, hairstyle = R.mipmap.ic_hairstyle_female_3, title = "职场"))
|
||||
add(HairstyleBean(index = 4, id = 4, icon = R.mipmap.ic_hairstyle_female_4, hairstyle = R.mipmap.ic_hairstyle_female_4, title = "休闲"))
|
||||
add(HairstyleBean(index = 5, id = 5, icon = R.mipmap.ic_hairstyle_female_5, hairstyle = R.mipmap.ic_hairstyle_female_5, title = "日韩"))
|
||||
add(HairstyleBean(index = 6, id = 6, icon = R.mipmap.ic_hairstyle_female_6, hairstyle = R.mipmap.ic_hairstyle_female_6, title = "艺术"))
|
||||
}
|
||||
|
||||
// 发型(男)
|
||||
// 定义发型列表-男生
|
||||
val hairstyleForMans = mutableListOf<HairstyleBean>().apply {
|
||||
add(HairstyleBean(index = 0, id = 7, icon = R.mipmap.ic_cutout_color_less, hairstyle = null, title = "无"))
|
||||
add(HairstyleBean(index = 1, id = 8, icon = R.mipmap.ic_hairstyle_man_1, hairstyle = R.mipmap.ic_hairstyle_man_1, title = "青春"))
|
||||
add(HairstyleBean(index = 2, id = 9, icon = R.mipmap.ic_hairstyle_man_2, hairstyle = R.mipmap.ic_hairstyle_man_2, title = "学生"))
|
||||
add(HairstyleBean(index = 3, id = 10, icon = R.mipmap.ic_hairstyle_man_3, hairstyle = R.mipmap.ic_hairstyle_man_3, title = "职场"))
|
||||
add(HairstyleBean(index = 4, id = 11, icon = R.mipmap.ic_hairstyle_man_4, hairstyle = R.mipmap.ic_hairstyle_man_4, title = "休闲"))
|
||||
add(HairstyleBean(index = 5, id = 12, icon = R.mipmap.ic_hairstyle_man_5, hairstyle = R.mipmap.ic_hairstyle_man_5, title = "日韩"))
|
||||
add(HairstyleBean(index = 6, id = 13, icon = R.mipmap.ic_hairstyle_man_6, hairstyle = R.mipmap.ic_hairstyle_man_6, title = "艺术"))
|
||||
}
|
||||
|
||||
// 定义尺寸列表
|
||||
val resizes = mutableListOf<ResizeBean>().apply {
|
||||
add(ResizeBean(id = 0, size = "23x35mm", title = "标准一寸", width = 25f, height = 35f))
|
||||
add(ResizeBean(id = 1, size = "22x23mm", title = "小一寸", width = 22f, height = 32f))
|
||||
add(ResizeBean(id = 2, size = "35x53mm", title = "标准二寸", width = 35f, height = 53f))
|
||||
add(ResizeBean(id = 3, size = "33x48mm", title = "小二寸", width = 33f, height = 48f))
|
||||
add(ResizeBean(id = 4, size = "26x32mm", title = "身份证", width = 26f, height = 32f))
|
||||
add(ResizeBean(id = 5, size = "33x48mm", title = "护照", width = 33f, height = 48f))
|
||||
add(ResizeBean(id = 6, size = "22x32mm", title = "驾驶证", width = 22f, height = 32f))
|
||||
add(ResizeBean(id = 7, size = "26x32mm", title = "社保卡", width = 26f, height = 32f))
|
||||
add(ResizeBean(id = 8, size = "26x32mm", title = "四六级", width = 26f, height = 32f))
|
||||
add(ResizeBean(id = 9, size = "33x48mm", title = "司法考试", width = 33f, height = 48f))
|
||||
add(ResizeBean(id = 10, size = "40x60mm", title = "结婚证", width = 40f, height = 60f))
|
||||
add(ResizeBean(id = 11, size = "35x45mm", title = "中国签证", width = 35f, height = 45f))
|
||||
}
|
||||
|
||||
// 定义尺寸列表(与您提供的示例完全匹配)
|
||||
val formats = mutableListOf<FormatBean>().apply {
|
||||
add(FormatBean(id = 0, title = "JPG"))
|
||||
add(FormatBean(id = 1, title = "PNG"))
|
||||
add(FormatBean(id = 2, title = "GIF"))
|
||||
add(FormatBean(id = 3, title = "SVG"))
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,217 @@
|
|||
package com.img.rabbit.pages
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.util.Log
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import com.img.rabbit.pages.screen.mine.FeedbackScreen
|
||||
import com.img.rabbit.pages.screen.mine.OnlineServiceScreen
|
||||
import com.img.rabbit.pages.screen.mine.SettingScreen
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.NavigationBar
|
||||
import androidx.compose.material3.NavigationBarItem
|
||||
import androidx.compose.material3.NavigationBarItemDefaults
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.img.rabbit.R
|
||||
import com.img.rabbit.pages.screen.HomeScreen
|
||||
import com.img.rabbit.pages.screen.MineScreen
|
||||
import com.img.rabbit.pages.screen.make.CutoutScreen
|
||||
import com.img.rabbit.pages.screen.make.FormatScreen
|
||||
import com.img.rabbit.pages.screen.make.LongImageScreen
|
||||
import com.img.rabbit.pages.screen.make.ResizeScreen
|
||||
import com.img.rabbit.pages.screen.mine.setting.AboutScreen
|
||||
import com.img.rabbit.pages.screen.mine.setting.AccountBindScreen
|
||||
import com.img.rabbit.pages.screen.mine.setting.AccountManagerScreen
|
||||
import com.img.rabbit.pages.screen.other.CameraGuideScreen
|
||||
import com.img.rabbit.route.ScreenRoute
|
||||
import com.img.rabbit.viewmodel.GeneralViewModel
|
||||
import com.img.rabbit.viewmodel.LoginViewModel
|
||||
|
||||
// 定义底部导航的标签页
|
||||
sealed class TabItem(val title: String, val normalIconRes: Int, val selectedIconRes: Int, val normalColor: Color, val selectedColor: Color) {
|
||||
object Home : TabItem("首页", R.mipmap.ic_home_normal, R.mipmap.ic_home_selected, Color(0xFFAAAAAA), Color(0xFF1A1A1A))
|
||||
object Mine : TabItem("我的", R.mipmap.ic_mine_normal, R.mipmap.ic_mine_selected, Color(0xFFAAAAAA), Color(0xFF1A1A1A))
|
||||
}
|
||||
|
||||
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
|
||||
@Composable
|
||||
fun MainScreen(generalViewModel: GeneralViewModel, loginViewModel: LoginViewModel) {
|
||||
val navController = rememberNavController()
|
||||
val networkStatus by generalViewModel.networkStatus.observeAsState(initial = true)
|
||||
val isNavigationBarVisible by generalViewModel.isNavigationBarVisible.observeAsState(initial = true)
|
||||
val tabItems = listOf(
|
||||
TabItem.Home,
|
||||
TabItem.Mine
|
||||
)
|
||||
var selectedTab: TabItem by remember { mutableStateOf(TabItem.Home) }
|
||||
|
||||
// 监听返回事件
|
||||
val currentBackStackEntry = navController.currentBackStackEntry
|
||||
|
||||
LaunchedEffect(currentBackStackEntry) {
|
||||
// 当返回到MineScreen页面时执行的操作
|
||||
if (currentBackStackEntry?.destination?.route == "home") {
|
||||
// 显示TabBar
|
||||
generalViewModel.setNavigationBarVisible(true)
|
||||
}
|
||||
}
|
||||
|
||||
// 网络状态监听
|
||||
LaunchedEffect(networkStatus) {
|
||||
if (!networkStatus) {
|
||||
// 网络断开时的处理
|
||||
Log.w("NetworkStatus","网络断开")
|
||||
}else{
|
||||
Log.w("NetworkStatus","网络已连接")
|
||||
}
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
bottomBar = {
|
||||
if (isNavigationBarVisible) {
|
||||
Box {
|
||||
NavigationBar(
|
||||
containerColor = Color.White,
|
||||
contentColor = Color.Transparent
|
||||
) {
|
||||
tabItems.forEachIndexed { index, item ->
|
||||
val iconRes = if (selectedTab == tabItems[index]) {
|
||||
item.selectedIconRes
|
||||
} else {
|
||||
item.normalIconRes
|
||||
}
|
||||
NavigationBarItem(
|
||||
icon = {
|
||||
Icon(
|
||||
painter = painterResource(id = iconRes),
|
||||
contentDescription = item.title,
|
||||
tint = Color.Unspecified
|
||||
)
|
||||
},
|
||||
label = { Text(item.title, color = if (selectedTab == tabItems[index]) item.selectedColor else item.normalColor) },
|
||||
selected = selectedTab == tabItems[index],
|
||||
onClick = { selectedTab = tabItems[index] },
|
||||
colors = NavigationBarItemDefaults.colors(
|
||||
indicatorColor = Color.Transparent
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
// 顶部横线
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(1.dp)
|
||||
.background(Color.Gray.copy(alpha = 0.3f))
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
// 导航主机
|
||||
NavHost(
|
||||
navController = navController,
|
||||
startDestination = ScreenRoute.Home.route
|
||||
) {
|
||||
// Tab页面
|
||||
composable(ScreenRoute.Home.route) {
|
||||
HomeScreen(
|
||||
navController = navController,
|
||||
generalViewModel = generalViewModel
|
||||
)
|
||||
}
|
||||
composable(ScreenRoute.Mine.route) {
|
||||
MineScreen(
|
||||
navController = navController,
|
||||
generalViewModel = generalViewModel
|
||||
)
|
||||
}
|
||||
// 抠图页面(Cutout)
|
||||
composable(ScreenRoute.Cutout.route) {
|
||||
CutoutScreen(navController = navController)
|
||||
}
|
||||
// 证件页面(Certificate)
|
||||
composable(ScreenRoute.Resize.route) {
|
||||
ResizeScreen(navController = navController)
|
||||
}
|
||||
// 格式页面(Format)
|
||||
composable(ScreenRoute.Format.route) {
|
||||
FormatScreen(navController = navController)
|
||||
}
|
||||
// 拍照指南页面(CameraGuide)
|
||||
composable(ScreenRoute.CameraGuide.route) {
|
||||
CameraGuideScreen(navController = navController)
|
||||
}
|
||||
// 长图页面(LongImage)
|
||||
composable(ScreenRoute.LongImage.route) {
|
||||
LongImageScreen(navController = navController)
|
||||
}
|
||||
|
||||
|
||||
// 我的页面(Mine)
|
||||
composable(ScreenRoute.Feedback.route) {
|
||||
FeedbackScreen(navController = navController)
|
||||
}
|
||||
composable(ScreenRoute.OnlineService.route) {
|
||||
OnlineServiceScreen(navController = navController)
|
||||
}
|
||||
composable(ScreenRoute.Setting.route) {
|
||||
SettingScreen(navController = navController)
|
||||
}
|
||||
|
||||
// 设置页面(Setting)
|
||||
composable(ScreenRoute.BindAccount.route) {
|
||||
AccountBindScreen(navController = navController)
|
||||
}
|
||||
composable(ScreenRoute.ManagerAccount.route) {
|
||||
AccountManagerScreen(navController = navController)
|
||||
}
|
||||
composable(ScreenRoute.AboutMine.route) {
|
||||
AboutScreen(navController = navController)
|
||||
}
|
||||
|
||||
// 登录页面(Login)
|
||||
composable(ScreenRoute.Login.route) {
|
||||
LoginScreen(
|
||||
navController = navController,
|
||||
generalViewModel = generalViewModel,
|
||||
loginViewModel = loginViewModel
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 根据选中的Tab切换导航路由
|
||||
LaunchedEffect(selectedTab) {
|
||||
when (selectedTab) {
|
||||
TabItem.Home -> navController.navigate(ScreenRoute.Home.route) {
|
||||
popUpTo(ScreenRoute.Home.route) { inclusive = true }
|
||||
}
|
||||
TabItem.Mine -> navController.navigate(ScreenRoute.Mine.route) {
|
||||
popUpTo(ScreenRoute.Mine.route) { inclusive = true }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,147 @@
|
|||
package com.img.rabbit.pages
|
||||
|
||||
import androidx.compose.animation.core.Animatable
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.wrapContentHeight
|
||||
import androidx.compose.foundation.layout.wrapContentWidth
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.img.rabbit.R
|
||||
import com.img.rabbit.utils.NetworkStatus
|
||||
|
||||
@Composable
|
||||
fun NetworkDisconnectedPage(onNetworkStatus: (Boolean) -> Unit) {
|
||||
val context = LocalContext.current
|
||||
val scale = remember { Animatable(0f) }
|
||||
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize().background(color = Color.White)
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(id = R.mipmap.ic_normal_top_mask),
|
||||
contentDescription = null,
|
||||
contentScale = ContentScale.FillWidth,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight()
|
||||
)
|
||||
|
||||
/*
|
||||
// 返回按钮
|
||||
Image(
|
||||
painter = painterResource(id = R.mipmap.ic_back),
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.padding(top = 56.dp, start = 16.dp)
|
||||
.wrapContentSize()
|
||||
.clickable {
|
||||
// 点击返回按钮,返回上一页
|
||||
(context as MainActivity).onBackPressed()
|
||||
//navController.popBackStack()
|
||||
}
|
||||
)
|
||||
*/
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
) {
|
||||
|
||||
Image(
|
||||
painter = painterResource(id = R.mipmap.ic_network_disconnected),
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight()
|
||||
.padding(top = 253.dp)
|
||||
)
|
||||
Text(
|
||||
text = "网络连接断开,请检查网络设置",
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
color = Color(0xFF1A1A1A),
|
||||
modifier = Modifier
|
||||
.wrapContentWidth()
|
||||
.wrapContentHeight()
|
||||
.align(Alignment.CenterHorizontally)
|
||||
.padding(top = 23.dp)
|
||||
)
|
||||
|
||||
Text(
|
||||
text = "刷新页面",
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
color = Color(0xFFAAAAAA),
|
||||
modifier = Modifier
|
||||
.wrapContentWidth()
|
||||
.wrapContentHeight()
|
||||
.align(Alignment.CenterHorizontally)
|
||||
.padding(top = 57.dp)
|
||||
.clickable(
|
||||
indication = null,
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
) {
|
||||
// 点击刷新按钮,刷新页面
|
||||
onNetworkStatus(NetworkStatus.isNetworkAvailable(context))
|
||||
}
|
||||
)
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight()
|
||||
.padding(horizontal = 71.dp)
|
||||
.align(Alignment.CenterHorizontally)
|
||||
.padding(top = 11.dp)
|
||||
.background(color = Color(0xFF252525), shape = RoundedCornerShape(359.dp))
|
||||
.clickable(
|
||||
indication = null,
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
) {
|
||||
// 点击开启网络权限按钮,跳转至系统设置页面
|
||||
NetworkStatus.openNetworkSettings(context)
|
||||
}
|
||||
){
|
||||
Text(
|
||||
text = "开启网络权限",
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = Color(0xFFC2FF43),
|
||||
modifier = Modifier
|
||||
.wrapContentWidth()
|
||||
.wrapContentHeight()
|
||||
.align(Alignment.CenterHorizontally)
|
||||
.padding(top = 12.dp, bottom = 12.dp)
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun PreviewNetworkDisconnectedPage() {
|
||||
NetworkDisconnectedPage(onNetworkStatus = {})
|
||||
}
|
||||
|
|
@ -0,0 +1,883 @@
|
|||
package com.img.rabbit.pages.screen
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.offset
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.layout.wrapContentHeight
|
||||
import androidx.compose.foundation.layout.wrapContentSize
|
||||
import androidx.compose.foundation.layout.wrapContentWidth
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import com.img.rabbit.R
|
||||
import com.img.rabbit.route.ScreenRoute
|
||||
import com.img.rabbit.viewmodel.GeneralViewModel
|
||||
|
||||
@Composable
|
||||
fun HomeScreen(navController: NavHostController,generalViewModel: GeneralViewModel) {
|
||||
val context = LocalContext.current
|
||||
val scrollState = rememberScrollState()
|
||||
|
||||
// 监听返回事件
|
||||
val currentBackStackEntry = navController.currentBackStackEntry
|
||||
|
||||
LaunchedEffect(currentBackStackEntry) {
|
||||
// 当返回到MineScreen页面时执行的操作
|
||||
if (currentBackStackEntry?.destination?.route == "home") {
|
||||
// 显示TabBar
|
||||
generalViewModel.setNavigationBarVisible(true)
|
||||
}
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
){
|
||||
Image(
|
||||
painter = painterResource(id = R.mipmap.ic_home_top_mask),
|
||||
contentDescription = null,
|
||||
contentScale = ContentScale.FillWidth,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
Column (
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight()
|
||||
.offset(y = (-17).dp)
|
||||
.padding(bottom = 56.dp)
|
||||
.background(
|
||||
color = Color.White,
|
||||
shape = RoundedCornerShape(topStart = 18.dp, topEnd = 18.dp)
|
||||
)
|
||||
.verticalScroll(scrollState)
|
||||
){
|
||||
//选尺寸制作
|
||||
Image(
|
||||
painter = painterResource(id = R.mipmap.ic_home_title_1_size),
|
||||
contentDescription = null,
|
||||
contentScale = ContentScale.Crop,
|
||||
modifier = Modifier
|
||||
.wrapContentWidth()
|
||||
.padding(start = 16.dp, end = 16.dp, top = 12.dp)
|
||||
)
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(start = 16.dp, end = 16.dp, top = 12.dp)
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.wrapContentWidth()
|
||||
.weight(1f)
|
||||
.clickable(
|
||||
indication = null,
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
) {
|
||||
// 隐藏TabBar
|
||||
generalViewModel.setNavigationBarVisible(false)
|
||||
// 跳转抠图页面
|
||||
navController.apply {
|
||||
currentBackStackEntry?.savedStateHandle?.set("width", 25f)
|
||||
currentBackStackEntry?.savedStateHandle?.set("height", 35f)
|
||||
navigate(ScreenRoute.Cutout.route)
|
||||
}
|
||||
}
|
||||
){
|
||||
Image(
|
||||
painter = painterResource(id = R.mipmap.ic_home_size_bg),
|
||||
contentDescription = null,
|
||||
)
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.wrapContentSize()
|
||||
.padding(start = 8.dp)
|
||||
.align(Alignment.Center)
|
||||
) {
|
||||
Text(
|
||||
text = "标准一寸",
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = Color(0xFF1A1A1A),
|
||||
modifier = Modifier.wrapContentSize()
|
||||
)
|
||||
Text(
|
||||
text = "25x35mm",
|
||||
fontSize = 10.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
color = Color(0xFF767676),
|
||||
modifier = Modifier.wrapContentSize()
|
||||
)
|
||||
}
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.width(10.dp)
|
||||
)
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.wrapContentWidth()
|
||||
.weight(1f)
|
||||
.clickable(
|
||||
indication = null,
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
) {
|
||||
// 隐藏TabBar
|
||||
generalViewModel.setNavigationBarVisible(false)
|
||||
// 跳转抠图页面
|
||||
navController.apply {
|
||||
currentBackStackEntry?.savedStateHandle?.set("width", 22f)
|
||||
currentBackStackEntry?.savedStateHandle?.set("height", 32f)
|
||||
navigate(ScreenRoute.Cutout.route)
|
||||
}
|
||||
}
|
||||
){
|
||||
Image(
|
||||
painter = painterResource(id = R.mipmap.ic_home_size_bg),
|
||||
contentDescription = null,
|
||||
)
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.wrapContentSize()
|
||||
.padding(start = 8.dp)
|
||||
.align(Alignment.Center)
|
||||
) {
|
||||
Text(
|
||||
text = "小一寸",
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = Color(0xFF1A1A1A),
|
||||
modifier = Modifier.wrapContentSize()
|
||||
)
|
||||
Text(
|
||||
text = "22x32mm",
|
||||
fontSize = 10.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
color = Color(0xFF767676),
|
||||
modifier = Modifier.wrapContentSize()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(start = 16.dp, end = 16.dp, top = 12.dp)
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.wrapContentWidth()
|
||||
.weight(1f)
|
||||
.clickable(
|
||||
indication = null,
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
) {
|
||||
// 隐藏TabBar
|
||||
generalViewModel.setNavigationBarVisible(false)
|
||||
// 跳转抠图页面
|
||||
navController.apply {
|
||||
currentBackStackEntry?.savedStateHandle?.set("width", 35f)
|
||||
currentBackStackEntry?.savedStateHandle?.set("height", 53f)
|
||||
navigate(ScreenRoute.Cutout.route)
|
||||
}
|
||||
}
|
||||
){
|
||||
Image(
|
||||
painter = painterResource(id = R.mipmap.ic_home_size_bg),
|
||||
contentDescription = null,
|
||||
)
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.wrapContentSize()
|
||||
.padding(start = 8.dp)
|
||||
.align(Alignment.Center)
|
||||
) {
|
||||
Text(
|
||||
text = "标准二寸",
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = Color(0xFF1A1A1A),
|
||||
modifier = Modifier.wrapContentSize()
|
||||
)
|
||||
Text(
|
||||
text = "35x53mm",
|
||||
fontSize = 10.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
color = Color(0xFF767676),
|
||||
modifier = Modifier.wrapContentSize()
|
||||
)
|
||||
}
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.width(10.dp)
|
||||
)
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.wrapContentWidth()
|
||||
.weight(1f)
|
||||
.clickable(
|
||||
indication = null,
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
) {
|
||||
// 隐藏TabBar
|
||||
generalViewModel.setNavigationBarVisible(false)
|
||||
// 跳转抠图页面
|
||||
navController.apply {
|
||||
currentBackStackEntry?.savedStateHandle?.set("width", 33f)
|
||||
currentBackStackEntry?.savedStateHandle?.set("height", 48f)
|
||||
navigate(ScreenRoute.Cutout.route)
|
||||
}
|
||||
}
|
||||
){
|
||||
Image(
|
||||
painter = painterResource(id = R.mipmap.ic_home_size_bg),
|
||||
contentDescription = null,
|
||||
)
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.wrapContentSize()
|
||||
.padding(start = 8.dp)
|
||||
.align(Alignment.Center)
|
||||
) {
|
||||
Text(
|
||||
text = "小二寸",
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = Color(0xFF1A1A1A),
|
||||
modifier = Modifier.wrapContentSize()
|
||||
)
|
||||
Text(
|
||||
text = "33x48mm",
|
||||
fontSize = 10.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
color = Color(0xFF767676),
|
||||
modifier = Modifier.wrapContentSize()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//证据制作
|
||||
Image(
|
||||
painter = painterResource(id = R.mipmap.ic_home_title_2_certificate),
|
||||
contentDescription = null,
|
||||
contentScale = ContentScale.Crop,
|
||||
modifier = Modifier
|
||||
.wrapContentWidth()
|
||||
.padding(start = 16.dp, end = 16.dp, top = 18.dp)
|
||||
)
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(start = 16.dp, end = 16.dp, top = 12.dp)
|
||||
){
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.wrapContentWidth()
|
||||
.weight(1f)
|
||||
.clickable(
|
||||
indication = null,
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
) {
|
||||
// 隐藏TabBar
|
||||
generalViewModel.setNavigationBarVisible(false)
|
||||
// 跳转抠图页面
|
||||
navController.apply {
|
||||
currentBackStackEntry?.savedStateHandle?.set("width", 26f)
|
||||
currentBackStackEntry?.savedStateHandle?.set("height", 32f)
|
||||
navigate(ScreenRoute.Cutout.route)
|
||||
}
|
||||
}
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(id = R.mipmap.ic_home_certificate_bg),
|
||||
contentDescription = null,
|
||||
)
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.wrapContentSize()
|
||||
.padding(start = 8.dp)
|
||||
.align(Alignment.Center)
|
||||
) {
|
||||
Text(
|
||||
text = "身份证",
|
||||
fontSize = 12.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = Color(0xFF1A1A1A),
|
||||
modifier = Modifier.wrapContentSize()
|
||||
)
|
||||
Text(
|
||||
text = "26x32mm",
|
||||
fontSize = 10.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
color = Color(0xFFAAAAAA),
|
||||
modifier = Modifier.wrapContentSize()
|
||||
)
|
||||
}
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.width(8.dp)
|
||||
)
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.wrapContentWidth()
|
||||
.weight(1f)
|
||||
.clickable(
|
||||
indication = null,
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
) {
|
||||
// 隐藏TabBar
|
||||
generalViewModel.setNavigationBarVisible(false)
|
||||
// 跳转抠图页面
|
||||
navController.apply {
|
||||
currentBackStackEntry?.savedStateHandle?.set("width", 33f)
|
||||
currentBackStackEntry?.savedStateHandle?.set("height", 48f)
|
||||
navigate(ScreenRoute.Cutout.route)
|
||||
}
|
||||
}
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(id = R.mipmap.ic_home_certificate_bg),
|
||||
contentDescription = null,
|
||||
)
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.wrapContentSize()
|
||||
.padding(start = 8.dp)
|
||||
.align(Alignment.Center)
|
||||
) {
|
||||
Text(
|
||||
text = "护照",
|
||||
fontSize = 12.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = Color(0xFF1A1A1A),
|
||||
modifier = Modifier.wrapContentSize()
|
||||
)
|
||||
Text(
|
||||
text = "33x48mm",
|
||||
fontSize = 10.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
color = Color(0xFFAAAAAA),
|
||||
modifier = Modifier.wrapContentSize()
|
||||
)
|
||||
}
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.width(8.dp)
|
||||
)
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.wrapContentWidth()
|
||||
.weight(1f)
|
||||
.clickable(
|
||||
indication = null,
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
) {
|
||||
// 隐藏TabBar
|
||||
generalViewModel.setNavigationBarVisible(false)
|
||||
// 跳转抠图页面
|
||||
navController.apply {
|
||||
currentBackStackEntry?.savedStateHandle?.set("width", 22f)
|
||||
currentBackStackEntry?.savedStateHandle?.set("height", 32f)
|
||||
navigate(ScreenRoute.Cutout.route)
|
||||
}
|
||||
}
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(id = R.mipmap.ic_home_certificate_bg),
|
||||
contentDescription = null,
|
||||
)
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.wrapContentSize()
|
||||
.padding(start = 8.dp)
|
||||
.align(Alignment.Center)
|
||||
) {
|
||||
Text(
|
||||
text = "驾驶证",
|
||||
fontSize = 12.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = Color(0xFF1A1A1A),
|
||||
modifier = Modifier.wrapContentSize()
|
||||
)
|
||||
Text(
|
||||
text = "22x32mm",
|
||||
fontSize = 10.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
color = Color(0xFFAAAAAA),
|
||||
modifier = Modifier.wrapContentSize()
|
||||
)
|
||||
}
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.width(8.dp)
|
||||
)
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.wrapContentWidth()
|
||||
.weight(1f)
|
||||
.clickable(
|
||||
indication = null,
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
) {
|
||||
// 隐藏TabBar
|
||||
generalViewModel.setNavigationBarVisible(false)
|
||||
// 跳转抠图页面
|
||||
navController.apply {
|
||||
currentBackStackEntry?.savedStateHandle?.set("width", 26f)
|
||||
currentBackStackEntry?.savedStateHandle?.set("height", 32f)
|
||||
navigate(ScreenRoute.Cutout.route)
|
||||
}
|
||||
}
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(id = R.mipmap.ic_home_certificate_bg),
|
||||
contentDescription = null,
|
||||
)
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.wrapContentSize()
|
||||
.padding(start = 8.dp)
|
||||
.align(Alignment.Center)
|
||||
) {
|
||||
Text(
|
||||
text = "社保卡",
|
||||
fontSize = 12.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = Color(0xFF1A1A1A),
|
||||
modifier = Modifier.wrapContentSize()
|
||||
)
|
||||
Text(
|
||||
text = "26x32mm",
|
||||
fontSize = 10.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
color = Color(0xFFAAAAAA),
|
||||
modifier = Modifier.wrapContentSize()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(start = 16.dp, end = 16.dp, top = 12.dp)
|
||||
){
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.wrapContentWidth()
|
||||
.weight(1f)
|
||||
.clickable(
|
||||
indication = null,
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
) {
|
||||
// 隐藏TabBar
|
||||
generalViewModel.setNavigationBarVisible(false)
|
||||
// 跳转抠图页面
|
||||
navController.apply {
|
||||
currentBackStackEntry?.savedStateHandle?.set("width", 26f)
|
||||
currentBackStackEntry?.savedStateHandle?.set("height", 32f)
|
||||
navigate(ScreenRoute.Cutout.route)
|
||||
}
|
||||
}
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(id = R.mipmap.ic_home_certificate_bg),
|
||||
contentDescription = null,
|
||||
)
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.wrapContentSize()
|
||||
.padding(start = 8.dp)
|
||||
.align(Alignment.Center)
|
||||
) {
|
||||
Text(
|
||||
text = "四六级",
|
||||
fontSize = 12.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = Color(0xFF1A1A1A),
|
||||
modifier = Modifier.wrapContentSize()
|
||||
)
|
||||
Text(
|
||||
text = "26x32mm",
|
||||
fontSize = 10.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
color = Color(0xFFAAAAAA),
|
||||
modifier = Modifier.wrapContentSize()
|
||||
)
|
||||
}
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.width(8.dp)
|
||||
)
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.wrapContentWidth()
|
||||
.weight(1f)
|
||||
.clickable(
|
||||
indication = null,
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
) {
|
||||
// 隐藏TabBar
|
||||
generalViewModel.setNavigationBarVisible(false)
|
||||
// 跳转抠图页面
|
||||
navController.apply {
|
||||
currentBackStackEntry?.savedStateHandle?.set("width", 33f)
|
||||
currentBackStackEntry?.savedStateHandle?.set("height", 48f)
|
||||
navigate(ScreenRoute.Cutout.route)
|
||||
}
|
||||
}
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(id = R.mipmap.ic_home_certificate_bg),
|
||||
contentDescription = null,
|
||||
)
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.wrapContentSize()
|
||||
.padding(start = 8.dp)
|
||||
.align(Alignment.Center)
|
||||
) {
|
||||
Text(
|
||||
text = "司法考试",
|
||||
fontSize = 12.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = Color(0xFF1A1A1A),
|
||||
modifier = Modifier.wrapContentSize()
|
||||
)
|
||||
Text(
|
||||
text = "33x48mm",
|
||||
fontSize = 10.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
color = Color(0xFFAAAAAA),
|
||||
modifier = Modifier.wrapContentSize()
|
||||
)
|
||||
}
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.width(8.dp)
|
||||
)
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.wrapContentWidth()
|
||||
.weight(1f)
|
||||
.clickable(
|
||||
indication = null,
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
) {
|
||||
// 隐藏TabBar
|
||||
generalViewModel.setNavigationBarVisible(false)
|
||||
// 跳转抠图页面
|
||||
navController.apply {
|
||||
currentBackStackEntry?.savedStateHandle?.set("width", 40f)
|
||||
currentBackStackEntry?.savedStateHandle?.set("height", 60f)
|
||||
navigate(ScreenRoute.Cutout.route)
|
||||
}
|
||||
}
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(id = R.mipmap.ic_home_certificate_bg),
|
||||
contentDescription = null,
|
||||
)
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.wrapContentSize()
|
||||
.padding(start = 8.dp)
|
||||
.align(Alignment.Center)
|
||||
) {
|
||||
Text(
|
||||
text = "结婚证",
|
||||
fontSize = 12.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = Color(0xFF1A1A1A),
|
||||
modifier = Modifier.wrapContentSize()
|
||||
)
|
||||
Text(
|
||||
text = "40x60mm",
|
||||
fontSize = 10.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
color = Color(0xFFAAAAAA),
|
||||
modifier = Modifier.wrapContentSize()
|
||||
)
|
||||
}
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.width(8.dp)
|
||||
)
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.wrapContentWidth()
|
||||
.weight(1f)
|
||||
.clickable(
|
||||
indication = null,
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
) {
|
||||
// 隐藏TabBar
|
||||
generalViewModel.setNavigationBarVisible(false)
|
||||
// 跳转抠图页面
|
||||
navController.apply {
|
||||
currentBackStackEntry?.savedStateHandle?.set("width", 35f)
|
||||
currentBackStackEntry?.savedStateHandle?.set("height", 45f)
|
||||
navigate(ScreenRoute.Cutout.route)
|
||||
}
|
||||
}
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(id = R.mipmap.ic_home_certificate_bg),
|
||||
contentDescription = null,
|
||||
)
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.wrapContentSize()
|
||||
.padding(start = 8.dp)
|
||||
.align(Alignment.Center)
|
||||
) {
|
||||
Text(
|
||||
text = "中国签证",
|
||||
fontSize = 12.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = Color(0xFF1A1A1A),
|
||||
modifier = Modifier.wrapContentSize()
|
||||
)
|
||||
Text(
|
||||
text = "35x45mm",
|
||||
fontSize = 10.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
color = Color(0xFFAAAAAA),
|
||||
modifier = Modifier.wrapContentSize()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//其他
|
||||
Image(
|
||||
painter = painterResource(id = R.mipmap.ic_home_title_3_other),
|
||||
contentDescription = null,
|
||||
contentScale = ContentScale.Crop,
|
||||
modifier = Modifier
|
||||
.wrapContentWidth()
|
||||
.padding(start = 16.dp, end = 16.dp, top = 18.dp)
|
||||
)
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(start = 16.dp, end = 16.dp, top = 12.dp)
|
||||
){
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.wrapContentWidth()
|
||||
.weight(1f)
|
||||
.clickable(
|
||||
indication = null,
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
) {
|
||||
// 隐藏TabBar
|
||||
generalViewModel.setNavigationBarVisible(false)
|
||||
// 跳转格式页面
|
||||
navController.navigate(ScreenRoute.LongImage.route)
|
||||
}
|
||||
) {
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.wrapContentSize()
|
||||
.padding(start = 8.dp)
|
||||
.align(Alignment.Center)
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(id = R.mipmap.ic_home_other_1_puzzle),
|
||||
contentDescription = null,
|
||||
)
|
||||
|
||||
Text(
|
||||
text = "拼长图",
|
||||
fontSize = 12.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = Color(0xFF1A1A1A),
|
||||
modifier = Modifier.wrapContentSize().padding(top = 12.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.width(8.dp)
|
||||
)
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.wrapContentWidth()
|
||||
.weight(1f)
|
||||
.clickable(
|
||||
indication = null,
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
) {
|
||||
// 隐藏TabBar
|
||||
generalViewModel.setNavigationBarVisible(false)
|
||||
// 跳转格式页面
|
||||
navController.navigate(ScreenRoute.Format.route)
|
||||
}
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.wrapContentSize()
|
||||
.padding(start = 8.dp)
|
||||
.align(Alignment.Center)
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(id = R.mipmap.ic_home_other_2_format),
|
||||
contentDescription = null,
|
||||
)
|
||||
|
||||
Text(
|
||||
text = "格式转换",
|
||||
fontSize = 12.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = Color(0xFF1A1A1A),
|
||||
modifier = Modifier.wrapContentSize().padding(top = 12.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.width(8.dp)
|
||||
)
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.wrapContentWidth()
|
||||
.weight(1f)
|
||||
.clickable(
|
||||
indication = null,
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
) {
|
||||
// 隐藏TabBar
|
||||
generalViewModel.setNavigationBarVisible(false)
|
||||
// 跳转证件页面
|
||||
navController.apply {
|
||||
currentBackStackEntry?.savedStateHandle?.set("width", 25f)
|
||||
currentBackStackEntry?.savedStateHandle?.set("height", 35f)
|
||||
navigate(ScreenRoute.Resize.route)
|
||||
}
|
||||
}
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.wrapContentSize()
|
||||
.padding(start = 8.dp)
|
||||
.align(Alignment.Center)
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(id = R.mipmap.ic_home_other_3_size),
|
||||
contentDescription = null,
|
||||
)
|
||||
|
||||
Text(
|
||||
text = "改尺寸",
|
||||
fontSize = 12.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = Color(0xFF1A1A1A),
|
||||
modifier = Modifier.wrapContentSize().padding(top = 12.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.width(8.dp)
|
||||
)
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.wrapContentWidth()
|
||||
.weight(1f)
|
||||
.clickable(
|
||||
indication = null,
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
) {
|
||||
// 隐藏TabBar
|
||||
generalViewModel.setNavigationBarVisible(false)
|
||||
// 跳转拍照指南页面
|
||||
navController.navigate(ScreenRoute.CameraGuide.route)
|
||||
}
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.wrapContentSize()
|
||||
.padding(start = 8.dp)
|
||||
.align(Alignment.Center)
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(id = R.mipmap.ic_home_other_4_camera),
|
||||
contentDescription = null,
|
||||
)
|
||||
|
||||
Text(
|
||||
text = "拍照指南",
|
||||
fontSize = 12.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = Color(0xFF1A1A1A),
|
||||
modifier = Modifier.wrapContentSize().padding(top = 12.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier.fillMaxWidth().height(56.dp)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun PreviewHomeScreen() {
|
||||
HomeScreen(navController = rememberNavController(),generalViewModel = viewModel())
|
||||
}
|
||||
|
|
@ -0,0 +1,437 @@
|
|||
package com.img.rabbit.pages.screen
|
||||
|
||||
import android.widget.Toast
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.offset
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.wrapContentHeight
|
||||
import androidx.compose.foundation.layout.wrapContentSize
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.draw.shadow
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import com.img.rabbit.R
|
||||
import com.img.rabbit.viewmodel.GeneralViewModel
|
||||
|
||||
@Composable
|
||||
fun MineScreen(
|
||||
navController: NavHostController,
|
||||
generalViewModel: GeneralViewModel,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val vipMember by remember { mutableStateOf(false) }
|
||||
|
||||
// 监听返回事件
|
||||
val currentBackStackEntry = navController.currentBackStackEntry
|
||||
|
||||
LaunchedEffect(currentBackStackEntry) {
|
||||
// 当返回到MineScreen页面时执行的操作
|
||||
if (currentBackStackEntry?.destination?.route == "mine") {
|
||||
// 显示TabBar
|
||||
generalViewModel.setNavigationBarVisible(true)
|
||||
}
|
||||
}
|
||||
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize().background(Color(0xFFF9F9F9))
|
||||
){
|
||||
Image(
|
||||
painter = painterResource(id = R.mipmap.ic_mine_top_mask),
|
||||
contentDescription = null,
|
||||
contentScale = ContentScale.FillWidth,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(start = 16.dp, end = 16.dp, top = 112.dp)
|
||||
) {
|
||||
// 头像和登录/注册
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(id = R.mipmap.ic_user_avatar_default),
|
||||
contentDescription = null,
|
||||
contentScale = ContentScale.FillWidth,
|
||||
modifier = Modifier
|
||||
.size(64.dp)
|
||||
.clip(RoundedCornerShape(90.dp))
|
||||
.clickable(
|
||||
indication = null,
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
) {
|
||||
// 处理点击事件
|
||||
Toast.makeText(context, "头像", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
)
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(start = 16.dp)
|
||||
.align(Alignment.CenterVertically)
|
||||
.clickable(
|
||||
indication = null,
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
) {
|
||||
// 隐藏TabBar
|
||||
generalViewModel.setNavigationBarVisible(false)
|
||||
// 跳转登录页面
|
||||
navController.navigate("login")
|
||||
}
|
||||
) {
|
||||
Text(
|
||||
text = "登录/注册",
|
||||
fontSize = 18.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = Color(0xFF1A1A1A),
|
||||
modifier = Modifier.wrapContentSize()
|
||||
)
|
||||
Text(
|
||||
text = "登录体验更多功能哦~",
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = Color(0xFF767676),
|
||||
modifier = Modifier
|
||||
.wrapContentSize()
|
||||
.padding(top = 10.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// VIP bar
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight()
|
||||
.padding(top = 33.dp)
|
||||
){
|
||||
|
||||
if(vipMember){
|
||||
Image(
|
||||
painter = painterResource(id = R.mipmap.ic_mine_vip_bar_bg1),
|
||||
contentDescription = null,
|
||||
contentScale = ContentScale.FillWidth,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
}else{
|
||||
Image(
|
||||
painter = painterResource(id = R.mipmap.ic_mine_vip_bar_bg),
|
||||
contentDescription = null,
|
||||
contentScale = ContentScale.FillWidth,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
}
|
||||
|
||||
Row(
|
||||
modifier = Modifier.wrapContentSize()
|
||||
) {
|
||||
Text(
|
||||
text = if (vipMember) "月度会员" else "开通会员",
|
||||
style = TextStyle(
|
||||
brush = Brush.linearGradient(
|
||||
colors = listOf(Color(0xFFC8FF54), Color(0xFFFFFFFF))
|
||||
),
|
||||
fontSize = 18.sp,
|
||||
fontWeight = FontWeight.Bold
|
||||
),
|
||||
modifier = Modifier
|
||||
.wrapContentSize()
|
||||
.padding(start = 14.dp, top = 10.dp)
|
||||
)
|
||||
Text(
|
||||
text = if (vipMember) "2025.12.22前会员到期" else "解锁微圈相册全部特权",
|
||||
fontSize = 12.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
color = Color(0xFFFFFFFF),
|
||||
modifier = Modifier
|
||||
.wrapContentSize()
|
||||
.padding(start = 14.dp, top = 10.dp)
|
||||
.align(Alignment.CenterVertically)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (!vipMember){
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(end = 17.dp)
|
||||
.offset(y = (-40).dp)
|
||||
) {
|
||||
|
||||
Image(
|
||||
painter = painterResource(id = R.mipmap.ic_mine_open),
|
||||
contentDescription = null,
|
||||
contentScale = ContentScale.FillWidth,
|
||||
modifier = Modifier
|
||||
.wrapContentSize()
|
||||
.align(Alignment.End)
|
||||
.clickable(
|
||||
indication = null,
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
) {
|
||||
// 处理点击事件
|
||||
Toast.makeText(context, "开通会员", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
//功能项
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight()
|
||||
.padding(top = 16.dp)
|
||||
.background(
|
||||
Color(0xFFFFFFFF),
|
||||
RoundedCornerShape(18.dp)
|
||||
)
|
||||
.shadow(
|
||||
elevation = 4.dp,
|
||||
shape = RoundedCornerShape(8.dp),
|
||||
ambientColor = Color(0x4DE5E5E5),
|
||||
spotColor = Color(0x4DE5E5E5)
|
||||
)
|
||||
){
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(52.dp)
|
||||
.clickable(
|
||||
indication = null,
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
) {
|
||||
// 隐藏TabBar
|
||||
generalViewModel.setNavigationBarVisible(false)
|
||||
// 跳转反馈页面
|
||||
navController.navigate("feedback")
|
||||
},
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(id = R.mipmap.ic_mine_feedback),
|
||||
contentDescription = null,
|
||||
contentScale = ContentScale.FillWidth,
|
||||
modifier = Modifier
|
||||
.wrapContentSize()
|
||||
.padding(start = 12.dp)
|
||||
)
|
||||
Box(
|
||||
modifier = Modifier.weight(1f)
|
||||
){
|
||||
Text(
|
||||
text = "意见反馈",
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = Color(0xFF1A1A1A),
|
||||
modifier = Modifier
|
||||
.wrapContentSize()
|
||||
.padding(start = 8.dp)
|
||||
)
|
||||
}
|
||||
|
||||
Image(
|
||||
painter = painterResource(id = R.mipmap.ic_arrow_right),
|
||||
contentDescription = null,
|
||||
contentScale = ContentScale.FillWidth,
|
||||
modifier = Modifier
|
||||
.wrapContentSize()
|
||||
.padding(end = 12.dp) // 改为end padding
|
||||
)
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier.fillMaxWidth().height(0.5.dp).padding(horizontal = 12.dp).background(
|
||||
Color(0x4DBBBBBB)
|
||||
)
|
||||
)
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(52.dp)
|
||||
.clickable(
|
||||
indication = null,
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
) {
|
||||
// 隐藏TabBar
|
||||
generalViewModel.setNavigationBarVisible(false)
|
||||
// 跳转在线客服页面
|
||||
navController.navigate("onlineService")
|
||||
},
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(id = R.mipmap.ic_mine_service),
|
||||
contentDescription = null,
|
||||
contentScale = ContentScale.FillWidth,
|
||||
modifier = Modifier
|
||||
.wrapContentSize()
|
||||
.padding(start = 12.dp)
|
||||
)
|
||||
Box(
|
||||
modifier = Modifier.weight(1f)
|
||||
){
|
||||
Text(
|
||||
text = "在线客服",
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = Color(0xFF1A1A1A),
|
||||
modifier = Modifier
|
||||
.wrapContentSize()
|
||||
.padding(start = 8.dp)
|
||||
)
|
||||
}
|
||||
|
||||
Image(
|
||||
painter = painterResource(id = R.mipmap.ic_arrow_right),
|
||||
contentDescription = null,
|
||||
contentScale = ContentScale.FillWidth,
|
||||
modifier = Modifier
|
||||
.wrapContentSize()
|
||||
.padding(end = 12.dp) // 改为end padding
|
||||
)
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier.fillMaxWidth().height(0.5.dp).padding(horizontal = 12.dp).background(
|
||||
Color(0x4DBBBBBB)
|
||||
)
|
||||
)
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(52.dp)
|
||||
.clickable(
|
||||
indication = null,
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
) {
|
||||
// 处理点击事件
|
||||
Toast.makeText(context, "版本更新", Toast.LENGTH_SHORT).show()
|
||||
},
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(id = R.mipmap.ic_mine_update),
|
||||
contentDescription = null,
|
||||
contentScale = ContentScale.FillWidth,
|
||||
modifier = Modifier
|
||||
.wrapContentSize()
|
||||
.padding(start = 12.dp)
|
||||
)
|
||||
Box(
|
||||
modifier = Modifier.weight(1f)
|
||||
){
|
||||
Text(
|
||||
text = "版本更新",
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = Color(0xFF1A1A1A),
|
||||
modifier = Modifier
|
||||
.wrapContentSize()
|
||||
.padding(start = 8.dp)
|
||||
)
|
||||
}
|
||||
|
||||
Image(
|
||||
painter = painterResource(id = R.mipmap.ic_arrow_right),
|
||||
contentDescription = null,
|
||||
contentScale = ContentScale.FillWidth,
|
||||
modifier = Modifier
|
||||
.wrapContentSize()
|
||||
.padding(end = 12.dp) // 改为end padding
|
||||
)
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier.fillMaxWidth().height(0.5.dp).padding(horizontal = 12.dp).background(
|
||||
Color(0x4DBBBBBB)
|
||||
)
|
||||
)
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(52.dp)
|
||||
.clickable(
|
||||
indication = null,
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
) {
|
||||
// 隐藏TabBar
|
||||
generalViewModel.setNavigationBarVisible(false)
|
||||
// 跳转设置页面
|
||||
navController.navigate("setting")
|
||||
},
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(id = R.mipmap.ic_mine_setting),
|
||||
contentDescription = null,
|
||||
contentScale = ContentScale.FillWidth,
|
||||
modifier = Modifier
|
||||
.wrapContentSize()
|
||||
.padding(start = 12.dp)
|
||||
)
|
||||
Box(
|
||||
modifier = Modifier.weight(1f)
|
||||
){
|
||||
Text(
|
||||
text = "设置",
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = Color(0xFF1A1A1A),
|
||||
modifier = Modifier
|
||||
.wrapContentSize()
|
||||
.padding(start = 8.dp)
|
||||
)
|
||||
}
|
||||
|
||||
Image(
|
||||
painter = painterResource(id = R.mipmap.ic_arrow_right),
|
||||
contentDescription = null,
|
||||
contentScale = ContentScale.FillWidth,
|
||||
modifier = Modifier
|
||||
.wrapContentSize()
|
||||
.padding(end = 12.dp) // 改为end padding
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun PreviewMineScreen(){
|
||||
MineScreen(navController = rememberNavController(), generalViewModel = viewModel())
|
||||
}
|
||||
|
|
@ -0,0 +1,621 @@
|
|||
package com.img.rabbit.pages.screen.make
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.foundation.Canvas
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.gestures.awaitFirstDown
|
||||
import androidx.compose.foundation.gestures.detectTransformGestures
|
||||
import androidx.compose.foundation.gestures.forEachGesture
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.aspectRatio
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.wrapContentHeight
|
||||
import androidx.compose.foundation.layout.wrapContentSize
|
||||
import androidx.compose.foundation.layout.wrapContentWidth
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clipToBounds
|
||||
import androidx.compose.ui.draw.drawWithContent
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.geometry.center
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.asAndroidBitmap
|
||||
import androidx.compose.ui.graphics.asImageBitmap
|
||||
import androidx.compose.ui.graphics.drawscope.withTransform
|
||||
import androidx.compose.ui.graphics.layer.drawLayer
|
||||
import androidx.compose.ui.graphics.rememberGraphicsLayer
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.compose.ui.unit.toSize
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import coil3.compose.AsyncImage
|
||||
import com.img.rabbit.R
|
||||
import com.img.rabbit.bean.ClothingBean
|
||||
import com.img.rabbit.bean.HairstyleBean
|
||||
import com.img.rabbit.components.AppearanceType
|
||||
import com.img.rabbit.components.DrawingBoardPicker
|
||||
import com.img.rabbit.config.CommonData.clothingForFemales
|
||||
import com.img.rabbit.config.CommonData.clothingForMans
|
||||
import com.img.rabbit.config.CommonData.colors
|
||||
import com.img.rabbit.config.CommonData.hairstyleForFemales
|
||||
import com.img.rabbit.config.CommonData.hairstyleForMans
|
||||
import com.img.rabbit.config.CommonData.resizes
|
||||
import com.img.rabbit.pages.toolbar.TitleBar
|
||||
import com.img.rabbit.utils.ExportFormat
|
||||
import com.img.rabbit.utils.ImageUtils
|
||||
import com.img.rabbit.utils.ImageUtils.saveCanvasToGallery
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
import com.img.rabbit.utils.PhotoCutter
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
|
||||
// 建议的状态数据结构
|
||||
data class TransformState(
|
||||
val offset: Offset = Offset.Zero,
|
||||
val scale: Float = 1f,
|
||||
val rotation: Float = 0f
|
||||
)
|
||||
|
||||
// 服装和发型资源管理类
|
||||
object ResourceManager {
|
||||
// 加载服装资源
|
||||
fun loadClothingResource(context: Context, resId: Int): Bitmap? {
|
||||
return try {
|
||||
BitmapFactory.decodeResource(context.resources, resId)
|
||||
} catch (e: Exception) {
|
||||
Log.e("ResourceManager", "加载服装资源失败: ${e.message}")
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
// 加载发型资源
|
||||
fun loadHairstyleResource(context: Context, resId: Int): Bitmap? {
|
||||
return try {
|
||||
BitmapFactory.decodeResource(context.resources, resId)
|
||||
} catch (e: Exception) {
|
||||
Log.e("ResourceManager", "加载发型资源失败: ${e.message}")
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun CutoutScreen(navController: NavController) {
|
||||
val context = LocalContext.current
|
||||
// 图片显示区域 - 支持头像编辑(用于最终数据的导出保存Bitmap)
|
||||
val graphicsLayer = rememberGraphicsLayer()
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
// 抠图加载中状态
|
||||
val isLoading = remember { mutableStateOf(false) }
|
||||
|
||||
val widthParam = navController.previousBackStackEntry?.savedStateHandle?.get<Float>("width") ?: 23f
|
||||
val heightParam = navController.previousBackStackEntry?.savedStateHandle?.get<Float>("height") ?: 35f
|
||||
|
||||
//显示服装和发型
|
||||
val viewClothing = remember { mutableStateOf(ViewType.VISIBLE) }
|
||||
val viewHairstyle = remember { mutableStateOf(ViewType.VISIBLE) }
|
||||
|
||||
// 人物图片选择和抠图相关状态
|
||||
val selectedImageUri = remember { mutableStateOf<Uri?>(null) }
|
||||
val cutoutResultBitmap = remember { mutableStateOf<Bitmap?>(null) }
|
||||
|
||||
// 头像编辑状态
|
||||
val headTransform = remember { mutableStateOf(TransformState()) }
|
||||
val clothesTransform = remember { mutableStateOf(TransformState()) }
|
||||
val hairTransform = remember { mutableStateOf(TransformState()) }
|
||||
|
||||
var selectedTarget by remember { mutableStateOf<String?>(null) }
|
||||
|
||||
// 辅助函数:判断点击位置
|
||||
fun hitTest(tap: Offset, state: TransformState, bmp: Bitmap?, canvasCenter: Offset): Boolean {
|
||||
if (bmp == null) return false
|
||||
val w = bmp.width * state.scale
|
||||
val h = bmp.height * state.scale
|
||||
val left = canvasCenter.x + state.offset.x - w / 2
|
||||
val top = canvasCenter.y + state.offset.y - h / 2
|
||||
return tap.x in left..(left + w) && tap.y in top..(top + h)
|
||||
}
|
||||
|
||||
// 服装和发型资源
|
||||
val selectedClothingBitmap = remember { mutableStateOf<Bitmap?>(null) }
|
||||
val selectedHairstyleBitmap = remember { mutableStateOf<Bitmap?>(null) }
|
||||
|
||||
// 当前选中的外观
|
||||
val selectedAppearance = remember { mutableStateOf(AppearanceType.BACKGROUND) }
|
||||
// 当前选中的颜色索引
|
||||
val selectedColorIndex = remember { mutableIntStateOf(0) }
|
||||
// 当前选中的衣服
|
||||
val selectedClothing = remember { mutableStateOf(clothingForFemales.getOrNull(0)) }
|
||||
// 当前选中的发型
|
||||
val selectedHairstyle = remember { mutableStateOf(hairstyleForFemales.getOrNull(0)) }
|
||||
// 当前选中的尺寸
|
||||
val selectedSize = remember { mutableStateOf(resizes.first { it.width == widthParam && it.height == heightParam }) }
|
||||
|
||||
|
||||
// 图片选择启动器
|
||||
val imagePickerLauncher = rememberLauncherForActivityResult(
|
||||
contract = ActivityResultContracts.GetContent()
|
||||
) { uri ->
|
||||
uri?.let {
|
||||
selectedImageUri.value = it
|
||||
isLoading.value = true
|
||||
|
||||
// 执行抠图操作
|
||||
Thread {
|
||||
try {
|
||||
val originalBitmap = ImageUtils.getBitmapFromUri(context, it)
|
||||
originalBitmap?.let { bitmap ->
|
||||
PhotoCutter.cutPureHead(bitmap) { croppedBitmap ->
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
context.mainExecutor.execute {
|
||||
cutoutResultBitmap.value = croppedBitmap
|
||||
isLoading.value = false
|
||||
|
||||
// 重置头像变换
|
||||
headTransform.value = TransformState()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e("CutoutScreen", "抠图失败: ${e.message}")
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
context.mainExecutor.execute {
|
||||
isLoading.value = false
|
||||
Toast.makeText(context, "抠图失败,请重试", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}.start()
|
||||
}
|
||||
}
|
||||
|
||||
// 加载服装资源
|
||||
fun loadClothingResource(clothing: ClothingBean?) {
|
||||
if(clothing?.clothing == null){
|
||||
selectedClothingBitmap.value = null
|
||||
}else{
|
||||
Thread {
|
||||
val bitmap = ResourceManager.loadClothingResource(context, clothing.clothing)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
context.mainExecutor.execute {
|
||||
selectedClothingBitmap.value = bitmap
|
||||
}
|
||||
}
|
||||
}.start()
|
||||
}
|
||||
}
|
||||
|
||||
// 加载发型资源
|
||||
fun loadHairstyleResource(hairstyle: HairstyleBean?) {
|
||||
if(hairstyle?.hairstyle == null){
|
||||
selectedHairstyleBitmap.value = null
|
||||
}else{
|
||||
Thread {
|
||||
val bitmap = ResourceManager.loadHairstyleResource(context, hairstyle.hairstyle)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
context.mainExecutor.execute {
|
||||
selectedHairstyleBitmap.value = bitmap
|
||||
}
|
||||
}
|
||||
}.start()
|
||||
}
|
||||
}
|
||||
|
||||
Scaffold {
|
||||
LaunchedEffect(Unit) {
|
||||
imagePickerLauncher.launch("image/*")
|
||||
}
|
||||
Column(
|
||||
modifier = Modifier.fillMaxSize().background(Color(0xFFF4F4F4))
|
||||
) {
|
||||
TitleBar(navController = navController, paddingValues = it, title = "", showSave = true){
|
||||
// 保存图片
|
||||
coroutineScope.launch {
|
||||
// 从 Layer 捕获 Bitmap
|
||||
val bitmap = graphicsLayer.toImageBitmap().asAndroidBitmap()
|
||||
// 保存图片到系统相册(图片已按比例裁剪)
|
||||
saveCanvasToGallery(context, bitmap, ExportFormat.JPG){fileName, isSuccess ->
|
||||
if(isSuccess){
|
||||
Toast.makeText(context, "已保存为: $fileName", Toast.LENGTH_SHORT).show()
|
||||
}else{
|
||||
Toast.makeText(context, "保存失败", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
// 保存图片到系统相册(指定尺寸,如果targetWidth与targetHeight比原始值小太多会导致图片模糊)
|
||||
saveCanvasToGallery(context, bitmap, ExportFormat.JPG, selectedSize.value.width.toInt(), selectedSize.value.height.toInt()){fileName, isSuccess ->
|
||||
if(isSuccess){
|
||||
Toast.makeText(context, "已保存为: $fileName", Toast.LENGTH_SHORT).show()
|
||||
}else{
|
||||
Toast.makeText(context, "保存失败", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
// 图片显示区域 - 支持头像编辑
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(start = 38.dp, end = 38.dp
|
||||
)
|
||||
.aspectRatio(selectedSize.value.width/selectedSize.value.height)
|
||||
.background(Color(0xFFFFFFFF))
|
||||
.border(1.dp, Color(0xFFD8D8D8))
|
||||
) {
|
||||
if (isLoading.value) {
|
||||
// 加载中状态
|
||||
Column(
|
||||
modifier = Modifier.align(Alignment.Center)
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(id = R.mipmap.ic_loading),
|
||||
contentDescription = "加载效果图片",
|
||||
modifier = Modifier.size(48.dp)
|
||||
)
|
||||
Text(
|
||||
text = "正在抠图中...",
|
||||
color = Color(0xFF767676),
|
||||
fontSize = 12.sp,
|
||||
modifier = Modifier.padding(top = 8.dp)
|
||||
)
|
||||
}
|
||||
} else if (cutoutResultBitmap.value != null) {
|
||||
// 显示合成结果
|
||||
Canvas(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.drawWithContent {
|
||||
// 将内容绘制到 graphicsLayer 中
|
||||
graphicsLayer.record {
|
||||
this@drawWithContent.drawContent()
|
||||
}
|
||||
drawLayer(graphicsLayer)
|
||||
}
|
||||
.clipToBounds()
|
||||
.pointerInput(Unit) {
|
||||
forEachGesture {
|
||||
awaitPointerEventScope {
|
||||
val down = awaitFirstDown()
|
||||
val center = size.toSize().center
|
||||
|
||||
// 1. 修改判定逻辑:只有当服装可见时,才进行它的 hitTest
|
||||
selectedTarget = when {
|
||||
// 处理发型
|
||||
viewHairstyle.value == ViewType.VISIBLE && hitTest(down.position, hairTransform.value, selectedHairstyleBitmap.value, center) -> "HAIR"
|
||||
|
||||
// 处理服装
|
||||
viewClothing.value == ViewType.VISIBLE && hitTest(down.position, clothesTransform.value, selectedClothingBitmap.value, center) -> "CLOTHES"
|
||||
|
||||
hitTest(down.position, headTransform.value, cutoutResultBitmap.value, center) -> "HEAD"
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.pointerInput(Unit) {
|
||||
detectTransformGestures { _, pan, zoom, rotation ->
|
||||
selectedTarget?.let { target ->
|
||||
// 更新逻辑保持不变...
|
||||
val stateRef = when(target) {
|
||||
"HEAD" -> headTransform
|
||||
"CLOTHES" -> clothesTransform
|
||||
else -> hairTransform
|
||||
}
|
||||
val bmp = when(target) {
|
||||
"HEAD" -> cutoutResultBitmap.value
|
||||
"CLOTHES" -> selectedClothingBitmap.value
|
||||
else -> selectedHairstyleBitmap.value
|
||||
}
|
||||
|
||||
bmp?.let { b ->
|
||||
val current = stateRef.value
|
||||
val newScale = (current.scale * zoom).coerceIn(0.1f, 5f)
|
||||
stateRef.value = current.copy(
|
||||
offset = current.offset + pan,
|
||||
scale = newScale,
|
||||
rotation = if (target == "HEAD") current.rotation + rotation else current.rotation
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
) {
|
||||
val canvasCenter = center
|
||||
|
||||
// 1. 绘制背景
|
||||
drawRect(colors[selectedColorIndex.intValue])
|
||||
|
||||
// 2. 绘制头像
|
||||
cutoutResultBitmap.value?.let { bmp ->
|
||||
withTransform({
|
||||
translate(headTransform.value.offset.x, headTransform.value.offset.y)
|
||||
rotate(headTransform.value.rotation, canvasCenter)
|
||||
scale(headTransform.value.scale, headTransform.value.scale, canvasCenter)
|
||||
}) {
|
||||
drawImage(bmp.asImageBitmap(), Offset(canvasCenter.x - bmp.width/2, canvasCenter.y - bmp.height/2))
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 绘制服装 (根据状态[viewClothing]显示)
|
||||
if (viewClothing.value == ViewType.VISIBLE) {
|
||||
selectedClothingBitmap.value?.let { bmp ->
|
||||
withTransform({
|
||||
translate(clothesTransform.value.offset.x, clothesTransform.value.offset.y)
|
||||
scale(clothesTransform.value.scale, clothesTransform.value.scale, canvasCenter)
|
||||
}) {
|
||||
drawImage(bmp.asImageBitmap(), Offset(canvasCenter.x - bmp.width/2, canvasCenter.y - bmp.height/2))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 绘制发型 (根据状态[viewHairstyle]显示)
|
||||
if (viewHairstyle.value == ViewType.VISIBLE) {
|
||||
selectedHairstyleBitmap.value?.let { bmp ->
|
||||
withTransform({
|
||||
translate(hairTransform.value.offset.x, hairTransform.value.offset.y)
|
||||
scale(hairTransform.value.scale, hairTransform.value.scale, canvasCenter)
|
||||
}) {
|
||||
drawImage(bmp.asImageBitmap(), Offset(canvasCenter.x - bmp.width/2, canvasCenter.y - bmp.height/2))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} else if (selectedImageUri.value != null) {
|
||||
// 显示选中的原始图片
|
||||
AsyncImage(
|
||||
model = selectedImageUri.value,
|
||||
contentDescription = "选中的图片",
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentScale = androidx.compose.ui.layout.ContentScale.Fit
|
||||
)
|
||||
} else {
|
||||
// 空状态
|
||||
Column(
|
||||
modifier = Modifier.align(Alignment.Center)
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(id = R.mipmap.ic_image_empty_pld),
|
||||
contentDescription = "截图",
|
||||
)
|
||||
Text(
|
||||
text = "点击选择图片",
|
||||
color = Color(0xFFD8D8D8),
|
||||
fontSize = 12.sp,
|
||||
modifier = Modifier.align(Alignment.CenterHorizontally)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 底部控制区域
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.align(Alignment.BottomCenter)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
if (selectedAppearance.value == AppearanceType.CLOTHING || selectedAppearance.value == AppearanceType.HAIRSTYLE) {
|
||||
// 服装和发型查看
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().weight(1f).padding(start = 16.dp)
|
||||
) {
|
||||
ViewOption(
|
||||
viewType = viewClothing.value,
|
||||
title = "服装",
|
||||
onClick = {
|
||||
viewClothing.value = if (viewClothing.value == ViewType.HIDE) ViewType.VISIBLE else ViewType.HIDE
|
||||
}
|
||||
)
|
||||
Box(modifier = Modifier.wrapContentSize().padding(end = 8.dp))
|
||||
ViewOption(
|
||||
viewType = viewHairstyle.value,
|
||||
title = "发型",
|
||||
onClick = {
|
||||
viewHairstyle.value = if (viewHairstyle.value == ViewType.HIDE) ViewType.VISIBLE else ViewType.HIDE
|
||||
}
|
||||
)
|
||||
}
|
||||
} else {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().weight(1f).padding(start = 16.dp)
|
||||
) {}
|
||||
}
|
||||
|
||||
/*
|
||||
// 说明
|
||||
Row(
|
||||
modifier = Modifier.wrapContentSize().padding(end = 16.dp).align(Alignment.Bottom)
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(id = R.mipmap.ic_info),
|
||||
contentDescription = "说明",
|
||||
modifier = Modifier
|
||||
.size(14.dp)
|
||||
.align(Alignment.CenterVertically)
|
||||
.clip(CircleShape)
|
||||
.background(Color(0xFFF4F4F4))
|
||||
)
|
||||
Text(
|
||||
text = "使用说明",
|
||||
color = Color(0xFF767676),
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
modifier = Modifier.align(Alignment.Bottom).padding(start = 4.dp)
|
||||
)
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
// 底部画板
|
||||
DrawingBoardPicker(
|
||||
// 颜色
|
||||
colors = colors,
|
||||
// 服装
|
||||
clothingForMans = clothingForMans,
|
||||
clothingForFemales = clothingForFemales,
|
||||
// 发型
|
||||
hairstyleForMans = hairstyleForMans,
|
||||
hairstyleForFemales = hairstyleForFemales,
|
||||
// 尺寸
|
||||
sizes = resizes,
|
||||
// 选中
|
||||
selectedAppearance = selectedAppearance,
|
||||
selectedColorIndex = selectedColorIndex,
|
||||
selectedClothing = selectedClothing,
|
||||
selectedHairstyle = selectedHairstyle,
|
||||
selectedSize = selectedSize,
|
||||
// 新增回调函数
|
||||
onClothingSelected = { clothing ->
|
||||
loadClothingResource(clothing)
|
||||
},
|
||||
onHairstyleSelected = { hairstyle ->
|
||||
loadHairstyleResource(hairstyle)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 保存合成结果
|
||||
private fun saveCompositeResult(context: Context, compositeBitmap: Bitmap): Uri? {
|
||||
try {
|
||||
val timeStamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date())
|
||||
val imageFileName = "COMPOSITE_${timeStamp}.png"
|
||||
|
||||
val storageDir = context.getExternalFilesDir(android.os.Environment.DIRECTORY_PICTURES)
|
||||
val imageFile = File(storageDir, imageFileName)
|
||||
|
||||
val outputStream = FileOutputStream(imageFile)
|
||||
compositeBitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream)
|
||||
outputStream.flush()
|
||||
outputStream.close()
|
||||
|
||||
// 更新媒体库
|
||||
val mediaScanIntent = android.content.Intent(android.content.Intent.ACTION_MEDIA_SCANNER_SCAN_FILE)
|
||||
val uri = Uri.fromFile(imageFile)
|
||||
mediaScanIntent.data = uri
|
||||
context.sendBroadcast(mediaScanIntent)
|
||||
|
||||
return uri
|
||||
} catch (e: Exception) {
|
||||
Log.e("CutoutScreen", "保存合成结果失败: ${e.message}")
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ViewOption(
|
||||
viewType: ViewType,
|
||||
title: String,
|
||||
onClick: () -> Unit,
|
||||
){
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.wrapContentWidth()
|
||||
.wrapContentHeight()
|
||||
.background(
|
||||
Color(0xFFFFFFFF),
|
||||
shape = RoundedCornerShape(359.dp),
|
||||
)
|
||||
.border(
|
||||
width = 1.dp,
|
||||
color = if(viewType == ViewType.VISIBLE) Color(0xFF000000) else Color(0xFFEDEDED),
|
||||
shape = RoundedCornerShape(359.dp)
|
||||
)
|
||||
.clickable(
|
||||
indication = null,
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
) {
|
||||
onClick()
|
||||
}
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.align(Alignment.Center)
|
||||
.padding(start = 5.dp, end = 7.dp)
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(id = if(viewType == ViewType.VISIBLE) R.mipmap.ic_view else R.mipmap.ic_hide),
|
||||
contentDescription = "图标",
|
||||
modifier = Modifier
|
||||
.wrapContentWidth()
|
||||
.wrapContentHeight()
|
||||
.align(Alignment.CenterVertically)
|
||||
)
|
||||
Text(
|
||||
title,
|
||||
color = if(viewType == ViewType.VISIBLE) Color(0xFF1A1A1A) else Color(0xFF767676),
|
||||
fontSize = 14.sp,
|
||||
lineHeight = 14.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
modifier = Modifier
|
||||
.wrapContentWidth()
|
||||
.wrapContentHeight()
|
||||
.padding(vertical = 2.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
enum class ViewType {
|
||||
HIDE,
|
||||
VISIBLE
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun PreviewCutoutScreen() {
|
||||
CutoutScreen(navController = rememberNavController())
|
||||
}
|
||||
|
|
@ -0,0 +1,176 @@
|
|||
package com.img.rabbit.pages.screen.make
|
||||
|
||||
import android.net.Uri
|
||||
import android.widget.Toast
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.aspectRatio
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.navigation.NavController
|
||||
import coil3.compose.AsyncImage
|
||||
import com.img.rabbit.R
|
||||
import com.img.rabbit.components.DrawingBoardFormatPicker
|
||||
import com.img.rabbit.config.CommonData.formats
|
||||
import com.img.rabbit.pages.toolbar.TitleBar
|
||||
import com.img.rabbit.utils.ExportFormat
|
||||
import com.img.rabbit.utils.ImageUtils.convertToGallery
|
||||
import com.img.rabbit.utils.ImageUtils.getBitmapFromUri
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Composable
|
||||
fun FormatScreen(navController: NavController) {
|
||||
val context = LocalContext.current
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
|
||||
// 当前选中的格式
|
||||
val selectedFormat = remember { mutableStateOf(formats.getOrNull(0)) }
|
||||
|
||||
// 人物图片选择相关状态
|
||||
val selectedImageUri = remember { mutableStateOf<Uri?>(null) }
|
||||
|
||||
// 图片选择启动器
|
||||
val imagePickerLauncher = rememberLauncherForActivityResult(
|
||||
contract = ActivityResultContracts.GetContent()
|
||||
) { uri ->
|
||||
uri?.let {
|
||||
selectedImageUri.value = it
|
||||
}
|
||||
}
|
||||
|
||||
Scaffold{
|
||||
LaunchedEffect(Unit) {
|
||||
imagePickerLauncher.launch("image/*")
|
||||
}
|
||||
Column(
|
||||
modifier = Modifier.fillMaxSize().background(Color(0xFFF4F4F4))
|
||||
) {
|
||||
TitleBar(navController = navController, paddingValues = it, title = "", showSave = true) {
|
||||
val bitmap = selectedImageUri.value?.let {uri->
|
||||
getBitmapFromUri(context, uri)
|
||||
}
|
||||
if(bitmap != null){
|
||||
// 保存图片
|
||||
coroutineScope.launch {
|
||||
// 转换图片格式
|
||||
val format = when(selectedFormat.value?.title){
|
||||
ExportFormat.PNG.name -> ExportFormat.PNG
|
||||
ExportFormat.GIF.name -> ExportFormat.GIF
|
||||
ExportFormat.SVG.name -> ExportFormat.SVG
|
||||
else -> ExportFormat.JPG
|
||||
}
|
||||
convertToGallery(
|
||||
context = context,
|
||||
sourceBitmap = bitmap,
|
||||
format = format,
|
||||
onSubmitResult = { fileName, isSuccess ->
|
||||
if (isSuccess) {
|
||||
Toast.makeText(context, "已保存至 $fileName", Toast.LENGTH_SHORT).show()
|
||||
} else {
|
||||
Toast.makeText(context, "保存失败", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
){
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(start = 38.dp, end = 38.dp, top = 14.dp)
|
||||
.aspectRatio(300f / 420f)
|
||||
.background(Color(0xFFFFFFFF))
|
||||
.border(1.dp, Color(0xFFD8D8D8))
|
||||
){
|
||||
if (selectedImageUri.value != null) {
|
||||
// 显示选中的原始图片
|
||||
AsyncImage(
|
||||
model = selectedImageUri.value,
|
||||
contentDescription = "选中的图片",
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentScale = androidx.compose.ui.layout.ContentScale.Fit
|
||||
)
|
||||
} else {
|
||||
// 空状态
|
||||
Column(
|
||||
modifier = Modifier.align(Alignment.Center)
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(id = R.mipmap.ic_image_empty_pld),
|
||||
contentDescription = "空图",
|
||||
)
|
||||
Text(
|
||||
text = "去相册选一张美照吧~",
|
||||
color = Color(0xFFD8D8D8),
|
||||
fontSize = 12.sp,
|
||||
modifier = Modifier.align(Alignment.CenterHorizontally)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.align(Alignment.BottomCenter)
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 14.dp, vertical = 9.dp)
|
||||
.background(Color(0xFFAAAAAA), shape = RoundedCornerShape(7.dp))
|
||||
.align(Alignment.CenterHorizontally)
|
||||
){
|
||||
|
||||
Text(
|
||||
text = "请选择图片导出格式,用于适配各种平台",
|
||||
color = Color(0xFFFFFFFF),
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
modifier = Modifier.align(Alignment.Center)
|
||||
)
|
||||
}
|
||||
|
||||
Box(Modifier.height(14.dp))
|
||||
|
||||
//底部画板
|
||||
DrawingBoardFormatPicker(
|
||||
//格式列表数据
|
||||
formats = formats,
|
||||
//选中
|
||||
selectedFormat = selectedFormat,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,251 @@
|
|||
package com.img.rabbit.pages.screen.make
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.graphics.Bitmap
|
||||
import android.widget.Toast
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.gestures.detectDragGestures
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.BoxWithConstraints
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||
import androidx.compose.foundation.layout.offset
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.layout.wrapContentHeight
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.KeyboardArrowDown
|
||||
import androidx.compose.material.icons.filled.KeyboardArrowUp
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.ModalBottomSheet
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Slider
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.rememberModalBottomSheetState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.draw.clipToBounds
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.ImageBitmap
|
||||
import androidx.compose.ui.graphics.asImageBitmap
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import androidx.compose.ui.window.DialogProperties
|
||||
import androidx.navigation.NavController
|
||||
import com.img.rabbit.bean.LongImageBean
|
||||
import com.img.rabbit.pages.toolbar.TitleBar
|
||||
import com.img.rabbit.utils.ExportFormat
|
||||
import com.img.rabbit.utils.ImageUtils.getBitmapFromUri
|
||||
import com.img.rabbit.utils.ImageUtils.saveLongToGallery
|
||||
import github.leavesczy.matisse.CoilImageEngine
|
||||
import github.leavesczy.matisse.Matisse
|
||||
import github.leavesczy.matisse.MatisseContract
|
||||
import github.leavesczy.matisse.MediaResource
|
||||
import github.leavesczy.matisse.MediaType
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
|
||||
import androidx.compose.ui.graphics.asAndroidBitmap
|
||||
import io.moyuru.cropify.Cropify
|
||||
import io.moyuru.cropify.CropifyOption
|
||||
import io.moyuru.cropify.rememberCropifyState
|
||||
|
||||
@Composable
|
||||
fun LongImageScreen(navController: NavController) {
|
||||
val context = LocalContext.current
|
||||
val scope = rememberCoroutineScope()
|
||||
var imageItems by remember { mutableStateOf<List<LongImageBean>>(emptyList()) }
|
||||
var editingIndex by remember { mutableStateOf<Int?>(null) }
|
||||
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
val mediaPickerLauncher =
|
||||
rememberLauncherForActivityResult(contract = MatisseContract()) { result: List<MediaResource>? ->
|
||||
if (!result.isNullOrEmpty()) {
|
||||
scope.launch(Dispatchers.IO) {
|
||||
val newItems = result.mapNotNull { mediaResource ->
|
||||
val bitmap = getBitmapFromUri(context, mediaResource.uri)
|
||||
bitmap?.let {
|
||||
LongImageBean(uri = mediaResource.uri, bitmap = it)
|
||||
}
|
||||
}
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
imageItems = imageItems + newItems
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Scaffold {
|
||||
LaunchedEffect(Unit) {
|
||||
val matisse = Matisse(
|
||||
maxSelectable = 10,
|
||||
imageEngine = CoilImageEngine(),
|
||||
mediaType = MediaType.ImageOnly
|
||||
)
|
||||
mediaPickerLauncher.launch(matisse)
|
||||
}
|
||||
Column (
|
||||
modifier = Modifier.fillMaxSize().background(Color(0xFFF4F4F4))
|
||||
) {
|
||||
TitleBar(
|
||||
navController = navController,
|
||||
paddingValues = it,
|
||||
title = "拼长图",
|
||||
showSave = true
|
||||
){
|
||||
coroutineScope.launch {
|
||||
saveLongToGallery(context = context, items = imageItems, format = ExportFormat.JPG){ fileName, isSuccess ->
|
||||
if (isSuccess) {
|
||||
Toast.makeText(context, "已保存至 $fileName", Toast.LENGTH_SHORT).show()
|
||||
} else {
|
||||
Toast.makeText(context, "保存失败", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 预览列表(支持每张图独立缩放)
|
||||
LazyColumn(
|
||||
modifier = Modifier.weight(1f).padding(start = 30.dp, end = 30.dp, bottom = 30.dp)
|
||||
) {
|
||||
itemsIndexed(
|
||||
items = imageItems,
|
||||
// 使用 URI 和 Bitmap 的 hashCode 组合作为 Key
|
||||
key = { index, item -> "${item.uri}_${item.bitmap.hashCode()}" }
|
||||
) { index, item ->
|
||||
Box(modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clipToBounds()
|
||||
.clickable { editingIndex = index }
|
||||
) {
|
||||
Image(
|
||||
bitmap = item.bitmap.asImageBitmap(),
|
||||
contentDescription = null,
|
||||
contentScale = ContentScale.FillWidth,
|
||||
modifier = Modifier.fillMaxWidth().wrapContentHeight()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
editingIndex?.let { index ->
|
||||
FullScreenCropDialog(
|
||||
item = imageItems[index],
|
||||
onDismiss = { editingIndex = null },
|
||||
onConfirmed = { croppedBitmap ->
|
||||
val newList = imageItems.toMutableList()
|
||||
newList[index] = imageItems[index].copy(bitmap = croppedBitmap)
|
||||
imageItems = newList
|
||||
editingIndex = null
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Composable
|
||||
fun FullScreenCropDialog(
|
||||
item: LongImageBean,
|
||||
onDismiss: () -> Unit,
|
||||
onConfirmed: (Bitmap) -> Unit
|
||||
) {
|
||||
val cropifyState = rememberCropifyState()
|
||||
val cropifyOption = remember {
|
||||
CropifyOption(
|
||||
frameColor = Color.Cyan,
|
||||
gridColor = Color.White.copy(alpha = 0.5f),
|
||||
)
|
||||
}
|
||||
|
||||
Dialog(
|
||||
onDismissRequest = onDismiss,
|
||||
properties = DialogProperties(usePlatformDefaultWidth = false)
|
||||
) {
|
||||
Surface(modifier = Modifier.fillMaxSize(), color = Color.Black) {
|
||||
Box(modifier = Modifier.fillMaxSize()) {
|
||||
Cropify(
|
||||
bitmap = item.bitmap.asImageBitmap(),
|
||||
state = cropifyState,
|
||||
option = cropifyOption, // 传入配置项
|
||||
modifier = Modifier.fillMaxSize().padding(bottom = 100.dp),
|
||||
onImageCropped = { croppedImageBitmap ->
|
||||
onConfirmed(croppedImageBitmap.asAndroidBitmap())
|
||||
}
|
||||
)
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomCenter)
|
||||
.padding(bottom = 40.dp)
|
||||
.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceEvenly
|
||||
) {
|
||||
TextButton(onClick = onDismiss) {
|
||||
Text("取消", color = Color.White)
|
||||
}
|
||||
Button(
|
||||
onClick = {
|
||||
// 4. 触发裁剪动作
|
||||
cropifyState.crop()
|
||||
}
|
||||
) {
|
||||
Text("确定裁剪")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun PreviewLongImageScreen(){
|
||||
LongImageScreen(navController = NavController(LocalContext.current))
|
||||
}
|
||||
|
|
@ -0,0 +1,316 @@
|
|||
package com.img.rabbit.pages.screen.make
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.foundation.Canvas
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.gestures.awaitFirstDown
|
||||
import androidx.compose.foundation.gestures.detectTransformGestures
|
||||
import androidx.compose.foundation.gestures.forEachGesture
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.aspectRatio
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clipToBounds
|
||||
import androidx.compose.ui.draw.drawWithContent
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.geometry.center
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.asAndroidBitmap
|
||||
import androidx.compose.ui.graphics.asImageBitmap
|
||||
import androidx.compose.ui.graphics.drawscope.withTransform
|
||||
import androidx.compose.ui.graphics.layer.drawLayer
|
||||
import androidx.compose.ui.graphics.rememberGraphicsLayer
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.compose.ui.unit.toSize
|
||||
import androidx.navigation.NavController
|
||||
import com.img.rabbit.R
|
||||
import com.img.rabbit.components.DrawingBoardCertificatePicker
|
||||
import com.img.rabbit.config.CommonData.resizes
|
||||
import com.img.rabbit.pages.toolbar.TitleBar
|
||||
import com.img.rabbit.utils.ExportFormat
|
||||
import com.img.rabbit.utils.ImageUtils
|
||||
import com.img.rabbit.utils.ImageUtils.saveCanvasToGallery
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Composable
|
||||
fun ResizeScreen(navController: NavController) {
|
||||
val context = LocalContext.current
|
||||
val dealTarget = "RESIZE"
|
||||
// 图片显示区域 - 支持头像编辑(用于最终数据的导出保存Bitmap)
|
||||
val graphicsLayer = rememberGraphicsLayer()
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
//加载图片参数
|
||||
val widthParam = navController.previousBackStackEntry?.savedStateHandle?.get<Float>("width") ?: 23f
|
||||
val heightParam = navController.previousBackStackEntry?.savedStateHandle?.get<Float>("height") ?: 35f
|
||||
|
||||
|
||||
// 当前选中的尺寸
|
||||
val selectedSize = remember { mutableStateOf(resizes.first { it.width == widthParam && it.height == heightParam }) }
|
||||
|
||||
// 加载中状态
|
||||
val isLoading = remember { mutableStateOf(false) }
|
||||
// 人物图片选择相关状态
|
||||
val selectedImageUri = remember { mutableStateOf<Uri?>(null) }
|
||||
val resizeBitmap = remember { mutableStateOf<Bitmap?>(null) }
|
||||
|
||||
|
||||
var selectedTarget by remember { mutableStateOf<String?>(null) }
|
||||
// 编辑状态
|
||||
val transform = remember { mutableStateOf(TransformState()) }
|
||||
|
||||
// 图片选择启动器
|
||||
val imagePickerLauncher = rememberLauncherForActivityResult(
|
||||
contract = ActivityResultContracts.GetContent()
|
||||
) { uri ->
|
||||
uri?.let {
|
||||
selectedImageUri.value = it
|
||||
isLoading.value = true
|
||||
|
||||
// 执行抠图操作
|
||||
Thread {
|
||||
try {
|
||||
val originalBitmap = ImageUtils.getBitmapFromUri(context, it)
|
||||
originalBitmap?.let { bitmap ->
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
context.mainExecutor.execute {
|
||||
resizeBitmap.value = bitmap
|
||||
isLoading.value = false
|
||||
|
||||
// 重置头像变换
|
||||
transform.value = TransformState()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} catch (e: Exception) {
|
||||
Log.e("ResizeScreen", "加载图片失败: ${e.message}")
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
context.mainExecutor.execute {
|
||||
isLoading.value = false
|
||||
Toast.makeText(context, "加载失败,请重试", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}.start()
|
||||
}
|
||||
}
|
||||
|
||||
// 辅助函数:判断点击位置
|
||||
fun hitTest(tap: Offset, state: TransformState, bmp: Bitmap?, canvasCenter: Offset): Boolean {
|
||||
if (bmp == null) return false
|
||||
val w = bmp.width * state.scale
|
||||
val h = bmp.height * state.scale
|
||||
val left = canvasCenter.x + state.offset.x - w / 2
|
||||
val top = canvasCenter.y + state.offset.y - h / 2
|
||||
return tap.x in left..(left + w) && tap.y in top..(top + h)
|
||||
}
|
||||
|
||||
Scaffold{
|
||||
LaunchedEffect(Unit) {
|
||||
imagePickerLauncher.launch("image/*")
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier.fillMaxSize().background(Color(0xFFF4F4F4))
|
||||
) {
|
||||
TitleBar(navController = navController, paddingValues = it, title = "", showSave = true){
|
||||
// 保存图片
|
||||
coroutineScope.launch {
|
||||
// 从 Layer 捕获 Bitmap
|
||||
val bitmap = graphicsLayer.toImageBitmap().asAndroidBitmap()
|
||||
// 保存图片到系统相册(图片已按比例裁剪)
|
||||
saveCanvasToGallery(context, bitmap, ExportFormat.JPG){fileName, isSuccess ->
|
||||
if(isSuccess){
|
||||
Toast.makeText(context, "已保存为: $fileName", Toast.LENGTH_SHORT).show()
|
||||
}else{
|
||||
Toast.makeText(context, "保存失败", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
// 保存图片到系统相册(指定尺寸,如果targetWidth与targetHeight比原始值小太多会导致图片模糊)
|
||||
saveCanvasToGallery(context, bitmap, ExportFormat.JPG, selectedSize.value.width.toInt(), selectedSize.value.height.toInt()){fileName, isSuccess ->
|
||||
if(isSuccess){
|
||||
Toast.makeText(context, "已保存为: $fileName", Toast.LENGTH_SHORT).show()
|
||||
}else{
|
||||
Toast.makeText(context, "保存失败", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
){
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(start = 38.dp, end = 38.dp, top = 14.dp)
|
||||
.aspectRatio(selectedSize.value.width/selectedSize.value.height)
|
||||
.background(Color(0xFFFFFFFF))
|
||||
.border(1.dp, Color(0xFFD8D8D8))
|
||||
){
|
||||
|
||||
if (isLoading.value) {
|
||||
// 加载中状态
|
||||
Column(
|
||||
modifier = Modifier.align(Alignment.Center)
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(id = R.mipmap.ic_loading),
|
||||
contentDescription = "加载效果图片",
|
||||
modifier = Modifier.size(48.dp)
|
||||
)
|
||||
Text(
|
||||
text = "加载中...",
|
||||
color = Color(0xFF767676),
|
||||
fontSize = 12.sp,
|
||||
modifier = Modifier.padding(top = 8.dp)
|
||||
)
|
||||
}
|
||||
} else if (resizeBitmap.value != null) {
|
||||
// 显示合成结果(原始图片)
|
||||
Canvas(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.drawWithContent {
|
||||
// 将内容绘制到 graphicsLayer 中
|
||||
graphicsLayer.record {
|
||||
this@drawWithContent.drawContent()
|
||||
}
|
||||
drawLayer(graphicsLayer)
|
||||
}
|
||||
.clipToBounds()
|
||||
.pointerInput(Unit) {
|
||||
forEachGesture {
|
||||
awaitPointerEventScope {
|
||||
val down = awaitFirstDown()
|
||||
val center = size.toSize().center
|
||||
|
||||
selectedTarget = when {
|
||||
hitTest(down.position, transform.value, resizeBitmap.value, center) -> dealTarget
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.pointerInput(Unit) {
|
||||
detectTransformGestures { _, pan, zoom, rotation ->
|
||||
selectedTarget?.let { target ->
|
||||
// 更新逻辑保持不变...
|
||||
val stateRef = transform
|
||||
|
||||
val bmp = resizeBitmap.value
|
||||
|
||||
bmp?.let { b ->
|
||||
val current = stateRef.value
|
||||
val newScale = (current.scale * zoom).coerceIn(0.1f, 5f)
|
||||
stateRef.value = current.copy(
|
||||
offset = current.offset + pan,
|
||||
scale = newScale,
|
||||
rotation = if (target == dealTarget) current.rotation + rotation else current.rotation
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
) {
|
||||
val canvasCenter = center
|
||||
resizeBitmap.value?.let { bmp ->
|
||||
withTransform({
|
||||
translate(transform.value.offset.x, transform.value.offset.y)
|
||||
rotate(transform.value.rotation, canvasCenter)
|
||||
scale(transform.value.scale, transform.value.scale, canvasCenter)
|
||||
}) {
|
||||
drawImage(bmp.asImageBitmap(), Offset(canvasCenter.x - bmp.width/2, canvasCenter.y - bmp.height/2))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
// 空状态
|
||||
Column(
|
||||
modifier = Modifier.align(Alignment.Center)
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(id = R.mipmap.ic_image_empty_pld),
|
||||
contentDescription = "截图",
|
||||
)
|
||||
Text(
|
||||
text = "去相册选一张美照吧~",
|
||||
color = Color(0xFFD8D8D8),
|
||||
fontSize = 12.sp,
|
||||
modifier = Modifier.align(Alignment.CenterHorizontally)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.align(Alignment.BottomCenter)
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 14.dp, vertical = 9.dp)
|
||||
.background(Color(0xFFAAAAAA), shape = RoundedCornerShape(7.dp))
|
||||
.align(Alignment.CenterHorizontally)
|
||||
){
|
||||
|
||||
Text(
|
||||
text = "将已有证件照尺寸进行修改,用于适配各种场景",
|
||||
color = Color(0xFFFFFFFF),
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
modifier = Modifier.align(Alignment.Center)
|
||||
)
|
||||
}
|
||||
|
||||
Box(Modifier.height(14.dp))
|
||||
|
||||
//底部画板
|
||||
DrawingBoardCertificatePicker(
|
||||
//尺寸列表数据
|
||||
sizes = resizes,
|
||||
//选中
|
||||
selectedSize = selectedSize,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,419 @@
|
|||
package com.img.rabbit.pages.screen.mine
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.layout.wrapContentHeight
|
||||
import androidx.compose.foundation.layout.wrapContentSize
|
||||
import androidx.compose.foundation.layout.wrapContentWidth
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.BasicTextField
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import com.img.rabbit.components.ImagePicker
|
||||
import com.img.rabbit.pages.toolbar.TitleBar
|
||||
import com.img.rabbit.viewmodel.FeedbackViewModel
|
||||
|
||||
@Composable
|
||||
fun FeedbackScreen(navController: NavHostController, viewModel: FeedbackViewModel = viewModel()) {
|
||||
Scaffold{
|
||||
Column(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
TitleBar(navController = navController, paddingValues = it, title = "意见反馈")
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.fillMaxHeight()
|
||||
.padding(start = 16.dp, end = 16.dp)
|
||||
){
|
||||
// 意见反馈内容
|
||||
Column(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
// 意见反馈类型
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().wrapContentHeight()
|
||||
) {
|
||||
Text(
|
||||
text = "*",
|
||||
color = Color(0xFFEA0000),
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
)
|
||||
Text(
|
||||
text = "您想反馈的功能类型",
|
||||
color = Color(0xFF1A1A1A),
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
modifier = Modifier.wrapContentSize()
|
||||
)
|
||||
}
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().wrapContentHeight().padding(top = 12.dp)
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(34.dp)
|
||||
.weight(1f)
|
||||
.background(
|
||||
Color(if (viewModel.feedbackType == FeedbackViewModel.FeedbackType.FUNCTION) 0xFF252525 else 0xFFFFFFFF),
|
||||
shape = RoundedCornerShape(8.dp)
|
||||
)
|
||||
.border(
|
||||
width = 1.dp,
|
||||
Color(0xFFD8D8D8),
|
||||
shape = RoundedCornerShape(8.dp)
|
||||
)
|
||||
.clickable(
|
||||
indication = null,
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
) { viewModel.setFeedbackType(FeedbackViewModel.FeedbackType.FUNCTION) }
|
||||
){
|
||||
Text(
|
||||
text = "功能问题",
|
||||
color = Color(if (viewModel.feedbackType == FeedbackViewModel.FeedbackType.FUNCTION) 0xFFC2FF43 else 0xFF1A1A1A),
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
modifier = Modifier
|
||||
.wrapContentSize()
|
||||
.align(Alignment.Center)
|
||||
)
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier.width(21.dp)
|
||||
)
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(34.dp)
|
||||
.weight(1f)
|
||||
.background(
|
||||
Color(if (viewModel.feedbackType == FeedbackViewModel.FeedbackType.FEATURE) 0xFF252525 else 0xFFFFFFFF),
|
||||
shape = RoundedCornerShape(8.dp)
|
||||
)
|
||||
.border(
|
||||
width = 1.dp,
|
||||
Color(0xFFD8D8D8),
|
||||
shape = RoundedCornerShape(8.dp)
|
||||
)
|
||||
.clickable(
|
||||
indication = null,
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
) { viewModel.setFeedbackType(FeedbackViewModel.FeedbackType.FEATURE) }
|
||||
){
|
||||
Text(
|
||||
text = "优化建议",
|
||||
color = Color(if (viewModel.feedbackType == FeedbackViewModel.FeedbackType.FEATURE) 0xFFC2FF43 else 0xFF1A1A1A),
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
modifier = Modifier
|
||||
.wrapContentSize()
|
||||
.align(Alignment.Center)
|
||||
)
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier.width(21.dp)
|
||||
)
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(34.dp)
|
||||
.weight(1f)
|
||||
.background(
|
||||
Color(if (viewModel.feedbackType == FeedbackViewModel.FeedbackType.OTHER) 0xFF252525 else 0xFFFFFFFF),
|
||||
shape = RoundedCornerShape(8.dp)
|
||||
)
|
||||
.border(
|
||||
width = 1.dp,
|
||||
Color(0xFFD8D8D8),
|
||||
shape = RoundedCornerShape(8.dp)
|
||||
)
|
||||
.clickable(
|
||||
indication = null,
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
) { viewModel.setFeedbackType(FeedbackViewModel.FeedbackType.OTHER) }
|
||||
){
|
||||
Text(
|
||||
text = "其他",
|
||||
color = Color(if (viewModel.feedbackType == FeedbackViewModel.FeedbackType.OTHER) 0xFFC2FF43 else 0xFF1A1A1A),
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
modifier = Modifier
|
||||
.wrapContentSize()
|
||||
.align(Alignment.Center)
|
||||
)
|
||||
}
|
||||
}
|
||||
//补充反馈内容
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth().wrapContentHeight().padding(top = 18.dp)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().wrapContentHeight()
|
||||
) {
|
||||
Text(
|
||||
text = "*",
|
||||
color = Color(0xFFEA0000),
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
)
|
||||
Text(
|
||||
text = "请补充详细问题或意见",
|
||||
color = Color(0xFF1A1A1A),
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
modifier = Modifier.wrapContentSize()
|
||||
)
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(150.dp)
|
||||
.padding(top = 12.dp)
|
||||
.background(
|
||||
Color(0xFFEEEEEE),
|
||||
shape = RoundedCornerShape(12.dp)
|
||||
)
|
||||
.border(
|
||||
width = 1.dp,
|
||||
Color(0xFFEEEEEE),
|
||||
shape = RoundedCornerShape(12.dp)
|
||||
)
|
||||
){
|
||||
BasicTextField(
|
||||
value = viewModel.feedbackMore,
|
||||
onValueChange = {content->
|
||||
if (content.length <= 200) {
|
||||
viewModel.setFeedbackMore(content)
|
||||
}
|
||||
},
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(Color.Transparent)
|
||||
.padding(start = 18.dp, top = 7.dp, end = 18.dp, bottom = 7.dp),
|
||||
textStyle = androidx.compose.ui.text.TextStyle(
|
||||
color = Color.Black,
|
||||
fontSize = 14.sp
|
||||
),
|
||||
singleLine = false,
|
||||
keyboardOptions = androidx.compose.foundation.text.KeyboardOptions(
|
||||
keyboardType = androidx.compose.ui.text.input.KeyboardType.Text,
|
||||
imeAction = androidx.compose.ui.text.input.ImeAction.Done
|
||||
),
|
||||
decorationBox = { innerTextField ->
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(Color.Transparent)
|
||||
) {
|
||||
if (viewModel.feedbackMore.isEmpty()) {
|
||||
Text(
|
||||
"请在这里输入内容",
|
||||
color = Color(0xFF767676),
|
||||
fontSize = 14.sp
|
||||
)
|
||||
}
|
||||
innerTextField()
|
||||
}
|
||||
}
|
||||
)
|
||||
Text(
|
||||
text = "${viewModel.feedbackMore.length}/200",
|
||||
color = Color(0xFF767676),
|
||||
fontSize = 14.sp,
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomEnd)
|
||||
.padding(end = 18.dp, bottom = 7.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
//提供相关图片
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth().wrapContentHeight().padding(top = 18.dp)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().wrapContentHeight()
|
||||
) {
|
||||
Text(
|
||||
text = "请提供相关问题的图片",
|
||||
color = Color(0xFF1A1A1A),
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
)
|
||||
Text(
|
||||
text = "(选填,最多可上传三张)",
|
||||
color = Color(0xFFAAAAAA),
|
||||
fontSize = 11.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
modifier = Modifier.wrapContentSize()
|
||||
.align(Alignment.CenterVertically)
|
||||
)
|
||||
}
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight().padding(top = 12.dp)
|
||||
) {
|
||||
ImagePicker(
|
||||
modifier = Modifier.fillMaxWidth().wrapContentHeight(),
|
||||
imageHeight = 100.dp,
|
||||
aspectRatio = 100f / 100f,
|
||||
maxCount = 3,
|
||||
addButtonName = "添加图片",
|
||||
currentImageUris = viewModel.currentImageUris,
|
||||
currentImagePaths = emptyList(), // 新增参数
|
||||
onImagesUpdated = { uris, _ ->
|
||||
viewModel.setCurrentImageUris(uris)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
//联系方式
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth().wrapContentHeight().padding(top = 18.dp)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().wrapContentHeight()
|
||||
) {
|
||||
Text(
|
||||
text = "联系方式",
|
||||
color = Color(0xFF1A1A1A),
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
)
|
||||
Text(
|
||||
text = "(选填,可以留下您的手机号、微信、邮箱)",
|
||||
color = Color(0xFFAAAAAA),
|
||||
fontSize = 11.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
modifier = Modifier.wrapContentSize()
|
||||
.align(Alignment.CenterVertically)
|
||||
)
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight()
|
||||
.padding(top = 12.dp)
|
||||
.background(
|
||||
Color(0xFFEEEEEE),
|
||||
shape = RoundedCornerShape(8.dp)
|
||||
)
|
||||
.border(
|
||||
width = 1.dp,
|
||||
Color(0xFFEEEEEE),
|
||||
shape = RoundedCornerShape(8.dp)
|
||||
)
|
||||
){
|
||||
BasicTextField(
|
||||
value = viewModel.feedbackContact,
|
||||
onValueChange = {content->
|
||||
if (content.length <= 50) {
|
||||
viewModel.setFeedbackContact(content)
|
||||
}
|
||||
},
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight()
|
||||
.background(Color.Transparent)
|
||||
.padding(start = 18.dp, top = 10.dp, end = 18.dp, bottom = 10.dp),
|
||||
textStyle = androidx.compose.ui.text.TextStyle(
|
||||
color = Color.Black,
|
||||
fontSize = 14.sp
|
||||
),
|
||||
singleLine = true,
|
||||
maxLines = 1,
|
||||
keyboardOptions = androidx.compose.foundation.text.KeyboardOptions(
|
||||
keyboardType = androidx.compose.ui.text.input.KeyboardType.Text,
|
||||
imeAction = androidx.compose.ui.text.input.ImeAction.Done
|
||||
),
|
||||
decorationBox = { innerTextField ->
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight()
|
||||
.background(Color.Transparent)
|
||||
) {
|
||||
if (viewModel.feedbackContact.isEmpty()) {
|
||||
Text(
|
||||
"请输入您的联系方式",
|
||||
color = Color(0xFF767676),
|
||||
fontSize = 14.sp
|
||||
)
|
||||
}
|
||||
innerTextField()
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//提交
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight()
|
||||
.padding(start = 33.dp, end = 33.dp, bottom = 30.dp)
|
||||
.background(
|
||||
Color(0xFF252525),
|
||||
shape = RoundedCornerShape(359.dp),
|
||||
)
|
||||
.clickable(
|
||||
indication = null,
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
) {
|
||||
//TODO 提交反馈
|
||||
viewModel.submitFeedback()
|
||||
}
|
||||
.align(Alignment.BottomCenter)
|
||||
) {
|
||||
Text(
|
||||
"提交",
|
||||
color = Color(0xFFC2FF43),
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
modifier = Modifier
|
||||
.wrapContentWidth()
|
||||
.wrapContentHeight()
|
||||
.padding(vertical = 12.dp)
|
||||
.align(Alignment.Center)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun PreviewFeedbackScreen(){
|
||||
FeedbackScreen(navController = rememberNavController())
|
||||
}
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
package com.img.rabbit.pages.screen.mine
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import com.img.rabbit.R
|
||||
|
||||
@Composable
|
||||
fun OnlineServiceScreen(navController: NavHostController) {
|
||||
Scaffold{
|
||||
Column(
|
||||
modifier = Modifier.fillMaxSize().padding(it)
|
||||
) {
|
||||
// 顶部栏
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
// 返回按钮
|
||||
Icon(
|
||||
painter = painterResource(id = R.mipmap.ic_back),
|
||||
contentDescription = "返回",
|
||||
modifier = Modifier
|
||||
.clickable(
|
||||
indication = null,
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
) { navController.popBackStack() }
|
||||
.padding(end = 16.dp)
|
||||
)
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth().padding(end = 26.dp)
|
||||
) {
|
||||
|
||||
Text(
|
||||
text = "",
|
||||
color = Color(0xFF1A1A1A),
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
modifier = Modifier.align(Alignment.CenterHorizontally)
|
||||
)
|
||||
}
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier.fillMaxWidth().padding(end = 26.dp)
|
||||
){
|
||||
//TODO 在线客服内容
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun PreviewOnlineServiceScreen(){
|
||||
OnlineServiceScreen(navController = rememberNavController())
|
||||
}
|
||||
|
|
@ -0,0 +1,320 @@
|
|||
package com.img.rabbit.pages.screen.mine
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.wrapContentHeight
|
||||
import androidx.compose.foundation.layout.wrapContentSize
|
||||
import androidx.compose.foundation.layout.wrapContentWidth
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.shadow
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import com.img.rabbit.R
|
||||
import com.img.rabbit.pages.toolbar.TitleBar
|
||||
|
||||
@Composable
|
||||
fun SettingScreen(navController: NavHostController) {
|
||||
Scaffold{
|
||||
Column(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
TitleBar(navController = navController, paddingValues = it, title = "设置")
|
||||
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize().padding(start = 17.dp, end = 17.dp)
|
||||
){
|
||||
//TODO 设置内容
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.wrapContentSize()
|
||||
.padding(top = 16.dp)
|
||||
.background(
|
||||
Color(0xFFFFFFFF),
|
||||
RoundedCornerShape(18.dp)
|
||||
)
|
||||
.shadow(
|
||||
elevation = 4.dp,
|
||||
shape = RoundedCornerShape(8.dp),
|
||||
ambientColor = Color(0x4DE5E5E5),
|
||||
spotColor = Color(0x4DE5E5E5)
|
||||
)
|
||||
){
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(52.dp)
|
||||
.clickable(
|
||||
indication = null,
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
) {
|
||||
// 跳转页面
|
||||
//navController.navigate("feedback")
|
||||
},
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier.weight(1f)
|
||||
){
|
||||
Text(
|
||||
text = "清除缓存",
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = Color(0xFF1A1A1A),
|
||||
modifier = Modifier
|
||||
.wrapContentSize()
|
||||
.padding(start = 8.dp)
|
||||
)
|
||||
}
|
||||
|
||||
Row(
|
||||
modifier = Modifier.wrapContentSize()
|
||||
) {
|
||||
Text(
|
||||
"12MB",
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
modifier = Modifier
|
||||
.wrapContentSize()
|
||||
.align(Alignment.CenterVertically),
|
||||
color = Color(0xFF767676),
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
Image(
|
||||
painter = painterResource(id = R.mipmap.ic_arrow_right),
|
||||
contentDescription = null,
|
||||
contentScale = ContentScale.FillWidth,
|
||||
modifier = Modifier
|
||||
.wrapContentSize()
|
||||
.padding(end = 12.dp)
|
||||
.align(Alignment.CenterVertically)
|
||||
)
|
||||
}
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier.fillMaxWidth().height(0.5.dp).padding(horizontal = 12.dp).background(
|
||||
Color(0x4DBBBBBB)
|
||||
)
|
||||
)
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(52.dp)
|
||||
.clickable(
|
||||
indication = null,
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
) {
|
||||
// 跳转绑定页面
|
||||
navController.navigate("bindAccount")
|
||||
},
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier.weight(1f)
|
||||
){
|
||||
Text(
|
||||
text = "账号绑定",
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = Color(0xFF1A1A1A),
|
||||
modifier = Modifier
|
||||
.wrapContentSize()
|
||||
.padding(start = 8.dp)
|
||||
)
|
||||
}
|
||||
|
||||
Image(
|
||||
painter = painterResource(id = R.mipmap.ic_arrow_right),
|
||||
contentDescription = null,
|
||||
contentScale = ContentScale.FillWidth,
|
||||
modifier = Modifier
|
||||
.wrapContentSize()
|
||||
.padding(end = 12.dp)
|
||||
.align(Alignment.CenterVertically)
|
||||
)
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier.fillMaxWidth().height(0.5.dp).padding(horizontal = 12.dp).background(
|
||||
Color(0x4DBBBBBB)
|
||||
)
|
||||
)
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(52.dp)
|
||||
.clickable(
|
||||
indication = null,
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
) {
|
||||
// 跳转账号管理页面
|
||||
navController.navigate("managerAccount")
|
||||
},
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier.weight(1f)
|
||||
){
|
||||
Text(
|
||||
text = "账号管理",
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = Color(0xFF1A1A1A),
|
||||
modifier = Modifier
|
||||
.wrapContentSize()
|
||||
.padding(start = 8.dp)
|
||||
)
|
||||
}
|
||||
|
||||
Image(
|
||||
painter = painterResource(id = R.mipmap.ic_arrow_right),
|
||||
contentDescription = null,
|
||||
contentScale = ContentScale.FillWidth,
|
||||
modifier = Modifier
|
||||
.wrapContentSize()
|
||||
.padding(end = 12.dp)
|
||||
.align(Alignment.CenterVertically)
|
||||
)
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier.fillMaxWidth().height(0.5.dp).padding(horizontal = 12.dp).background(
|
||||
Color(0x4DBBBBBB)
|
||||
)
|
||||
)
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(52.dp)
|
||||
.clickable(
|
||||
indication = null,
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
) {
|
||||
// 跳转关于我们页面
|
||||
navController.navigate("aboutMine")
|
||||
},
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier.weight(1f)
|
||||
){
|
||||
Text(
|
||||
text = "关于我们",
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = Color(0xFF1A1A1A),
|
||||
modifier = Modifier
|
||||
.wrapContentSize()
|
||||
.padding(start = 8.dp)
|
||||
)
|
||||
}
|
||||
|
||||
Image(
|
||||
painter = painterResource(id = R.mipmap.ic_arrow_right),
|
||||
contentDescription = null,
|
||||
contentScale = ContentScale.FillWidth,
|
||||
modifier = Modifier
|
||||
.wrapContentSize()
|
||||
.padding(end = 12.dp)
|
||||
.align(Alignment.CenterVertically)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
//切换/退出账号
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth().align(Alignment.BottomCenter)
|
||||
) {
|
||||
//账号切换
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight()
|
||||
.padding(start = 33.dp, end = 33.dp, bottom = 18.dp)
|
||||
.background(
|
||||
Color(0x00000000),
|
||||
shape = RoundedCornerShape(359.dp),
|
||||
)
|
||||
.border(width = 1.dp, color = Color(0xFF000000), shape = RoundedCornerShape(359.dp))
|
||||
.clickable(
|
||||
indication = null,
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
) {
|
||||
//TODO 账号切换
|
||||
navController.navigate("managerAccount")
|
||||
}
|
||||
) {
|
||||
Text(
|
||||
"账号切换",
|
||||
color = Color(0xFF1A1A1A),
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
modifier = Modifier
|
||||
.wrapContentWidth()
|
||||
.wrapContentHeight()
|
||||
.padding(vertical = 12.dp)
|
||||
.align(Alignment.Center)
|
||||
)
|
||||
}
|
||||
|
||||
//退出登录
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight()
|
||||
.padding(start = 33.dp, end = 33.dp, bottom = 30.dp)
|
||||
.background(
|
||||
Color(0xFF252525),
|
||||
shape = RoundedCornerShape(359.dp),
|
||||
)
|
||||
.clickable(
|
||||
indication = null,
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
) {
|
||||
//TODO 退出登录
|
||||
}
|
||||
) {
|
||||
Text(
|
||||
"退出登录",
|
||||
color = Color(0xFFC2FF43),
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
modifier = Modifier
|
||||
.wrapContentWidth()
|
||||
.wrapContentHeight()
|
||||
.padding(vertical = 12.dp)
|
||||
.align(Alignment.Center)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun PreviewSettingScreen(){
|
||||
SettingScreen(navController = rememberNavController())
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package com.img.rabbit.pages.screen.mine
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
|
||||
@Composable
|
||||
fun VersionUpdateScreen(navController: NavHostController) {
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun PreviewVersionUpdateScreen(){
|
||||
VersionUpdateScreen(navController = rememberNavController())
|
||||
}
|
||||
|
|
@ -0,0 +1,186 @@
|
|||
package com.img.rabbit.pages.screen.mine.setting
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.aspectRatio
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.layout.wrapContentSize
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.shadow
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import com.img.rabbit.R
|
||||
import com.img.rabbit.config.Common.agreementUrl
|
||||
import com.img.rabbit.config.Common.privacyUrl
|
||||
import com.img.rabbit.pages.toolbar.TitleBar
|
||||
import com.img.rabbit.utils.UrlLinkUtils.openAgreement
|
||||
|
||||
@Composable
|
||||
fun AboutScreen(navController: NavHostController) {
|
||||
Scaffold{
|
||||
val context = LocalContext.current
|
||||
Column(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
TitleBar(navController = navController, paddingValues = it, title = "关于我们")
|
||||
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize().padding(start = 17.dp, end = 17.dp)
|
||||
){
|
||||
//TODO 设置内容
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(top = 100.dp)
|
||||
){
|
||||
Image(
|
||||
painter = painterResource(id = R.mipmap.ic_launcher_logo),
|
||||
contentDescription = "关于我们",
|
||||
modifier = Modifier
|
||||
.width(100.dp)
|
||||
.aspectRatio(1f)
|
||||
.align(Alignment.CenterHorizontally)
|
||||
)
|
||||
|
||||
|
||||
|
||||
Text(
|
||||
text = "截图兔",
|
||||
fontSize = 18.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = Color(0xFF1A1A1A),
|
||||
modifier = Modifier.align(Alignment.CenterHorizontally).padding(top = 24.dp)
|
||||
)
|
||||
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.wrapContentSize()
|
||||
.padding(top = 24.dp)
|
||||
.background(
|
||||
Color(0xFFFFFFFF),
|
||||
RoundedCornerShape(18.dp)
|
||||
)
|
||||
.shadow(
|
||||
elevation = 4.dp,
|
||||
shape = RoundedCornerShape(8.dp),
|
||||
ambientColor = Color(0x4DE5E5E5),
|
||||
spotColor = Color(0x4DE5E5E5)
|
||||
)
|
||||
){
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(52.dp)
|
||||
.clickable(
|
||||
indication = null,
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
) {
|
||||
// 跳转用户协议页面
|
||||
openAgreement(context, agreementUrl)
|
||||
},
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier.weight(1f)
|
||||
){
|
||||
Text(
|
||||
text = "用户协议",
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = Color(0xFF1A1A1A),
|
||||
modifier = Modifier
|
||||
.wrapContentSize()
|
||||
.padding(start = 8.dp)
|
||||
)
|
||||
}
|
||||
|
||||
Image(
|
||||
painter = painterResource(id = R.mipmap.ic_arrow_right),
|
||||
contentDescription = null,
|
||||
contentScale = ContentScale.FillWidth,
|
||||
modifier = Modifier
|
||||
.wrapContentSize()
|
||||
.padding(end = 12.dp)
|
||||
.align(Alignment.CenterVertically)
|
||||
)
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier.fillMaxWidth().height(0.5.dp).padding(horizontal = 12.dp).background(
|
||||
Color(0x4DBBBBBB)
|
||||
)
|
||||
)
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(52.dp)
|
||||
.clickable(
|
||||
indication = null,
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
) {
|
||||
// 跳转隐私政策页面
|
||||
openAgreement(context, privacyUrl)
|
||||
},
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier.weight(1f)
|
||||
){
|
||||
Text(
|
||||
text = "隐私政策",
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = Color(0xFF1A1A1A),
|
||||
modifier = Modifier
|
||||
.wrapContentSize()
|
||||
.padding(start = 8.dp)
|
||||
)
|
||||
}
|
||||
|
||||
Image(
|
||||
painter = painterResource(id = R.mipmap.ic_arrow_right),
|
||||
contentDescription = null,
|
||||
contentScale = ContentScale.FillWidth,
|
||||
modifier = Modifier
|
||||
.wrapContentSize()
|
||||
.padding(end = 12.dp)
|
||||
.align(Alignment.CenterVertically)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun PreviewAboutScreen(){
|
||||
AboutScreen(navController = rememberNavController())
|
||||
}
|
||||
|
|
@ -0,0 +1,380 @@
|
|||
package com.img.rabbit.pages.screen.mine.setting
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.layout.wrapContentHeight
|
||||
import androidx.compose.foundation.layout.wrapContentSize
|
||||
import androidx.compose.foundation.layout.wrapContentWidth
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.shadow
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import com.img.rabbit.R
|
||||
import com.img.rabbit.pages.toolbar.TitleBar
|
||||
|
||||
@SuppressLint("UnrememberedMutableState")
|
||||
@Composable
|
||||
fun AccountBindScreen(navController: NavHostController) {
|
||||
val showDialogStatus = mutableStateOf(false)
|
||||
Scaffold{
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
){
|
||||
Column(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
TitleBar(navController = navController, paddingValues = it, title = "账号绑定")
|
||||
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize().padding(start = 17.dp, end = 17.dp)
|
||||
){
|
||||
//TODO 设置内容
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.wrapContentSize()
|
||||
.padding(top = 16.dp)
|
||||
.background(
|
||||
Color(0xFFFFFFFF),
|
||||
RoundedCornerShape(18.dp)
|
||||
)
|
||||
.shadow(
|
||||
elevation = 4.dp,
|
||||
shape = RoundedCornerShape(8.dp),
|
||||
ambientColor = Color(0x4DE5E5E5),
|
||||
spotColor = Color(0x4DE5E5E5)
|
||||
)
|
||||
){
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(52.dp)
|
||||
.clickable(
|
||||
indication = null,
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
) {
|
||||
//TODO 跳转绑定与解绑手机号
|
||||
showDialogStatus.value = !showDialogStatus.value
|
||||
},
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier.weight(1f)
|
||||
){
|
||||
Text(
|
||||
text = "手机号",
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = Color(0xFF1A1A1A),
|
||||
modifier = Modifier
|
||||
.wrapContentSize()
|
||||
.padding(start = 8.dp)
|
||||
)
|
||||
}
|
||||
|
||||
Row(
|
||||
modifier = Modifier.wrapContentSize()
|
||||
) {
|
||||
Text(
|
||||
"+86 123****123",
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
modifier = Modifier
|
||||
.wrapContentSize()
|
||||
.align(Alignment.CenterVertically),
|
||||
color = Color(0xFF767676),
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
Image(
|
||||
painter = painterResource(id = R.mipmap.ic_arrow_right),
|
||||
contentDescription = null,
|
||||
contentScale = ContentScale.FillWidth,
|
||||
modifier = Modifier
|
||||
.wrapContentSize()
|
||||
.padding(end = 12.dp)
|
||||
.align(Alignment.CenterVertically)
|
||||
)
|
||||
}
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier.fillMaxWidth().height(0.5.dp).padding(horizontal = 12.dp).background(
|
||||
Color(0x4DBBBBBB)
|
||||
)
|
||||
)
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(52.dp)
|
||||
.clickable(
|
||||
indication = null,
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
) {
|
||||
//TODO 跳转绑定或解绑微信
|
||||
},
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier.weight(1f)
|
||||
){
|
||||
Text(
|
||||
text = "微信账号",
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = Color(0xFF1A1A1A),
|
||||
modifier = Modifier
|
||||
.wrapContentSize()
|
||||
.padding(start = 8.dp)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
|
||||
Row(
|
||||
modifier = Modifier.wrapContentSize()
|
||||
) {
|
||||
Text(
|
||||
"去绑定",
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
modifier = Modifier
|
||||
.wrapContentSize()
|
||||
.align(Alignment.CenterVertically),
|
||||
color = Color(0xFF767676),
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
Image(
|
||||
painter = painterResource(id = R.mipmap.ic_arrow_right),
|
||||
contentDescription = null,
|
||||
contentScale = ContentScale.FillWidth,
|
||||
modifier = Modifier
|
||||
.wrapContentSize()
|
||||
.padding(end = 12.dp)
|
||||
.align(Alignment.CenterVertically)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(showDialogStatus.value){
|
||||
UnBindPhoneDialog(
|
||||
showDialogStatus = showDialogStatus,
|
||||
onStatusChange = {
|
||||
showDialogStatus.value = it
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun UnBindPhoneDialog(
|
||||
showDialogStatus: MutableState<Boolean>,
|
||||
onStatusChange: (Boolean) -> Unit
|
||||
){
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(Color(0x80000000))
|
||||
.clickable(
|
||||
indication = null,
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
){
|
||||
showDialogStatus.value = false
|
||||
},
|
||||
verticalArrangement = Arrangement.Center
|
||||
){
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight()
|
||||
.padding(horizontal = 36.dp)
|
||||
.background(Color.White, shape = RoundedCornerShape(26.dp))
|
||||
.align(Alignment.CenterHorizontally)
|
||||
.clickable(
|
||||
indication = null,
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
){
|
||||
//什么都不用做,只是解决点击穿透问题
|
||||
}
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(id = R.mipmap.ic_dialog_top_mask1),
|
||||
contentDescription = null,
|
||||
contentScale = ContentScale.FillWidth,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
)
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight()
|
||||
.padding(top = 46.dp)
|
||||
) {
|
||||
Text(
|
||||
text = "解开绑定的手机号?",
|
||||
fontSize = 18.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = Color(0xFF1A1A1A),
|
||||
modifier = Modifier
|
||||
.wrapContentSize()
|
||||
.align(Alignment.CenterHorizontally)
|
||||
)
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(14.dp)
|
||||
)
|
||||
|
||||
Text(
|
||||
text = "当前绑定的手机号码为",
|
||||
fontSize = 12.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
color = Color(0xFF767676),
|
||||
modifier = Modifier
|
||||
.wrapContentSize()
|
||||
.align(Alignment.CenterHorizontally)
|
||||
)
|
||||
|
||||
Text(
|
||||
text = "+86 123****456",
|
||||
fontSize = 12.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
color = Color(0xFF767676),
|
||||
modifier = Modifier
|
||||
.wrapContentSize()
|
||||
.align(Alignment.CenterHorizontally)
|
||||
)
|
||||
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(16.dp)
|
||||
)
|
||||
|
||||
//切换/退出账号
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().padding(start = 18.dp, end = 18.dp, bottom = 20.dp)
|
||||
) {
|
||||
//取消解绑手机号
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight()
|
||||
.weight(1f)
|
||||
.background(
|
||||
Color(0x00000000),
|
||||
shape = RoundedCornerShape(359.dp),
|
||||
)
|
||||
.border(width = 1.dp, color = Color(0xFF000000), shape = RoundedCornerShape(359.dp))
|
||||
.clickable(
|
||||
indication = null,
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
) {
|
||||
//TODO 取消解绑手机号
|
||||
showDialogStatus.value = false
|
||||
}
|
||||
) {
|
||||
Text(
|
||||
"取消",
|
||||
color = Color(0xFF1A1A1A),
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
modifier = Modifier
|
||||
.wrapContentWidth()
|
||||
.wrapContentHeight()
|
||||
.padding(vertical = 12.dp)
|
||||
.align(Alignment.Center)
|
||||
)
|
||||
}
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.width(11.dp)
|
||||
)
|
||||
|
||||
|
||||
|
||||
//退出登录
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight()
|
||||
.weight(1f)
|
||||
.background(
|
||||
Color(0xFF252525),
|
||||
shape = RoundedCornerShape(359.dp),
|
||||
)
|
||||
.clickable(
|
||||
indication = null,
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
) {
|
||||
//TODO 确定解绑手机号
|
||||
showDialogStatus.value = false
|
||||
}
|
||||
) {
|
||||
Text(
|
||||
"确定",
|
||||
color = Color(0xFFC2FF43),
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
modifier = Modifier
|
||||
.wrapContentWidth()
|
||||
.wrapContentHeight()
|
||||
.padding(vertical = 12.dp)
|
||||
.align(Alignment.Center)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun PreviewAccountBindScreen(){
|
||||
AccountBindScreen(navController = rememberNavController())
|
||||
}
|
||||
|
||||
@SuppressLint("UnrememberedMutableState")
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun PreviewUnBindPhoneDialog(){
|
||||
UnBindPhoneDialog(showDialogStatus = mutableStateOf(true), onStatusChange = {})
|
||||
}
|
||||
|
|
@ -0,0 +1,270 @@
|
|||
package com.img.rabbit.pages.screen.mine.setting
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.wrapContentHeight
|
||||
import androidx.compose.foundation.layout.wrapContentSize
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Checkbox
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.draw.scale
|
||||
import androidx.compose.ui.draw.shadow
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import com.img.rabbit.R
|
||||
import com.img.rabbit.bean.UserInfo
|
||||
import com.img.rabbit.pages.toolbar.TitleBar
|
||||
|
||||
@Composable
|
||||
fun AccountManagerScreen(navController: NavHostController) {
|
||||
val userList by remember {
|
||||
mutableStateOf(
|
||||
listOf(
|
||||
UserInfo(1, "张三", true),
|
||||
UserInfo(2, "李四", false),
|
||||
UserInfo(3, "王五", false),
|
||||
)
|
||||
)
|
||||
}
|
||||
Scaffold{
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
){
|
||||
Image(
|
||||
painter = painterResource(id = R.mipmap.ic_account_switch_top_mask),
|
||||
contentDescription = "蒙层",
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentScale = ContentScale.Crop
|
||||
)
|
||||
|
||||
|
||||
Column(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
TitleBar(navController = navController, paddingValues = it, title = "")
|
||||
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth().padding(top = 100.dp)
|
||||
) {
|
||||
|
||||
Text(
|
||||
text = "轻触头像以切换帐号",
|
||||
color = Color(0xFF3D3D3D),
|
||||
fontSize = 24.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
modifier = Modifier.align(Alignment.CenterHorizontally)
|
||||
)
|
||||
}
|
||||
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize().padding(top = 55.dp, start = 17.dp, end = 17.dp)
|
||||
){
|
||||
//TODO 设置内容
|
||||
LazyColumn {
|
||||
// 添加五个项目
|
||||
items(userList.size) { index ->
|
||||
ListItem(userList[index])
|
||||
}
|
||||
item {
|
||||
AddItem(navController)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ListItem(item: UserInfo){
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight()
|
||||
.padding(bottom = 10.dp)
|
||||
.background(
|
||||
color = Color(0xFFFFFFFF),
|
||||
shape = RoundedCornerShape(8.dp)
|
||||
)
|
||||
.shadow(
|
||||
elevation = 4.dp,
|
||||
shape = RoundedCornerShape(8.dp),
|
||||
ambientColor = Color(0x4DE5E5E5),
|
||||
spotColor = Color(0x4DE5E5E5)
|
||||
)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 11.dp, vertical = 17.dp)
|
||||
.clickable(
|
||||
indication = null,
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
) {
|
||||
//TODO 切换账号
|
||||
//navController.navigate("feedback")
|
||||
},
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(id = R.mipmap.ic_launcher_logo),
|
||||
contentDescription = null,
|
||||
contentScale = ContentScale.FillWidth,
|
||||
modifier = Modifier
|
||||
.size(46.dp)
|
||||
.align(Alignment.CenterVertically)
|
||||
.clip(RoundedCornerShape(8.dp))
|
||||
)
|
||||
|
||||
Column(
|
||||
modifier = Modifier.weight(1f)
|
||||
){
|
||||
Text(
|
||||
text = "小林",
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = Color(0xFF1A1A1A),
|
||||
modifier = Modifier
|
||||
.wrapContentSize()
|
||||
.padding(start = 8.dp)
|
||||
)
|
||||
Text(
|
||||
text = "ID:123456",
|
||||
fontSize = 12.sp,
|
||||
color = Color(0xFF767676),
|
||||
fontWeight = FontWeight.Normal,
|
||||
modifier = Modifier
|
||||
.wrapContentSize()
|
||||
.padding(start = 8.dp)
|
||||
)
|
||||
}
|
||||
|
||||
Row(
|
||||
modifier = Modifier.wrapContentSize()
|
||||
) {
|
||||
Checkbox(
|
||||
checked = item.login,
|
||||
onCheckedChange = { isChecked ->
|
||||
//viewModel.setIsPolicyAgreement(isChecked)
|
||||
},
|
||||
modifier = Modifier
|
||||
.size(16.dp)
|
||||
.scale(0.35f)
|
||||
.padding(start = 6.dp)
|
||||
.background(
|
||||
if (item.login) Color(0xFF252525)
|
||||
else Color.Transparent,
|
||||
shape = RoundedCornerShape(36.dp)
|
||||
)
|
||||
.border(
|
||||
width = 1.dp,
|
||||
color = if (item.login) Color(0xFF252525)
|
||||
else Color(0xFFCCCCCC),
|
||||
shape = RoundedCornerShape(36.dp)
|
||||
)
|
||||
.align(Alignment.CenterVertically),
|
||||
colors = androidx.compose.material3.CheckboxDefaults.colors(
|
||||
checkedColor = Color.Transparent, // 隐藏默认背景
|
||||
uncheckedColor = Color.Transparent, // 隐藏默认背景
|
||||
checkmarkColor = Color.White
|
||||
)
|
||||
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Composable
|
||||
private fun AddItem(navController: NavController){
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight()
|
||||
.padding(bottom = 16.dp)
|
||||
.background(
|
||||
color = Color(0xFFFFFFFF),
|
||||
shape = RoundedCornerShape(8.dp)
|
||||
)
|
||||
.shadow(
|
||||
elevation = 4.dp,
|
||||
shape = RoundedCornerShape(8.dp),
|
||||
ambientColor = Color(0x4DE5E5E5),
|
||||
spotColor = Color(0x4DE5E5E5)
|
||||
)
|
||||
.clickable(
|
||||
indication = null,
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
) {
|
||||
//TODO 添加账号
|
||||
navController.navigate("login")
|
||||
}
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 11.dp, vertical = 17.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(id = R.mipmap.ic_picture_add),
|
||||
contentDescription = null,
|
||||
contentScale = ContentScale.FillWidth,
|
||||
modifier = Modifier
|
||||
.size(46.dp)
|
||||
.align(Alignment.CenterVertically)
|
||||
.background(Color(0xFFF3F3F3), shape = RoundedCornerShape(8.dp))
|
||||
.clip(RoundedCornerShape(8.dp))
|
||||
)
|
||||
|
||||
Column(
|
||||
modifier = Modifier.weight(1f)
|
||||
){
|
||||
Text(
|
||||
text = "添加账号",
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = Color(0xFF1A1A1A),
|
||||
modifier = Modifier
|
||||
.wrapContentSize()
|
||||
.padding(start = 8.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun PreviewAccountManagerScreen(){
|
||||
AccountManagerScreen(navController = rememberNavController())
|
||||
}
|
||||
|
|
@ -0,0 +1,220 @@
|
|||
package com.img.rabbit.pages.screen.other
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.wrapContentHeight
|
||||
import androidx.compose.foundation.layout.wrapContentSize
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.navigation.NavController
|
||||
import com.img.rabbit.R
|
||||
import com.img.rabbit.pages.toolbar.TitleBar
|
||||
|
||||
@Composable
|
||||
fun CameraGuideScreen(navController: NavController) {
|
||||
Scaffold {
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize().background(Color(0xFFF4F4F4))
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
horizontalAlignment = Alignment.End
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(id = R.mipmap.ic_camera_mask),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.wrapContentSize(),
|
||||
)
|
||||
}
|
||||
TitleBar(navController = navController, paddingValues = it, title = "", showSave = false)
|
||||
|
||||
Column(
|
||||
modifier = Modifier.fillMaxSize().padding(top = 104.dp)
|
||||
){
|
||||
Text(
|
||||
text = "如何拍照?",
|
||||
color = Color(0xFF1A1A1A),
|
||||
fontSize = 24.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
modifier = Modifier.padding(start = 16.dp)
|
||||
)
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight()
|
||||
.padding(start = 16.dp, end = 16.dp, top = 20.dp)
|
||||
.background(Color(0xFFFFFFFF),
|
||||
shape = RoundedCornerShape(16.dp)
|
||||
)
|
||||
) {
|
||||
Row (
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight()
|
||||
.padding(start = 16.dp, end = 16.dp, bottom = 20.dp)
|
||||
){
|
||||
Text(
|
||||
text = "1.注意光线均匀\n 2.不能耸肩或斜肩\n 3.正对镜头,双耳露出\n 4.不要佩戴眼镜\n 5.注意纯色墙作背景\n 6.避免衣服与背景色相同",
|
||||
color = Color(0xFF1A1A1A),
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
modifier = Modifier.padding(top = 20.dp)
|
||||
)
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth().padding(top = 11.dp),
|
||||
horizontalAlignment = Alignment.End
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(id = R.mipmap.ic_photo_sample),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.wrapContentSize().align(Alignment.End)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Text(
|
||||
text = "证件照背景颜色要求",
|
||||
color = Color(0xFF1A1A1A),
|
||||
fontSize = 24.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
modifier = Modifier.padding(top = 24.dp, start = 16.dp)
|
||||
)
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight()
|
||||
.padding(start = 16.dp, end = 16.dp, top = 20.dp)
|
||||
.background(Color(0xFFFFFFFF),
|
||||
shape = RoundedCornerShape(16.dp)
|
||||
)
|
||||
) {
|
||||
Row (
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight()
|
||||
.padding(start = 16.dp, end = 16.dp, top = 18.dp)
|
||||
){
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(18.dp)
|
||||
.align(Alignment.CenterVertically)
|
||||
.background(Color(0xFFFFFFFF), shape = RoundedCornerShape(56.dp))
|
||||
.border(0.5.dp, Color(0xFFAAAAAA), shape = RoundedCornerShape(56.dp))
|
||||
)
|
||||
Text(
|
||||
text = "白色背景:",
|
||||
color = Color(0xFF1A1A1A),
|
||||
fontSize = 12.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
lineHeight = 12.sp,
|
||||
modifier = Modifier.padding(start = 4.dp).align(Alignment.CenterVertically)
|
||||
)
|
||||
}
|
||||
Text(
|
||||
text = "用于护照、签证、驾驶证、身份证、二代身份证、驾驶证、黑白证件、医保卡、港澳通行证、护照等。",
|
||||
color = Color(0xFF1A1A1A),
|
||||
fontSize = 12.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
lineHeight = 12.sp,
|
||||
modifier = Modifier.padding(top = 12.dp, start = 16.dp, end = 16.dp)
|
||||
)
|
||||
|
||||
Row (
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight()
|
||||
.padding(start = 16.dp, end = 16.dp, top = 24.dp)
|
||||
){
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(18.dp)
|
||||
.align(Alignment.CenterVertically)
|
||||
.background(Color(0xFF438EDB), shape = RoundedCornerShape(56.dp))
|
||||
.border(0.5.dp, Color(0xFFAAAAAA), shape = RoundedCornerShape(56.dp))
|
||||
)
|
||||
Text(
|
||||
text = "蓝色背景:",
|
||||
color = Color(0xFF1A1A1A),
|
||||
fontSize = 12.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
lineHeight = 12.sp,
|
||||
modifier = Modifier.padding(start = 4.dp).align(Alignment.CenterVertically)
|
||||
)
|
||||
}
|
||||
Text(
|
||||
text = "用于毕业证、工作证、简历等 (蓝色数值为R:0 G:191 B:243 或 C:67 M:Z Y:0 k:0)",
|
||||
color = Color(0xFF1A1A1A),
|
||||
fontSize = 12.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
lineHeight = 12.sp,
|
||||
modifier = Modifier.padding(top = 12.dp, start = 16.dp, end = 16.dp)
|
||||
)
|
||||
|
||||
Row (
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight()
|
||||
.padding(start = 16.dp, end = 16.dp, top = 24.dp)
|
||||
){
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(18.dp)
|
||||
.align(Alignment.CenterVertically)
|
||||
.background(Color(0xFFFF0000), shape = RoundedCornerShape(56.dp))
|
||||
.border(0.5.dp, Color(0xFFAAAAAA), shape = RoundedCornerShape(56.dp))
|
||||
)
|
||||
Text(
|
||||
text = "红色背景:",
|
||||
color = Color(0xFF1A1A1A),
|
||||
fontSize = 12.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
lineHeight = 12.sp,
|
||||
modifier = Modifier.padding(start = 4.dp).align(Alignment.CenterVertically)
|
||||
)
|
||||
}
|
||||
Text(
|
||||
text = "用于保险、医保、IC卡、暂住证、结婚照 (红色数值为R:255 G:0 B:0 或 C:0 M:99 Y:100 K: 0)",
|
||||
color = Color(0xFF1A1A1A),
|
||||
fontSize = 12.sp,
|
||||
fontWeight = FontWeight.Normal,
|
||||
lineHeight = 12.sp,
|
||||
modifier = Modifier.padding(top = 12.dp, start = 16.dp, end = 16.dp)
|
||||
)
|
||||
|
||||
Box(modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(16.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun PreviewCameraGuideScreen() {
|
||||
CameraGuideScreen(navController = NavController(LocalContext.current))
|
||||
}
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
package com.img.rabbit.pages.toolbar
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.layout.wrapContentHeight
|
||||
import androidx.compose.foundation.layout.wrapContentWidth
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import com.img.rabbit.R
|
||||
|
||||
@Composable
|
||||
fun TitleBar(navController: NavController?, paddingValues: PaddingValues, title: String? = "", showSave: Boolean = false, onSubmit: (() -> Unit)? = null) {
|
||||
Box(
|
||||
modifier = Modifier.fillMaxWidth().padding(paddingValues)
|
||||
){
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth().padding(16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
// 返回按钮
|
||||
Icon(
|
||||
painter = painterResource(id = R.mipmap.ic_back),
|
||||
contentDescription = "返回",
|
||||
modifier = Modifier
|
||||
.clickable(
|
||||
indication = null,
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
) { navController?.popBackStack() }
|
||||
.padding(end = 26.dp)
|
||||
)
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth().weight(1f)
|
||||
) {
|
||||
|
||||
Text(
|
||||
text = title ?: "",
|
||||
color = Color(0xFF1A1A1A),
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
modifier = Modifier.align(Alignment.CenterHorizontally)
|
||||
)
|
||||
}
|
||||
|
||||
if(showSave) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.width(60.dp)
|
||||
.height(36.dp)
|
||||
.background(
|
||||
Color(0xFF252525),
|
||||
shape = RoundedCornerShape(359.dp),
|
||||
)
|
||||
.clickable(
|
||||
indication = null,
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
) {
|
||||
// 保存
|
||||
onSubmit?.invoke()
|
||||
}
|
||||
) {
|
||||
Text(
|
||||
"保存",
|
||||
color = Color(0xFFC2FF43),
|
||||
fontSize = 16.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
modifier = Modifier
|
||||
.wrapContentWidth()
|
||||
.wrapContentHeight()
|
||||
.align(Alignment.Center)
|
||||
)
|
||||
}
|
||||
}else{
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.width(60.dp)
|
||||
.height(36.dp)
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun PreviewTitleBar() {
|
||||
TitleBar(navController = rememberNavController(), paddingValues = PaddingValues(), title = "截图兔", showSave = true)
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
package com.img.rabbit.route
|
||||
|
||||
// 定义导航路由
|
||||
sealed class ScreenRoute(val route: String) {
|
||||
//Tab页面
|
||||
object Home : ScreenRoute("home")
|
||||
object Mine : ScreenRoute("mine")
|
||||
//首页(抠图)
|
||||
object Cutout : ScreenRoute("cutout")
|
||||
//首页(证件)
|
||||
object Resize : ScreenRoute("resize")
|
||||
//首页(格式)
|
||||
object Format : ScreenRoute("format")
|
||||
//首页(拍照指南)
|
||||
object CameraGuide : ScreenRoute("cameraGuide")
|
||||
//首页(长图)
|
||||
object LongImage : ScreenRoute("longImage")
|
||||
|
||||
//我的页面(Mine)
|
||||
object Feedback : ScreenRoute("feedback")
|
||||
object OnlineService : ScreenRoute("onlineService")
|
||||
object Login : ScreenRoute("login")
|
||||
object Setting : ScreenRoute("setting")
|
||||
|
||||
//设置页面(Setting)
|
||||
object BindAccount : ScreenRoute("bindAccount")
|
||||
object ManagerAccount : ScreenRoute("managerAccount")
|
||||
object AboutMine : ScreenRoute("aboutMine")
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
package com.img.rabbit.utils
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.graphics.Color
|
||||
import android.graphics.Typeface
|
||||
import android.text.Spannable
|
||||
import android.text.SpannableString
|
||||
import android.text.TextPaint
|
||||
import android.text.method.LinkMovementMethod
|
||||
import android.text.style.ClickableSpan
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import androidx.core.graphics.toColorInt
|
||||
import androidx.core.net.toUri
|
||||
|
||||
object AgreementTextHelper {
|
||||
|
||||
fun setupAgreementTextView(fullText: String, targets: Map<String, String>, textView: TextView, isUnderlineText: Boolean = true, onAgreementClick: (String) -> Unit) {
|
||||
val spannableString = SpannableString(fullText)
|
||||
|
||||
// 设置各个协议的点击区域
|
||||
targets.forEach { (targetKey, target) ->
|
||||
setClickableSpan(spannable = spannableString, target = target, isUnderlineText = isUnderlineText, color = "#FF767676".toColorInt()) {
|
||||
onAgreementClick(targetKey)
|
||||
}
|
||||
}
|
||||
|
||||
textView.text = spannableString
|
||||
textView.movementMethod = LinkMovementMethod.getInstance()
|
||||
textView.highlightColor = Color.TRANSPARENT
|
||||
|
||||
// 设置长按不显示复制菜单
|
||||
textView.setOnLongClickListener { true }
|
||||
}
|
||||
|
||||
private fun setClickableSpan(
|
||||
spannable: Spannable,
|
||||
target: String,
|
||||
isUnderlineText: Boolean,
|
||||
color: Int,
|
||||
onClick: () -> Unit
|
||||
) {
|
||||
val start = spannable.indexOf(target)
|
||||
if (start >= 0) {
|
||||
val end = start + target.length
|
||||
val clickableSpan = object : ClickableSpan() {
|
||||
override fun onClick(widget: View) {
|
||||
onClick()
|
||||
}
|
||||
|
||||
override fun updateDrawState(ds: TextPaint) {
|
||||
super.updateDrawState(ds)
|
||||
ds.color = color
|
||||
ds.isUnderlineText = isUnderlineText
|
||||
ds.typeface = Typeface.DEFAULT_BOLD
|
||||
}
|
||||
}
|
||||
spannable.setSpan(clickableSpan, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
package com.img.rabbit.utils
|
||||
|
||||
import android.content.Context
|
||||
import androidx.datastore.core.DataStore
|
||||
import androidx.datastore.preferences.core.Preferences
|
||||
import androidx.datastore.preferences.core.booleanPreferencesKey
|
||||
import androidx.datastore.preferences.core.edit
|
||||
import androidx.datastore.preferences.preferencesDataStore
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
// 创建DataStore实例
|
||||
val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "app_settings")
|
||||
|
||||
// DataStore键定义
|
||||
object AppPreferencesKeys {
|
||||
val NAVIGATION_BAR_VISIBLE = booleanPreferencesKey("navigation_bar_visible")
|
||||
val USER_LOGGED_IN = booleanPreferencesKey("user_logged_in")
|
||||
}
|
||||
|
||||
class AppDataStore(private val context: Context) {
|
||||
|
||||
// 获取NavigationBar显示状态
|
||||
val isNavigationBarVisible: Flow<Boolean> = context.dataStore.data
|
||||
.map { preferences ->
|
||||
preferences[AppPreferencesKeys.NAVIGATION_BAR_VISIBLE] ?: true
|
||||
}
|
||||
|
||||
// 设置NavigationBar显示状态
|
||||
suspend fun setNavigationBarVisible(visible: Boolean) {
|
||||
context.dataStore.edit { preferences ->
|
||||
preferences[AppPreferencesKeys.NAVIGATION_BAR_VISIBLE] = visible
|
||||
}
|
||||
}
|
||||
|
||||
// 获取用户登录状态
|
||||
val isUserLoggedIn: Flow<Boolean> = context.dataStore.data
|
||||
.map { preferences ->
|
||||
preferences[AppPreferencesKeys.USER_LOGGED_IN] ?: false
|
||||
}
|
||||
|
||||
// 设置用户登录状态
|
||||
suspend fun setUserLoggedIn(loggedIn: Boolean) {
|
||||
context.dataStore.edit { preferences ->
|
||||
preferences[AppPreferencesKeys.USER_LOGGED_IN] = loggedIn
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,128 @@
|
|||
package com.img.rabbit.utils;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.PrintWriter;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Color;
|
||||
import android.net.Uri;
|
||||
import android.os.Environment;
|
||||
import android.provider.MediaStore;
|
||||
import android.widget.Toast;
|
||||
|
||||
public class Bitmap2SVG
|
||||
{
|
||||
public static boolean convert(Context context, File output, Bitmap input){
|
||||
Bitmap2SVG svg;
|
||||
try
|
||||
{
|
||||
svg = new Bitmap2SVG( new PrintWriter( new BufferedWriter( new FileWriter( output ) ) ), true );
|
||||
} catch (IOException e)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
svg.printSVGHeader( input.getWidth(), input.getHeight() );
|
||||
svg.printSVGBody( input );
|
||||
svg.printSVGFooter();
|
||||
|
||||
saveToGallery(context, output, "displayName.svg");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean trc_del;
|
||||
private PrintWriter pw;
|
||||
private int w, h;
|
||||
|
||||
private Bitmap2SVG( PrintWriter pw, boolean trc_delete )
|
||||
{
|
||||
this.pw = pw;
|
||||
trc_del = trc_delete;
|
||||
}
|
||||
private void printSVGHeader( int width, int height )
|
||||
{
|
||||
pw.println( "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>" );
|
||||
|
||||
pw.print( "<svg xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\" width=\"" );
|
||||
pw.print( width );
|
||||
pw.print( "\" height=\"" );
|
||||
pw.print( height );
|
||||
pw.println( "\">" );
|
||||
}
|
||||
private void printSVGFooter()
|
||||
{
|
||||
pw.println( "</svg>" );
|
||||
pw.close();
|
||||
}
|
||||
private void printSVGBody( Bitmap bmp )
|
||||
{
|
||||
int x, y, c, a;
|
||||
w = bmp.getWidth();
|
||||
h = bmp.getHeight();
|
||||
for ( y = 0 ; y < h ; ++y )
|
||||
{
|
||||
for ( x = 0 ; x < w ;++x )
|
||||
{
|
||||
c = bmp.getPixel( x, y );
|
||||
a = Color.alpha( c );
|
||||
if ( trc_del && a == 0 ){ continue; }
|
||||
printDot( x, y, rgb( c ), a );
|
||||
}
|
||||
}
|
||||
}
|
||||
private void printDot( int x, int y, String rgb, int a )
|
||||
{
|
||||
pw.print( "<rect width=\"1\" height=\"1\" x=\"" );
|
||||
pw.print( x );
|
||||
pw.print( "\" y=\"" );
|
||||
pw.print( y );
|
||||
pw.print( "\" " );
|
||||
pw.print( "style=\"fill:#" );
|
||||
pw.print( rgb );
|
||||
if ( a < 255 )
|
||||
{
|
||||
pw.print( ";fill-opacity:" );
|
||||
pw.print( a / 255.0 );
|
||||
}
|
||||
pw.println( ";\" />" );
|
||||
}
|
||||
private static String rgb( int c )
|
||||
{
|
||||
return String.format( "%02x%02x%02x", Color.red( c ), Color.green( c ), Color.blue( c ) );
|
||||
}
|
||||
|
||||
public static void saveToGallery(Context context, File svgFile, String displayName) {
|
||||
ContentValues values = new ContentValues();
|
||||
// 关键点:显式指定 MIME 类型为 SVG
|
||||
values.put(MediaStore.Images.Media.MIME_TYPE, "image/svg+xml");
|
||||
values.put(MediaStore.Images.Media.DISPLAY_NAME, displayName + ".svg");
|
||||
values.put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_PICTURES);
|
||||
|
||||
ContentResolver resolver = context.getContentResolver();
|
||||
Uri uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
|
||||
|
||||
if (uri != null) {
|
||||
try (OutputStream os = resolver.openOutputStream(uri);
|
||||
FileInputStream fis = new FileInputStream(svgFile)) {
|
||||
byte[] buffer = new byte[1024];
|
||||
int len;
|
||||
while ((len = fis.read(buffer)) != -1) {
|
||||
os.write(buffer, 0, len);
|
||||
}
|
||||
// 此时文件已带上正确的 MIME 类型存入相册
|
||||
Toast.makeText(context, "SVG已保存", Toast.LENGTH_SHORT).show();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
package com.img.rabbit.utils
|
||||
|
||||
import android.content.Context
|
||||
import android.widget.ImageView
|
||||
import coil3.load
|
||||
import coil3.request.crossfade
|
||||
import coil3.request.transformations
|
||||
import coil3.transform.RoundedCornersTransformation
|
||||
import com.luck.picture.lib.engine.ImageEngine
|
||||
import com.luck.picture.lib.utils.ActivityCompatHelper
|
||||
|
||||
class CoilEngine : ImageEngine {
|
||||
|
||||
// 加载普通图片
|
||||
override fun loadImage(context: Context, url: String, imageView: ImageView) {
|
||||
if (!ActivityCompatHelper.assertValidRequest(context)) return
|
||||
imageView.load(url)
|
||||
}
|
||||
|
||||
override fun loadImage(
|
||||
context: Context?,
|
||||
imageView: ImageView?,
|
||||
url: String?,
|
||||
maxWidth: Int,
|
||||
maxHeight: Int
|
||||
) {
|
||||
if (!ActivityCompatHelper.assertValidRequest(context)) return
|
||||
imageView?.load(url)
|
||||
}
|
||||
|
||||
// 加载相册目录封面
|
||||
override fun loadAlbumCover(context: Context, url: String, imageView: ImageView) {
|
||||
if (!ActivityCompatHelper.assertValidRequest(context)) return
|
||||
imageView.load(url) {
|
||||
transformations(RoundedCornersTransformation(8f))
|
||||
size(180, 180) // 优化内存,封面图没必要太大
|
||||
}
|
||||
}
|
||||
|
||||
// 加载图片列表中的小图
|
||||
override fun loadGridImage(context: Context, url: String, imageView: ImageView) {
|
||||
if (!ActivityCompatHelper.assertValidRequest(context)) return
|
||||
imageView.load(url) {
|
||||
size(200, 200)
|
||||
crossfade(true)
|
||||
}
|
||||
}
|
||||
|
||||
// 暂停/恢复加载(Coil 自动处理,可留空)
|
||||
override fun pauseRequests(context: Context?) {}
|
||||
override fun resumeRequests(context: Context?) {}
|
||||
|
||||
companion object {
|
||||
private var instance: CoilEngine? = null
|
||||
fun createCoilEngine(): CoilEngine {
|
||||
return instance ?: synchronized(this) {
|
||||
instance ?: CoilEngine().also { instance = it }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,134 @@
|
|||
package com.img.rabbit.utils
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.provider.MediaStore
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import java.io.File
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
|
||||
/**
|
||||
* 图片选择工具类
|
||||
*/
|
||||
object ImagePickerUtils {
|
||||
|
||||
/**
|
||||
* 创建相机拍照的Intent
|
||||
*/
|
||||
fun createCameraIntent(context: Context): Pair<Intent, File> {
|
||||
val timeStamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date())
|
||||
val storageDir = context.externalCacheDir ?: context.cacheDir
|
||||
val imageFile = File.createTempFile(
|
||||
"JPEG_${timeStamp}_",
|
||||
".jpg",
|
||||
storageDir
|
||||
)
|
||||
|
||||
val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE).apply {
|
||||
putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(imageFile))
|
||||
addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
||||
}
|
||||
|
||||
return Pair(intent, imageFile)
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建相册选择的Intent
|
||||
*/
|
||||
fun createGalleryIntent(): Intent {
|
||||
return Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI).apply {
|
||||
type = "image/*"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取图片的Uri路径
|
||||
*/
|
||||
fun getImageUri(context: Context, file: File): Uri {
|
||||
return androidx.core.content.FileProvider.getUriForFile(
|
||||
context,
|
||||
"${context.packageName}.fileprovider",
|
||||
file
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 图片选择结果数据类
|
||||
*/
|
||||
data class ImagePickerResult(
|
||||
val uri: Uri? = null,
|
||||
val filePath: String? = null,
|
||||
val error: String? = null
|
||||
)
|
||||
|
||||
/**
|
||||
* 可组合函数:创建相机启动器
|
||||
*/
|
||||
@Composable
|
||||
fun rememberCameraLauncher(
|
||||
context: Context = LocalContext.current,
|
||||
onResult: (ImagePickerResult) -> Unit
|
||||
) = rememberLauncherForActivityResult(
|
||||
ActivityResultContracts.TakePicture()
|
||||
) { success ->
|
||||
if (success) {
|
||||
// 这里需要与createCameraIntent配合使用,实际使用时需要保存临时文件路径
|
||||
onResult(ImagePickerResult(error = "需要配合createCameraIntent使用"))
|
||||
} else {
|
||||
onResult(ImagePickerResult(error = "拍照取消或失败"))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 可组合函数:创建相册启动器(单个图片选择)
|
||||
*/
|
||||
@Composable
|
||||
fun rememberGalleryLauncher(
|
||||
onResult: (ImagePickerResult) -> Unit
|
||||
) = rememberLauncherForActivityResult(
|
||||
ActivityResultContracts.PickVisualMedia()
|
||||
) { uri ->
|
||||
if (uri != null) {
|
||||
onResult(ImagePickerResult(uri = uri))
|
||||
} else {
|
||||
onResult(ImagePickerResult(error = "未选择图片"))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 可组合函数:创建相册启动器(多个图片选择)
|
||||
*/
|
||||
@Composable
|
||||
fun rememberMultipleGalleryLauncher(
|
||||
onResult: (List<ImagePickerResult>) -> Unit
|
||||
) = rememberLauncherForActivityResult(
|
||||
ActivityResultContracts.PickMultipleVisualMedia()
|
||||
) { uris ->
|
||||
val results = uris.map { uri ->
|
||||
ImagePickerResult(uri = uri)
|
||||
}
|
||||
onResult(results)
|
||||
}
|
||||
|
||||
/**
|
||||
* 可组合函数:创建通用图片选择启动器(支持相机和相册)
|
||||
*/
|
||||
@Composable
|
||||
fun rememberImagePickerLauncher(
|
||||
onResult: (ImagePickerResult) -> Unit
|
||||
) = rememberLauncherForActivityResult(
|
||||
ActivityResultContracts.GetContent()
|
||||
) { uri ->
|
||||
if (uri != null) {
|
||||
onResult(ImagePickerResult(uri = uri))
|
||||
} else {
|
||||
onResult(ImagePickerResult(error = "未选择图片"))
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,562 @@
|
|||
package com.img.rabbit.utils
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.ContentValues
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Environment
|
||||
import android.provider.MediaStore
|
||||
import android.util.Log
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.io.IOException
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import kotlin.apply
|
||||
|
||||
import android.graphics.*
|
||||
import androidx.core.graphics.createBitmap
|
||||
import com.img.rabbit.bean.LongImageBean
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.OutputStream
|
||||
import androidx.core.graphics.withClip
|
||||
|
||||
object ImageUtils {
|
||||
fun decodeSampledBitmapFromResource(
|
||||
context: Context,
|
||||
resId: Int,
|
||||
reqWidth: Int,
|
||||
reqHeight: Int
|
||||
): Bitmap {
|
||||
// 首先设置inJustDecodeBounds=true来获取图片尺寸
|
||||
val options = BitmapFactory.Options().apply {
|
||||
inJustDecodeBounds = true
|
||||
}
|
||||
BitmapFactory.decodeResource(context.resources, resId, options)
|
||||
|
||||
// 计算inSampleSize
|
||||
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight)
|
||||
Log.d("ImageUtils", "Sample size: ${options.inSampleSize}")
|
||||
|
||||
// 根据计算的inSampleSize来解码图片
|
||||
options.inJustDecodeBounds = false
|
||||
return BitmapFactory.decodeResource(context.resources, resId, options)
|
||||
}
|
||||
|
||||
// 新增:从byte数组加载图片的方法
|
||||
fun decodeSampledBitmapFromByteArray(
|
||||
byteArray: ByteArray,
|
||||
reqWidth: Int,
|
||||
reqHeight: Int
|
||||
): Bitmap {
|
||||
// 首先设置inJustDecodeBounds=true来获取图片尺寸
|
||||
val options = BitmapFactory.Options().apply {
|
||||
inJustDecodeBounds = true
|
||||
}
|
||||
BitmapFactory.decodeByteArray(byteArray, 0, byteArray.size, options)
|
||||
|
||||
// 计算inSampleSize
|
||||
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight)
|
||||
|
||||
// 根据计算的inSampleSize来解码图片
|
||||
options.inJustDecodeBounds = false
|
||||
return BitmapFactory.decodeByteArray(byteArray, 0, byteArray.size, options)
|
||||
}
|
||||
|
||||
private fun calculateInSampleSize(
|
||||
options: BitmapFactory.Options,
|
||||
reqWidth: Int,
|
||||
reqHeight: Int
|
||||
): Int {
|
||||
// 原始图片的尺寸
|
||||
val height = options.outHeight
|
||||
val width = options.outWidth
|
||||
var inSampleSize = 1
|
||||
|
||||
if (height > reqHeight || width > reqWidth) {
|
||||
val halfHeight = height / 2
|
||||
val halfWidth = width / 2
|
||||
|
||||
// 计算最大的inSampleSize值,该值为2的幂,并保持
|
||||
// 高度和宽度均大于请求的高度和宽度
|
||||
while ((halfHeight / inSampleSize) >= reqHeight
|
||||
&& (halfWidth / inSampleSize) >= reqWidth) {
|
||||
inSampleSize *= 2
|
||||
}
|
||||
}
|
||||
return inSampleSize
|
||||
}
|
||||
|
||||
/**
|
||||
* 将Bitmap保存到设备存储
|
||||
* @param bitmap 要保存的Bitmap对象
|
||||
* @param context 上下文
|
||||
* @param format 图片格式(Bitmap.CompressFormat.JPEG 或 Bitmap.CompressFormat.PNG)
|
||||
* @param quality 压缩质量(0-100)
|
||||
* @return 保存的文件路径,如果保存失败则返回null
|
||||
*/
|
||||
fun saveBitmapToStorage(
|
||||
bitmap: Bitmap,
|
||||
context: Context,
|
||||
format: Bitmap.CompressFormat = Bitmap.CompressFormat.PNG,
|
||||
quality: Int = 90
|
||||
): String? {
|
||||
val timeStamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date())
|
||||
val imageFileName = "JPEG_${timeStamp}_"
|
||||
|
||||
return try {
|
||||
val imageFile: File
|
||||
val imagePath: String?
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
// Android 10及以上使用MediaStore
|
||||
val contentValues = ContentValues().apply {
|
||||
put(MediaStore.Images.Media.DISPLAY_NAME, "$imageFileName.${format.name.lowercase()}")
|
||||
put(MediaStore.Images.Media.MIME_TYPE, "image/${format.name.lowercase()}")
|
||||
put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_PICTURES + File.separator + "DoctorApp")
|
||||
}
|
||||
|
||||
val resolver = context.contentResolver
|
||||
val imageUri: Uri? = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)
|
||||
|
||||
imageUri?.let { uri ->
|
||||
resolver.openOutputStream(uri)?.use { outputStream ->
|
||||
bitmap.compress(format, quality, outputStream)
|
||||
}
|
||||
return uri.toString()
|
||||
}
|
||||
} else {
|
||||
// Android 9及以下使用传统文件存储
|
||||
val storageDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES + File.separator + "DoctorApp")
|
||||
storageDir?.let { dir ->
|
||||
if (!dir.exists() && !dir.mkdirs()) {
|
||||
Log.e("ImageUtils", "无法创建存储目录")
|
||||
return null
|
||||
}
|
||||
|
||||
imageFile = File(dir, "$imageFileName.${format.name.lowercase()}")
|
||||
val outputStream = FileOutputStream(imageFile)
|
||||
bitmap.compress(format, quality, outputStream)
|
||||
outputStream.flush()
|
||||
outputStream.close()
|
||||
|
||||
// 将图片添加到媒体库
|
||||
MediaStore.Images.Media.insertImage(context.contentResolver, imageFile.absolutePath, imageFile.name, null)
|
||||
|
||||
// 通知媒体扫描器扫描文件
|
||||
context.sendBroadcast(Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(imageFile)))
|
||||
|
||||
return imageFile.absolutePath
|
||||
}
|
||||
}
|
||||
null
|
||||
} catch (e: IOException) {
|
||||
Log.e("ImageUtils", "保存图片失败", e)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将字节数组直接保存为图片文件
|
||||
* @param byteArray 图片字节数组
|
||||
* @param context 上下文
|
||||
* @param format 图片格式
|
||||
* @return 保存的文件路径,如果保存失败则返回null
|
||||
*/
|
||||
fun saveByteArrayToStorage(
|
||||
byteArray: ByteArray,
|
||||
context: Context,
|
||||
format: Bitmap.CompressFormat = Bitmap.CompressFormat.PNG
|
||||
): String? {
|
||||
return try {
|
||||
val bitmap = BitmapFactory.decodeByteArray(byteArray, 0, byteArray.size)
|
||||
saveBitmapToStorage(bitmap, context, format)
|
||||
} catch (e: Exception) {
|
||||
Log.e("ImageUtils", "保存字节数组为图片失败", e)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从URI获取Bitmap
|
||||
* @param context 上下文
|
||||
* @param uri 图片URI
|
||||
* @return Bitmap对象,如果获取失败则返回null
|
||||
*/
|
||||
fun getBitmapFromUri(context: Context, uri: Uri): Bitmap? {
|
||||
return try {
|
||||
context.contentResolver.openInputStream(uri)?.use { inputStream ->
|
||||
BitmapFactory.decodeStream(inputStream)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e("ImageUtils", "从URI获取Bitmap失败", e)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将URI对应的图片保存到存储
|
||||
* @param context 上下文
|
||||
* @param uri 图片URI
|
||||
* @param format 图片格式
|
||||
* @param quality 压缩质量
|
||||
* @return 保存的文件路径,如果保存失败则返回null
|
||||
*/
|
||||
fun saveUriToStorage(
|
||||
context: Context,
|
||||
uri: Uri,
|
||||
format: Bitmap.CompressFormat = Bitmap.CompressFormat.PNG,
|
||||
quality: Int = 90
|
||||
): String? {
|
||||
return try {
|
||||
val bitmap = getBitmapFromUri(context, uri)
|
||||
bitmap?.let {
|
||||
saveBitmapToStorage(it, context, format, quality)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e("ImageUtils", "保存URI图片失败", e)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 保存长图到相册
|
||||
* @param context 上下文
|
||||
* @param items 长图数据列表
|
||||
* @param format 导出格式
|
||||
* @param onSubmitResult 回调函数,用于通知保存结果
|
||||
*/
|
||||
fun saveLongToGallery(
|
||||
context: Context,
|
||||
items: List<LongImageBean>,
|
||||
format: ExportFormat,
|
||||
onSubmitResult: (fileName: String, isSuccess: Boolean) -> Unit
|
||||
) {
|
||||
if (items.isEmpty()) return
|
||||
|
||||
// 1. 定义长图的统一输出宽度
|
||||
val canvasWidth = 1080f
|
||||
|
||||
// 2. 计算每张图按比例缩放后的总高度
|
||||
var totalHeight = 0f
|
||||
val renderConfigs = items.map { item ->
|
||||
// 计算当前图片填满 1080 宽度所需的缩放比例
|
||||
val scale = canvasWidth / item.bitmap.width
|
||||
// 缩放后的实际物理高度
|
||||
val scaledHeight = item.bitmap.height * scale
|
||||
totalHeight += scaledHeight
|
||||
scale to scaledHeight
|
||||
}
|
||||
|
||||
// 3. 创建大图画布
|
||||
val resultBitmap = Bitmap.createBitmap(canvasWidth.toInt(), totalHeight.toInt(), Bitmap.Config.ARGB_8888)
|
||||
val canvas = Canvas(resultBitmap)
|
||||
val paint = Paint(Paint.ANTI_ALIAS_FLAG) // 开启抗锯齿,防止缩放模糊
|
||||
var currentY = 0f
|
||||
|
||||
// 4. 依次绘制每一张图片
|
||||
items.forEachIndexed { index, item ->
|
||||
val (scale, scaledHeight) = renderConfigs[index]
|
||||
|
||||
val matrix = Matrix()
|
||||
// 先按比例缩放
|
||||
matrix.postScale(scale, scale)
|
||||
// 再平移到当前 Y 轴位置(实现无缝衔接)
|
||||
matrix.postTranslate(0f, currentY)
|
||||
|
||||
canvas.drawBitmap(item.bitmap, matrix, paint)
|
||||
|
||||
// 关键:累加【缩放后】的高度,确保下一张图紧贴上一张
|
||||
currentY += scaledHeight
|
||||
}
|
||||
|
||||
// 5. 调用你原有的保存逻辑
|
||||
saveCanvasToGallery(context, resultBitmap, format, onSubmitResult)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 保存位图到系统相册
|
||||
* @param context 上下文
|
||||
* @param bitmap 要保存的位图
|
||||
* @param format 导出格式(PNG/JPG)
|
||||
* @param onSubmitResult 回调函数,参数为文件名和是否成功
|
||||
*/
|
||||
fun saveCanvasToGallery(
|
||||
context: Context,
|
||||
bitmap: Bitmap,
|
||||
format: ExportFormat,
|
||||
onSubmitResult: (fileName: String, isSuccess: Boolean) -> Unit
|
||||
) {
|
||||
val extension = if (format == ExportFormat.JPG) "jpg" else "png"
|
||||
val mimeType = if (format == ExportFormat.JPG) "image/jpeg" else "image/png"
|
||||
val compressFormat = if (format == ExportFormat.JPG) Bitmap.CompressFormat.JPEG else Bitmap.CompressFormat.PNG
|
||||
|
||||
val filename = "MyRabbit_${System.currentTimeMillis()}.$extension"
|
||||
|
||||
val contentValues = ContentValues().apply {
|
||||
put(MediaStore.MediaColumns.DISPLAY_NAME, filename)
|
||||
put(MediaStore.MediaColumns.MIME_TYPE, mimeType)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
put(MediaStore.MediaColumns.RELATIVE_PATH, "DCIM/Camera")
|
||||
}
|
||||
}
|
||||
|
||||
val uri = context.contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)
|
||||
uri?.let {
|
||||
context.contentResolver.openOutputStream(it)?.use { stream ->
|
||||
// PNG 忽略 quality 参数(无损),JPG 使用 100 表示最高质量
|
||||
bitmap.compress(compressFormat, 100, stream)
|
||||
onSubmitResult(filename, true)
|
||||
}
|
||||
} ?: run {
|
||||
onSubmitResult(filename, false)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存位图到系统相册(如果targetWidth与targetHeight比原始值小太多会导致图片模糊)
|
||||
* @param context 上下文
|
||||
* @param bitmap 要保存的位图
|
||||
* @param format 导出格式(PNG/JPG)
|
||||
* @param targetWidth 目标宽度
|
||||
* @param targetHeight 目标高度
|
||||
* @param onSubmitResult 回调函数,参数为文件名和是否成功
|
||||
*/
|
||||
@SuppressLint("UseKtx")
|
||||
fun saveCanvasToGallery(
|
||||
context: Context,
|
||||
bitmap: Bitmap,
|
||||
format: ExportFormat,
|
||||
targetWidth: Int,
|
||||
targetHeight: Int,
|
||||
onSubmitResult: (fileName: String, isSuccess: Boolean) -> Unit
|
||||
) {
|
||||
// 1. 修复 Hardware Bitmap 报错并确保使用 ARGB_8888 高精度配置
|
||||
val softwareBitmap = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O &&
|
||||
bitmap.config == Bitmap.Config.HARDWARE) {
|
||||
bitmap.copy(Bitmap.Config.ARGB_8888, false)
|
||||
} else {
|
||||
// 如果不是硬件位图,也建议检查并转换为高精度配置以防模糊
|
||||
bitmap
|
||||
}
|
||||
|
||||
val extension = if (format == ExportFormat.JPG) "jpg" else "png"
|
||||
val mimeType = if (format == ExportFormat.JPG) "image/jpeg" else "image/png"
|
||||
val compressFormat = if (format == ExportFormat.JPG) Bitmap.CompressFormat.JPEG else Bitmap.CompressFormat.PNG
|
||||
|
||||
// 2. 创建目标尺寸位图 (显式指定 Config.ARGB_8888 保证清晰度)
|
||||
val scaledResult = Bitmap.createBitmap(targetWidth, targetHeight, Bitmap.Config.ARGB_8888)
|
||||
val canvas = Canvas(scaledResult)
|
||||
|
||||
// 3. 处理背景色 (如果是 JPG 填充白色,PNG 保持透明)
|
||||
if (format == ExportFormat.JPG) {
|
||||
canvas.drawColor(Color.WHITE)
|
||||
}
|
||||
|
||||
// 4. 【核心算法:计算等比例缩放】
|
||||
val srcWidth = softwareBitmap.width.toFloat()
|
||||
val srcHeight = softwareBitmap.height.toFloat()
|
||||
|
||||
// 使用 float 计算比例,避免整数除法丢失精度导致模糊
|
||||
val scale = (targetWidth.toFloat() / srcWidth).coerceAtMost(targetHeight.toFloat() / srcHeight)
|
||||
|
||||
val finalWidth = srcWidth * scale
|
||||
val finalHeight = srcHeight * scale
|
||||
val left = (targetWidth - finalWidth) / 2f
|
||||
val top = (targetHeight - finalHeight) / 2f
|
||||
|
||||
// 5. 【关键:高清绘制配置】
|
||||
val paint = Paint().apply {
|
||||
isAntiAlias = true // 开启抗锯齿,边缘更平滑
|
||||
isFilterBitmap = true // 开启位图过滤,这是解决缩放模糊的最核心设置
|
||||
isDither = true // 开启抖动,色彩过渡更自然
|
||||
}
|
||||
|
||||
val destRect = RectF(left, top, left + finalWidth, top + finalHeight)
|
||||
// 绘图
|
||||
canvas.drawBitmap(softwareBitmap, null, destRect, paint)
|
||||
|
||||
// 6. 保存到 MediaStore
|
||||
val filename = "MyRabbit_${System.currentTimeMillis()}.$extension"
|
||||
val contentValues = ContentValues().apply {
|
||||
put(MediaStore.MediaColumns.DISPLAY_NAME, filename)
|
||||
put(MediaStore.MediaColumns.MIME_TYPE, mimeType)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
put(MediaStore.MediaColumns.RELATIVE_PATH, "DCIM/Camera")
|
||||
}
|
||||
}
|
||||
|
||||
val uri = context.contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)
|
||||
|
||||
try {
|
||||
uri?.let {
|
||||
context.contentResolver.openOutputStream(it)?.use { stream ->
|
||||
// 100 表示不压缩质量
|
||||
scaledResult.compress(compressFormat, 100, stream)
|
||||
onSubmitResult(filename, true)
|
||||
}
|
||||
} ?: onSubmitResult(filename, false)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
onSubmitResult(filename, false)
|
||||
} finally {
|
||||
// 7. 内存回收
|
||||
if (softwareBitmap != bitmap) softwareBitmap.recycle()
|
||||
// 注意:如果后面还要用 scaledResult,不要在这里 recycle
|
||||
// scaledResult.recycle()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为相册可保存的格式(PNG/JPG/SVG/GIF)
|
||||
* @param context 上下文
|
||||
* @param sourceBitmap 原始位图
|
||||
* @param format 导出格式(PNG/JPG/SVG/GIF)
|
||||
* @param onSubmitResult 回调函数,参数为文件名和是否成功
|
||||
*/
|
||||
fun convertToGallery(
|
||||
context: Context,
|
||||
sourceBitmap: Bitmap,
|
||||
format: ExportFormat,
|
||||
onSubmitResult: (fileName: String, isSuccess: Boolean) -> Unit
|
||||
) {
|
||||
// 1. 预处理:缩放并获得高清软件位图(防止 Hardware Bitmap 报错)
|
||||
val softwareBitmap = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && sourceBitmap.config == Bitmap.Config.HARDWARE) {
|
||||
sourceBitmap.copy(Bitmap.Config.ARGB_8888, false)
|
||||
} else sourceBitmap
|
||||
|
||||
val scaledBitmap = createScaledBitmap(softwareBitmap, sourceBitmap.width, sourceBitmap.height)
|
||||
|
||||
// 2. 准备 MediaStore 容器
|
||||
val filename = "MyRabbit_Export_${System.currentTimeMillis()}.${format.extension}"
|
||||
val contentValues = if (format == ExportFormat.SVG) {
|
||||
ContentValues().apply {
|
||||
put(MediaStore.MediaColumns.DISPLAY_NAME, filename)
|
||||
put(MediaStore.MediaColumns.MIME_TYPE, "image/svg+xml") // 关键:SVG 的 MIME 类型
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
put(MediaStore.MediaColumns.RELATIVE_PATH, "DCIM/Camera")
|
||||
}
|
||||
}
|
||||
}else{
|
||||
ContentValues().apply {
|
||||
put(MediaStore.MediaColumns.DISPLAY_NAME, filename)
|
||||
put(MediaStore.MediaColumns.MIME_TYPE, format.mimeType)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
put(MediaStore.MediaColumns.RELATIVE_PATH, "DCIM/Camera")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val uri = context.contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)
|
||||
|
||||
// 3. 根据格式执行不同的转换编码
|
||||
try {
|
||||
uri?.let {
|
||||
context.contentResolver.openOutputStream(it)?.use { stream ->
|
||||
when (format) {
|
||||
ExportFormat.JPG -> scaledBitmap.compress(Bitmap.CompressFormat.JPEG, 100, stream)
|
||||
ExportFormat.PNG -> scaledBitmap.compress(Bitmap.CompressFormat.PNG, 100, stream)
|
||||
ExportFormat.GIF -> encodeToGif(scaledBitmap, stream)
|
||||
ExportFormat.SVG -> encodeToSvg(scaledBitmap, stream)
|
||||
}
|
||||
onSubmitResult(filename, true)
|
||||
}
|
||||
} ?: onSubmitResult(filename, false)
|
||||
} catch (e: Exception) {
|
||||
onSubmitResult(e.message ?: "Unknown Error", false)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 创建等比例适配的缩放位图
|
||||
* @param src 原始位图
|
||||
* @param destW 目标宽度
|
||||
* @param destH 目标高度
|
||||
* @return 缩放后的位图
|
||||
*/
|
||||
private fun createScaledBitmap(src: Bitmap, destW: Int, destH: Int): Bitmap {
|
||||
val result = createBitmap(destW, destH)
|
||||
val canvas = Canvas(result)
|
||||
val paint = Paint(Paint.ANTI_ALIAS_FLAG or Paint.FILTER_BITMAP_FLAG or Paint.DITHER_FLAG)
|
||||
|
||||
// 等比例适配逻辑
|
||||
val scale = (destW.toFloat() / src.width).coerceAtMost(destH.toFloat() / src.height)
|
||||
val left = (destW - src.width * scale) / 2f
|
||||
val top = (destH - src.height * scale) / 2f
|
||||
|
||||
canvas.drawBitmap(src, null, RectF(left, top, left + src.width * scale, top + src.height * scale), paint)
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* 具体的 GIF 编码逻辑
|
||||
* @param bitmap 原始位图
|
||||
* @param outputStream 输出流
|
||||
*/
|
||||
private fun encodeToGif(bitmap: Bitmap, outputStream: OutputStream) {
|
||||
// Android 原生不支持直接写 GIF,通常需使用外部库或简单的 Bitmap 压缩
|
||||
// 这里演示通过 Bitmap 压缩模拟格式导出逻辑
|
||||
try {
|
||||
// 注意:Android 原生 compress 不支持 GIF 格式,
|
||||
// 实际开发中若需生成多帧 GIF,建议使用 'jp.co.cyberagent.android:gpuimage' 或第三方编码器
|
||||
bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream)
|
||||
outputStream.flush()
|
||||
outputStream.close()
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 具体的 SVG 编码逻辑
|
||||
* @param bitmap 原始位图
|
||||
* @param stream 输出流
|
||||
*/
|
||||
private fun encodeToSvg(bitmap: Bitmap, stream: OutputStream) {
|
||||
try {
|
||||
val width = bitmap.width
|
||||
val height = bitmap.height
|
||||
|
||||
val bos = ByteArrayOutputStream()
|
||||
// 使用 PNG 保证透明度
|
||||
bitmap.compress(Bitmap.CompressFormat.PNG, 100, bos)
|
||||
|
||||
// 必须使用 NO_WRAP 标志,如果不加 NO_WRAP,Base64 字符串每 76 个字符会插入一个换行符,导致 SVG 标签断开
|
||||
val base64Data = android.util.Base64.encodeToString(bos.toByteArray(), android.util.Base64.NO_WRAP)
|
||||
|
||||
// XML 命名空间地址
|
||||
val svgContent = """
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="$width" height="$height" viewBox="0 0 $width $height"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<image width="$width" height="$height"
|
||||
xlink:href="data:image/png;base64,$base64Data"
|
||||
href="data:image/png;base64,$base64Data" />
|
||||
</svg>
|
||||
""".trimIndent()
|
||||
|
||||
stream.write(svgContent.toByteArray(Charsets.UTF_8))
|
||||
stream.flush()
|
||||
} catch (e: Exception) {
|
||||
Log.e("ImageUtils", "SVG 编码失败: ${e.message}")
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
enum class ExportFormat(val extension: String, val mimeType: String) {
|
||||
JPG("jpg", "image/jpeg"),
|
||||
PNG("png", "image/png"),
|
||||
GIF("gif", "image/gif"),
|
||||
SVG("svg", "image/svg+xml")
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
package com.img.rabbit.utils
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.net.ConnectivityManager
|
||||
import android.net.Network
|
||||
import android.net.NetworkCapabilities
|
||||
import android.net.NetworkRequest
|
||||
import android.os.Build
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
|
||||
object NetworkMonitor {
|
||||
private val _networkStatus = MutableLiveData<Boolean>()
|
||||
val networkStatus: LiveData<Boolean> = _networkStatus
|
||||
|
||||
private var connectivityManager: ConnectivityManager? = null
|
||||
private var networkCallback: ConnectivityManager.NetworkCallback? = null
|
||||
|
||||
@SuppressLint("ObsoleteSdkInt")
|
||||
fun initialize(context: Context) {
|
||||
connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
// Android 7.0+ 使用registerDefaultNetworkCallback
|
||||
networkCallback = object : ConnectivityManager.NetworkCallback() {
|
||||
override fun onAvailable(network: Network) {
|
||||
_networkStatus.postValue(true)
|
||||
}
|
||||
|
||||
override fun onLost(network: Network) {
|
||||
_networkStatus.postValue(false)
|
||||
}
|
||||
|
||||
override fun onUnavailable() {
|
||||
_networkStatus.postValue(false)
|
||||
}
|
||||
}
|
||||
connectivityManager?.registerDefaultNetworkCallback(networkCallback as ConnectivityManager.NetworkCallback)
|
||||
} else {
|
||||
// Android 7.0以下使用传统方式
|
||||
val networkRequest = NetworkRequest.Builder()
|
||||
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
|
||||
.build()
|
||||
|
||||
networkCallback = object : ConnectivityManager.NetworkCallback() {
|
||||
override fun onAvailable(network: Network) {
|
||||
_networkStatus.postValue(true)
|
||||
}
|
||||
|
||||
override fun onLost(network: Network) {
|
||||
_networkStatus.postValue(false)
|
||||
}
|
||||
}
|
||||
connectivityManager?.registerNetworkCallback(networkRequest, networkCallback as ConnectivityManager.NetworkCallback)
|
||||
}
|
||||
|
||||
// 初始化当前网络状态
|
||||
_networkStatus.value = isNetworkAvailable(context)
|
||||
}
|
||||
|
||||
@SuppressLint("ObsoleteSdkInt")
|
||||
fun isNetworkAvailable(context: Context): Boolean {
|
||||
val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
||||
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
val network = connectivityManager.activeNetwork
|
||||
val capabilities = connectivityManager.getNetworkCapabilities(network)
|
||||
capabilities != null && (
|
||||
capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) ||
|
||||
capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) ||
|
||||
capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)
|
||||
)
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
val networkInfo = connectivityManager.activeNetworkInfo
|
||||
@Suppress("DEPRECATION")
|
||||
networkInfo != null && networkInfo.isConnected
|
||||
}
|
||||
}
|
||||
|
||||
fun unregister() {
|
||||
networkCallback?.let {
|
||||
connectivityManager?.unregisterNetworkCallback(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
package com.img.rabbit.utils
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.net.ConnectivityManager
|
||||
import android.net.NetworkCapabilities
|
||||
import android.os.Build
|
||||
|
||||
object NetworkStatus {
|
||||
|
||||
@SuppressLint("ObsoleteSdkInt")
|
||||
fun isNetworkAvailable(context: Context): Boolean {
|
||||
val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
val network = connectivityManager.activeNetwork
|
||||
val capabilities = connectivityManager.getNetworkCapabilities(network)
|
||||
capabilities != null && capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
val networkInfo = connectivityManager.activeNetworkInfo
|
||||
networkInfo != null && networkInfo.isConnected
|
||||
}
|
||||
}
|
||||
fun openNetworkSettings(context: Context) {
|
||||
context.startActivity(android.content.Intent(android.provider.Settings.ACTION_WIRELESS_SETTINGS))
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,167 @@
|
|||
package com.img.rabbit.utils
|
||||
|
||||
import android.Manifest
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.provider.Settings
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.core.content.ContextCompat
|
||||
|
||||
/**
|
||||
* 权限管理工具类
|
||||
*/
|
||||
object PermissionUtils {
|
||||
|
||||
// 相机权限
|
||||
const val CAMERA_PERMISSION = Manifest.permission.CAMERA
|
||||
|
||||
// 存储权限(根据Android版本区分)
|
||||
val STORAGE_PERMISSIONS = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
arrayOf(Manifest.permission.READ_MEDIA_IMAGES)
|
||||
} else {
|
||||
arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE)
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查权限是否已授予
|
||||
*/
|
||||
fun hasPermissions(context: Context, vararg permissions: String): Boolean {
|
||||
return permissions.all { permission ->
|
||||
ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查相机权限
|
||||
*/
|
||||
fun hasCameraPermission(context: Context): Boolean {
|
||||
return hasPermissions(context, CAMERA_PERMISSION)
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查存储权限
|
||||
*/
|
||||
fun hasStoragePermission(context: Context): Boolean {
|
||||
return hasPermissions(context, *STORAGE_PERMISSIONS)
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查所有图片相关权限
|
||||
*/
|
||||
fun hasImagePermissions(context: Context): Boolean {
|
||||
return hasCameraPermission(context) && hasStoragePermission(context)
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查相册权限(只需要存储权限)
|
||||
*/
|
||||
fun hasGalleryPermission(context: Context): Boolean {
|
||||
return hasStoragePermission(context)
|
||||
}
|
||||
|
||||
/**
|
||||
* 跳转到应用设置页面
|
||||
*/
|
||||
fun openAppSettings(context: Context) {
|
||||
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
|
||||
data = Uri.fromParts("package", context.packageName, null)
|
||||
}
|
||||
if (context is Activity) {
|
||||
context.startActivity(intent)
|
||||
} else {
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
context.startActivity(intent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 权限状态数据类
|
||||
*/
|
||||
data class PermissionState(
|
||||
val hasCameraPermission: Boolean = false,
|
||||
val hasStoragePermission: Boolean = false,
|
||||
val shouldShowRationale: Boolean = false
|
||||
)
|
||||
|
||||
/**
|
||||
* 可组合函数:管理权限请求
|
||||
*/
|
||||
@Composable
|
||||
fun rememberPermissionState(): MutableState<PermissionState> {
|
||||
val context = LocalContext.current
|
||||
return remember {
|
||||
mutableStateOf(
|
||||
PermissionState(
|
||||
hasCameraPermission = PermissionUtils.hasCameraPermission(context),
|
||||
hasStoragePermission = PermissionUtils.hasStoragePermission(context)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 可组合函数:创建权限请求启动器
|
||||
*/
|
||||
@Composable
|
||||
fun rememberPermissionLauncher(
|
||||
context: Context = LocalContext.current,
|
||||
onGranted: () -> Unit = {},
|
||||
onDenied: () -> Unit = {},
|
||||
onRationale: () -> Unit = {}
|
||||
) = rememberLauncherForActivityResult(
|
||||
ActivityResultContracts.RequestMultiplePermissions()
|
||||
) { permissions ->
|
||||
val allGranted = permissions.values.all { it }
|
||||
val shouldShowRationale = permissions.keys.any { permission ->
|
||||
androidx.core.app.ActivityCompat.shouldShowRequestPermissionRationale(
|
||||
context as Activity,
|
||||
permission
|
||||
)
|
||||
}
|
||||
|
||||
if (allGranted) {
|
||||
onGranted()
|
||||
} else if (shouldShowRationale) {
|
||||
onRationale()
|
||||
} else {
|
||||
onDenied()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 可组合函数:创建相机权限启动器
|
||||
*/
|
||||
@Composable
|
||||
fun rememberCameraPermissionLauncher(
|
||||
context: Context = LocalContext.current,
|
||||
onGranted: () -> Unit = {},
|
||||
onDenied: () -> Unit = {},
|
||||
onRationale: () -> Unit = {}
|
||||
) = rememberLauncherForActivityResult(
|
||||
ActivityResultContracts.RequestPermission()
|
||||
) { isGranted ->
|
||||
if (isGranted) {
|
||||
onGranted()
|
||||
} else {
|
||||
val shouldShowRationale = androidx.core.app.ActivityCompat.shouldShowRequestPermissionRationale(
|
||||
context as Activity,
|
||||
Manifest.permission.CAMERA
|
||||
)
|
||||
if (shouldShowRationale) {
|
||||
onRationale()
|
||||
} else {
|
||||
onDenied()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,129 @@
|
|||
package com.img.rabbit.utils
|
||||
|
||||
import android.graphics.*
|
||||
import com.google.mlkit.vision.common.InputImage
|
||||
import com.google.mlkit.vision.segmentation.Segmentation
|
||||
import com.google.mlkit.vision.segmentation.selfie.SelfieSegmenterOptions
|
||||
import androidx.core.graphics.createBitmap
|
||||
|
||||
import android.graphics.*
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import com.google.mlkit.vision.face.FaceContour
|
||||
import com.google.mlkit.vision.face.FaceDetection
|
||||
import com.google.mlkit.vision.face.FaceDetectorOptions
|
||||
import androidx.core.graphics.get
|
||||
|
||||
// 变换状态模型
|
||||
data class TransformState(
|
||||
val offset: Offset = Offset.Zero,
|
||||
val scale: Float = 1f,
|
||||
val rotation: Float = 0f
|
||||
)
|
||||
|
||||
|
||||
|
||||
object PhotoCutter {
|
||||
|
||||
fun cutPureHead(sourceBitmap: Bitmap, callback: (Bitmap?) -> Unit) {
|
||||
// 1. 配置分割器:单图模式
|
||||
val options = SelfieSegmenterOptions.Builder()
|
||||
.setDetectorMode(SelfieSegmenterOptions.SINGLE_IMAGE_MODE)
|
||||
.build()
|
||||
val segmenter = Segmentation.getClient(options)
|
||||
val image = InputImage.fromBitmap(sourceBitmap, 0)
|
||||
|
||||
// 2. 开始处理
|
||||
segmenter.process(image)
|
||||
.addOnSuccessListener { mask ->
|
||||
val resultBitmap = generateTransparentBitmap(sourceBitmap, mask.buffer)
|
||||
callback(resultBitmap)
|
||||
}
|
||||
.addOnFailureListener {
|
||||
callback(null)
|
||||
}
|
||||
}
|
||||
|
||||
private fun generateTransparentBitmap(
|
||||
source: Bitmap,
|
||||
maskBuffer: java.nio.ByteBuffer,
|
||||
): Bitmap {
|
||||
val width = source.width
|
||||
val height = source.height
|
||||
val result = createBitmap(width, height)
|
||||
|
||||
// 将掩码缓冲区重置
|
||||
maskBuffer.rewind()
|
||||
|
||||
// 逐像素处理
|
||||
val pixels = IntArray(width * height)
|
||||
source.getPixels(pixels, 0, width, 0, 0, width, height)
|
||||
|
||||
for (i in 0 until width * height) {
|
||||
// 获取当前像素的人像概率(0.0 ~ 1.0)
|
||||
val confidence = maskBuffer.float
|
||||
|
||||
// 如果置信度低于 0.5,认为该像素是背景,设为完全透明
|
||||
if (confidence < 0.5) {
|
||||
pixels[i] = Color.TRANSPARENT
|
||||
}
|
||||
}
|
||||
|
||||
result.setPixels(pixels, 0, width, 0, 0, width, height)
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
//object PhotoCutter {
|
||||
//
|
||||
// fun cutHeadFromImage(sourceBitmap: Bitmap, callback: (Bitmap?) -> Unit) {
|
||||
// // 1. 配置分割器:单图模式
|
||||
// val options = SelfieSegmenterOptions.Builder()
|
||||
// .setDetectorMode(SelfieSegmenterOptions.SINGLE_IMAGE_MODE)
|
||||
// .build()
|
||||
// val segmenter = Segmentation.getClient(options)
|
||||
// val image = InputImage.fromBitmap(sourceBitmap, 0)
|
||||
//
|
||||
// // 2. 开始处理
|
||||
// segmenter.process(image)
|
||||
// .addOnSuccessListener { mask ->
|
||||
// val resultBitmap = generateTransparentBitmap(sourceBitmap, mask.buffer, mask.width, mask.height)
|
||||
// callback(resultBitmap)
|
||||
// }
|
||||
// .addOnFailureListener {
|
||||
// callback(null)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// private fun generateTransparentBitmap(
|
||||
// source: Bitmap,
|
||||
// maskBuffer: java.nio.ByteBuffer,
|
||||
// maskWidth: Int,
|
||||
// maskHeight: Int
|
||||
// ): Bitmap {
|
||||
// val width = source.width
|
||||
// val height = source.height
|
||||
// val result = createBitmap(width, height)
|
||||
//
|
||||
// // 将掩码缓冲区重置
|
||||
// maskBuffer.rewind()
|
||||
//
|
||||
// // 逐像素处理
|
||||
// val pixels = IntArray(width * height)
|
||||
// source.getPixels(pixels, 0, width, 0, 0, width, height)
|
||||
//
|
||||
// for (i in 0 until width * height) {
|
||||
// // 获取当前像素的人像概率(0.0 ~ 1.0)
|
||||
// val confidence = maskBuffer.float
|
||||
//
|
||||
// // 如果置信度低于 0.5,认为该像素是背景,设为完全透明
|
||||
// if (confidence < 0.5) {
|
||||
// pixels[i] = Color.TRANSPARENT
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// result.setPixels(pixels, 0, width, 0, 0, width, height)
|
||||
// return result
|
||||
// }
|
||||
//}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
package com.img.rabbit.utils
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import androidx.core.net.toUri
|
||||
|
||||
object UrlLinkUtils {
|
||||
fun openAgreement(context: Context, url: String) {
|
||||
// 打开服务协议
|
||||
Intent(Intent.ACTION_VIEW, url.toUri()).apply {
|
||||
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
}.let { intent ->
|
||||
context.startActivity(intent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
package com.img.rabbit.viewmodel
|
||||
|
||||
import android.net.Uri
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.lifecycle.ViewModel
|
||||
|
||||
class FeedbackViewModel : ViewModel() {
|
||||
val isLoading = mutableStateOf(true)
|
||||
|
||||
fun setLoading(loading: Boolean) {
|
||||
isLoading.value = loading
|
||||
}
|
||||
|
||||
// 反馈类型
|
||||
private val _feedbackType = mutableStateOf(FeedbackType.FUNCTION)
|
||||
val feedbackType: FeedbackType
|
||||
get() = _feedbackType.value
|
||||
|
||||
fun setFeedbackType(feedbackType: FeedbackType) {
|
||||
_feedbackType.value = feedbackType
|
||||
}
|
||||
|
||||
// 补充反馈
|
||||
private val _feedbackMore = mutableStateOf("")
|
||||
val feedbackMore: String
|
||||
get() = _feedbackMore.value
|
||||
|
||||
fun setFeedbackMore(feedbackMore: String) {
|
||||
_feedbackMore.value = feedbackMore
|
||||
}
|
||||
|
||||
//提供图片
|
||||
private val _currentImageUris = mutableStateOf(emptyList<Uri>())
|
||||
val currentImageUris: List<Uri>
|
||||
get() = _currentImageUris.value
|
||||
|
||||
fun setCurrentImageUris(currentImageUris: List<Uri>) {
|
||||
_currentImageUris.value = currentImageUris
|
||||
}
|
||||
|
||||
// 联系方式
|
||||
private val _feedbackContact = mutableStateOf("")
|
||||
val feedbackContact: String
|
||||
get() = _feedbackContact.value
|
||||
|
||||
fun setFeedbackContact(feedbackContact: String) {
|
||||
_feedbackContact.value = feedbackContact
|
||||
}
|
||||
|
||||
// 提交反馈
|
||||
fun submitFeedback() {
|
||||
setLoading(true)
|
||||
}
|
||||
|
||||
enum class FeedbackType(val keyName: String, val value: String) {
|
||||
FUNCTION("function", "功能问题"),
|
||||
FEATURE("feature", "功能建议"),
|
||||
OTHER("other", "其他")
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
package com.img.rabbit.viewmodel
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.net.ConnectivityManager
|
||||
import android.net.Network
|
||||
import android.net.NetworkCapabilities
|
||||
import android.net.NetworkRequest
|
||||
import android.os.Build
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import com.tencent.mmkv.MMKV
|
||||
|
||||
@SuppressLint("ObsoleteSdkInt")
|
||||
class GeneralViewModel(application: Application) : AndroidViewModel(application) {
|
||||
val kv by lazy { MMKV.defaultMMKV() }
|
||||
private val _networkStatus = MutableLiveData<Boolean>()
|
||||
val networkStatus: LiveData<Boolean> = _networkStatus
|
||||
fun setNetworkStatus(status: Boolean) {
|
||||
_networkStatus.value = status
|
||||
}
|
||||
|
||||
private val _isNavigationBarVisible = MutableLiveData<Boolean>()
|
||||
val isNavigationBarVisible: LiveData<Boolean> = _isNavigationBarVisible
|
||||
|
||||
private val connectivityManager =
|
||||
application.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
||||
|
||||
private val networkCallback = object : ConnectivityManager.NetworkCallback() {
|
||||
override fun onAvailable(network: Network) {
|
||||
_networkStatus.postValue(true)
|
||||
}
|
||||
|
||||
override fun onLost(network: Network) {
|
||||
_networkStatus.postValue(false)
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
// 注册网络监听
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
connectivityManager.registerDefaultNetworkCallback(networkCallback)
|
||||
} else {
|
||||
val request = NetworkRequest.Builder()
|
||||
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
|
||||
.build()
|
||||
connectivityManager.registerNetworkCallback(request, networkCallback)
|
||||
}
|
||||
|
||||
// 初始化状态
|
||||
_networkStatus.value = isNetworkAvailable()
|
||||
}
|
||||
|
||||
private fun isNetworkAvailable(): Boolean {
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
val network = connectivityManager.activeNetwork
|
||||
val capabilities = connectivityManager.getNetworkCapabilities(network)
|
||||
capabilities != null && capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
val networkInfo = connectivityManager.activeNetworkInfo
|
||||
networkInfo != null && networkInfo.isConnected
|
||||
}
|
||||
}
|
||||
|
||||
fun setNavigationBarVisible(visible: Boolean){
|
||||
_isNavigationBarVisible.value = visible
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
super.onCleared()
|
||||
connectivityManager.unregisterNetworkCallback(networkCallback)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,151 @@
|
|||
package com.img.rabbit.viewmodel
|
||||
|
||||
import android.app.Activity
|
||||
import android.util.Log
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.compose.runtime.State
|
||||
import com.img.rabbit.pages.LoginScreenType
|
||||
import com.g.gysdk.GYManager
|
||||
import com.g.gysdk.GYResponse
|
||||
import com.g.gysdk.GyCallBack
|
||||
import com.g.gysdk.GyConfig
|
||||
import com.img.rabbit.bean.OnekeyPreLogin
|
||||
import kotlinx.serialization.json.Json
|
||||
|
||||
class LoginViewModel : ViewModel() {
|
||||
private val TAG = "LoginViewModel"
|
||||
private val ONEKEY_TAG = "OneKeyLoginViewModel"
|
||||
val loginScreenType = mutableStateOf(LoginScreenType.LOGIN_NORMAL)
|
||||
// 登录用户名
|
||||
val userName = mutableStateOf("")
|
||||
// 登录验证码
|
||||
val captcha = mutableStateOf("")
|
||||
|
||||
// 是否同意政策协议
|
||||
private val _policyAgreement = mutableStateOf(false)
|
||||
val isPolicyAgreement: State<Boolean> = _policyAgreement
|
||||
|
||||
|
||||
|
||||
private val isGYUIDValid = mutableStateOf(false)
|
||||
var oneKeyPreLogin: OnekeyPreLogin? = null
|
||||
|
||||
|
||||
fun setUserName(loginName: String) {
|
||||
userName.value = loginName
|
||||
}
|
||||
|
||||
fun setCaptcha(loginCaptcha: String) {
|
||||
captcha.value = loginCaptcha
|
||||
}
|
||||
|
||||
fun setIsPolicyAgreement(isAgreement: Boolean) {
|
||||
_policyAgreement.value = isAgreement
|
||||
}
|
||||
|
||||
|
||||
|
||||
private val _isLogin = mutableStateOf(false)
|
||||
val isLogin: State<Boolean> = _isLogin
|
||||
|
||||
fun setLogin(isLogin: Boolean) {
|
||||
_isLogin.value = isLogin
|
||||
}
|
||||
|
||||
fun oneKeyLoginForGeTuiSdk(activity: Activity, onShowOneKeyScreen:(Boolean)->Unit) {
|
||||
// 初始化 SDK
|
||||
GYManager.getInstance().init(GyConfig.with(activity.applicationContext).callBack(object : GyCallBack {
|
||||
override fun onSuccess(response: GYResponse?) {
|
||||
isGYUIDValid.value = true
|
||||
Log.i(ONEKEY_TAG, "--->初始化: onSuccess")
|
||||
|
||||
//预登录(提高拉起速度,可选但推荐)
|
||||
GYManager.getInstance().ePreLogin(/* timeout = */ 8000, /* gyCallBack = */ object : GyCallBack {
|
||||
override fun onSuccess(response: GYResponse?) {
|
||||
Log.i(ONEKEY_TAG, "--->预登录: onSuccess--->${response}")
|
||||
val preLoginData = response?.msg
|
||||
if (!preLoginData.isNullOrEmpty()) {
|
||||
try {
|
||||
val json = Json { ignoreUnknownKeys = true }
|
||||
oneKeyPreLogin =
|
||||
json.decodeFromString<OnekeyPreLogin>(preLoginData)
|
||||
|
||||
// 打印解析后的详细数据
|
||||
Log.i(ONEKEY_TAG, "=== 预登录数据解析结果 ===")
|
||||
Log.i(ONEKEY_TAG, "流程ID: ${oneKeyPreLogin?.process_id}")
|
||||
Log.i(ONEKEY_TAG, "运营商类型: ${oneKeyPreLogin?.operatorType}")
|
||||
Log.i(ONEKEY_TAG, "客户端类型: ${oneKeyPreLogin?.clienttype}")
|
||||
Log.i(ONEKEY_TAG, "访问码: ${oneKeyPreLogin?.accessCode}")
|
||||
Log.i(ONEKEY_TAG, "手机号: ${oneKeyPreLogin?.number}")
|
||||
Log.i(ONEKEY_TAG, "过期时间: ${oneKeyPreLogin?.expiredTime}")
|
||||
Log.i(ONEKEY_TAG, "错误码: ${oneKeyPreLogin?.errorCode}")
|
||||
Log.i(ONEKEY_TAG, "错误描述: ${oneKeyPreLogin?.errorDesc}")
|
||||
Log.i(ONEKEY_TAG, "耗时: ${oneKeyPreLogin?.costTime}ms")
|
||||
Log.i(ONEKEY_TAG, "================================")
|
||||
|
||||
// 根据解析结果决定是否继续(根据errorCode判断)
|
||||
if (oneKeyPreLogin?.errorCode == 0) {
|
||||
oneKeyLoginValid(
|
||||
activity = activity,
|
||||
onShowOneKeyScreen = onShowOneKeyScreen
|
||||
)
|
||||
} else {
|
||||
onShowOneKeyScreen(false)
|
||||
Log.e(ONEKEY_TAG, "预登录校验失败: ${oneKeyPreLogin?.errorDesc}")
|
||||
}
|
||||
|
||||
} catch (e: Exception) {
|
||||
Log.e(ONEKEY_TAG, "JSON解析失败: ${e.message}")
|
||||
Log.e(ONEKEY_TAG, "原始数据: $preLoginData")
|
||||
// 解析失败时继续执行原逻辑
|
||||
onShowOneKeyScreen(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFailed(response: GYResponse?) {
|
||||
onShowOneKeyScreen(false)
|
||||
Log.i(ONEKEY_TAG, "--->预登录: onFailed--->${response}")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
override fun onFailed(response: GYResponse?) {
|
||||
onShowOneKeyScreen(false)
|
||||
Log.i(ONEKEY_TAG, "--->初始化: onFailed--->${response}")
|
||||
}
|
||||
}).build())
|
||||
}
|
||||
|
||||
private fun oneKeyLoginValid(activity: Activity, onShowOneKeyScreen:(Boolean)->Unit) {
|
||||
if (GYManager.getInstance().isPreLoginResultValid) {
|
||||
//预登录有效,启动登录授权页
|
||||
onShowOneKeyScreen(true)
|
||||
Log.i(ONEKEY_TAG, "--->预登录校验有效A: onSuccess")
|
||||
} else {
|
||||
//考虑到是用户在等待,建议超时8s以上,至少设置5s以上
|
||||
GYManager.getInstance().ePreLogin(5000, object : GyCallBack {
|
||||
override fun onSuccess(response: GYResponse?) {
|
||||
//预登录成功,启动登录授权页
|
||||
onShowOneKeyScreen(true)
|
||||
Log.i(ONEKEY_TAG, "--->预登录校验有效B: onSuccess--->${response}")
|
||||
}
|
||||
|
||||
override fun onFailed(response: GYResponse?) {
|
||||
//预登录失败,提示用户稍后重试
|
||||
onShowOneKeyScreen(false)
|
||||
Log.i(ONEKEY_TAG, "--->预登录校验有效B: onFailed--->${response}")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 请求验证码
|
||||
*/
|
||||
fun requestCaptcha() {
|
||||
// TODO: 发送请求获取验证码
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.img.rabbit.viewmodel
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
|
||||
class SplashViewModel : ViewModel() {
|
||||
val isLoading = androidx.compose.runtime.mutableStateOf(true)
|
||||
|
||||
fun setLoading(loading: Boolean) {
|
||||
isLoading.value = loading
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- 未选中状态 -->
|
||||
<item android:state_checked="false" android:drawable="@mipmap/ic_tick_circle_normal" />
|
||||
<!-- 选中状态 -->
|
||||
<item android:state_checked="true" android:drawable="@mipmap/ic_tick_circle_checked" />
|
||||
<!-- 默认状态(未选中) -->
|
||||
<item android:drawable="@mipmap/ic_tick_circle_normal" />
|
||||
</selector>
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="#FF252525" />
|
||||
<corners android:radius="359dp" />
|
||||
</shape>
|
||||
|
|
@ -0,0 +1,113 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:background="@android:color/transparent">
|
||||
<!-- <View-->
|
||||
<!-- android:layout_width="match_parent"-->
|
||||
<!-- android:layout_height="380dp"-->
|
||||
<!-- android:background="@mipmap/ic_main_previous_mask"-->
|
||||
<!-- app:layout_constraintTop_toTopOf="parent"-->
|
||||
<!-- app:layout_constraintStart_toStartOf="parent"/>-->
|
||||
|
||||
<!-- <androidx.constraintlayout.widget.ConstraintLayout-->
|
||||
<!-- android:layout_width="120dp"-->
|
||||
<!-- android:layout_height="44dp"-->
|
||||
<!-- android:layout_marginTop="56dp"-->
|
||||
<!-- app:layout_constraintTop_toTopOf="parent"-->
|
||||
<!-- app:layout_constraintStart_toStartOf="parent">-->
|
||||
|
||||
<!-- <ImageView-->
|
||||
<!-- android:layout_width="24dp"-->
|
||||
<!-- android:layout_height="24dp"-->
|
||||
<!-- android:layout_marginStart="16dp"-->
|
||||
<!-- android:src="@mipmap/ic_back"-->
|
||||
<!-- app:layout_constraintTop_toTopOf="parent"-->
|
||||
<!-- app:layout_constraintStart_toStartOf="parent"/>-->
|
||||
|
||||
<!-- </androidx.constraintlayout.widget.ConstraintLayout>-->
|
||||
|
||||
<TextView
|
||||
android:id="@+id/layout_one_key_login_tv_phone"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="230dp"
|
||||
android:text="186***42876"
|
||||
android:textColor="#FF1A1A1A"
|
||||
android:textSize="36sp"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/layout_one_key_login_tv_service"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="天翼账号提供认证服务"
|
||||
android:textColor="#FF767676"
|
||||
android:textSize="14sp"
|
||||
app:layout_constraintTop_toBottomOf="@id/layout_one_key_login_tv_phone"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/layout_one_key_login_btn"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="56dp"
|
||||
android:layout_marginTop="46dp"
|
||||
android:layout_marginStart="30dp"
|
||||
android:layout_marginEnd="30dp"
|
||||
android:gravity="center"
|
||||
android:background="@drawable/shape_252525_cor359"
|
||||
android:drawableBottom="@drawable/shape_252525_cor359"
|
||||
android:text="本机号码一键绑定"
|
||||
android:textColor="#FFC2FF43"
|
||||
android:textSize="16sp"
|
||||
app:layout_constraintTop_toBottomOf="@id/layout_one_key_login_tv_service"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"/>
|
||||
|
||||
<!--协议政策-->
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/layout_one_key_login_agreement"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="14dp"
|
||||
android:layout_marginStart="30dp"
|
||||
android:layout_marginEnd="30dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/layout_one_key_login_btn">
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/layout_one_key_login_agreement_checkbox"
|
||||
android:layout_width="16dp"
|
||||
android:layout_height="16dp"
|
||||
android:layout_marginTop="3dp"
|
||||
android:button="@drawable/selector_circle_checkbox"
|
||||
android:background="@null"
|
||||
android:checked="true"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/layout_one_key_login_agreement_tv"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="5dp"
|
||||
android:layout_marginEnd="5dp"
|
||||
android:gravity="center"
|
||||
android:text="登录即认可《天翼账号服务与隐私协议》、《用户协议》和《隐私政策》并使用本机号码登录"
|
||||
android:textColor="#FF767676"
|
||||
android:textSize="14sp"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/layout_one_key_login_agreement_checkbox"
|
||||
app:layout_constraintEnd_toEndOf="parent"/>
|
||||
|
||||
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
After Width: | Height: | Size: 354 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 820 B |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 6.9 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 110 KiB |
|
After Width: | Height: | Size: 131 KiB |
|
After Width: | Height: | Size: 104 KiB |
|
After Width: | Height: | Size: 138 KiB |
|
After Width: | Height: | Size: 157 KiB |
|
After Width: | Height: | Size: 150 KiB |
|
After Width: | Height: | Size: 135 KiB |
|
After Width: | Height: | Size: 157 KiB |
|
After Width: | Height: | Size: 141 KiB |
|
After Width: | Height: | Size: 140 KiB |
|
After Width: | Height: | Size: 70 KiB |
|
After Width: | Height: | Size: 134 KiB |
|
After Width: | Height: | Size: 179 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 4.8 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 112 KiB |
|
After Width: | Height: | Size: 5.3 KiB |