1.创建项目

导入版本

1.gradle/libs.versions.toml

[versions]
accompanistPermissions = "0.36.0"
agp = "8.5.0-beta01"
coilCompose = "2.7.0"
constraintlayoutComposeVersion = "1.0.1"
hiltAndroid = "2.51.1"
hiltNavigationCompose = "1.2.0"
kotlin = "1.9.25"
coreKtx = "1.10.1"
junit = "4.13.2"
junitVersion = "1.1.5"
espressoCore = "3.5.1"
activityCompose = "1.9.2"
composeBom = "2024.09.01"
kotlinxSerializationJson = "1.6.3"
lifecycleViewmodelKtx = "2.8.5"
composeRuntime = "1.7.1"
lingver = "1.3.0"
loggingInterceptorVersion = "4.12.0"
navigationCompose = "2.8.0"
retrofit = "2.11.0"
roomRuntime = "2.6.1"
rxandroid = "2.1.1"
rxandroidVersion = "3.0.1"
rxjava = "2.2.21"
rxjava3 = "3.1.9"
rxlifecycle = "3.1.0"

[libraries]

androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
#region viewmodel livedata compose
androidx-lifecycle-extensions = { module = "androidx.lifecycle:lifecycle-extensions", version.ref = "lifecycleViewmodelKtx" }
androidx-lifecycle-lifecycle-runtime-compose = { module = "androidx.lifecycle:lifecycle-runtime-compose", version.ref = "lifecycleViewmodelKtx" }
androidx-lifecycle-livedata-ktx = { module = "androidx.lifecycle:lifecycle-livedata-ktx", version.ref = "lifecycleViewmodelKtx" }
androidx-lifecycle-viewmodel-compose = { module = "androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "lifecycleViewmodelKtx" }
androidx-lifecycle-viewmodel-ktx = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "lifecycleViewmodelKtx" }
androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleViewmodelKtx" }
androidx-lifecycle-viewmodel-savedstate = { module = "androidx.lifecycle:lifecycle-viewmodel-savedstate", version.ref = "lifecycleViewmodelKtx" }
constraintlayout-compose = { module = "androidx.constraintlayout:constraintlayout-compose", version.ref = "constraintlayoutComposeVersion" }
runtime-livedata = { module = "androidx.compose.runtime:runtime-livedata", version.ref = "composeRuntime" }
androidx-runtime = { module = "androidx.compose.runtime:runtime", version.ref = "composeRuntime" }
androidx-runtime-rxjava2 = { module = "androidx.compose.runtime:runtime-rxjava2" }
#endregion compose

#region kotlin serialization
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" }
#endregion

#region hilt
hilt-android = { module = "com.google.dagger:hilt-android", version.ref = "hiltAndroid" }
hilt-android-compiler = { module = "com.google.dagger:hilt-android-compiler", version.ref = "hiltAndroid" }
androidx-hilt-navigation-compose = { module = "androidx.hilt:hilt-navigation-compose", version.ref = "hiltNavigationCompose" }
#endregion

#region navigation
androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "navigationCompose" }
#endregion

#region retrofit2 and okhttp3
okhttp3-logging-interceptor = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "loggingInterceptorVersion" }
retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" }
converter-scalars = { module = "com.squareup.retrofit2:converter-scalars", version.ref = "retrofit" }
converter-gson = { module = "com.squareup.retrofit2:converter-gson", version.ref = "retrofit" }

#endregion okhttp3



#region rxjava 2
adapter-rxjava2 = { module = "com.squareup.retrofit2:adapter-rxjava2", version.ref = "retrofit" }
rxandroid = { module = "io.reactivex.rxjava2:rxandroid", version.ref = "rxandroid" }
rxjava = { module = "io.reactivex.rxjava2:rxjava", version.ref = "rxjava" }
#endregion


#region rxjava 3
rxjava3-rxandroid = { module = "io.reactivex.rxjava3:rxandroid", version.ref = "rxandroidVersion" }
rxjava3-rxjava = { module = "io.reactivex.rxjava3:rxjava", version.ref = "rxjava3" }
adapter-rxjava3 = { module = "com.squareup.retrofit2:adapter-rxjava3", version.ref = "retrofit" }
rxlifecycle = { module = "com.trello.rxlifecycle3:rxlifecycle", version.ref = "rxlifecycle" }
rxlifecycle-android-lifecycle-kotlin = { module = "com.trello.rxlifecycle3:rxlifecycle-android-lifecycle-kotlin", version.ref = "rxlifecycle" }
rxlifecycle-components = { module = "com.trello.rxlifecycle3:rxlifecycle-components", version.ref = "rxlifecycle" }

#endregion


#region room
androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "roomRuntime" }
androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref = "roomRuntime" }


#endregion



#region coil
coil-compose = { module = "io.coil-kt:coil-compose", version.ref = "coilCompose" }
#endregion


#region accompanist
accompanist-permissions = { module = "com.google.accompanist:accompanist-permissions", version.ref = "accompanistPermissions" }
#endregion

#region 国际化
lingver = { module = "com.github.YarikSOffice:lingver", version.ref = "lingver" }
#endregion

junit = { group = "junit", name = "junit", version.ref = "junit" }
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
androidx-ui = { group = "androidx.compose.ui", name = "ui" }
androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" }
androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
androidx-material3 = { group = "androidx.compose.material3", name = "material3" }

[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }

我整理好的版本,都用过了适配

2.模块目录下的 build.gradle.kts

plugins {
    alias(libs.plugins.android.application)
    alias(libs.plugins.jetbrains.kotlin.android)
    id("kotlin-kapt")
    id("com.google.dagger.hilt.android")
    kotlin("plugin.serialization")
    id("androidx.room")
}




android {
    namespace = "com.composeapp"
    compileSdk = 34

    defaultConfig {
        applicationId = "com.composeapp"
        minSdk = 24
        targetSdk = 34
        versionCode = 1
        versionName = "1.0"

        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
        vectorDrawables {
            useSupportLibrary = true
        }

    }
    room {
        schemaDirectory("$projectDir/schemas")
    }

    buildTypes {
        release {
            isMinifyEnabled = false
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro"
            )
        }

    }
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_1_8
        targetCompatibility = JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = "1.8"
    }
    buildFeatures {
        compose = true
    }
    composeOptions {
        kotlinCompilerExtensionVersion = "1.5.15"
    }
    packaging {
        resources {
            excludes += "/META-INF/{AL2.0,LGPL2.1}"
        }
    }


}

dependencies {

    implementation(libs.androidx.core.ktx)

    implementation(libs.androidx.activity.compose)
    implementation(platform(libs.androidx.compose.bom))
    implementation(libs.androidx.ui)
    implementation(libs.androidx.ui.graphics)
    implementation(libs.androidx.ui.tooling.preview)
    implementation(libs.androidx.material3)
    testImplementation(libs.junit)
    androidTestImplementation(libs.androidx.junit)
    androidTestImplementation(libs.androidx.espresso.core)
    androidTestImplementation(platform(libs.androidx.compose.bom))
    androidTestImplementation(libs.androidx.ui.test.junit4)
    debugImplementation(libs.androidx.ui.tooling)
    debugImplementation(libs.androidx.ui.test.manifest)

    implementation(libs.kotlinx.serialization.json)


    //region ViewModel Livedata compose runtime
    implementation(libs.androidx.lifecycle.viewmodel.ktx)
    implementation(libs.androidx.lifecycle.viewmodel.compose)
    implementation(libs.androidx.lifecycle.livedata.ktx)
    implementation(libs.androidx.lifecycle.runtime.ktx)
    implementation(libs.androidx.lifecycle.lifecycle.runtime.compose)
    implementation(libs.androidx.lifecycle.viewmodel.savedstate)
    implementation(libs.androidx.runtime)
    implementation(libs.runtime.livedata)
    //endregion


    //    region hilt
    implementation(libs.hilt.android)
    kapt(libs.hilt.android.compiler)
    implementation(libs.androidx.hilt.navigation.compose)
    // endregion


    //region  navigation
    implementation(libs.androidx.navigation.compose)
    //endregion navigation


    //region retrofit2 okhttp

    //Retrofit 核心库
    implementation(libs.retrofit)
    //响应数据自动序列化
    //JSON
    implementation(libs.converter.gson)
    //String类型
    implementation(libs.converter.scalars)
    //拦截器 logging
    implementation(libs.okhttp3.logging.interceptor)
    //endregion


    //region rxjava2
//    implementation(libs.androidx.runtime.rxjava2)
//    implementation(libs.adapter.rxjava2)
//    implementation(libs.rxjava)
//    implementation(libs.rxandroid)
    //endregion


    //region  rxjava3
    implementation(libs.rxjava3.rxjava)
    implementation(libs.adapter.rxjava3)
    implementation(libs.rxlifecycle)
    implementation(libs.rxlifecycle.android.lifecycle.kotlin)
    implementation(libs.rxlifecycle.components)
    implementation(libs.rxjava3.rxandroid)
    //endregion


    //region room
    implementation(libs.androidx.room.runtime)
    annotationProcessor(libs.androidx.room.compiler)
    kapt(libs.androidx.room.compiler)
    //endregion


    //region coil 异步网络图片加载
    implementation(libs.coil.compose)
    //endregion


    //region accompanist
    implementation(libs.accompanist.permissions)
    //endregion


    //region 国际化
    implementation(libs.lingver)

    // endregion

    //region compose 约束布局
    implementation (libs.constraintlayout.compose)

    //endregion

}


3.工程目录下的 build.gradle.kts

// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
    alias(libs.plugins.android.application) apply false
    alias(libs.plugins.jetbrains.kotlin.android) apply false
    id("com.google.dagger.hilt.android") version "2.51.1" apply false
    kotlin("kapt") version "1.9.25" apply false

    kotlin("jvm") version "1.9.25" apply false // or kotlin("multiplatform") or any other kotlin plugin
    kotlin("plugin.serialization") version "1.9.25" apply false

    val room_version = "2.6.1"
    id("androidx.room") version room_version apply false
}

4.工程目录下setting.gradle.kts

pluginManagement {
    repositories {
        google {
            content {
                includeGroupByRegex("com\\.android.*")
                includeGroupByRegex("com\\.google.*")
                includeGroupByRegex("androidx.*")
            }
        }
        mavenCentral()
        gradlePluginPortal()
    }
}
dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        google()
        mavenCentral()
        maven("https://jitpack.io")
    }
}

rootProject.name = "ComposeApp"
include(":app")
 

2.搭建Hilt环境

1.创建Application

@HiltAndroidApp
class MyApplication : Application() {


    companion object {

        const val SharedPreferencesFileName = "MyApplication";
        private lateinit var application: Application;
        lateinit var sharedPreferences: SharedPreferences
        fun getApplication(): Application {
            return application;
        }
    }

    @Override
    override fun onCreate() {
        super.onCreate()
//        Log.i("测试","app启动了 child="+child)
        application = this

        sharedPreferences =
            getSharedPreferences(SharedPreferencesFileName, Context.MODE_PRIVATE);

        /**
         * 国际化绑定 已经帮存入 sharedPreferences里了
         */
         Lingver.init(this)
    }

}

加上注解  @HiltAndroidApp

2.在用到注入的Activity上加上注解

@AndroidEntryPoint
class MainActivity : ComponentActivity() {

    /**
     * 将依赖注入好的viewModel放入这里
     */
    private val viewModel: XianPageViewModel by viewModels()

    @Inject
    lateinit var child: Child;
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContent {

            val sharedPreferences = MyApplication.sharedPreferences
            Log.i("share", "这是shared  全局 $sharedPreferences")

//            sharedPreferences.edit().putString("token", "123456").apply()

            //关闭 导航状态栏
            // 隐藏状态栏
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
                window.insetsController?.hide(WindowInsets.Type.statusBars())
            } else {
                @Suppress("DEPRECATION")
                window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_FULLSCREEN
            }

            this.lifecycleScope
            val string = sharedPreferences.getString("token", "")
            Log.i("share", "获取token  全局 $string")
            ComposeAppTheme {
//                ScaffoldExample()
//                MyApp()
//                NamePageRxjava3()
//                NamePage()
//                RoomTestPage()
//                NfcPage()
//                NfcMain()
//                ImageIconPage()
//                PermissionPage()

//                LiveDataTest()
//                LanguageSelector()
                LanguageSelectorKuangjia()

            }
        }


//        Log.i("测试","activity启动了 this="+this)
//        Log.i("测试","activity启动了 child="+child.activity)
//        Log.i("测试","activity启动了 child="+child.application)


        Log.i("测试", "activity启动了 viewModel=" + viewModel)


    }


}

3.测试注入

/**
 * 第一种: 相当于spring @Component
 */
@ActivityScoped
class Child @Inject constructor(@ActivityContext val activity: Context,@ApplicationContext val application: Context){

    val name = "你好"
//    val applicationContext = application

}

3.注入网络模块

1.单例类

/**
 * 全局唯一网络模块
 */
@Module
@InstallIn(SingletonComponent::class)
object NetModel {


    private const val Tag = "Retrofit:";
    private const val URL = "http://192.168.202.57:8080";

    @Singleton
    @Provides
    fun provideOkHttpClient(tokenInterceptor: TokenInterceptor): OkHttpClient {
        //构建日志拦截器
        val httpLoggingInterceptor = HttpLoggingInterceptor(object : HttpLoggingInterceptor.Logger {
            override fun log(message: String) {
                Log.i(Tag, message)
            }
        }).setLevel(HttpLoggingInterceptor.Level.BODY)
        //构建
        return OkHttpClient.Builder()
            .connectTimeout(5, TimeUnit.SECONDS)
            .readTimeout(30, TimeUnit.SECONDS)
            .addInterceptor(tokenInterceptor)
            .addInterceptor(httpLoggingInterceptor)
            .build()
    }



    @RxJava2Inject
    @Singleton
    @Provides
    fun provideRetrofitRxJava2(okHttpClient: OkHttpClient): Retrofit {
        return Retrofit.Builder()
            .baseUrl(URL)
            .client(okHttpClient)

//            .addConverterFactory(ScalarsConverterFactory.create())//添加Gson转换器
            .addConverterFactory(GsonConverterFactory.create())//添加Gson转换器
            //添加Rxjava适配
//            .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
            .build()
    }

    @RxJava3Inject
    @Singleton
    @Provides
    fun provideRetrofitRxJava3(okHttpClient: OkHttpClient): Retrofit {
        return Retrofit.Builder()
            .baseUrl(URL)
            .client(okHttpClient)
            .addCallAdapterFactory(RxJava3CallAdapterFactory.create())
//            .addConverterFactory(ScalarsConverterFactory.create())//添加Gson转换器
            .addConverterFactory(GsonConverterFactory.create())//添加Gson转换器

            //添加Rxjava适配
            .build()
    }

    @Singleton
    @Provides
    fun provideMainTestService(@RxJava3Inject retrofit: Retrofit): MainTestService {
        return retrofit.create(MainTestService::class.java)
    }



}

2.Token拦截器

@Singleton
class TokenInterceptor @Inject constructor() : Interceptor {
    override fun intercept(chain: Interceptor.Chain): Response {

        val request = chain.request()

        val token: String = MyApplication.sharedPreferences.getString("token", "2001").toString()
        val url = request.url.newBuilder().addQueryParameter("token1", token).build()


        val header = request.headers.newBuilder().add("token2", token).build()



        val requestNew = request.newBuilder().url(url).headers(header).build()

        return chain.proceed(requestNew);

    }
}

3.要是注入两个相同类型的

创建自定义注解

@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class RxJava2Inject
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class RxJava3Inject

在构造的时候声明

 @RxJava2Inject
    @Singleton
    @Provides
    fun provideRetrofitRxJava2(okHttpClient: OkHttpClient): Retrofit {
        return Retrofit.Builder()
            .baseUrl(URL)
            .client(okHttpClient)

//            .addConverterFactory(ScalarsConverterFactory.create())//添加Gson转换器
            .addConverterFactory(GsonConverterFactory.create())//添加Gson转换器
            //添加Rxjava适配
//            .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
            .build()
    }

    @RxJava3Inject
    @Singleton
    @Provides
    fun provideRetrofitRxJava3(okHttpClient: OkHttpClient): Retrofit {
        return Retrofit.Builder()
            .baseUrl(URL)
            .client(okHttpClient)
            .addCallAdapterFactory(RxJava3CallAdapterFactory.create())
//            .addConverterFactory(ScalarsConverterFactory.create())//添加Gson转换器
            .addConverterFactory(GsonConverterFactory.create())//添加Gson转换器

            //添加Rxjava适配
            .build()
    }

注入的时候声明注入哪个


    @Singleton
    @Provides
    fun provideMainTestService(@RxJava3Inject retrofit: Retrofit): MainTestService {
        return retrofit.create(MainTestService::class.java)
    }

4.声明接口

interface MainTestService {

//    @GET("/phone/gis/test")
//   suspend fun getTestData(): Response<String>

    @GET("/phone/gis/test")
    suspend fun getTestDataCall(): Call<TestDto>

    @GET("/phone/gis/test")
    suspend fun getTestData(): Response<TestDto>

    @GET("/phone/gis/test/{name}")
    suspend fun getTestDataTruth(@Path("name") name: String, @Query("age") age: Int): TestDto

    /**
     * 使用Rxjava 不允许加 suspend 关键字
     */
    @GET("/phone/gis/test/{name}")
    fun getTestDataRx(@Path("name") name: String, @Query("age") age: Int): Observable<TestDto>

}

RxJava写 接口,千万不要声明suspend ,要不然序列化JSON 会报错,无法实例化 Observable

5.要是发送http请求还需要权限

network_security_config.xml 

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <base-config cleartextTrafficPermitted="true" />
</network-security-config>

6.网络权限

    <uses-permission android:name="android.permission.INTERNET"/>

4.ViewModel

/**
 * viewmodel不能设置作用域,否则会报错
 *
 * ViewModel 部分中提到的 viewModel() 函数会自动使用 Hilt 通过 @HiltViewModel 注解构建的 ViewModel
 */
@HiltViewModel
class ScopePageViewModel @Inject constructor():ViewModel() {


    init {
        val job: Job = viewModelScope.launch { }//可以单独取消
    }

}

然后注入就可以了

5.LiveData

@HiltViewModel
class LiveDataTestViewModel @Inject constructor() : ViewModel() {


    val items:MutableLiveData<MutableList<Item>> = MutableLiveData(mutableListOf())

    init {
        val item1 = Item("测试模块1",true)
        items.value?.add(item1)
        val item2 = Item("测试模块2",true)
        items.value?.add(item2)
    }


   fun updateItem(item:Item){
        item.updateIsShow(!item.isShow.value!!)
    }




}

用法 

@Composable
fun LiveDataTest(viewModel: LiveDataTestViewModel = hiltViewModel()) {

    val items = viewModel.items.observeAsState(mutableListOf())



    LazyColumn(
        modifier = Modifier
            .fillMaxSize()
            .padding(30.dp)
    ) {

        item {
            Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceAround) {

                val width = Modifier
                    .width(150.dp)
                    .height(50.dp)
                Button(onClick = {
                    viewModel.updateItem(items.value[0])
                }, modifier = width) {
                    Text(text = stringResource(R.string._1))
                }
                Button(onClick = { viewModel.updateItem(items.value[1]) }, modifier = width) {
                    Text(text = "隐藏或打开测试模块2")
                }

            }


        }
        items(items.value) {

            ItemComponent(item = it)

        }


    }


}

6.整合navigation


@Serializable
data class Profile(val name: String)


@Serializable
object FriendsList

@Serializable
object ProfileScreenChild2

@Serializable
data class ProfileScreenChild1(val param: String)


@Serializable
data class Aaa(val param: String)

// Define the ProfileScreen composable.
@Composable
fun ProfileScreen(
    profile: Profile,
    parentViewModel: XianPageViewModel?,
    viewModel: XianPageViewModel = hiltViewModel(),
    onNavigateToFriendsList: () -> Unit

) {


//    ProfileScreen启动了com.composeapp.ui.view.XianPageViewModel@841f9de这是viewmodel
    Log.i("测试", "ProfileScreen启动了" + viewModel.toString() + "这是viewmodel")
    Log.i(
        "测试",
        "ProfileScreen启动了  这是父级的viewModel实例" + parentViewModel?.toString() + "这是父类viewmodel"
    )

    Column {
        Text("导航参数: ${profile.name}")
        ProfileScreenChild()
        Button(onClick = { onNavigateToFriendsList() }) {
            Text("Go to Friends List")
        }
    }
}

@Composable
fun ProfileScreenChild(viewModel: XianPageViewModel = hiltViewModel()) {
//     ProfileScreenChild启动了com.composeapp.ui.view.XianPageViewModel@841f9de这是viewmodel
    Log.i("测试", "ProfileScreenChild启动了" + viewModel.toString() + "这是viewmodel") //在同一个导航中是一致的
//    Text("子组件的值: ${viewModel.data.value}")


    val childNavController = rememberNavController()
    NavHost(childNavController, startDestination = ProfileScreenChild1("你好测试子组件Pchild1")) {
        composable<ProfileScreenChild2> { backStackEntry ->
            //专门用来拿到当前导航的堆栈里的ViewModel
            val parentEntry = remember(backStackEntry) {
                //必须类型和值都一样才能找到,否则抛异常
                childNavController.getBackStackEntry(ProfileScreenChild1("你好测试子组件Pchild1"))//必须值要一样才能找到,值不一致就找不到,跟序列化JSON比
            }
//            viewmodel 当组件移除组件树的时候,将会被销毁
            val parentViewModel = hiltViewModel<XianPageViewModel>(parentEntry)

            ProfileScreenChild2(parentViewModel)

        }
        composable<ProfileScreenChild1> { backStackEntry ->
            val child1 = backStackEntry.toRoute<ProfileScreenChild1>()
            ProfileScreenChild1(profileScreenChild1 = child1) {
                childNavController.navigate(ProfileScreenChild2)
            }
        }
    }


}

@Composable
fun ProfileScreenChild2(
    parentViewModel: XianPageViewModel,
    viewModel: XianPageViewModel = hiltViewModel()
) {
    Log.i(
        "测试",
        "ProfileScreenChild2启动了" + parentViewModel.toString() + "这是parent viewmodel"
    ) //在同一个导航中是一致的
    Log.i(
        "测试",
        "ProfileScreenChild2启动了" + viewModel.toString() + "这是viewmodel"
    ) //在同一个导航中是一致的
    Log.i(
        "测试",
        "ProfileScreenChild2启动了" + (viewModel === parentViewModel) + "这是viewmodel"
    ) //在同一个导航中是一致的
    Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
        Button(onClick = { /*TODO*/ }) {
            Text(text = "ProfileScreenChild2")
        }
    }
}

@Composable
fun ProfileScreenChild1(
    viewModel: XianPageViewModel = hiltViewModel(),
    profileScreenChild1: ProfileScreenChild1,
    toProChild2: () -> Unit
) {
    Log.i(
        "测试",
        "ProfileScreenChild1启动了" + viewModel.toString() + "这是viewmodel  这是param${profileScreenChild1.param}"
    ) //在同一个导航中是一致的
    Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
        Button(onClick = { toProChild2() }) {
            Text(text = "ProfileScreenChild1")
        }
    }

}


// Define the FriendsListScreen composable.
@Composable
fun FriendsListScreen(
    onNavigateToProfile: (String) -> Unit,
    viewModel: XianPageViewModel = hiltViewModel()
) {
    Log.i("测试", "FriendsListScreen启动了" + viewModel.toString() + "这是viewmodel")
    val (text, setText) = remember { mutableStateOf("") }
    Column {
        Text("Friends List")
        TextField(value = text, onValueChange = setText)
        Button(onClick = { onNavigateToProfile(text) }) {
            Text("Go to Profile")
        }
    }
}

// Define the MyApp composable, including the `NavController` and `NavHost`.
@Composable
fun MyApp(viewModel: XianPageViewModel = viewModel()) {

    Log.i("测试", "MyApp启动了 viewmodel=$viewModel")//是Activity同一个
    val navController = rememberNavController()
    NavHost(navController, startDestination = FriendsList) {
        composable<Profile> { backStackEntry ->
//            获取路由对象实例
            val profile: Profile = backStackEntry.toRoute()

            Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
                ProfileScreen(
                    profile = profile,
                    parentViewModel = null,
                    onNavigateToFriendsList = {
                        navController.navigate(route = FriendsList)
                    }
                )
            }

        }
        composable<FriendsList> {
            Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
                FriendsListScreen(
                    onNavigateToProfile = { it ->
                        navController.navigate(
                            route = Profile(name = "传入了新的参数:${it}")
                        )
                    }
                )
            }

        }
    }
}

7.整合room

1.实体类


@Entity
@Serializable
data class User(@PrimaryKey val uid:String,
                @ColumnInfo(name = "name")
                val name:String,
                val time:Long,
               val date:String?=null,
                val age :Int
) {
}

2.dao



@Dao
interface UserDao {

    @Query("SELECT * FROM user")
    fun getAll(): List<User>

    @Query("SELECT * FROM user WHERE uid = :userId")
    fun loadAllByIds(userId: String): List<User>

    @Query("SELECT * FROM user WHERE name LIKE :name")
    fun findByName(name: String): List<User>

    @Insert
    fun insertAll(vararg users: User)

    @Delete
    fun delete(user: User)




}

3.数据库



@Database(entities = [User::class], version = 3, exportSchema = true)
abstract class AppDataBase: RoomDatabase() {


    companion object{
        private const val DB_NAME = "my_app.db"

        fun getInstance(context: Context): AppDataBase{
            val db = Room.databaseBuilder(
                context,
                AppDataBase::class.java, DB_NAME
            )
                .addMigrations(MIGRATION_1_2,MIGRATION_2_3)
                .build()
            return db;
        }


        val MIGRATION_1_2 = object : Migration(1, 2) {
            override fun migrate(db: SupportSQLiteDatabase) {
                // 执行 SQL 语句来迁移数据库
                db.execSQL("ALTER TABLE user ADD COLUMN date TEXT")
            }
        }


        val MIGRATION_2_3 = object : Migration(2, 3) {
            override fun migrate(db: SupportSQLiteDatabase) {
                // 执行 SQL 语句来迁移数据库
                db.execSQL("ALTER TABLE user ADD COLUMN age INTEGER NOT NULL default 1")
            }
        }

    }
    /**
     * 数据库当中的一张表
     */
    abstract fun UserDao():UserDao


}

4.Dao实例注入


/**
 * 全局唯一数据库模块
 */
@Module
@InstallIn(SingletonComponent::class)
object DataBaseModel {


    @Singleton
    @Provides
    fun provideAppDataBase(@ApplicationContext context:Context): AppDataBase {
        return AppDataBase.getInstance(context)
    }


    @Singleton
    @Provides
    fun provideUserDao(appDataBase: AppDataBase): UserDao {
        return appDataBase.UserDao()
    }



}

8.Permission



@SuppressLint("CheckResult")
@OptIn(ExperimentalPermissionsApi::class)
@Composable
fun PermissionPage(){


    // Camera permission state
    val cameraPermissionState = rememberPermissionState(
        android.Manifest.permission.WRITE_EXTERNAL_STORAGE
    )

    Observable.fromCallable {
        Log.i("执行","执行在"+Thread.currentThread().name)
        "true"
    }.subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe({
            Log.i("执行","执行成功$it  "+Thread.currentThread().name)
        },{
            Log.i("执行","执行失败"+it.message)
        })

    Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center){
        if (cameraPermissionState.status.isGranted) {
            Text("相机权限授予成功")
        } else {
            Column {
                val textToShow = if (cameraPermissionState.status.shouldShowRationale) {
                    // If the user has denied the permission but the rationale can be shown,
                    // then gently explain why the app requires this permission
                    "相机权限是核心. Please grant the permission."
                } else {
                    // If it's the first time the user lands on this feature, or the user
                    // doesn't want to be asked again for this permission, explain that the
                    // permission is required
                    "必须给予相机权限. " +
                            "Please grant the permission"
                }
                Text(textToShow)
                Button(onClick = { cameraPermissionState.launchPermissionRequest() }) {
                    Text("Request permission")
                }
            }
        }
    }



}

9.Kotlin json 序列化



import kotlinx.serialization.Serializable
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json

@Serializable
data class Project(val name: String, val language: String)

fun main() {
    // Serializing objects
    val data = Project("kotlinx.serialization", "Kotlin")
    val string = Json.encodeToString(data)
    println(string) // {"name":"kotlinx.serialization","language":"Kotlin"}
    // Deserializing back into objects
    val obj = Json.decodeFromString<Project>(string)
    println(obj) // Project(name=kotlinx.serialization, language=Kotlin)
}

10.kotlin 冷流



import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking

fun main() {

    runBlocking {

      val coldFlow =  flow {
            for (i in 1..5){
                delay(3000)
                emit(i)
            }
        }



        // 订阅者 1
        launch {
            coldFlow.collect { value ->
                println("Subscriber 1 received: $value")
            }
        }

        // 订阅者 2
        launch {
            delay(2000) // 延迟订阅,确保第二个订阅者在第一个订阅者之后
            coldFlow.collect { value ->

                println("Subscriber 2 received${Thread.currentThread().name}: $value")
            }
        }

        println("执行")






    }





}

11.kotlin 热流



import androidx.compose.runtime.collectAsState
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking

fun main() {

runBlocking {
    // 创建一个 StateFlow 实例,初始状态为 0
    val stateFlow = MutableStateFlow(0)

    // 启动一个协程来更新 StateFlow 的状态
    launch {
        repeat(5) {
            delay(500) // 模拟数据生成
            stateFlow.value += 1

        }
    }

    // 订阅 StateFlow 并打印最新状态
    launch {
        stateFlow.collect { value ->
            println("Subscriber received: $value")
        }
    }
}

}


import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking

fun main() {
    runBlocking {
        // 创建一个 SharedFlow
        val sharedFlow = MutableSharedFlow<Int>()

        // 启动一个协程来发射数据
        launch {
            repeat(10) {
                delay(100) // 模拟异步操作
                sharedFlow.emit(it) // 发射数据

            }
        }

        // 订阅 SharedFlow
        launch {
            sharedFlow.collect { value ->
                println("Subscriber 1 received: $value")
            }
        }

        // 另一个订阅者
        launch {
            sharedFlow.collect { value ->
                println("Subscriber 2 received: $value")
            }
        }
    }
}

12.全局语言切换

在 Application created 方法 

    /**
         * 国际化绑定 已经帮存入 sharedPreferences里了
         */
         Lingver.init(this)



@Composable
fun LanguageSelectorKuangjia(localeViewmodel: LocaleViewmodel= hiltViewModel()) {

//    val locale = MyApplication.sharedPreferences.getString("locale",null)
//
//    val localeState = localeViewmodel.locale.observeAsState(
        if (locale == null) Locale.getDefault() else Locale(locale)
//        locale?.let { Locale(it) }?:Locale.getDefault()
//    )
    val current = LocalContext.current

    Column(modifier = Modifier
        .fillMaxSize()
        .padding(30.dp)) {
        // Example buttons to switch languages
        Row {
            Button(onClick = {
                Lingver.getInstance().setLocale(current,Locale.SIMPLIFIED_CHINESE)
                (current as Activity).recreate()
            }) {
                Text("中文")
            }
            Button(onClick = {
                Lingver.getInstance().setLocale(current,Locale.ENGLISH)
                (current as Activity).recreate()
            }) {
                Text("英文")
            }
        }
        // Apply the selected language
//        LanguageSwitcherKuangjia(localeState.value) {
            // Your UI content here, which will reflect the selected language
            Text(text = stringResource(id = R.string._1))
//        }
    }

}



自己写的扩展函数

/**
 * 转变语言扩展函数 会保留在当前的Navigation 导航里
 */
fun Context.changeLocale(locale: Locale) {
    Lingver.getInstance().setLocale(this,locale)
    (this as Activity).recreate()
}

在 导航中 重创建,也会跟随导航目的地,不会重置

13.使用 阿里矢量图

iconfont-阿里巴巴矢量图标库

1.存一个地方

2.下载这个插件

3.右键一个空文件夹

4.操作

 

14.文件分享

1.文件提供者

     <provider
            android:authorities="${applicationId}.fileprovider"
            android:name="androidx.core.content.FileProvider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/file_paths" />
        </provider>

2. 外部存储空间权限文件

file_paths.xml

<?xml version="1.0" encoding="utf-8"?>
<paths>
    <external-path name="external_files" path="."/>
</paths>

3. MediaStore



import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.Environment;
import android.provider.MediaStore;

public class ExportMedia {
    public static Uri saveExcelFileToMediaStoreInExternalDownloadExport(Context context, String fileName) {
        // 创建 ContentValues 对象
        ContentValues values = new ContentValues();
        values.put(MediaStore.MediaColumns.DISPLAY_NAME, fileName);
        values.put(MediaStore.MediaColumns.MIME_TYPE, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        values.put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS+"/EXPORT"); // 下载目录

        // 插入文件到 MediaStore
       return context.getContentResolver().insert(MediaStore.Files.getContentUri("external"), values);
    }



    public Uri queryExcelFiles(Context context,  String fileName) {
        String[] projection = {
                MediaStore.Files.FileColumns._ID,
                MediaStore.Files.FileColumns.DISPLAY_NAME,
                MediaStore.Files.FileColumns.MIME_TYPE
        };

        String selection = MediaStore.Files.FileColumns.MIME_TYPE + "=?";
        String[] selectionArgs = {"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"}; // .xlsx 的 MIME 类型

        Cursor cursor = context.getContentResolver().query(
                MediaStore.Files.getContentUri("external"),
                projection,
                selection,
                selectionArgs,
                null
        );

        try {
            while (cursor.moveToNext()) {
                long id = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns._ID));
                String displayName = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns.DISPLAY_NAME));
                if (displayName.equals(fileName)){
                    // 处理查询结果,例如分享文件
                    return ContentUris.withAppendedId(MediaStore.Files.getContentUri("external"), id);
                }
            }
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        }finally {
            if (cursor != null){
                cursor.close();
            }
        }
        return null;
    }
}
public class ShareUtils {

    public static void shareExcel(Context context,String path){
        // 文件路径,确保文件存在
        File fileToShare = new File(path);

        // 创建分享意图
        Intent shareIntent = new Intent(Intent.ACTION_SEND);
        shareIntent.setType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); // MIME 类型

        // 使用 FileProvider 获取 content URI
        Uri fileUri = FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".fileprovider", fileToShare);
        shareIntent.putExtra(Intent.EXTRA_STREAM, fileUri);

        // 允许临时读取 URI 权限
        shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

        // 启动分享对话框
        context.startActivity(Intent.createChooser(shareIntent, "分享一个Excel"));

    }
    public static void shareExcel(Context context,Uri uri){


        // 创建分享意图
        Intent shareIntent = new Intent(Intent.ACTION_SEND);
        shareIntent.setType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); // MIME 类型

        // 使用 FileProvider 获取 content URI
        shareIntent.putExtra(Intent.EXTRA_STREAM, uri);

        // 允许临时读取 URI 权限
        shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);

        // 启动分享对话框
        context.startActivity(Intent.createChooser(shareIntent, "分享一个Excel"));

    }
}
    <!--在sdcard中创建/删除文件的权限 -->
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" tools:ignore="ProtectedPermissions" />
    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
        tools:ignore="ScopedStorage" />

15. NFC

1.开启权限

 <uses-permission android:name="android.permission.NFC" />

2.创建工具类



import android.nfc.tech.NfcA
import java.security.MessageDigest


object NfcHelper {


    private fun readUid(nfcA: NfcA): ByteArray {
        val command = byteArrayOf(xxxx.toByte(), xxxxx.toByte())
        val uid = nfcA.transceive(command)
        return byteArrayOf(uid[0], uid[1], uid[2], uid[3], uid[4], uid[5], uid[6], uid[7])
    }

    /**
     * 处理密码
     *
     * @param nfcA
     * @return
     */
    private fun handlePwd(nfcA: NfcA): ByteArray {
        val uidByte = readUid(nfcA).copyOf(7)
        val secret = byteArrayOf(xxxxxxxxxxxxxxxxxx)
        //两个数组重载运算符 拼接在一起
        val byteUnique = uidByte + secret

        //加密SHA -256
        val digest = MessageDigest.getInstance("SHA-256")
        // 更新 MessageDigest 实例,传入要哈希的数据
        digest.update(byteUnique)
        // 计算哈希值并返回 取前四位
        val pwdBytes = digest.digest().copyOf(4)


        return pwdBytes

    }

    /**
     * 新nfc芯片验证密码
     *
     * @param nfcA
     * @return
     */
    fun authNew(nfcA: NfcA) {
        val pwd = handlePwd(nfcA)
        //拼接两个数组
        val command = byteArrayOf(0x1B.toByte()) + pwd
        nfcA.transceive(command)
    }


    /**
     * 新nfc 芯片读方法
     *
     * @param nfcA
     * @param startPage
     * @param endPage
     * @return
     */
    fun readTag(nfcA: NfcA, startPage: Int, endPage: Int): List<Byte> {
        val list = arrayListOf<Byte>()
        for (i in startPage..endPage) {
            val data = nfcA.transceive(byteArrayOf(0x30, i.toByte()))
            list.addAll(data.asList().subList(0,4))
        }
        return list;
    }

    /**
     * 新nfc 芯片写方法
     *
     * @param nfcA
     * @param writeByte
     * @param block     扇区,也就是页
     */
    fun writeTag(nfcA: NfcA, writeByte: ByteArray, page: Int) {
        val cmd = byteArrayOf(0xA2.toByte(), page.toByte()) + writeByte
        nfcA.transceive(cmd)
    }


}

3.Model

/**
 * 全局唯一NFC模块
 */
@Module
@InstallIn(SingletonComponent::class)
object NfcModel {


    @Singleton
    @Provides
    fun provideNfcAdapter(@ApplicationContext context: Context): NfcAdapter {
        return NfcAdapter.getDefaultAdapter(context)
    }

}

4.viewModel

import android.nfc.NfcAdapter
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.scopes.ActivityScoped
import javax.inject.Inject

/**
 * viewmodel不能设置作用域,否则会报错
 */
@HiltViewModel
class NfcViewModel @Inject constructor(val nfcAdapter: NfcAdapter):ViewModel() {

    // 使用 MutableLiveData 来持有数据



}

5.页面

@Composable
fun NfcMain(){

    var open by remember { mutableStateOf(false) }


   Column(Modifier.fillMaxSize()) {

       Box(modifier = Modifier
           .fillMaxWidth()
           .height(100.dp), contentAlignment = Alignment.Center){
           Button(onClick = { open = !open }) {
               Text(text = if (open) "点击关闭NFC" else "点击开启NFC")
           }
       }

       if (open){
           NfcPage()
       }else{
           Box(modifier = Modifier
               .fillMaxSize().background(Color.Blue)
               , contentAlignment = Alignment.Center){
               Text(text = "NFC未开启")
           }
       }




   }






}




@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun NfcPage(modifier: Modifier = Modifier, nfcViewModel: NfcViewModel = hiltViewModel()) {
    var presses by remember { mutableIntStateOf(0) }


    val readOrWrite = remember {
        mutableStateOf(false)
    }


    val (text, setText) = remember { mutableStateOf("") }

    val nfcAdapter = nfcViewModel.nfcAdapter

    val list = remember {
        mutableStateListOf<String>()
    }
    val context = LocalContext.current
    LaunchedEffect(key1 = Unit) {


        val pendingIntent = PendingIntent.getActivity(
            context, 0, Intent(
                MyApplication.getApplication(), context::class.java
            )
                .addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), PendingIntent.FLAG_IMMUTABLE
        )
        val filters = arrayOf(IntentFilter(NfcAdapter.ACTION_TECH_DISCOVERED))
        val arrayOf = arrayOf(arrayOf<String>(NfcA::class.java.name))

        nfcAdapter.enableReaderMode(context as Activity?, { tag ->
            val nfcA = NfcA.get(tag)
            Log.i("NfcA", "NfcA: $nfcA")

            nfcA.timeout = 3000
            try {
                nfcA.connect()



                Log.i("测试","nfc超时时间 ${nfcA.timeout} ")
                Log.i("测试","nfc最大长度 ${nfcA.maxTransceiveLength} ")

                NfcHelper.authNew(nfcA)

//                NewNfcHelper.authNew(nfcA)


                if (readOrWrite.value){
                    val readTag = NfcHelper.writeTag(nfcA, byteArrayOf(2,3,4,1), 4)
                    Log.i("测试","写入成功 $readTag")
                }else{
                    val readTag = NfcHelper.readTag(nfcA, 4, 4)
                    Log.i("测试","读取成功 $readTag")
                }

            } catch (e: Exception) {
                e.printStackTrace()
            } finally {
                nfcA.close()
            }




        }, NfcAdapter.FLAG_READER_NFC_A, null);


        nfcAdapter.enableForegroundDispatch(context, pendingIntent, filters, arrayOf)

    }

    DisposableEffect(key1 = Unit) {

        this.onDispose {

            nfcAdapter.disableForegroundDispatch(context as Activity)
            nfcAdapter.disableReaderMode(context as Activity)

        }
    }

    Scaffold(
        topBar = {
            TopAppBar(
                colors = topAppBarColors(
                    containerColor = MaterialTheme.colorScheme.primaryContainer,
                    titleContentColor = MaterialTheme.colorScheme.primary,
                ),
                title = {
                    Box(
                        modifier = Modifier
                            .fillMaxWidth(),
                        contentAlignment = Alignment.Center // 设置 Box 的内容居中
                    ) {
                        TextField(
                            value = text, onValueChange = setText,
                            modifier = Modifier
                                .width(200.dp)
                                .height(50.dp),// 调整宽度
                            // 调整高度
                            textStyle = MaterialTheme.typography.bodyLarge.copy(
                                fontSize = 16.sp,
                                lineHeight = TextUnit(200f, TextUnitType.Sp)
                            ), // 调整字体大小
                        ) // 调整内边距
                    }


                }
            )
        },
        bottomBar = {
            BottomAppBar(
                containerColor = MaterialTheme.colorScheme.primaryContainer,
                contentColor = MaterialTheme.colorScheme.primary,
            ) {
                Row(
                    modifier = Modifier
                        .fillMaxWidth()
                        .padding(horizontal = 16.dp),
                    horizontalArrangement = Arrangement.spacedBy(space = 12.dp),
                    verticalAlignment = Alignment.CenterVertically
                ) {
                    Button(modifier = Modifier.weight(1f), onClick = {


                    }) {
                        Text(text = "添加")
                    }
                    Button(modifier = Modifier.weight(1f), onClick = {
                        readOrWrite.value = !readOrWrite.value
                    }) {
                        Text(text = if (readOrWrite.value) "写入" else "读取")
                    }
                    Button(modifier = Modifier.weight(1f), onClick = {

                        list.clear()

                    }) {
                        Text(text = "清空")
                    }
                }

            }
        },
        floatingActionButton = {
            FloatingActionButton(onClick = { presses++ }) {
                Icon(Icons.Default.Build, contentDescription = "Add")
            }
        }
    ) { innerPadding ->

        LazyColumn(modifier = Modifier.padding(innerPadding)) {
            items(items = list, key = { it }) {
                Text(
                    text = it,
                    modifier = Modifier
                        .fillMaxWidth()
                        .height(40.dp),
                    textAlign = TextAlign.Center
                )
            }

        }
    }
}

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部