├── App ├── .gitignore ├── .idea │ ├── .gitignore │ ├── compiler.xml │ ├── deploymentTargetDropDown.xml │ ├── gradle.xml │ ├── inspectionProfiles │ │ └── Project_Default.xml │ ├── misc.xml │ └── vcs.xml ├── app │ ├── .gitignore │ ├── build.gradle │ ├── proguard-rules.pro │ └── src │ │ ├── androidTest │ │ └── java │ │ │ └── icu │ │ │ └── bughub │ │ │ └── app │ │ │ └── app │ │ │ └── ExampleInstrumentedTest.kt │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ │ └── icu │ │ │ │ └── bughub │ │ │ │ └── app │ │ │ │ └── app │ │ │ │ ├── MainActivity.kt │ │ │ │ ├── compositionLocal │ │ │ │ └── CompositionLocal.kt │ │ │ │ ├── extension │ │ │ │ └── LazyListStateExtension.kt │ │ │ │ ├── model │ │ │ │ ├── Network.kt │ │ │ │ ├── entity │ │ │ │ │ ├── ArticleEntity.kt │ │ │ │ │ ├── BaseResponse.kt │ │ │ │ │ ├── Category.kt │ │ │ │ │ ├── DataType.kt │ │ │ │ │ ├── NavigationItem.kt │ │ │ │ │ ├── SwiperEntity.kt │ │ │ │ │ ├── UserInfoEntity.kt │ │ │ │ │ └── VideoEntity.kt │ │ │ │ └── service │ │ │ │ │ ├── ArticleService.kt │ │ │ │ │ ├── HomeService.kt │ │ │ │ │ ├── UserInfoManager.kt │ │ │ │ │ ├── UserService.kt │ │ │ │ │ └── VideoService.kt │ │ │ │ ├── ui │ │ │ │ ├── components │ │ │ │ │ ├── ArticleItem.kt │ │ │ │ │ ├── ChartView.kt │ │ │ │ │ ├── CircleRing.kt │ │ │ │ │ ├── DailyTaskContent.kt │ │ │ │ │ ├── NavHostApp.kt │ │ │ │ │ ├── NotificationContent.kt │ │ │ │ │ ├── SwiperContent.kt │ │ │ │ │ ├── TopAppBar.kt │ │ │ │ │ ├── VideoItem.kt │ │ │ │ │ └── video │ │ │ │ │ │ ├── PlayerValue.kt │ │ │ │ │ │ ├── VideoPlayer.kt │ │ │ │ │ │ ├── VideoView.kt │ │ │ │ │ │ └── VodController.kt │ │ │ │ ├── navigation │ │ │ │ │ └── Destinations.kt │ │ │ │ ├── screens │ │ │ │ │ ├── ArticleDetailScreen.kt │ │ │ │ │ ├── LoginScreen.kt │ │ │ │ │ ├── MainFrame.kt │ │ │ │ │ ├── MineScreen.kt │ │ │ │ │ ├── StudyScreen.kt │ │ │ │ │ ├── TaskScreen.kt │ │ │ │ │ └── VideoDetailScreen.kt │ │ │ │ └── theme │ │ │ │ │ ├── Color.kt │ │ │ │ │ ├── Shape.kt │ │ │ │ │ ├── Theme.kt │ │ │ │ │ └── Type.kt │ │ │ │ └── viewmodel │ │ │ │ ├── ArticleViewModel.kt │ │ │ │ ├── MainViewModel.kt │ │ │ │ ├── TaskViewModel.kt │ │ │ │ ├── UserViewModel.kt │ │ │ │ └── VideoViewModel.kt │ │ └── res │ │ │ ├── drawable-v24 │ │ │ └── ic_launcher_foreground.xml │ │ │ ├── drawable │ │ │ ├── archives.png │ │ │ ├── bg.jpg │ │ │ ├── browser_history.png │ │ │ ├── ic_launcher_background.xml │ │ │ ├── newbanner3.png │ │ │ ├── points.png │ │ │ ├── questions.png │ │ │ ├── settings.png │ │ │ └── version.png │ │ │ ├── layout │ │ │ └── video.xml │ │ │ ├── mipmap-anydpi-v26 │ │ │ ├── ic_launcher.xml │ │ │ └── ic_launcher_round.xml │ │ │ ├── mipmap-hdpi │ │ │ ├── ic_launcher.webp │ │ │ └── ic_launcher_round.webp │ │ │ ├── mipmap-mdpi │ │ │ ├── ic_launcher.webp │ │ │ └── ic_launcher_round.webp │ │ │ ├── mipmap-xhdpi │ │ │ ├── ic_launcher.webp │ │ │ └── ic_launcher_round.webp │ │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.webp │ │ │ └── ic_launcher_round.webp │ │ │ ├── mipmap-xxxhdpi │ │ │ ├── ic_launcher.webp │ │ │ └── ic_launcher_round.webp │ │ │ └── values │ │ │ ├── colors.xml │ │ │ ├── strings.xml │ │ │ └── themes.xml │ │ └── test │ │ └── java │ │ └── icu │ │ └── bughub │ │ └── app │ │ └── app │ │ └── ExampleUnitTest.kt ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── webview │ ├── .gitignore │ ├── build.gradle │ ├── consumer-rules.pro │ ├── proguard-rules.pro │ └── src │ ├── androidTest │ └── java │ │ └── icu │ │ └── bughub │ │ └── app │ │ └── module │ │ └── webview │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ └── java │ │ └── icu │ │ └── bughub │ │ └── app │ │ └── module │ │ └── webview │ │ └── WebView.kt │ └── test │ └── java │ └── icu │ └── bughub │ └── app │ └── module │ └── webview │ └── ExampleUnitTest.kt └── ComposeBasic ├── .gitignore ├── .idea ├── .gitignore ├── compiler.xml ├── gradle.xml ├── inspectionProfiles │ └── Project_Default.xml ├── misc.xml └── vcs.xml ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── icu │ │ └── bughub │ │ └── app │ │ └── composebasic │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── icu │ │ │ └── bughub │ │ │ └── app │ │ │ └── composebasic │ │ │ ├── MainActivity.kt │ │ │ ├── components │ │ │ ├── AnimationCanvasSample.kt │ │ │ ├── AnimationSample.kt │ │ │ ├── BoxSample.kt │ │ │ ├── ButtonSample.kt │ │ │ ├── CanvasSample.kt │ │ │ ├── CardSample.kt │ │ │ ├── CheckBoxSample.kt │ │ │ ├── ColumnSample.kt │ │ │ ├── CompositionLocalSample.kt │ │ │ ├── ConstaintLayoutSample.kt │ │ │ ├── ConstraintsSample.kt │ │ │ ├── DialogSample.kt │ │ │ ├── DividerSample.kt │ │ │ ├── DropdownMenuSample.kt │ │ │ ├── IconSample.kt │ │ │ ├── ImageSample.kt │ │ │ ├── LazyColumnSample.kt │ │ │ ├── LazyRowSample.kt │ │ │ ├── LazyVerticalGridSample.kt │ │ │ ├── LifecycleSample.kt │ │ │ ├── ListItemSample.kt │ │ │ ├── ModifierSample.kt │ │ │ ├── NavSample.kt │ │ │ ├── PermissionSample.kt │ │ │ ├── ProgressIndicatorSample.kt │ │ │ ├── RadioButtonSample.kt │ │ │ ├── RowSample.kt │ │ │ ├── ScaffoldSample.kt │ │ │ ├── SliderSample.kt │ │ │ ├── SpacerSample.kt │ │ │ ├── StateSample.kt │ │ │ ├── Stepper.kt │ │ │ ├── SurfaceSample.kt │ │ │ ├── SwipeToDismissSample.kt │ │ │ ├── SwitchSample.kt │ │ │ ├── TabSample.kt │ │ │ ├── TextFieldSample.kt │ │ │ └── TextSample.kt │ │ │ └── ui │ │ │ └── theme │ │ │ ├── Color.kt │ │ │ ├── Shape.kt │ │ │ ├── Theme.kt │ │ │ └── Type.kt │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ ├── ic_android_black_24dp.xml │ │ ├── ic_launcher_background.xml │ │ └── newbanner4.png │ │ ├── layout │ │ └── main.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── themes.xml │ └── test │ └── java │ └── icu │ └── bughub │ └── app │ └── composebasic │ └── ExampleUnitTest.kt ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /App/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | local.properties 16 | -------------------------------------------------------------------------------- /App/.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /App/.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /App/.idea/deploymentTargetDropDown.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /App/.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 20 | 21 | -------------------------------------------------------------------------------- /App/.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 20 | -------------------------------------------------------------------------------- /App/.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 55 | 56 | 57 | 58 | 59 | 60 | 62 | -------------------------------------------------------------------------------- /App/.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /App/app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /App/app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | id 'org.jetbrains.kotlin.android' 4 | id 'org.jetbrains.kotlin.plugin.parcelize' 5 | } 6 | 7 | android { 8 | compileSdk 32 9 | 10 | defaultConfig { 11 | applicationId "icu.bughub.app.app" 12 | minSdk 23 13 | targetSdk 32 14 | versionCode 1 15 | versionName "1.0" 16 | 17 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 18 | vectorDrawables { 19 | useSupportLibrary true 20 | } 21 | 22 | ndk { 23 | abiFilters "arm64-v8a" 24 | } 25 | } 26 | 27 | buildTypes { 28 | release { 29 | minifyEnabled false 30 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 31 | } 32 | } 33 | compileOptions { 34 | sourceCompatibility JavaVersion.VERSION_1_8 35 | targetCompatibility JavaVersion.VERSION_1_8 36 | } 37 | kotlinOptions { 38 | jvmTarget = '1.8' 39 | } 40 | buildFeatures { 41 | compose true 42 | } 43 | composeOptions { 44 | kotlinCompilerExtensionVersion compose_version 45 | } 46 | packagingOptions { 47 | resources { 48 | excludes += '/META-INF/{AL2.0,LGPL2.1}' 49 | } 50 | 51 | pickFirst '**/libc++_shared.so' 52 | doNotStrip "*/arm64-v8a/libYTCommon.so" 53 | } 54 | } 55 | 56 | dependencies { 57 | 58 | implementation 'androidx.core:core-ktx:1.7.0' 59 | implementation "androidx.compose.ui:ui:$compose_version" 60 | implementation "androidx.compose.material:material:$compose_version" 61 | implementation "androidx.compose.ui:ui-tooling-preview:$compose_version" 62 | 63 | 64 | def lifecycle_version = "2.4.1" 65 | 66 | implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version" 67 | implementation "androidx.lifecycle:lifecycle-viewmodel-compose:$lifecycle_version" 68 | implementation 'androidx.activity:activity-compose:1.4.0' 69 | 70 | //accompanist 71 | def accompanist_version = "0.24.3-alpha" 72 | implementation "com.google.accompanist:accompanist-systemuicontroller:$accompanist_version" 73 | implementation "com.google.accompanist:accompanist-insets:$accompanist_version" 74 | implementation "com.google.accompanist:accompanist-pager:$accompanist_version" 75 | implementation "com.google.accompanist:accompanist-navigation-animation:$accompanist_version" 76 | implementation "com.google.accompanist:accompanist-placeholder-material:$accompanist_version" 77 | implementation "com.google.accompanist:accompanist-swiperefresh:$accompanist_version" 78 | 79 | //图标扩展 80 | implementation "androidx.compose.material:material-icons-extended:$compose_version" 81 | 82 | implementation "androidx.constraintlayout:constraintlayout-compose:1.0.0" 83 | 84 | 85 | //图片加载 86 | implementation("io.coil-kt:coil-compose:2.0.0-rc01") 87 | 88 | //webview 89 | implementation project(path: ':webview') 90 | 91 | 92 | //播放器 93 | implementation 'com.tencent.liteav:LiteAVSDK_Player:latest.release' 94 | 95 | //datastore 96 | implementation "androidx.datastore:datastore-preferences:1.0.0" 97 | 98 | def retrofit = "2.9.0" 99 | //网络请求框架 100 | implementation "com.squareup.retrofit2:retrofit:$retrofit" 101 | //GSON 102 | // implementation "com.squareup.retrofit2:converter-gson:$retrofit" 103 | //moshi 104 | implementation "com.squareup.retrofit2:converter-moshi:$retrofit" 105 | 106 | def moshi_version = "1.13.0" 107 | implementation "com.squareup.moshi:moshi-kotlin:$moshi_version" 108 | 109 | 110 | testImplementation 'junit:junit:4.13.2' 111 | androidTestImplementation 'androidx.test.ext:junit:1.1.3' 112 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' 113 | androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version" 114 | debugImplementation "androidx.compose.ui:ui-tooling:$compose_version" 115 | } -------------------------------------------------------------------------------- /App/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | 23 | -keep class com.tencent.** { *; } -------------------------------------------------------------------------------- /App/app/src/androidTest/java/icu/bughub/app/app/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package icu.bughub.app.app 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("icu.bughub.app.app", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /App/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 16 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /App/app/src/main/java/icu/bughub/app/app/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package icu.bughub.app.app 2 | 3 | import android.os.Build 4 | import android.os.Bundle 5 | import android.util.Log 6 | import android.view.View 7 | import androidx.activity.ComponentActivity 8 | import androidx.activity.compose.setContent 9 | import androidx.compose.foundation.layout.fillMaxSize 10 | import androidx.compose.material.MaterialTheme 11 | import androidx.compose.material.Surface 12 | import androidx.compose.material.Text 13 | import androidx.compose.runtime.Composable 14 | import androidx.compose.ui.Modifier 15 | import androidx.compose.ui.graphics.Color 16 | import androidx.compose.ui.graphics.toArgb 17 | import androidx.compose.ui.tooling.preview.Preview 18 | import androidx.core.view.WindowCompat 19 | import icu.bughub.app.app.ui.components.NavHostApp 20 | import icu.bughub.app.app.ui.screens.MainFrame 21 | import icu.bughub.app.app.ui.theme.AppTheme 22 | 23 | class MainActivity : ComponentActivity() { 24 | override fun onCreate(savedInstanceState: Bundle?) { 25 | super.onCreate(savedInstanceState) 26 | 27 | //获取状态栏高度 28 | // var statusBarHeight = 0 29 | // val resourceId = resources.getIdentifier("status_bar_height", "dimen", "android") 30 | // if (resourceId > 0) { 31 | // statusBarHeight = resources.getDimensionPixelSize(resourceId) 32 | // } 33 | 34 | //处理不同机型,状态栏不透明问题 35 | // window.statusBarColor = Color.Transparent.value.toInt() 36 | // //处理不同机型,导航栏遮盖内容问题 37 | // window.decorView.systemUiVisibility = 38 | // View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_STABLE 39 | 40 | //让内容,显示在状态栏和系统导航栏后面:状态栏和导航栏会遮盖部分内容 41 | WindowCompat.setDecorFitsSystemWindows(window, false) 42 | 43 | setContent { 44 | AppTheme { 45 | // A surface container using the 'background' color from the theme 46 | Surface( 47 | modifier = Modifier.fillMaxSize(), 48 | color = MaterialTheme.colors.background 49 | ) { 50 | NavHostApp() 51 | } 52 | } 53 | } 54 | } 55 | } 56 | 57 | @Composable 58 | fun Greeting(name: String) { 59 | Text(text = "Hello $name!") 60 | } 61 | 62 | @Preview(showBackground = true) 63 | @Composable 64 | fun DefaultPreview() { 65 | AppTheme { 66 | Greeting("Android") 67 | } 68 | } -------------------------------------------------------------------------------- /App/app/src/main/java/icu/bughub/app/app/compositionLocal/CompositionLocal.kt: -------------------------------------------------------------------------------- 1 | package icu.bughub.app.app.compositionLocal 2 | 3 | import androidx.compose.runtime.compositionLocalOf 4 | import icu.bughub.app.app.viewmodel.UserViewModel 5 | 6 | val LocalUserViewModel = 7 | compositionLocalOf { error("User View Model Context Not Found") } -------------------------------------------------------------------------------- /App/app/src/main/java/icu/bughub/app/app/extension/LazyListStateExtension.kt: -------------------------------------------------------------------------------- 1 | package icu.bughub.app.app.extension 2 | 3 | import android.util.Log 4 | import androidx.compose.foundation.lazy.LazyListState 5 | import androidx.compose.runtime.* 6 | 7 | @Composable 8 | fun LazyListState.OnBottomReached(buffer: Int = 0, loadMore: () -> Unit) { 9 | 10 | require(buffer >= 0) { "buffer 值必须是正整数" } 11 | 12 | //是否应该加载更多的状态 13 | val shouldLoadMore = remember { 14 | //由另一个状态计算派生 15 | derivedStateOf { 16 | 17 | //获取最后显示的 item 18 | val lastVisibleItem = 19 | layoutInfo.visibleItemsInfo.lastOrNull() ?: return@derivedStateOf true 20 | 21 | //如果最后显示的 item 是最后一个 item 的话,说明已经触底,需要加载更多 22 | lastVisibleItem.index == layoutInfo.totalItemsCount - 1 - buffer 23 | 24 | } 25 | } 26 | 27 | LaunchedEffect(shouldLoadMore) { 28 | //详见文档:https://developer.android.com/jetpack/compose/side-effects#snapshotFlow 29 | snapshotFlow { shouldLoadMore.value } 30 | .collect { 31 | if (it) { 32 | loadMore() 33 | } 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /App/app/src/main/java/icu/bughub/app/app/model/Network.kt: -------------------------------------------------------------------------------- 1 | package icu.bughub.app.app.model 2 | 3 | import com.squareup.moshi.Moshi 4 | import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory 5 | import retrofit2.Retrofit 6 | import retrofit2.converter.moshi.MoshiConverterFactory 7 | 8 | object Network { 9 | 10 | //文档地址:https://docs.apipost.cn/preview/1a28e17fa3c8f473/16838456ae6dc5c7 11 | //mock 数据请求 url 12 | private const val baseUrl = 13 | "https://mock.apipost.cn/app/mock/project/ced69cf2-9206-4a42-895e-dd7442a888df/" 14 | 15 | private val retrofit = Retrofit.Builder() 16 | .baseUrl(baseUrl) 17 | .addConverterFactory( 18 | MoshiConverterFactory.create( 19 | Moshi.Builder() 20 | .add(KotlinJsonAdapterFactory()) 21 | .build() 22 | ) 23 | ).build() 24 | 25 | fun createService(clazz: Class): T { 26 | return retrofit.create(clazz) 27 | } 28 | } -------------------------------------------------------------------------------- /App/app/src/main/java/icu/bughub/app/app/model/entity/ArticleEntity.kt: -------------------------------------------------------------------------------- 1 | package icu.bughub.app.app.model.entity 2 | 3 | import com.squareup.moshi.Json 4 | import com.squareup.moshi.JsonClass 5 | 6 | @JsonClass(generateAdapter = true) 7 | data class ArticleEntity( 8 | val title: String, 9 | var source: String, 10 | @Json(name = "time") 11 | var timestamp: String, 12 | var content: String? = "" 13 | ) 14 | 15 | data class ArticleListResponse(val data: List?) : BaseResponse() 16 | 17 | data class ArticleInfoResponse(val data: ArticleEntity?) : BaseResponse() -------------------------------------------------------------------------------- /App/app/src/main/java/icu/bughub/app/app/model/entity/BaseResponse.kt: -------------------------------------------------------------------------------- 1 | package icu.bughub.app.app.model.entity 2 | 3 | open class BaseResponse { 4 | var code: Int = -1 5 | var message: String = "" 6 | } -------------------------------------------------------------------------------- /App/app/src/main/java/icu/bughub/app/app/model/entity/Category.kt: -------------------------------------------------------------------------------- 1 | package icu.bughub.app.app.model.entity 2 | 3 | /** 4 | * 分类 5 | * 6 | * @property title 7 | */ 8 | data class Category( 9 | val title: String, 10 | val id: String 11 | ) 12 | 13 | /** 14 | * Category Response 15 | * 16 | * @property data 17 | */ 18 | data class CategoryResponse(var data: List) : BaseResponse() {} 19 | 20 | 21 | -------------------------------------------------------------------------------- /App/app/src/main/java/icu/bughub/app/app/model/entity/DataType.kt: -------------------------------------------------------------------------------- 1 | package icu.bughub.app.app.model.entity 2 | 3 | import androidx.compose.ui.graphics.vector.ImageVector 4 | 5 | /** 6 | * 数据类型 7 | * 8 | * @property title 9 | * @property icon 10 | */ 11 | data class DataType( 12 | var title: String, 13 | var icon: ImageVector 14 | ) 15 | -------------------------------------------------------------------------------- /App/app/src/main/java/icu/bughub/app/app/model/entity/NavigationItem.kt: -------------------------------------------------------------------------------- 1 | package icu.bughub.app.app.model.entity 2 | 3 | import androidx.compose.ui.graphics.vector.ImageVector 4 | 5 | /** 6 | * 导航栏对象 7 | * 8 | * @property title 导航栏的标题 9 | * @property icon 导航栏图标 10 | */ 11 | data class NavigationItem( 12 | val title: String, //底部导航栏的标题 13 | val icon: ImageVector//底部导航栏图标 14 | ) 15 | -------------------------------------------------------------------------------- /App/app/src/main/java/icu/bughub/app/app/model/entity/SwiperEntity.kt: -------------------------------------------------------------------------------- 1 | package icu.bughub.app.app.model.entity 2 | 3 | import com.squareup.moshi.Json 4 | import com.squareup.moshi.JsonClass 5 | 6 | @JsonClass(generateAdapter = true) 7 | data class SwiperEntity( 8 | @Json(name = "imgUrl") val imageUrl: String, 9 | val title: String? = "" 10 | ) 11 | 12 | 13 | data class SwiperResponse(val data: List?) : BaseResponse() -------------------------------------------------------------------------------- /App/app/src/main/java/icu/bughub/app/app/model/entity/UserInfoEntity.kt: -------------------------------------------------------------------------------- 1 | package icu.bughub.app.app.model.entity 2 | 3 | data class UserInfoEntity(val userName: String) 4 | 5 | data class UserInfoResponse(val data: UserInfoEntity?) : BaseResponse() 6 | -------------------------------------------------------------------------------- /App/app/src/main/java/icu/bughub/app/app/model/entity/VideoEntity.kt: -------------------------------------------------------------------------------- 1 | package icu.bughub.app.app.model.entity 2 | 3 | import com.squareup.moshi.Json 4 | import com.squareup.moshi.JsonClass 5 | 6 | @JsonClass(generateAdapter = true) 7 | data class VideoEntity( 8 | val title: String, 9 | val type: String? = "", 10 | val duration: String, 11 | @Json(name = "cover") 12 | val imageUrl: String, 13 | val video: String? = "", 14 | val desc: String? = "" 15 | ) 16 | 17 | data class VideoListResponse(val data: List?) : BaseResponse() 18 | 19 | data class VideoInfoResponse(val data: VideoEntity?) : BaseResponse() -------------------------------------------------------------------------------- /App/app/src/main/java/icu/bughub/app/app/model/service/ArticleService.kt: -------------------------------------------------------------------------------- 1 | package icu.bughub.app.app.model.service 2 | 3 | import icu.bughub.app.app.model.Network 4 | import icu.bughub.app.app.model.entity.ArticleInfoResponse 5 | import icu.bughub.app.app.model.entity.ArticleListResponse 6 | import retrofit2.http.GET 7 | import retrofit2.http.Query 8 | 9 | interface ArticleService { 10 | 11 | @GET("article/list") 12 | suspend fun list( 13 | @Query("pageOffset") pageOffset: Int, 14 | @Query("pageSize") pageSize: Int 15 | ): ArticleListResponse 16 | 17 | @GET("article/info") 18 | suspend fun info(@Query("id") id: String): ArticleInfoResponse 19 | 20 | companion object { 21 | fun instance(): ArticleService { 22 | return Network.createService(ArticleService::class.java) 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /App/app/src/main/java/icu/bughub/app/app/model/service/HomeService.kt: -------------------------------------------------------------------------------- 1 | package icu.bughub.app.app.model.service 2 | 3 | import icu.bughub.app.app.model.Network 4 | import icu.bughub.app.app.model.entity.CategoryResponse 5 | import icu.bughub.app.app.model.entity.SwiperResponse 6 | import retrofit2.http.GET 7 | 8 | interface HomeService { 9 | 10 | @GET("category/list") 11 | suspend fun category(): CategoryResponse 12 | 13 | @GET("recommand/banner") 14 | suspend fun banner(): SwiperResponse 15 | 16 | companion object { 17 | fun instance(): HomeService { 18 | return Network.createService(HomeService::class.java) 19 | } 20 | } 21 | 22 | 23 | } -------------------------------------------------------------------------------- /App/app/src/main/java/icu/bughub/app/app/model/service/UserInfoManager.kt: -------------------------------------------------------------------------------- 1 | package icu.bughub.app.app.model.service 2 | 3 | import android.content.Context 4 | import androidx.datastore.core.DataStore 5 | import androidx.datastore.preferences.core.Preferences 6 | import androidx.datastore.preferences.core.booleanPreferencesKey 7 | import androidx.datastore.preferences.core.edit 8 | import androidx.datastore.preferences.core.stringPreferencesKey 9 | import androidx.datastore.preferences.preferencesDataStore 10 | import kotlinx.coroutines.flow.Flow 11 | import kotlinx.coroutines.flow.map 12 | 13 | class UserInfoManager(private val context: Context) { 14 | 15 | companion object { 16 | private val Context.userStore: DataStore by preferencesDataStore("user_store") 17 | 18 | val LOGGED = booleanPreferencesKey("LOGGED") 19 | val USERNAME = stringPreferencesKey("USERNAME") 20 | } 21 | 22 | val logged: Flow = context.userStore.data.map { it[LOGGED] ?: false } 23 | val userName: Flow = context.userStore.data.map { it[USERNAME] ?: "" } 24 | 25 | /** 26 | * 存储用户信息 27 | * 28 | * @param userName 29 | */ 30 | suspend fun save(userName: String) { 31 | context.userStore.edit { 32 | it[LOGGED] = userName.isNotEmpty() 33 | it[USERNAME] = userName 34 | } 35 | } 36 | 37 | /** 38 | * 清空用户登录数据 39 | * 40 | */ 41 | suspend fun clear() { 42 | context.userStore.edit { 43 | it[LOGGED] = false 44 | it[USERNAME] = "" 45 | } 46 | } 47 | 48 | } -------------------------------------------------------------------------------- /App/app/src/main/java/icu/bughub/app/app/model/service/UserService.kt: -------------------------------------------------------------------------------- 1 | package icu.bughub.app.app.model.service 2 | 3 | import icu.bughub.app.app.model.Network 4 | import icu.bughub.app.app.model.entity.UserInfoResponse 5 | import retrofit2.http.* 6 | 7 | interface UserService { 8 | @FormUrlEncoded 9 | @POST("user/signIn") 10 | suspend fun signIn( 11 | @Field("userName") useName: String, 12 | @Field("password") password: String, 13 | ): UserInfoResponse 14 | 15 | companion object { 16 | fun instance(): UserService { 17 | return Network.createService(UserService::class.java) 18 | } 19 | } 20 | 21 | } -------------------------------------------------------------------------------- /App/app/src/main/java/icu/bughub/app/app/model/service/VideoService.kt: -------------------------------------------------------------------------------- 1 | package icu.bughub.app.app.model.service 2 | 3 | import icu.bughub.app.app.model.Network 4 | import icu.bughub.app.app.model.entity.VideoInfoResponse 5 | import icu.bughub.app.app.model.entity.VideoListResponse 6 | import retrofit2.http.GET 7 | import retrofit2.http.Query 8 | 9 | interface VideoService { 10 | 11 | @GET("video/list") 12 | suspend fun list( 13 | @Query("pageOffset") pageOffset: Int, 14 | @Query("pageSize") pageSize: Int 15 | ): VideoListResponse 16 | 17 | @GET("video/info") 18 | suspend fun info( 19 | @Query("id") id: String 20 | ): VideoInfoResponse 21 | 22 | companion object { 23 | fun instance(): VideoService { 24 | return Network.createService(VideoService::class.java) 25 | } 26 | } 27 | 28 | } -------------------------------------------------------------------------------- /App/app/src/main/java/icu/bughub/app/app/ui/components/ArticleItem.kt: -------------------------------------------------------------------------------- 1 | package icu.bughub.app.app.ui.components 2 | 3 | 4 | import androidx.compose.foundation.layout.* 5 | import androidx.compose.material.Divider 6 | import androidx.compose.material.Text 7 | import androidx.compose.runtime.Composable 8 | import androidx.compose.ui.Modifier 9 | import androidx.compose.ui.graphics.Color 10 | import androidx.compose.ui.text.style.TextOverflow 11 | import androidx.compose.ui.tooling.preview.Preview 12 | import androidx.compose.ui.unit.dp 13 | import androidx.compose.ui.unit.sp 14 | import com.google.accompanist.placeholder.PlaceholderHighlight 15 | import com.google.accompanist.placeholder.material.placeholder 16 | import com.google.accompanist.placeholder.material.shimmer 17 | import icu.bughub.app.app.model.entity.ArticleEntity 18 | 19 | 20 | /** 21 | * 文章列表 item 22 | * 23 | * @param article 24 | * @param modifier 25 | */ 26 | @Composable 27 | fun ArticleItem(article: ArticleEntity, loaded: Boolean, modifier: Modifier = Modifier) { 28 | 29 | Column(modifier = modifier.padding(8.dp)) { 30 | Text( 31 | text = article.title, 32 | color = Color(0xFF333333), 33 | fontSize = 16.sp, 34 | maxLines = 2, 35 | overflow = TextOverflow.Ellipsis, 36 | modifier = Modifier 37 | .padding(bottom = 8.dp) 38 | .placeholder(visible = !loaded, highlight = PlaceholderHighlight.shimmer()) 39 | ) 40 | 41 | Row(horizontalArrangement = Arrangement.SpaceBetween, modifier = Modifier.fillMaxWidth()) { 42 | Text( 43 | "来源:${article.source}", 44 | color = Color(0xFF999999), 45 | fontSize = 10.sp, 46 | maxLines = 1, 47 | overflow = TextOverflow.Ellipsis, 48 | modifier = Modifier 49 | .placeholder(visible = !loaded, highlight = PlaceholderHighlight.shimmer()) 50 | ) 51 | 52 | Text( 53 | article.timestamp, 54 | color = Color(0xFF999999), 55 | fontSize = 10.sp, 56 | maxLines = 1, 57 | overflow = TextOverflow.Ellipsis, 58 | modifier = Modifier 59 | .placeholder(visible = !loaded, highlight = PlaceholderHighlight.shimmer()) 60 | ) 61 | } 62 | 63 | Spacer(Modifier.height(8.dp)) 64 | 65 | Divider() 66 | } 67 | 68 | } 69 | 70 | -------------------------------------------------------------------------------- /App/app/src/main/java/icu/bughub/app/app/ui/components/ChartView.kt: -------------------------------------------------------------------------------- 1 | package icu.bughub.app.app.ui.components 2 | 3 | 4 | import androidx.compose.foundation.Canvas 5 | import androidx.compose.foundation.layout.size 6 | import androidx.compose.runtime.Composable 7 | import androidx.compose.ui.Modifier 8 | import androidx.compose.ui.geometry.Offset 9 | import androidx.compose.ui.graphics.Color 10 | import androidx.compose.ui.graphics.drawscope.Stroke 11 | import androidx.compose.ui.platform.LocalConfiguration 12 | import androidx.compose.ui.tooling.preview.Preview 13 | import androidx.compose.ui.unit.dp 14 | 15 | 16 | @Composable 17 | fun ChartView(points: List, modifier: Modifier = Modifier) { 18 | 19 | //每一行的高度DP 20 | val heightForRow = 24 21 | 22 | //总行数 23 | val countForRow = 5 24 | 25 | //小圆圈半径DP 26 | val circleRadius = 2.5 27 | 28 | val perY = 8 // 24 * 5 / 15 每8dp代表1积分,也就是每一行3积分 29 | 30 | //画布宽度 = 屏幕宽度 - padding * 2 31 | val canvasWidth = LocalConfiguration.current.screenWidthDp - 8 * 2 32 | 33 | //画布高度 = 行高 * 总行数 + 小圆圈直径 34 | val canvasHeight = heightForRow * countForRow + circleRadius * 2 35 | 36 | //7平分的宽度 37 | val averageOfWidth = canvasWidth / 7 38 | 39 | Canvas( 40 | modifier = modifier.size( 41 | width = canvasWidth.dp, 42 | height = canvasHeight.dp 43 | ) 44 | ) { 45 | //画背景横线 46 | for (index in 0..countForRow) { 47 | //行高 * index + 小圆圈半径 48 | val y = (heightForRow * index + circleRadius).dp.toPx() 49 | drawLine( 50 | Color(0xFFEEEEEE), 51 | start = Offset(0f, y), 52 | end = Offset(size.width, y), 53 | strokeWidth = 2.5f 54 | ) 55 | } 56 | 57 | //画圆圈、拆线 58 | for (index in 0 until points.count()) { 59 | //x: 7等分宽度 * index + 7等分宽度 / 2 60 | //y: 行高 * 行数 - 积分 * 每一积分代表的 dp 值 - 小圆圈半径 61 | val circleCenter = Offset( 62 | (averageOfWidth * index + averageOfWidth / 2).dp.toPx(), 63 | (heightForRow * countForRow - points[index] * perY + circleRadius).dp.toPx() 64 | ) 65 | drawCircle( 66 | Color(0xFF149EE7), 67 | radius = circleRadius.dp.toPx(), 68 | center = circleCenter, 69 | style = Stroke(width = 5f) 70 | ) 71 | 72 | if (index < points.count() - 1) { 73 | //下一个点的坐标:也就是circleCenter的计算方法中 index + 1 即可 74 | val nextPointOffset = Offset( 75 | (averageOfWidth * (index + 1) + averageOfWidth / 2).dp.toPx(), 76 | (heightForRow * countForRow - points[(index + 1)] * perY + circleRadius).dp.toPx() 77 | ) 78 | 79 | drawLine( 80 | Color(0xFF149EE7), 81 | start = circleCenter, 82 | end = nextPointOffset, 83 | strokeWidth = 5f 84 | ) 85 | } 86 | } 87 | } 88 | 89 | } 90 | 91 | -------------------------------------------------------------------------------- /App/app/src/main/java/icu/bughub/app/app/ui/components/CircleRing.kt: -------------------------------------------------------------------------------- 1 | package icu.bughub.app.app.ui.components 2 | 3 | 4 | import androidx.compose.foundation.Canvas 5 | import androidx.compose.foundation.layout.size 6 | import androidx.compose.runtime.Composable 7 | import androidx.compose.ui.Modifier 8 | import androidx.compose.ui.graphics.Color 9 | import androidx.compose.ui.graphics.StrokeCap 10 | import androidx.compose.ui.graphics.drawscope.Stroke 11 | import androidx.compose.ui.graphics.drawscope.rotate 12 | import androidx.compose.ui.tooling.preview.Preview 13 | import androidx.compose.ui.unit.dp 14 | import icu.bughub.app.app.viewmodel.TaskViewModel 15 | 16 | 17 | @Composable 18 | fun CircleRing(boxWidthDp: Int, vm: TaskViewModel) { 19 | 20 | val strokeWidth = 30f 21 | 22 | Canvas(modifier = Modifier.size(boxWidthDp.dp)) { 23 | //两个方案 24 | //1、使用startAngle = -10f 和 sweepAngle = 220f 并使用 rotate(180f)进行翻转 25 | //2、startAngle = 160f,sweepAngle = 220f, 26 | drawArc( 27 | Color(0, 0, 0, 15), 28 | startAngle = 160f, 29 | sweepAngle = 220f, 30 | useCenter = false, 31 | style = Stroke(strokeWidth, cap = StrokeCap.Round) 32 | ) 33 | 34 | drawArc( 35 | Color.White, 36 | startAngle = 160f, 37 | sweepAngle = vm.pointOfYearPercent, 38 | useCenter = false, 39 | style = Stroke(strokeWidth, cap = StrokeCap.Round) 40 | ) 41 | } 42 | 43 | } 44 | 45 | 46 | -------------------------------------------------------------------------------- /App/app/src/main/java/icu/bughub/app/app/ui/components/DailyTaskContent.kt: -------------------------------------------------------------------------------- 1 | package icu.bughub.app.app.ui.components 2 | 3 | 4 | import android.util.Log 5 | import androidx.compose.foundation.BorderStroke 6 | import androidx.compose.foundation.clickable 7 | import androidx.compose.foundation.layout.* 8 | import androidx.compose.foundation.shape.CircleShape 9 | import androidx.compose.foundation.text.InlineTextContent 10 | import androidx.compose.foundation.text.appendInlineContent 11 | import androidx.compose.material.* 12 | import androidx.compose.material.icons.Icons 13 | import androidx.compose.material.icons.filled.HelpOutline 14 | import androidx.compose.runtime.Composable 15 | import androidx.compose.ui.Alignment 16 | import androidx.compose.ui.Modifier 17 | import androidx.compose.ui.graphics.Color 18 | import androidx.compose.ui.text.Placeholder 19 | import androidx.compose.ui.text.PlaceholderVerticalAlign 20 | import androidx.compose.ui.text.buildAnnotatedString 21 | import androidx.compose.ui.tooling.preview.Preview 22 | import androidx.compose.ui.unit.dp 23 | import androidx.compose.ui.unit.sp 24 | 25 | 26 | @Composable 27 | fun DailyTaskContent() { 28 | 29 | DailyTaskItem( 30 | "登录", 31 | "5积分/每日首次登录", 32 | "已获5积分 / 每日上限5积分", 33 | 1f 34 | ) 35 | 36 | DailyTaskItem( 37 | "文章学习", 38 | "10积分/每有效阅读1篇文章", 39 | "已获50积分/每日上限100积分", 40 | 0.7f 41 | ) 42 | 43 | DailyTaskItem( 44 | "视听学习", 45 | "10积分/每有效观看视频或收听音频累计", 46 | "已获50积分/每日上限100积分", 47 | 0.5f 48 | ) 49 | 50 | } 51 | 52 | @Composable 53 | fun DailyTaskItem(title: String, secondaryText: String, desc: String, percent: Float) { 54 | 55 | val inlineContentId = "inlineContentId" 56 | 57 | val secondaryAnnotatedText = buildAnnotatedString { 58 | append(secondaryText) 59 | appendInlineContent(inlineContentId, "[icon]") 60 | } 61 | 62 | val inlineContent = mapOf( 63 | Pair(inlineContentId, 64 | InlineTextContent( 65 | Placeholder( 66 | width = 14.sp, 67 | height = 14.sp, 68 | placeholderVerticalAlign = PlaceholderVerticalAlign.AboveBaseline 69 | ) 70 | ) { 71 | Icon( 72 | imageVector = Icons.Default.HelpOutline, 73 | contentDescription = null, 74 | modifier = Modifier.clickable { 75 | Log.i("===", "点击了问号") 76 | }) 77 | }) 78 | ) 79 | 80 | Row( 81 | modifier = Modifier 82 | .fillMaxWidth() 83 | .padding(top = 12.dp), 84 | horizontalArrangement = Arrangement.SpaceBetween, 85 | verticalAlignment = Alignment.CenterVertically 86 | ) { 87 | 88 | Column(modifier = Modifier.weight(7.5f)) { 89 | Text(title, fontSize = 16.sp, color = Color(0xFF333333)) 90 | Text( 91 | secondaryAnnotatedText, 92 | inlineContent = inlineContent, 93 | fontSize = 14.sp, 94 | color = Color(0xFF333333) 95 | ) 96 | Row(verticalAlignment = Alignment.CenterVertically) { 97 | LinearProgressIndicator(progress = percent, modifier = Modifier.weight(3f)) 98 | Text( 99 | text = desc, 100 | fontSize = 10.sp, 101 | color = Color(0xFF333333), 102 | modifier = Modifier 103 | .weight(7f, fill = false) 104 | .padding(horizontal = 8.dp) 105 | ) 106 | } 107 | } 108 | 109 | OutlinedButton( 110 | onClick = { }, 111 | border = if (percent >= 1f) ButtonDefaults.outlinedBorder else BorderStroke( 112 | 1.dp, 113 | Color(0xFFFF5900) 114 | ), 115 | shape = CircleShape, 116 | colors = ButtonDefaults.outlinedButtonColors(contentColor = Color(0xFFFF5900)), 117 | modifier = Modifier 118 | .weight(2.5f), 119 | enabled = (percent < 1f) 120 | ) { 121 | Text(text = "去学习") 122 | } 123 | 124 | } 125 | 126 | } 127 | 128 | -------------------------------------------------------------------------------- /App/app/src/main/java/icu/bughub/app/app/ui/components/NavHostApp.kt: -------------------------------------------------------------------------------- 1 | package icu.bughub.app.app.ui.components 2 | 3 | 4 | import android.util.Log 5 | import androidx.compose.animation.AnimatedContentScope 6 | import androidx.compose.animation.ExperimentalAnimationApi 7 | import androidx.compose.animation.core.tween 8 | import androidx.compose.runtime.Composable 9 | import androidx.compose.runtime.CompositionLocalProvider 10 | import androidx.compose.ui.platform.LocalContext 11 | import androidx.compose.ui.tooling.preview.Preview 12 | import com.google.accompanist.insets.ProvideWindowInsets 13 | import com.google.accompanist.navigation.animation.AnimatedNavHost 14 | import com.google.accompanist.navigation.animation.composable 15 | import com.google.accompanist.navigation.animation.rememberAnimatedNavController 16 | import icu.bughub.app.app.compositionLocal.LocalUserViewModel 17 | import icu.bughub.app.app.ui.navigation.Destinations 18 | import icu.bughub.app.app.ui.screens.ArticleDetailScreen 19 | import icu.bughub.app.app.ui.screens.LoginScreen 20 | import icu.bughub.app.app.ui.screens.MainFrame 21 | import icu.bughub.app.app.ui.screens.VideoDetailScreen 22 | import icu.bughub.app.app.viewmodel.UserViewModel 23 | 24 | 25 | /** 26 | * 导航控制器 27 | * 28 | */ 29 | @OptIn(ExperimentalAnimationApi::class) 30 | @Composable 31 | fun NavHostApp() { 32 | 33 | val navController = rememberAnimatedNavController() 34 | ProvideWindowInsets { 35 | 36 | CompositionLocalProvider(LocalUserViewModel provides UserViewModel(LocalContext.current)) { 37 | 38 | val userViewModel = LocalUserViewModel.current 39 | 40 | AnimatedNavHost( 41 | navController = navController, 42 | startDestination = Destinations.HomeFrame.route 43 | ) { 44 | 45 | //首页大框架 46 | composable( 47 | Destinations.HomeFrame.route, 48 | enterTransition = { 49 | slideIntoContainer( 50 | AnimatedContentScope.SlideDirection.Right 51 | ) 52 | }, 53 | exitTransition = { 54 | slideOutOfContainer( 55 | AnimatedContentScope.SlideDirection.Left 56 | ) 57 | }, 58 | ) { 59 | MainFrame(onNavigateToArticle = { 60 | Log.i("===", "onNavigateToArticle") 61 | navController.navigate(Destinations.ArticleDetail.route) 62 | }, onNavigateToVideo = { 63 | navController.navigate(Destinations.VideoDetail.route) 64 | }, onNavigateToStudyHistory = { 65 | if (userViewModel.logged) { 66 | //已登录 67 | } else { 68 | //未登录 69 | navController.navigate(Destinations.Login.route) 70 | } 71 | }) 72 | } 73 | 74 | //文章详情页 75 | composable( 76 | Destinations.ArticleDetail.route, 77 | enterTransition = { 78 | slideIntoContainer(AnimatedContentScope.SlideDirection.Left) 79 | }, 80 | exitTransition = { 81 | slideOutOfContainer(AnimatedContentScope.SlideDirection.Right) 82 | }, 83 | ) { 84 | ArticleDetailScreen(onBack = { 85 | navController.popBackStack() 86 | }) 87 | } 88 | 89 | //视频详情页 90 | composable( 91 | Destinations.VideoDetail.route, 92 | enterTransition = { 93 | slideIntoContainer(AnimatedContentScope.SlideDirection.Left) 94 | }, 95 | exitTransition = { 96 | slideOutOfContainer(AnimatedContentScope.SlideDirection.Right) 97 | }, 98 | ) { 99 | VideoDetailScreen(onBack = { 100 | navController.popBackStack() 101 | }) 102 | } 103 | 104 | composable( 105 | Destinations.Login.route, 106 | enterTransition = { 107 | slideIntoContainer(AnimatedContentScope.SlideDirection.Left) 108 | }, 109 | exitTransition = { 110 | slideOutOfContainer(AnimatedContentScope.SlideDirection.Right) 111 | }, 112 | ) { 113 | LoginScreen { 114 | navController.popBackStack() 115 | } 116 | } 117 | 118 | } 119 | } 120 | } 121 | } 122 | 123 | @Preview 124 | @Composable 125 | fun NavHostAppPreview() { 126 | NavHostApp() 127 | } 128 | 129 | -------------------------------------------------------------------------------- /App/app/src/main/java/icu/bughub/app/app/ui/components/NotificationContent.kt: -------------------------------------------------------------------------------- 1 | package icu.bughub.app.app.ui.components 2 | 3 | 4 | import androidx.compose.foundation.background 5 | import androidx.compose.foundation.layout.* 6 | import androidx.compose.foundation.shape.RoundedCornerShape 7 | import androidx.compose.material.Text 8 | import androidx.compose.runtime.Composable 9 | import androidx.compose.runtime.DisposableEffect 10 | import androidx.compose.runtime.rememberCoroutineScope 11 | import androidx.compose.ui.Alignment 12 | import androidx.compose.ui.Modifier 13 | import androidx.compose.ui.draw.clip 14 | import androidx.compose.ui.graphics.Color 15 | import androidx.compose.ui.text.style.TextOverflow 16 | import androidx.compose.ui.tooling.preview.Preview 17 | import androidx.compose.ui.unit.dp 18 | import androidx.compose.ui.unit.sp 19 | import com.google.accompanist.pager.ExperimentalPagerApi 20 | import com.google.accompanist.pager.VerticalPager 21 | import com.google.accompanist.pager.rememberPagerState 22 | import icu.bughub.app.app.viewmodel.MainViewModel 23 | import kotlinx.coroutines.launch 24 | import java.util.* 25 | 26 | 27 | @OptIn(ExperimentalPagerApi::class) 28 | @Composable 29 | fun NotificationContent(vm: MainViewModel) { 30 | 31 | //虚拟页数 32 | val virtualCount = Int.MAX_VALUE 33 | 34 | //实际页数 35 | val actualCount = vm.notifications.size 36 | 37 | //初始图片下标 38 | val initialIndex = virtualCount / 2 39 | 40 | val pagerState = rememberPagerState(initialPage = initialIndex) 41 | 42 | val coroutineScope = rememberCoroutineScope() 43 | 44 | DisposableEffect(Unit) { 45 | val timer = Timer() 46 | 47 | timer.schedule(object : TimerTask() { 48 | override fun run() { 49 | coroutineScope.launch { pagerState.animateScrollToPage(pagerState.currentPage + 1) } 50 | } 51 | }, 2000, 3000) 52 | 53 | onDispose { 54 | timer.cancel() 55 | } 56 | } 57 | 58 | Row( 59 | modifier = Modifier 60 | .padding(8.dp) 61 | .clip(RoundedCornerShape(8.dp)) 62 | .background(Color(0x22149EE7)) 63 | .height(45.dp) 64 | .padding(horizontal = 8.dp), 65 | verticalAlignment = Alignment.CenterVertically, 66 | horizontalArrangement = Arrangement.SpaceBetween 67 | ) { 68 | Text( 69 | text = "最新活动", 70 | color = Color(0xFF149EE7), 71 | fontSize = 14.sp 72 | ) 73 | 74 | Spacer(modifier = Modifier.width(8.dp)) 75 | 76 | VerticalPager( 77 | count = virtualCount, 78 | state = pagerState, 79 | modifier = Modifier.weight(1f), 80 | horizontalAlignment = Alignment.Start 81 | ) { index -> 82 | 83 | val actualIndex = 84 | (index - initialIndex).floorMod(actualCount) 85 | 86 | Text( 87 | text = vm.notifications[actualIndex], 88 | color = Color(0xFF333333), 89 | fontSize = 14.sp, 90 | maxLines = 1, 91 | overflow = TextOverflow.Ellipsis, 92 | ) 93 | } 94 | 95 | Spacer(modifier = Modifier.width(8.dp)) 96 | 97 | Text( 98 | text = "更多", 99 | color = Color(0xFF666666), 100 | fontSize = 14.sp, 101 | maxLines = 1, 102 | ) 103 | } 104 | 105 | } 106 | 107 | 108 | -------------------------------------------------------------------------------- /App/app/src/main/java/icu/bughub/app/app/ui/components/SwiperContent.kt: -------------------------------------------------------------------------------- 1 | package icu.bughub.app.app.ui.components 2 | 3 | 4 | import androidx.compose.foundation.layout.aspectRatio 5 | import androidx.compose.foundation.layout.fillMaxWidth 6 | import androidx.compose.foundation.layout.padding 7 | import androidx.compose.foundation.shape.RoundedCornerShape 8 | import androidx.compose.runtime.Composable 9 | import androidx.compose.runtime.DisposableEffect 10 | import androidx.compose.runtime.LaunchedEffect 11 | import androidx.compose.runtime.rememberCoroutineScope 12 | import androidx.compose.ui.Modifier 13 | import androidx.compose.ui.draw.clip 14 | import androidx.compose.ui.layout.ContentScale 15 | import androidx.compose.ui.tooling.preview.Preview 16 | import androidx.compose.ui.unit.dp 17 | import coil.compose.AsyncImage 18 | import com.google.accompanist.pager.ExperimentalPagerApi 19 | import com.google.accompanist.pager.HorizontalPager 20 | import com.google.accompanist.pager.rememberPagerState 21 | import com.google.accompanist.placeholder.PlaceholderHighlight 22 | import com.google.accompanist.placeholder.material.placeholder 23 | import com.google.accompanist.placeholder.material.shimmer 24 | import com.google.accompanist.placeholder.placeholder 25 | import icu.bughub.app.app.viewmodel.MainViewModel 26 | import kotlinx.coroutines.launch 27 | import java.util.* 28 | 29 | 30 | @OptIn(ExperimentalPagerApi::class) 31 | @Composable 32 | fun SwiperContent(vm: MainViewModel) { 33 | 34 | //虚拟页数 35 | val virtualCount = Int.MAX_VALUE 36 | 37 | //实际页数 38 | val actualCount = vm.swiperData.size 39 | 40 | //初始图片下标 41 | val initialIndex = virtualCount / 2 42 | 43 | val pagerState = rememberPagerState(initialPage = initialIndex) 44 | 45 | val coroutineScope = rememberCoroutineScope() 46 | 47 | DisposableEffect(Unit) { 48 | 49 | coroutineScope.launch { 50 | vm.swiperData() 51 | } 52 | 53 | val timer = Timer() 54 | 55 | timer.schedule(object : TimerTask() { 56 | override fun run() { 57 | if (vm.swiperLoaded) { 58 | coroutineScope.launch { pagerState.animateScrollToPage(pagerState.currentPage + 1) } 59 | } 60 | } 61 | }, 3000, 3000) 62 | 63 | onDispose { 64 | timer.cancel() 65 | } 66 | } 67 | 68 | HorizontalPager( 69 | count = virtualCount, 70 | state = pagerState, 71 | modifier = Modifier 72 | .padding(horizontal = 8.dp) 73 | .clip(RoundedCornerShape(8.dp)), 74 | userScrollEnabled = vm.swiperLoaded 75 | ) { index -> 76 | val actualIndex = 77 | (index - initialIndex).floorMod(actualCount) //index - (index.floorDiv(actualCount)) * actualCount 78 | 79 | AsyncImage( 80 | model = vm.swiperData[actualIndex].imageUrl, 81 | contentDescription = null, 82 | modifier = Modifier 83 | .fillMaxWidth() 84 | .aspectRatio(7 / 3f) 85 | .placeholder( 86 | visible = !vm.swiperLoaded, 87 | highlight = PlaceholderHighlight.shimmer() 88 | ), 89 | contentScale = ContentScale.Crop 90 | ) 91 | } 92 | } 93 | 94 | fun Int.floorMod(other: Int): Int = when (other) { 95 | 0 -> this 96 | //将虚拟数据按照实际数据总数分为 N 组 97 | //当前虚拟下标是在这虚拟数据中的哪一组:虚拟下标floorDiv实际数据总数(虚拟下标/实际数据总数)。向下取整 98 | //虚拟下标 - (虚拟下标/实际数据总数) * 实际数据总数 99 | else -> this - floorDiv(other) * other 100 | } 101 | 102 | -------------------------------------------------------------------------------- /App/app/src/main/java/icu/bughub/app/app/ui/components/TopAppBar.kt: -------------------------------------------------------------------------------- 1 | package icu.bughub.app.app.ui.components 2 | 3 | 4 | import androidx.compose.foundation.background 5 | import androidx.compose.foundation.layout.* 6 | import androidx.compose.material.MaterialTheme 7 | import androidx.compose.material.Text 8 | import androidx.compose.runtime.Composable 9 | import androidx.compose.runtime.LaunchedEffect 10 | import androidx.compose.ui.Alignment 11 | import androidx.compose.ui.Modifier 12 | import androidx.compose.ui.graphics.Brush 13 | import androidx.compose.ui.graphics.Color 14 | import androidx.compose.ui.platform.LocalDensity 15 | import androidx.compose.ui.tooling.preview.Preview 16 | import androidx.compose.ui.unit.dp 17 | import com.google.accompanist.insets.LocalWindowInsets 18 | import com.google.accompanist.insets.statusBarsPadding 19 | import com.google.accompanist.systemuicontroller.rememberSystemUiController 20 | import icu.bughub.app.app.ui.theme.Blue200 21 | import icu.bughub.app.app.ui.theme.Blue700 22 | 23 | //标题栏高度 24 | val appBarHeight = 56.dp 25 | 26 | /** 27 | * 统一标题栏 28 | * 29 | * @param modifier 30 | * @param content 标题栏内容 31 | */ 32 | @Composable 33 | fun TopAppBar(modifier: Modifier = Modifier, content: @Composable () -> Unit) { 34 | 35 | val systemUiController = rememberSystemUiController() 36 | 37 | LaunchedEffect(key1 = Unit) { 38 | systemUiController.setStatusBarColor(Color.Transparent) 39 | } 40 | 41 | //转换状态栏高度 px 为 dp 42 | val statusBarHeightDp = with(LocalDensity.current) { 43 | LocalWindowInsets.current.statusBars.top.toDp() 44 | } 45 | 46 | Row( 47 | modifier = Modifier 48 | .fillMaxWidth() 49 | .background( 50 | Brush.linearGradient( 51 | listOf( 52 | Blue700, 53 | Blue200 54 | ) 55 | ) 56 | ) 57 | .height(appBarHeight + statusBarHeightDp) 58 | .padding(top = statusBarHeightDp) 59 | .then(modifier), 60 | horizontalArrangement = Arrangement.Center, 61 | verticalAlignment = Alignment.CenterVertically 62 | ) { 63 | content() 64 | } 65 | 66 | } 67 | 68 | @Preview 69 | @Composable 70 | fun TopAppBarPreview() { 71 | TopAppBar() { 72 | Text("标题") 73 | } 74 | } 75 | 76 | -------------------------------------------------------------------------------- /App/app/src/main/java/icu/bughub/app/app/ui/components/VideoItem.kt: -------------------------------------------------------------------------------- 1 | package icu.bughub.app.app.ui.components 2 | 3 | 4 | import androidx.compose.foundation.layout.aspectRatio 5 | import androidx.compose.foundation.layout.fillMaxWidth 6 | import androidx.compose.foundation.layout.padding 7 | import androidx.compose.foundation.shape.RoundedCornerShape 8 | import androidx.compose.material.Divider 9 | import androidx.compose.material.Text 10 | import androidx.compose.runtime.Composable 11 | import androidx.compose.ui.Modifier 12 | import androidx.compose.ui.draw.clip 13 | import androidx.compose.ui.graphics.Color 14 | import androidx.compose.ui.layout.ContentScale 15 | import androidx.compose.ui.layout.layout 16 | import androidx.compose.ui.layout.layoutId 17 | import androidx.compose.ui.text.style.TextOverflow 18 | import androidx.compose.ui.tooling.preview.Preview 19 | import androidx.compose.ui.unit.dp 20 | import androidx.compose.ui.unit.sp 21 | import androidx.constraintlayout.compose.ConstraintLayout 22 | import androidx.constraintlayout.compose.ConstraintSet 23 | import androidx.constraintlayout.compose.Dimension 24 | import coil.compose.AsyncImage 25 | import com.google.accompanist.placeholder.PlaceholderHighlight 26 | import com.google.accompanist.placeholder.material.placeholder 27 | import com.google.accompanist.placeholder.material.shimmer 28 | import icu.bughub.app.app.model.entity.VideoEntity 29 | 30 | 31 | @Composable 32 | fun VideoItem(modifier: Modifier = Modifier, videoEntity: VideoEntity, loaded: Boolean) { 33 | 34 | val constraintSet = ConstraintSet { 35 | val title = createRefFor("title") 36 | val cover = createRefFor("cover") 37 | val type = createRefFor("type") 38 | val duration = createRefFor("duration") 39 | val divider = createRefFor("divider") 40 | 41 | constrain(cover) { 42 | start.linkTo(parent.start) 43 | centerVerticallyTo(parent) 44 | 45 | width = Dimension.value(115.5.dp) 46 | } 47 | 48 | constrain(title) { 49 | start.linkTo(cover.end, margin = 8.dp) 50 | end.linkTo(parent.end) 51 | width = Dimension.fillToConstraints 52 | } 53 | 54 | constrain(type) { 55 | start.linkTo(title.start) 56 | bottom.linkTo(parent.bottom) 57 | } 58 | 59 | constrain(duration) { 60 | end.linkTo(parent.end) 61 | bottom.linkTo(parent.bottom) 62 | } 63 | 64 | constrain(divider) { 65 | bottom.linkTo(cover.bottom, margin = (-8).dp) 66 | } 67 | } 68 | 69 | ConstraintLayout( 70 | constraintSet, modifier = modifier 71 | .fillMaxWidth() 72 | .padding(8.dp) 73 | ) { 74 | AsyncImage( 75 | model = videoEntity.imageUrl, contentDescription = null, 76 | contentScale = ContentScale.Crop, 77 | modifier = Modifier 78 | .layoutId("cover") 79 | .aspectRatio(16 / 9f) 80 | .clip(RoundedCornerShape(8.dp)) 81 | .placeholder( 82 | visible = !loaded, highlight = PlaceholderHighlight.shimmer() 83 | ) 84 | ) 85 | 86 | Text( 87 | text = videoEntity.title, 88 | fontSize = 16.sp, 89 | color = Color(0xFF666666), 90 | maxLines = 2, 91 | overflow = TextOverflow.Ellipsis, 92 | modifier = Modifier 93 | .layoutId("title") 94 | .placeholder( 95 | visible = !loaded, highlight = PlaceholderHighlight.shimmer() 96 | ) 97 | ) 98 | 99 | Text( 100 | text = videoEntity.type ?: "", 101 | fontSize = 10.sp, 102 | color = Color(0xFF999999), 103 | maxLines = 1, 104 | overflow = TextOverflow.Ellipsis, 105 | modifier = Modifier 106 | .layoutId("type") 107 | .placeholder( 108 | visible = !loaded, highlight = PlaceholderHighlight.shimmer() 109 | ) 110 | ) 111 | 112 | Text( 113 | text = "时长:${videoEntity.duration}", 114 | fontSize = 10.sp, 115 | color = Color(0xFF999999), 116 | maxLines = 1, 117 | overflow = TextOverflow.Ellipsis, 118 | modifier = Modifier 119 | .layoutId("duration") 120 | .placeholder( 121 | visible = !loaded, highlight = PlaceholderHighlight.shimmer() 122 | ) 123 | ) 124 | 125 | Divider( 126 | modifier = Modifier 127 | .layoutId("divider") 128 | ) 129 | } 130 | 131 | 132 | } 133 | 134 | -------------------------------------------------------------------------------- /App/app/src/main/java/icu/bughub/app/app/ui/components/video/PlayerValue.kt: -------------------------------------------------------------------------------- 1 | package icu.bughub.app.app.ui.components.video 2 | 3 | import android.os.Parcelable 4 | import androidx.compose.runtime.getValue 5 | import androidx.compose.runtime.mutableStateOf 6 | import androidx.compose.runtime.setValue 7 | import kotlinx.parcelize.Parcelize 8 | 9 | /** 10 | * 播放器相关数据类 11 | * 12 | */ 13 | @Parcelize 14 | class PlayerValue : Parcelable { 15 | 16 | var coverUrl: String = "" 17 | 18 | var title: String = "" 19 | 20 | //存储 url 目的是为了横竖屏切换等重绘的场景 21 | var videoUrl: String = "" 22 | 23 | //视频总时长 24 | var duration by mutableStateOf(0L) 25 | 26 | //当前播放进度 27 | var currentPosition by mutableStateOf(0L) 28 | 29 | //当前状态 30 | var state by mutableStateOf(PlayState.None) 31 | 32 | } 33 | 34 | /** 35 | * 播放状态 36 | * 37 | */ 38 | enum class PlayState { 39 | None, //未播放 40 | Loading,//加载中 41 | Playing,//播放中 42 | Pause,//已暂停 43 | } -------------------------------------------------------------------------------- /App/app/src/main/java/icu/bughub/app/app/ui/components/video/VideoView.kt: -------------------------------------------------------------------------------- 1 | package icu.bughub.app.app.ui.components.video 2 | 3 | 4 | import android.view.LayoutInflater 5 | import androidx.compose.runtime.Composable 6 | import androidx.compose.ui.tooling.preview.Preview 7 | import androidx.compose.ui.viewinterop.AndroidView 8 | import com.tencent.rtmp.TXVodPlayer 9 | import com.tencent.rtmp.ui.TXCloudVideoView 10 | import icu.bughub.app.app.R 11 | 12 | 13 | @Composable 14 | fun VideoView(vodPlayer: TXVodPlayer) { 15 | 16 | AndroidView(factory = { context -> 17 | (LayoutInflater.from(context).inflate(R.layout.video, null, false) 18 | .findViewById(R.id.videoView) as TXCloudVideoView).apply { 19 | vodPlayer.setPlayerView(this) 20 | } 21 | }) 22 | 23 | } 24 | 25 | -------------------------------------------------------------------------------- /App/app/src/main/java/icu/bughub/app/app/ui/components/video/VodController.kt: -------------------------------------------------------------------------------- 1 | package icu.bughub.app.app.ui.components.video 2 | 3 | import android.content.Context 4 | import android.os.Bundle 5 | import android.util.Log 6 | import androidx.compose.runtime.Composable 7 | import androidx.compose.runtime.remember 8 | import androidx.compose.runtime.saveable.Saver 9 | import androidx.compose.runtime.saveable.SaverScope 10 | import androidx.compose.runtime.saveable.rememberSaveable 11 | import androidx.compose.ui.platform.LocalContext 12 | import com.tencent.rtmp.ITXVodPlayListener 13 | import com.tencent.rtmp.TXLiveConstants 14 | import com.tencent.rtmp.TXVodPlayer 15 | 16 | class VodController( 17 | context: Context, 18 | val playerValue: PlayerValue 19 | ) { 20 | 21 | val vodPlayer = TXVodPlayer(context).apply { 22 | setVodListener(object : ITXVodPlayListener { 23 | override fun onPlayEvent(player: TXVodPlayer?, event: Int, param: Bundle?) { 24 | when (event) { 25 | TXLiveConstants.PLAY_EVT_PLAY_LOADING -> { 26 | playerValue.state = PlayState.Loading 27 | } 28 | TXLiveConstants.PLAY_EVT_VOD_PLAY_PREPARED, 29 | TXLiveConstants.PLAY_EVT_RCV_FIRST_I_FRAME, 30 | TXLiveConstants.PLAY_EVT_VOD_LOADING_END, 31 | TXLiveConstants.PLAY_EVT_PLAY_BEGIN 32 | -> { 33 | playerValue.state = PlayState.Playing 34 | } 35 | //获取视频时长和进度 36 | TXLiveConstants.PLAY_EVT_PLAY_PROGRESS -> { 37 | playerValue.duration = 38 | param?.getInt(TXLiveConstants.EVT_PLAY_DURATION_MS)?.toLong() ?: 0L 39 | 40 | playerValue.currentPosition = 41 | param?.getInt(TXLiveConstants.EVT_PLAY_PROGRESS_MS)?.toLong() ?: 0L 42 | } 43 | } 44 | } 45 | 46 | override fun onNetStatus(player: TXVodPlayer?, param: Bundle?) { 47 | 48 | } 49 | }) 50 | } 51 | 52 | /** 53 | * 开始播放 54 | * 55 | * @param url 56 | */ 57 | fun startPlay() { 58 | playerValue.videoUrl = playerValue.videoUrl 59 | vodPlayer.startPlay(playerValue.videoUrl) 60 | } 61 | 62 | /** 63 | * 配置改变后重新播放 64 | * 65 | */ 66 | fun restore() { 67 | vodPlayer.setStartTime(playerValue.currentPosition / 1000f) 68 | startPlay() 69 | } 70 | 71 | /** 72 | * 停止播放 73 | * 74 | */ 75 | fun stopPlay() { 76 | vodPlayer.stopPlay(true) 77 | } 78 | 79 | /** 80 | * 暂停播放 81 | * 82 | */ 83 | fun pause() { 84 | vodPlayer.pause() 85 | playerValue.state = PlayState.Pause 86 | } 87 | 88 | /** 89 | * 恢复播放 90 | * 91 | */ 92 | fun resume() { 93 | vodPlayer.resume() 94 | } 95 | 96 | /** 97 | * 设置播放进度 98 | * 99 | * @param millSeconds 100 | */ 101 | fun seekTo(millSeconds: Long) { 102 | vodPlayer.seek((millSeconds / 1000).toInt()) 103 | } 104 | } 105 | 106 | @Composable 107 | fun rememberVodController( 108 | context: Context = LocalContext.current, 109 | videoUrl: String, 110 | coverUrl: String? = "", 111 | title: String? = "" 112 | ) = 113 | rememberSaveable(saver = object : Saver { 114 | override fun restore(value: PlayerValue): VodController? { 115 | return VodController(context, value) 116 | } 117 | 118 | override fun SaverScope.save(value: VodController): PlayerValue { 119 | return value.playerValue 120 | } 121 | }) { 122 | val playerValue = PlayerValue() 123 | playerValue.videoUrl = videoUrl 124 | playerValue.coverUrl = coverUrl ?: "" 125 | playerValue.title = title ?: "" 126 | return@rememberSaveable VodController(context, playerValue) 127 | } 128 | // remember { 129 | // VodController(context, videoUrl, coverUrl) 130 | // } -------------------------------------------------------------------------------- /App/app/src/main/java/icu/bughub/app/app/ui/navigation/Destinations.kt: -------------------------------------------------------------------------------- 1 | package icu.bughub.app.app.ui.navigation 2 | 3 | sealed class Destinations(val route: String) { 4 | //首页大框架 5 | object HomeFrame : Destinations("HomeFrame") 6 | 7 | //文章详情页 8 | object ArticleDetail : Destinations("ArticleDetail") 9 | 10 | //视频详情页 11 | object VideoDetail : Destinations("VideoDetail") 12 | 13 | //登录页 14 | object Login : Destinations("Login") 15 | } 16 | -------------------------------------------------------------------------------- /App/app/src/main/java/icu/bughub/app/app/ui/screens/MainFrame.kt: -------------------------------------------------------------------------------- 1 | package icu.bughub.app.app.ui.screens 2 | 3 | 4 | import androidx.compose.foundation.layout.Box 5 | import androidx.compose.foundation.layout.padding 6 | import androidx.compose.material.* 7 | import androidx.compose.material.icons.Icons 8 | import androidx.compose.material.icons.filled.DateRange 9 | import androidx.compose.material.icons.filled.Home 10 | import androidx.compose.material.icons.filled.Person 11 | import androidx.compose.runtime.* 12 | import androidx.compose.ui.Modifier 13 | import androidx.compose.ui.graphics.Color 14 | import androidx.compose.ui.tooling.preview.Preview 15 | import com.google.accompanist.insets.ProvideWindowInsets 16 | import com.google.accompanist.insets.navigationBarsPadding 17 | import icu.bughub.app.app.model.entity.NavigationItem 18 | 19 | 20 | @Composable 21 | fun MainFrame( 22 | onNavigateToArticle: () -> Unit = {}, 23 | onNavigateToVideo: () -> Unit = {}, 24 | onNavigateToStudyHistory: () -> Unit = {} 25 | ) { 26 | 27 | val navigationItems = listOf( 28 | NavigationItem(title = "学习", icon = Icons.Filled.Home), 29 | NavigationItem(title = "任务", icon = Icons.Filled.DateRange), 30 | NavigationItem(title = "我的", icon = Icons.Filled.Person), 31 | ) 32 | 33 | var currentNavigationIndex by remember { 34 | mutableStateOf(0) 35 | } 36 | 37 | Scaffold(bottomBar = { 38 | BottomNavigation( 39 | backgroundColor = MaterialTheme.colors.surface, 40 | modifier = Modifier.navigationBarsPadding(bottom = true) 41 | ) { 42 | navigationItems.forEachIndexed { index, navigationItem -> 43 | BottomNavigationItem( 44 | selected = currentNavigationIndex == index, 45 | onClick = { 46 | currentNavigationIndex = index 47 | }, 48 | //直接考试结果页面,进入查看页面,返回直接回到列表? 49 | icon = { 50 | Icon( 51 | imageVector = navigationItem.icon, 52 | contentDescription = null 53 | ) 54 | }, 55 | label = { 56 | Text(text = navigationItem.title) 57 | }, 58 | selectedContentColor = Color(0xFF149EE7), 59 | unselectedContentColor = Color(0xFF999999) 60 | ) 61 | } 62 | } 63 | } 64 | ) { 65 | Box(modifier = Modifier.padding(it)) { 66 | when (currentNavigationIndex) { 67 | 0 -> StudyScreen( 68 | onNavigateToArticle = onNavigateToArticle, 69 | onNavigateToVideo = onNavigateToVideo, 70 | onNavigateToStudyHistory = onNavigateToStudyHistory 71 | ) 72 | 1 -> TaskScreen() 73 | 2 -> MineScreen() 74 | } 75 | } 76 | } 77 | 78 | 79 | } 80 | 81 | @Preview 82 | @Composable 83 | fun MainFramePreview() { 84 | MainFrame() 85 | } 86 | 87 | -------------------------------------------------------------------------------- /App/app/src/main/java/icu/bughub/app/app/ui/screens/VideoDetailScreen.kt: -------------------------------------------------------------------------------- 1 | package icu.bughub.app.app.ui.screens 2 | 3 | 4 | import android.content.res.Configuration 5 | import androidx.compose.foundation.background 6 | import androidx.compose.foundation.clickable 7 | import androidx.compose.foundation.layout.* 8 | import androidx.compose.material.* 9 | import androidx.compose.material.icons.Icons 10 | import androidx.compose.material.icons.filled.NavigateBefore 11 | import androidx.compose.runtime.* 12 | import androidx.compose.ui.Alignment 13 | import androidx.compose.ui.Modifier 14 | import androidx.compose.ui.draw.alpha 15 | import androidx.compose.ui.graphics.Color 16 | import androidx.compose.ui.platform.LocalConfiguration 17 | import androidx.compose.ui.platform.LocalContext 18 | import androidx.compose.ui.text.style.TextAlign 19 | import androidx.compose.ui.tooling.preview.Preview 20 | import androidx.compose.ui.unit.dp 21 | import androidx.compose.ui.unit.sp 22 | import androidx.lifecycle.viewmodel.compose.viewModel 23 | import com.google.accompanist.insets.statusBarsPadding 24 | import com.google.accompanist.systemuicontroller.rememberSystemUiController 25 | import com.tencent.rtmp.TXVodPlayer 26 | import icu.bughub.app.app.ui.components.video.VideoPlayer 27 | import icu.bughub.app.app.ui.components.video.VideoView 28 | import icu.bughub.app.app.ui.components.video.rememberVodController 29 | import icu.bughub.app.app.ui.theme.Blue700 30 | import icu.bughub.app.app.viewmodel.VideoViewModel 31 | import icu.bughub.app.module.webview.WebView 32 | import icu.bughub.app.module.webview.rememberWebViewState 33 | 34 | 35 | @Composable 36 | fun VideoDetailScreen(videoViewModel: VideoViewModel = viewModel(), onBack: () -> Unit) { 37 | 38 | val systemUiController = rememberSystemUiController() 39 | 40 | LaunchedEffect(Unit) { 41 | videoViewModel.fetchInfo() 42 | } 43 | 44 | val webViewState = rememberWebViewState(data = videoViewModel.videoDesc) 45 | 46 | 47 | val configuration = LocalConfiguration.current 48 | 49 | var scaffoldModifier by remember { 50 | mutableStateOf(Modifier.alpha(1f)) 51 | } 52 | 53 | var videoBoxModifier by remember { 54 | mutableStateOf( 55 | Modifier.aspectRatio(16 / 9f) 56 | ) 57 | } 58 | 59 | 60 | Scaffold( 61 | topBar = { 62 | if (configuration.orientation == Configuration.ORIENTATION_PORTRAIT) { 63 | TopAppBar( 64 | title = { 65 | Text( 66 | text = "视频详情", 67 | fontSize = 18.sp 68 | ) 69 | }, 70 | navigationIcon = { 71 | Icon( 72 | imageVector = Icons.Default.NavigateBefore, 73 | contentDescription = null, 74 | modifier = Modifier 75 | .clickable { 76 | onBack() 77 | } 78 | .padding(8.dp) 79 | ) 80 | }, 81 | ) 82 | } 83 | }, 84 | modifier = scaffoldModifier, 85 | ) { 86 | Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { 87 | if (videoViewModel.infoLoaded) { 88 | Column(modifier = Modifier.fillMaxSize()) { 89 | val vodController = 90 | rememberVodController( 91 | videoUrl = videoViewModel.videoUrl, 92 | coverUrl = videoViewModel.coverUrl 93 | ) 94 | //TODO 横屏后,点击屏幕状态栏即显示出来,而且不会再隐藏,如何处理这个问题? 95 | LaunchedEffect(configuration.orientation) { 96 | vodController.restore() 97 | if (configuration.orientation == Configuration.ORIENTATION_PORTRAIT) { 98 | videoBoxModifier = Modifier 99 | .fillMaxWidth() 100 | .aspectRatio(16 / 9f) 101 | systemUiController.isSystemBarsVisible = true 102 | 103 | scaffoldModifier = Modifier 104 | .background(Blue700) 105 | .statusBarsPadding() 106 | 107 | } else { 108 | videoBoxModifier = Modifier 109 | .fillMaxSize() 110 | systemUiController.isSystemBarsVisible = false 111 | 112 | scaffoldModifier = Modifier 113 | } 114 | } 115 | 116 | //视频区域 117 | Box(modifier = videoBoxModifier) { 118 | VideoPlayer(vodController = vodController) 119 | } 120 | 121 | //想让标题一起滚动,有两个方案 122 | //方案一:把标题放到视频简介的 html 文本中去 123 | //方案二:计算 视频简介在 webview 中的高度,然后动态设置 webview 的高度 124 | WebView(state = webViewState) 125 | } 126 | } else { 127 | CircularProgressIndicator() 128 | } 129 | } 130 | } 131 | 132 | } 133 | 134 | 135 | -------------------------------------------------------------------------------- /App/app/src/main/java/icu/bughub/app/app/ui/theme/Color.kt: -------------------------------------------------------------------------------- 1 | package icu.bughub.app.app.ui.theme 2 | 3 | import androidx.compose.ui.graphics.Color 4 | 5 | 6 | val Blue700 = Color(0xFF149EE7) 7 | val Blue200 = Color(0xFF2DCDF5) -------------------------------------------------------------------------------- /App/app/src/main/java/icu/bughub/app/app/ui/theme/Shape.kt: -------------------------------------------------------------------------------- 1 | package icu.bughub.app.app.ui.theme 2 | 3 | import androidx.compose.foundation.shape.RoundedCornerShape 4 | import androidx.compose.material.Shapes 5 | import androidx.compose.ui.unit.dp 6 | 7 | val Shapes = Shapes( 8 | small = RoundedCornerShape(4.dp), 9 | medium = RoundedCornerShape(4.dp), 10 | large = RoundedCornerShape(0.dp) 11 | ) -------------------------------------------------------------------------------- /App/app/src/main/java/icu/bughub/app/app/ui/theme/Theme.kt: -------------------------------------------------------------------------------- 1 | package icu.bughub.app.app.ui.theme 2 | 3 | import androidx.compose.foundation.isSystemInDarkTheme 4 | import androidx.compose.material.MaterialTheme 5 | import androidx.compose.material.darkColors 6 | import androidx.compose.material.lightColors 7 | import androidx.compose.runtime.Composable 8 | 9 | private val DarkColorPalette = darkColors( 10 | primary = Blue700, 11 | primaryVariant = Blue700, 12 | secondary = Blue200 13 | ) 14 | 15 | private val LightColorPalette = lightColors( 16 | primary = Blue700, 17 | primaryVariant = Blue700, 18 | secondary = Blue200 19 | 20 | /* Other default colors to override 21 | background = Color.White, 22 | surface = Color.White, 23 | onPrimary = Color.White, 24 | onSecondary = Color.Black, 25 | onBackground = Color.Black, 26 | onSurface = Color.Black, 27 | */ 28 | ) 29 | 30 | @Composable 31 | fun AppTheme(darkTheme: Boolean = isSystemInDarkTheme(), content: @Composable () -> Unit) { 32 | // val colors = if (darkTheme) { 33 | // DarkColorPalette 34 | // } else { 35 | // LightColorPalette 36 | // } 37 | 38 | MaterialTheme( 39 | colors = LightColorPalette, 40 | typography = Typography, 41 | shapes = Shapes, 42 | content = content 43 | ) 44 | } -------------------------------------------------------------------------------- /App/app/src/main/java/icu/bughub/app/app/ui/theme/Type.kt: -------------------------------------------------------------------------------- 1 | package icu.bughub.app.app.ui.theme 2 | 3 | import androidx.compose.material.Typography 4 | import androidx.compose.ui.text.TextStyle 5 | import androidx.compose.ui.text.font.FontFamily 6 | import androidx.compose.ui.text.font.FontWeight 7 | import androidx.compose.ui.unit.sp 8 | 9 | // Set of Material typography styles to start with 10 | val Typography = Typography( 11 | body1 = TextStyle( 12 | fontFamily = FontFamily.Default, 13 | fontWeight = FontWeight.Normal, 14 | fontSize = 16.sp 15 | ) 16 | /* Other default text styles to override 17 | button = TextStyle( 18 | fontFamily = FontFamily.Default, 19 | fontWeight = FontWeight.W500, 20 | fontSize = 14.sp 21 | ), 22 | caption = TextStyle( 23 | fontFamily = FontFamily.Default, 24 | fontWeight = FontWeight.Normal, 25 | fontSize = 12.sp 26 | ) 27 | */ 28 | ) -------------------------------------------------------------------------------- /App/app/src/main/java/icu/bughub/app/app/viewmodel/ArticleViewModel.kt: -------------------------------------------------------------------------------- 1 | package icu.bughub.app.app.viewmodel 2 | 3 | import android.util.Log 4 | import androidx.compose.runtime.getValue 5 | import androidx.compose.runtime.mutableStateListOf 6 | import androidx.compose.runtime.mutableStateOf 7 | import androidx.compose.runtime.setValue 8 | import androidx.lifecycle.ViewModel 9 | import icu.bughub.app.app.model.entity.ArticleEntity 10 | import icu.bughub.app.app.model.service.ArticleService 11 | import kotlinx.coroutines.delay 12 | 13 | class ArticleViewModel : ViewModel() { 14 | 15 | private val articleService = ArticleService.instance() 16 | 17 | private val pageSize = 10 18 | private var pageOffset = 1 19 | 20 | //文章列表数据 21 | var list by mutableStateOf( 22 | listOf( 23 | ArticleEntity( 24 | title = "人社部向疫情防控期参与复工复产的劳动者表", 25 | source = "“学习强国”学习平台", 26 | timestamp = "2020-02-10" 27 | ), 28 | ArticleEntity( 29 | title = "人社部向疫情防控期参与复工复产的劳动者表人社部向疫情防控期参与复工复产的劳动者表", 30 | source = "“学习强国”学习平台", 31 | timestamp = "2020-02-10" 32 | ), ArticleEntity( 33 | title = "人社部向疫情防控期参与复工复产的劳动者表", 34 | source = "“学习强国”学习平台", 35 | timestamp = "2020-02-10" 36 | ), ArticleEntity( 37 | title = "人社部向疫情防控期参与复工复产的劳动者表", 38 | source = "“学习强国”学习平台", 39 | timestamp = "2020-02-10" 40 | ), ArticleEntity( 41 | title = "人社部向疫情防控期参与复工复产的劳动者表", 42 | source = "“学习强国”学习平台", 43 | timestamp = "2020-02-10" 44 | ), ArticleEntity( 45 | title = "人社部向疫情防控期参与复工复产的劳动者表", 46 | source = "“学习强国”学习平台", 47 | timestamp = "2020-02-10" 48 | ), ArticleEntity( 49 | title = "人社部向疫情防控期参与复工复产的劳动者表", 50 | source = "“学习强国”学习平台", 51 | timestamp = "2020-02-10" 52 | ), ArticleEntity( 53 | title = "人社部向疫情防控期参与复工复产的劳动者表", 54 | source = "“学习强国”学习平台", 55 | timestamp = "2020-02-10" 56 | ), ArticleEntity( 57 | title = "人社部向疫情防控期参与复工复产的劳动者表", 58 | source = "“学习强国”学习平台", 59 | timestamp = "2020-02-10" 60 | ), ArticleEntity( 61 | title = "人社部向疫情防控期参与复工复产的劳动者表", 62 | source = "“学习强国”学习平台", 63 | timestamp = "2020-02-10" 64 | ) 65 | ) 66 | ) 67 | private set 68 | 69 | //第一页文章列表数据是否加载完成 70 | var listLoaded by mutableStateOf(false) 71 | private set 72 | 73 | //是否正在刷新 74 | var refreshing by mutableStateOf(false) 75 | private set 76 | 77 | //是否还有更多 78 | private var hasMore = false 79 | 80 | suspend fun fetchArticleList() { 81 | val res = articleService.list(pageOffset = pageOffset, pageSize = pageSize) 82 | if (res.code == 0 && res.data != null) { 83 | val tmpList = mutableListOf() 84 | if (pageOffset != 1) { 85 | tmpList.addAll(list) 86 | } 87 | tmpList.addAll(res.data) 88 | //是否还有更多数据 89 | hasMore = res.data.size == pageSize 90 | list = tmpList 91 | listLoaded = true 92 | refreshing = false 93 | } else { 94 | pageOffset-- 95 | if (pageOffset <= 1) { 96 | pageOffset = 1 97 | } 98 | } 99 | } 100 | 101 | /** 102 | * 下拉刷新 103 | * 104 | */ 105 | suspend fun refresh() { 106 | pageOffset = 1 107 | // listLoaded = false 108 | refreshing = true 109 | fetchArticleList() 110 | } 111 | 112 | suspend fun loadMore() { 113 | if (hasMore) { 114 | pageOffset++ 115 | fetchArticleList() 116 | } 117 | } 118 | 119 | //HTML 头部 120 | private val htmlHeader = """ 121 | 122 | 123 | 124 | 125 | 126 | 127 | 132 | 133 | 134 | """.trimIndent() 135 | 136 | //html尾部 137 | private val htmlFooter = """ 138 | 139 | 140 | """.trimIndent() 141 | 142 | 143 | private var articleEntity: ArticleEntity? = null 144 | 145 | var content by mutableStateOf( 146 | """$htmlHeader 147 | ${articleEntity?.content ?: ""} 148 | $htmlFooter 149 | """.trimIndent() 150 | ) 151 | 152 | var infoLoaded by mutableStateOf(false) 153 | private set 154 | 155 | suspend fun fetchInfo() { 156 | val res = articleService.info("") 157 | if (res.code == 0 && res.data != null) { 158 | articleEntity = res.data 159 | content = """$htmlHeader 160 | ${articleEntity?.content ?: ""} 161 | $htmlFooter 162 | """.trimIndent() 163 | infoLoaded = true 164 | } 165 | } 166 | } -------------------------------------------------------------------------------- /App/app/src/main/java/icu/bughub/app/app/viewmodel/MainViewModel.kt: -------------------------------------------------------------------------------- 1 | package icu.bughub.app.app.viewmodel 2 | 3 | import androidx.compose.material.icons.Icons 4 | import androidx.compose.material.icons.filled.Description 5 | import androidx.compose.material.icons.filled.SmartDisplay 6 | import androidx.compose.runtime.getValue 7 | import androidx.compose.runtime.mutableStateOf 8 | import androidx.compose.runtime.setValue 9 | import androidx.lifecycle.ViewModel 10 | import icu.bughub.app.app.model.entity.Category 11 | import icu.bughub.app.app.model.entity.DataType 12 | import icu.bughub.app.app.model.entity.SwiperEntity 13 | import icu.bughub.app.app.model.service.HomeService 14 | 15 | class MainViewModel : ViewModel() { 16 | 17 | private val homeService = HomeService.instance() 18 | 19 | //分类数据是否加载成功 20 | var categoryLoaded by mutableStateOf(false) 21 | private set 22 | 23 | //分类数据 24 | var categories by mutableStateOf( 25 | listOf( 26 | Category("思想政治1", "1"), 27 | Category("法律法规2", "2"), 28 | Category("职业道德3", "3"), 29 | Category("诚信自律4", "4") 30 | ) 31 | ) 32 | private set 33 | 34 | suspend fun categoryData() { 35 | val categoryRes = homeService.category() 36 | if (categoryRes.code == 0) { 37 | categories = categoryRes.data 38 | categoryLoaded = true 39 | } else { 40 | //不成功的情况下,读取 message 41 | val message = categoryRes.message 42 | } 43 | } 44 | 45 | //当前分类下标 46 | var categoryIndex by mutableStateOf(0) 47 | private set 48 | 49 | /** 50 | * 更新分类下标 51 | * 52 | * @param index 53 | */ 54 | fun updateCategoryIndex(index: Int) { 55 | categoryIndex = index 56 | } 57 | 58 | 59 | //类型数据 60 | val types by mutableStateOf( 61 | listOf( 62 | DataType("相关资讯", Icons.Default.Description), 63 | DataType("视频课程", Icons.Default.SmartDisplay) 64 | ) 65 | ) 66 | 67 | //当前类型下标 68 | var currentTypeIndex by mutableStateOf(0) 69 | private set 70 | 71 | //是否文章列表 72 | var showArticleList by mutableStateOf(true) 73 | private set 74 | 75 | /** 76 | * 更新类型下标 77 | * 78 | * @param index 79 | */ 80 | fun updateTypeIndex(index: Int) { 81 | currentTypeIndex = index 82 | showArticleList = currentTypeIndex == 0 83 | } 84 | 85 | //轮播图数据 86 | var swiperData by mutableStateOf( 87 | listOf( 88 | SwiperEntity("https://docs.bughub.icu/compose/assets/banner5.jpg") 89 | ) 90 | ) 91 | private set 92 | 93 | var swiperLoaded by mutableStateOf(false) 94 | private set 95 | 96 | suspend fun swiperData() { 97 | val swiperRes = homeService.banner() 98 | if (swiperRes.code == 0 && swiperRes.data != null) { 99 | swiperData = swiperRes.data 100 | swiperLoaded = true 101 | } else { 102 | val message = swiperRes.message 103 | } 104 | } 105 | 106 | //通知数据 107 | val notifications = 108 | listOf("人社部向疫情防控期", "湖北黄冈新冠肺炎患者治愈病例破千连续5治愈病例破千连续5", "安徽单日新增确诊病例首次降至个位数累计") 109 | 110 | 111 | } -------------------------------------------------------------------------------- /App/app/src/main/java/icu/bughub/app/app/viewmodel/TaskViewModel.kt: -------------------------------------------------------------------------------- 1 | package icu.bughub.app.app.viewmodel 2 | 3 | import androidx.compose.runtime.getValue 4 | import androidx.compose.runtime.mutableStateOf 5 | import androidx.compose.runtime.setValue 6 | import androidx.lifecycle.ViewModel 7 | 8 | class TaskViewModel : ViewModel() { 9 | 10 | var taskDate by mutableStateOf("学习周期:2022.01.01-2022.12.31") 11 | private set 12 | 13 | var totalPointOfYear = 13500 14 | 15 | //学年积分 16 | var pointOfYear by mutableStateOf(10000) 17 | private set 18 | 19 | //学年积分进度 = 220f * pointOfYear / 学年总积分 20 | var pointOfYearPercent by mutableStateOf(0f) 21 | private set 22 | 23 | /** 24 | * 更新学年积分进度 25 | * 26 | */ 27 | fun updatePointPercent() { 28 | pointOfYearPercent = 220f * pointOfYear / totalPointOfYear 29 | } 30 | 31 | //一周积分情况 32 | var pointsOfWeek by mutableStateOf(listOf(0.0, 2.0, 6.0, 9.5, 10.0, 15.0, 5.0)) 33 | private set 34 | 35 | //日期 36 | val weeks = listOf("02.05", "02.06", "02.07", "02.08", "02.09", "02.10", "今日") 37 | 38 | 39 | //今日积分 40 | private var todayPoint = 13 41 | 42 | //今日提醒文字 43 | var tips by mutableStateOf("今日获得0积分,快去完成下面任务吧") 44 | private set 45 | 46 | /** 47 | * 更新任务提醒文字 48 | * 49 | */ 50 | fun updateTips() { 51 | tips = when (todayPoint) { 52 | 0 -> { 53 | "今日获得0积分,快去完成下面任务吧" 54 | } 55 | in 1..14 -> { 56 | "今日获得${todayPoint}积分,快去完成下面任务吧" 57 | } 58 | else -> { 59 | "今日获得${todayPoint}积分,已经完成任务" 60 | } 61 | } 62 | } 63 | 64 | } -------------------------------------------------------------------------------- /App/app/src/main/java/icu/bughub/app/app/viewmodel/UserViewModel.kt: -------------------------------------------------------------------------------- 1 | package icu.bughub.app.app.viewmodel 2 | 3 | import android.content.Context 4 | import android.util.Log 5 | import androidx.compose.runtime.getValue 6 | import androidx.compose.runtime.mutableStateOf 7 | import androidx.compose.runtime.setValue 8 | import androidx.lifecycle.ViewModel 9 | import androidx.lifecycle.viewModelScope 10 | import icu.bughub.app.app.model.entity.UserInfoEntity 11 | import icu.bughub.app.app.model.service.UserInfoManager 12 | import icu.bughub.app.app.model.service.UserService 13 | import kotlinx.coroutines.delay 14 | import kotlinx.coroutines.flow.firstOrNull 15 | import kotlinx.coroutines.launch 16 | import okhttp3.internal.and 17 | import java.security.MessageDigest 18 | import java.security.NoSuchAlgorithmException 19 | 20 | class UserViewModel(context: Context) : ViewModel() { 21 | 22 | private val userInfoManager = UserInfoManager(context) 23 | private val userService = UserService.instance() 24 | 25 | var userName by mutableStateOf("") 26 | 27 | var password by mutableStateOf("") 28 | 29 | var userInfo: UserInfoEntity? = null 30 | private set 31 | 32 | init { 33 | //其实这里可以使用 DataStore 的对象存储,直接存储整个对象。 34 | viewModelScope.launch { 35 | val userName = userInfoManager.userName.firstOrNull() 36 | userInfo = if (userName?.isNotEmpty() == true) { 37 | UserInfoEntity(userName) 38 | } else { 39 | null 40 | } 41 | } 42 | } 43 | 44 | //是否已登录 45 | val logged: Boolean 46 | get() { 47 | return userInfo != null 48 | } 49 | 50 | //是否正在登录 51 | var loading by mutableStateOf(false) 52 | private set 53 | 54 | var error by mutableStateOf("") 55 | private set 56 | 57 | /** 58 | * 登录操作 59 | * 60 | */ 61 | suspend fun login(onClose: () -> Unit) { 62 | error = "" 63 | loading = true 64 | val res = userService.signIn(userName, md5(password)) 65 | if (res.code == 0 && res.data != null) { 66 | userInfo = res.data 67 | userInfoManager.save(userName) 68 | onClose() 69 | } else { 70 | //失败 71 | error = res.message 72 | } 73 | loading = false 74 | } 75 | 76 | fun md5(content: String): String { 77 | val hash = MessageDigest.getInstance("MD5").digest(content.toByteArray()) 78 | val hex = StringBuilder(hash.size * 2) 79 | for (b in hash) { 80 | var str = Integer.toHexString(b.toInt()) 81 | if (b < 0x10) { 82 | str = "0$str" 83 | } 84 | hex.append(str.substring(str.length - 2)) 85 | } 86 | return hex.toString() 87 | } 88 | 89 | 90 | fun clear() { 91 | viewModelScope.launch { 92 | userInfoManager.clear() //清除本地数据存储 93 | userInfo = null //置空内存数据 94 | } 95 | } 96 | } -------------------------------------------------------------------------------- /App/app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /App/app/src/main/res/drawable/archives.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RandyWei/JetpackComposeCase/192eb509c12ea41eb8b9fef4ee69806a3a39afd7/App/app/src/main/res/drawable/archives.png -------------------------------------------------------------------------------- /App/app/src/main/res/drawable/bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RandyWei/JetpackComposeCase/192eb509c12ea41eb8b9fef4ee69806a3a39afd7/App/app/src/main/res/drawable/bg.jpg -------------------------------------------------------------------------------- /App/app/src/main/res/drawable/browser_history.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RandyWei/JetpackComposeCase/192eb509c12ea41eb8b9fef4ee69806a3a39afd7/App/app/src/main/res/drawable/browser_history.png -------------------------------------------------------------------------------- /App/app/src/main/res/drawable/newbanner3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RandyWei/JetpackComposeCase/192eb509c12ea41eb8b9fef4ee69806a3a39afd7/App/app/src/main/res/drawable/newbanner3.png -------------------------------------------------------------------------------- /App/app/src/main/res/drawable/points.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RandyWei/JetpackComposeCase/192eb509c12ea41eb8b9fef4ee69806a3a39afd7/App/app/src/main/res/drawable/points.png -------------------------------------------------------------------------------- /App/app/src/main/res/drawable/questions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RandyWei/JetpackComposeCase/192eb509c12ea41eb8b9fef4ee69806a3a39afd7/App/app/src/main/res/drawable/questions.png -------------------------------------------------------------------------------- /App/app/src/main/res/drawable/settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RandyWei/JetpackComposeCase/192eb509c12ea41eb8b9fef4ee69806a3a39afd7/App/app/src/main/res/drawable/settings.png -------------------------------------------------------------------------------- /App/app/src/main/res/drawable/version.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RandyWei/JetpackComposeCase/192eb509c12ea41eb8b9fef4ee69806a3a39afd7/App/app/src/main/res/drawable/version.png -------------------------------------------------------------------------------- /App/app/src/main/res/layout/video.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | -------------------------------------------------------------------------------- /App/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /App/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /App/app/src/main/res/mipmap-hdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RandyWei/JetpackComposeCase/192eb509c12ea41eb8b9fef4ee69806a3a39afd7/App/app/src/main/res/mipmap-hdpi/ic_launcher.webp -------------------------------------------------------------------------------- /App/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RandyWei/JetpackComposeCase/192eb509c12ea41eb8b9fef4ee69806a3a39afd7/App/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /App/app/src/main/res/mipmap-mdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RandyWei/JetpackComposeCase/192eb509c12ea41eb8b9fef4ee69806a3a39afd7/App/app/src/main/res/mipmap-mdpi/ic_launcher.webp -------------------------------------------------------------------------------- /App/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RandyWei/JetpackComposeCase/192eb509c12ea41eb8b9fef4ee69806a3a39afd7/App/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /App/app/src/main/res/mipmap-xhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RandyWei/JetpackComposeCase/192eb509c12ea41eb8b9fef4ee69806a3a39afd7/App/app/src/main/res/mipmap-xhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /App/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RandyWei/JetpackComposeCase/192eb509c12ea41eb8b9fef4ee69806a3a39afd7/App/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /App/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RandyWei/JetpackComposeCase/192eb509c12ea41eb8b9fef4ee69806a3a39afd7/App/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /App/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RandyWei/JetpackComposeCase/192eb509c12ea41eb8b9fef4ee69806a3a39afd7/App/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /App/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RandyWei/JetpackComposeCase/192eb509c12ea41eb8b9fef4ee69806a3a39afd7/App/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /App/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RandyWei/JetpackComposeCase/192eb509c12ea41eb8b9fef4ee69806a3a39afd7/App/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /App/app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FF149EE7 4 | -------------------------------------------------------------------------------- /App/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | App 3 | -------------------------------------------------------------------------------- /App/app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | -------------------------------------------------------------------------------- /App/app/src/test/java/icu/bughub/app/app/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package icu.bughub.app.app 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } -------------------------------------------------------------------------------- /App/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext { 3 | compose_version = '1.1.1' 4 | } 5 | }// Top-level build file where you can add configuration options common to all sub-projects/modules. 6 | plugins { 7 | id 'com.android.application' version '7.1.0-rc01' apply false 8 | id 'com.android.library' version '7.1.0-rc01' apply false 9 | id 'org.jetbrains.kotlin.android' version '1.6.10' apply false 10 | id 'org.jetbrains.kotlin.plugin.parcelize' version '1.6.10' apply false 11 | } 12 | 13 | task clean(type: Delete) { 14 | delete rootProject.buildDir 15 | } -------------------------------------------------------------------------------- /App/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app"s APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Kotlin code style for this project: "official" or "obsolete": 19 | kotlin.code.style=official 20 | # Enables namespacing of each library's R class so that its R class includes only the 21 | # resources declared in the library itself and none from the library's dependencies, 22 | # thereby reducing the size of the R class for that library 23 | android.nonTransitiveRClass=true -------------------------------------------------------------------------------- /App/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RandyWei/JetpackComposeCase/192eb509c12ea41eb8b9fef4ee69806a3a39afd7/App/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /App/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Thu Mar 03 06:15:00 CST 2022 2 | distributionBase=GRADLE_USER_HOME 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 7 | -------------------------------------------------------------------------------- /App/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /App/settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | gradlePluginPortal() 4 | google() 5 | mavenCentral() 6 | maven { url "https://maven.aliyun.com/repository/central" } 7 | } 8 | } 9 | dependencyResolutionManagement { 10 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 11 | repositories { 12 | google() 13 | mavenCentral() 14 | maven { url "https://maven.aliyun.com/repository/central" } 15 | } 16 | } 17 | rootProject.name = "App" 18 | include ':app' 19 | include ':webview' 20 | -------------------------------------------------------------------------------- /App/webview/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /App/webview/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.library' 3 | id 'org.jetbrains.kotlin.android' 4 | } 5 | 6 | android { 7 | compileSdk 32 8 | 9 | defaultConfig { 10 | minSdk 23 11 | targetSdk 32 12 | 13 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 14 | consumerProguardFiles "consumer-rules.pro" 15 | } 16 | 17 | buildTypes { 18 | release { 19 | minifyEnabled false 20 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 21 | } 22 | } 23 | compileOptions { 24 | sourceCompatibility JavaVersion.VERSION_1_8 25 | targetCompatibility JavaVersion.VERSION_1_8 26 | } 27 | buildFeatures { 28 | compose true 29 | } 30 | composeOptions { 31 | kotlinCompilerExtensionVersion compose_version 32 | } 33 | kotlinOptions { 34 | jvmTarget = '1.8' 35 | } 36 | } 37 | 38 | dependencies { 39 | 40 | implementation 'androidx.core:core-ktx:1.7.0' 41 | 42 | implementation "androidx.compose.ui:ui:$compose_version" 43 | implementation "androidx.compose.material:material:$compose_version" 44 | implementation "androidx.compose.ui:ui-tooling-preview:$compose_version" 45 | 46 | testImplementation 'junit:junit:4.13.2' 47 | androidTestImplementation 'androidx.test.ext:junit:1.1.3' 48 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' 49 | } -------------------------------------------------------------------------------- /App/webview/consumer-rules.pro: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RandyWei/JetpackComposeCase/192eb509c12ea41eb8b9fef4ee69806a3a39afd7/App/webview/consumer-rules.pro -------------------------------------------------------------------------------- /App/webview/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /App/webview/src/androidTest/java/icu/bughub/app/module/webview/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package icu.bughub.app.module.webview 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("icu.bughub.app.module.webview.test", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /App/webview/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /App/webview/src/main/java/icu/bughub/app/module/webview/WebView.kt: -------------------------------------------------------------------------------- 1 | package icu.bughub.app.module.webview 2 | 3 | 4 | import android.graphics.Bitmap 5 | import android.webkit.WebChromeClient 6 | import android.webkit.WebView 7 | import android.webkit.WebViewClient 8 | import androidx.compose.runtime.* 9 | import androidx.compose.ui.tooling.preview.Preview 10 | import androidx.compose.ui.viewinterop.AndroidView 11 | import kotlinx.coroutines.* 12 | import kotlinx.coroutines.flow.MutableSharedFlow 13 | import kotlinx.coroutines.flow.collect 14 | 15 | 16 | @Composable 17 | fun WebView(state: WebViewState) { 18 | 19 | var webView by remember { 20 | mutableStateOf(null) 21 | } 22 | 23 | //webview 变化或 state变化时重新订阅流数据 24 | LaunchedEffect(webView, state) { 25 | with(state) { 26 | //订阅流 27 | webView?.handleEvents() 28 | } 29 | } 30 | 31 | AndroidView(factory = { context -> 32 | WebView(context).apply { 33 | webChromeClient = object : WebChromeClient() { 34 | override fun onReceivedTitle(view: WebView?, title: String?) { 35 | super.onReceivedTitle(view, title) 36 | state.pageTitle = title 37 | } 38 | } 39 | 40 | webViewClient = object : WebViewClient() { 41 | override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) { 42 | super.onPageStarted(view, url, favicon) 43 | state.pageTitle = null 44 | } 45 | } 46 | 47 | with(settings) { 48 | javaScriptEnabled = true 49 | } 50 | }.also { webView = it } 51 | }) { view -> 52 | when (val content = state.content) { 53 | is WebContent.Url -> { 54 | val url = content.url 55 | //url 不空或者当前的 webview 加载的 url 不相同 56 | if (url.isNotEmpty() && url != view.url) { 57 | view.loadUrl(content.url) 58 | } 59 | } 60 | is WebContent.Data -> { 61 | view.loadDataWithBaseURL( 62 | content.baseUrl, 63 | content.data, 64 | null, 65 | "utf-8", 66 | null 67 | ) 68 | } 69 | } 70 | } 71 | 72 | } 73 | 74 | sealed class WebContent() { 75 | data class Url(val url: String) : WebContent() 76 | data class Data(val data: String, val baseUrl: String? = null) : WebContent() 77 | } 78 | 79 | class WebViewState(private val coroutineScope: CoroutineScope, webContent: WebContent) { 80 | //网页内容:url 或者 data(html 内容) 81 | var content by mutableStateOf(webContent) 82 | 83 | var pageTitle: String? by mutableStateOf(null) 84 | internal set 85 | 86 | //事件类型 87 | private enum class EventType { 88 | EVALUATE_JAVASCRIPT //执行 JS 方法 89 | } 90 | 91 | //共享流的数据类型 92 | private class Event(val type: EventType, val args: String, val callback: ((String) -> Unit)?) 93 | 94 | //共享流 95 | private val events: MutableSharedFlow = MutableSharedFlow() 96 | 97 | internal suspend fun WebView.handleEvents(): Unit = withContext(Dispatchers.Main) { 98 | events.collect { event -> 99 | when (event.type) { 100 | EventType.EVALUATE_JAVASCRIPT -> evaluateJavascript(event.args, event.callback) 101 | } 102 | } 103 | } 104 | 105 | //执行 js 方法 106 | fun evaluateJavascript(script: String, resultCallback: ((String) -> Unit)? = {}) { 107 | val event = Event(EventType.EVALUATE_JAVASCRIPT, script, resultCallback) 108 | coroutineScope.launch { events.emit(event) } //推送流 109 | } 110 | } 111 | 112 | @Composable 113 | fun rememberWebViewState(coroutineScope: CoroutineScope = rememberCoroutineScope(), url: String) = 114 | remember(key1 = url) { 115 | WebViewState(coroutineScope, WebContent.Url(url)) 116 | } 117 | 118 | @Composable 119 | fun rememberWebViewState( 120 | coroutineScope: CoroutineScope = rememberCoroutineScope(), 121 | data: String, 122 | baseUrl: String? = null 123 | ) = remember( 124 | key1 = data, 125 | key2 = baseUrl 126 | ) { 127 | WebViewState(coroutineScope, WebContent.Data(data, baseUrl)) 128 | } 129 | 130 | @Preview 131 | @Composable 132 | fun WebViewPreview() { 133 | WebView(rememberWebViewState(data = "

Header

")) 134 | } 135 | 136 | -------------------------------------------------------------------------------- /App/webview/src/test/java/icu/bughub/app/module/webview/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package icu.bughub.app.module.webview 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } -------------------------------------------------------------------------------- /ComposeBasic/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | local.properties 16 | -------------------------------------------------------------------------------- /ComposeBasic/.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /ComposeBasic/.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /ComposeBasic/.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 19 | 20 | -------------------------------------------------------------------------------- /ComposeBasic/.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 20 | -------------------------------------------------------------------------------- /ComposeBasic/.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /ComposeBasic/app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /ComposeBasic/app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | id 'org.jetbrains.kotlin.android' 4 | } 5 | 6 | android { 7 | compileSdk 32 8 | 9 | defaultConfig { 10 | applicationId "icu.bughub.app.composebasic" 11 | minSdk 23 12 | targetSdk 32 13 | versionCode 1 14 | versionName "1.0" 15 | 16 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 17 | vectorDrawables { 18 | useSupportLibrary true 19 | } 20 | } 21 | 22 | buildTypes { 23 | release { 24 | minifyEnabled false 25 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 26 | } 27 | } 28 | compileOptions { 29 | sourceCompatibility JavaVersion.VERSION_1_8 30 | targetCompatibility JavaVersion.VERSION_1_8 31 | } 32 | kotlinOptions { 33 | jvmTarget = '1.8' 34 | } 35 | buildFeatures { 36 | compose true 37 | } 38 | composeOptions { 39 | kotlinCompilerExtensionVersion compose_version 40 | } 41 | packagingOptions { 42 | resources { 43 | excludes += '/META-INF/{AL2.0,LGPL2.1}' 44 | } 45 | } 46 | } 47 | 48 | dependencies { 49 | 50 | implementation 'androidx.core:core-ktx:1.7.0' 51 | implementation "androidx.compose.ui:ui:$compose_version" 52 | implementation "androidx.compose.material:material:$compose_version" 53 | implementation "androidx.compose.ui:ui-tooling-preview:$compose_version" 54 | implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.4.0' 55 | implementation 'androidx.activity:activity-compose:1.4.0' 56 | 57 | implementation "androidx.compose.material:material-icons-extended:$compose_version" 58 | 59 | implementation "androidx.constraintlayout:constraintlayout-compose:1.0.0" 60 | 61 | implementation "androidx.navigation:navigation-compose:2.4.0-rc01" 62 | 63 | implementation "com.google.accompanist:accompanist-permissions:0.24.2-alpha" 64 | 65 | 66 | testImplementation 'junit:junit:4.13.2' 67 | androidTestImplementation 'androidx.test.ext:junit:1.1.3' 68 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' 69 | androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version" 70 | debugImplementation "androidx.compose.ui:ui-tooling:$compose_version" 71 | 72 | 73 | } -------------------------------------------------------------------------------- /ComposeBasic/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /ComposeBasic/app/src/androidTest/java/icu/bughub/app/composebasic/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package icu.bughub.app.composebasic 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("icu.bughub.app.composebasic", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /ComposeBasic/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 18 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /ComposeBasic/app/src/main/java/icu/bughub/app/composebasic/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package icu.bughub.app.composebasic 2 | 3 | import android.graphics.Color 4 | import android.os.Bundle 5 | import android.view.LayoutInflater 6 | import android.widget.TextView 7 | import androidx.activity.ComponentActivity 8 | import androidx.activity.compose.setContent 9 | import androidx.compose.foundation.layout.Column 10 | import androidx.compose.foundation.layout.fillMaxSize 11 | import androidx.compose.material.MaterialTheme 12 | import androidx.compose.material.Surface 13 | import androidx.compose.material.Text 14 | import androidx.compose.runtime.Composable 15 | import androidx.compose.ui.Modifier 16 | import androidx.compose.ui.platform.ComposeView 17 | import androidx.compose.ui.tooling.preview.Devices 18 | import androidx.compose.ui.tooling.preview.Preview 19 | import androidx.compose.ui.viewinterop.AndroidView 20 | import icu.bughub.app.composebasic.ui.theme.ComposeBasicTheme 21 | 22 | class MainActivity : ComponentActivity() { 23 | override fun onCreate(savedInstanceState: Bundle?) { 24 | super.onCreate(savedInstanceState) 25 | 26 | setContentView(R.layout.main) 27 | 28 | val composeView = findViewById(R.id.composeView) 29 | 30 | composeView.setContent { 31 | Column() { 32 | Greeting("Android") 33 | 34 | AndroidView(factory = { 35 | TextView(it) 36 | }){ 37 | it.apply { 38 | text = "这是 Compose View 里面的 TextView" 39 | } 40 | } 41 | 42 | } 43 | } 44 | 45 | 46 | // setContent { 47 | // ComposeBasicTheme { 48 | // // A surface container using the 'background' color from the theme 49 | // Surface( 50 | // modifier = Modifier.fillMaxSize(), 51 | // color = MaterialTheme.colors.background 52 | // ) { 53 | // Greeting("Android") 54 | // } 55 | // } 56 | // } 57 | } 58 | } 59 | 60 | @Composable 61 | fun Greeting(name: String) { 62 | Text(text = "Hello $name!") 63 | } 64 | 65 | @Preview( 66 | showSystemUi = true, 67 | name = "Preview Name" 68 | ) 69 | @Composable 70 | fun DefaultPreview() { 71 | ComposeBasicTheme { 72 | Greeting("Android") 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /ComposeBasic/app/src/main/java/icu/bughub/app/composebasic/components/AnimationCanvasSample.kt: -------------------------------------------------------------------------------- 1 | package icu.bughub.app.composebasic.components 2 | 3 | 4 | import androidx.compose.animation.core.animateOffsetAsState 5 | import androidx.compose.animation.core.tween 6 | import androidx.compose.foundation.Canvas 7 | import androidx.compose.foundation.background 8 | import androidx.compose.foundation.layout.* 9 | import androidx.compose.material.Button 10 | import androidx.compose.material.Surface 11 | import androidx.compose.material.Text 12 | import androidx.compose.runtime.Composable 13 | import androidx.compose.runtime.LaunchedEffect 14 | import androidx.compose.runtime.getValue 15 | import androidx.compose.runtime.rememberCoroutineScope 16 | import androidx.compose.ui.Modifier 17 | import androidx.compose.ui.geometry.Offset 18 | import androidx.compose.ui.graphics.Color 19 | import androidx.compose.ui.graphics.drawscope.translate 20 | import androidx.compose.ui.tooling.preview.Preview 21 | import androidx.compose.ui.unit.dp 22 | import kotlinx.coroutines.delay 23 | import kotlinx.coroutines.launch 24 | 25 | 26 | @Composable 27 | fun AnimationCanvasSample() { 28 | 29 | var endOffset = Offset(300f, 300f) 30 | 31 | val offsetAnimation by animateOffsetAsState( 32 | targetValue = endOffset, 33 | tween(durationMillis = 500, delayMillis = 500) 34 | ) 35 | 36 | val coroutineScope = rememberCoroutineScope() 37 | 38 | Column( 39 | modifier = Modifier 40 | .fillMaxSize() 41 | .background(Color(0xffeeeeee)) 42 | ) { 43 | 44 | Surface( 45 | color = Color.White, 46 | elevation = 1.dp, 47 | modifier = Modifier 48 | .fillMaxWidth() 49 | .height(65.dp) 50 | ) { 51 | Text(text = "TOP") 52 | } 53 | 54 | Surface( 55 | color = Color.White, 56 | elevation = 1.dp, 57 | modifier = Modifier 58 | .fillMaxWidth() 59 | .height(65.dp) 60 | .offset(y = 8.dp) 61 | ) { 62 | Text(text = "BOTTOM") 63 | } 64 | 65 | 66 | // Canvas( 67 | // modifier = Modifier 68 | // .size(300.dp) 69 | // .background(Color.Gray) 70 | // ) { 71 | // translate(offsetAnimation) { 72 | // drawLine(Color.Red, start = Offset.Zero, end = offsetAnimation) 73 | // } 74 | // 75 | // 76 | // } 77 | } 78 | 79 | } 80 | 81 | @Preview 82 | @Composable 83 | fun AnimationCanvasSamplePreview() { 84 | AnimationCanvasSample() 85 | } 86 | 87 | -------------------------------------------------------------------------------- /ComposeBasic/app/src/main/java/icu/bughub/app/composebasic/components/BoxSample.kt: -------------------------------------------------------------------------------- 1 | package icu.bughub.app.composebasic.components 2 | 3 | 4 | import androidx.compose.foundation.background 5 | import androidx.compose.foundation.layout.Box 6 | import androidx.compose.foundation.layout.BoxWithConstraints 7 | import androidx.compose.foundation.layout.size 8 | import androidx.compose.material.Text 9 | import androidx.compose.runtime.Composable 10 | import androidx.compose.ui.Alignment 11 | import androidx.compose.ui.Modifier 12 | import androidx.compose.ui.graphics.Color 13 | import androidx.compose.ui.tooling.preview.Preview 14 | import androidx.compose.ui.unit.dp 15 | 16 | 17 | @Composable 18 | fun BoxSample() { 19 | 20 | // Box() { 21 | // Box( 22 | // modifier = Modifier 23 | // .size(200.dp) 24 | // .background(Color.Red) 25 | // ) 26 | // 27 | // Box( 28 | // modifier = Modifier 29 | // .align(Alignment.Center) 30 | // .size(100.dp) 31 | // .background(Color.Green) 32 | // ) 33 | // } 34 | 35 | BoxWithConstraints() { 36 | 37 | if (maxWidth < maxHeight){ 38 | Box( 39 | modifier = Modifier 40 | .size(200.dp) 41 | .background(Color.Red) 42 | ) 43 | } else { 44 | Box( 45 | modifier = Modifier 46 | .align(Alignment.Center) 47 | .size(100.dp) 48 | .background(Color.Green) 49 | ) 50 | } 51 | 52 | } 53 | } 54 | 55 | @Preview 56 | @Composable 57 | fun BoxSamplePreview() { 58 | BoxSample() 59 | } 60 | 61 | -------------------------------------------------------------------------------- /ComposeBasic/app/src/main/java/icu/bughub/app/composebasic/components/ButtonSample.kt: -------------------------------------------------------------------------------- 1 | package icu.bughub.app.composebasic.components 2 | 3 | 4 | import android.util.Log 5 | import androidx.compose.material.* 6 | import androidx.compose.runtime.Composable 7 | import androidx.compose.ui.graphics.Color 8 | import androidx.compose.ui.tooling.preview.Preview 9 | 10 | 11 | @Composable 12 | fun ButtonSample() { 13 | // Button(onClick = { 14 | // Log.d("====", "我是中国人") 15 | // }, colors = ButtonDefaults.buttonColors(backgroundColor = Color.Yellow)) { 16 | // Text("不点不是中国人") 17 | // } 18 | 19 | // TextButton(onClick = { }) { 20 | // Text("不点不是中国人") 21 | // } 22 | 23 | OutlinedButton(onClick = { }) { 24 | Text("不点不是中国人") 25 | } 26 | } 27 | 28 | @Preview 29 | @Composable 30 | fun ButtonSamplePreview() { 31 | ButtonSample() 32 | } 33 | 34 | -------------------------------------------------------------------------------- /ComposeBasic/app/src/main/java/icu/bughub/app/composebasic/components/CanvasSample.kt: -------------------------------------------------------------------------------- 1 | package icu.bughub.app.composebasic.components 2 | 3 | 4 | import androidx.compose.foundation.Canvas 5 | import androidx.compose.foundation.layout.size 6 | import androidx.compose.runtime.Composable 7 | import androidx.compose.ui.Modifier 8 | import androidx.compose.ui.geometry.CornerRadius 9 | import androidx.compose.ui.geometry.Offset 10 | import androidx.compose.ui.geometry.Size 11 | import androidx.compose.ui.graphics.Color 12 | import androidx.compose.ui.graphics.ImageBitmap 13 | import androidx.compose.ui.graphics.PointMode 14 | import androidx.compose.ui.graphics.StrokeCap 15 | import androidx.compose.ui.graphics.drawscope.Stroke 16 | import androidx.compose.ui.platform.LocalContext 17 | import androidx.compose.ui.res.imageResource 18 | import androidx.compose.ui.tooling.preview.Preview 19 | import androidx.compose.ui.unit.dp 20 | import icu.bughub.app.composebasic.R 21 | 22 | 23 | @Composable 24 | fun CanvasSample() { 25 | 26 | var imageBitmap: ImageBitmap? = null 27 | with(LocalContext.current) { 28 | imageBitmap = ImageBitmap.imageResource(id = R.drawable.newbanner4) 29 | } 30 | 31 | Canvas(modifier = Modifier.size(400.dp)) { 32 | 33 | // drawLine( 34 | // Color.Yellow, 35 | // start = Offset(0f, 10f), 36 | // end = Offset(100f, 200f), 37 | // strokeWidth = 100f, 38 | // cap = StrokeCap.Round 39 | // ) 40 | // 41 | // drawRect( 42 | // Color.Green, 43 | // topLeft = Offset(100f, 200f), 44 | // size = Size(100f, 100f) 45 | // ) 46 | // 47 | //// imageBitmap?.let { 48 | //// drawImage(it) 49 | //// } 50 | // 51 | // drawRoundRect( 52 | // Color.Green, 53 | // topLeft = Offset(100f, 200f), 54 | // size = Size(200f, 200f), 55 | // cornerRadius = CornerRadius(50f, 50f) 56 | // ) 57 | // 58 | // drawCircle(Color.Blue, style = Stroke(width = 10f)) 59 | // 60 | // drawOval(Color.Red, size = Size(300f, 200f)) 61 | // 62 | // drawArc( 63 | // Color.Magenta, 64 | // startAngle = 0f, 65 | // sweepAngle = 30f, 66 | // useCenter = true, 67 | // style = Stroke(10f) 68 | // ) 69 | 70 | drawPoints( 71 | listOf( 72 | Offset(10f, 10f), 73 | Offset(20f, 50f), 74 | Offset(40f, 60f), 75 | Offset(60f, 100f) 76 | ), 77 | pointMode = PointMode.Points, 78 | color = Color.Magenta 79 | ) 80 | 81 | } 82 | 83 | } 84 | 85 | @Preview 86 | @Composable 87 | fun CanvasSamplePreview() { 88 | CanvasSample() 89 | } 90 | 91 | -------------------------------------------------------------------------------- /ComposeBasic/app/src/main/java/icu/bughub/app/composebasic/components/CardSample.kt: -------------------------------------------------------------------------------- 1 | package icu.bughub.app.composebasic.components 2 | 3 | 4 | import androidx.compose.foundation.BorderStroke 5 | import androidx.compose.foundation.layout.padding 6 | import androidx.compose.material.Card 7 | import androidx.compose.material.Text 8 | import androidx.compose.runtime.Composable 9 | import androidx.compose.ui.Modifier 10 | import androidx.compose.ui.graphics.Color 11 | import androidx.compose.ui.tooling.preview.Preview 12 | import androidx.compose.ui.unit.dp 13 | 14 | 15 | @Composable 16 | fun CardSample() { 17 | 18 | Card( 19 | backgroundColor = Color.Green, 20 | contentColor = Color.Red, 21 | border = BorderStroke(1.dp, Color.Red), 22 | elevation = 10.dp 23 | ) { 24 | Text( 25 | "Card Sample", 26 | modifier = Modifier.padding(8.dp), 27 | ) 28 | } 29 | 30 | } 31 | 32 | @Preview 33 | @Composable 34 | fun CardSamplePreview() { 35 | CardSample() 36 | } 37 | 38 | -------------------------------------------------------------------------------- /ComposeBasic/app/src/main/java/icu/bughub/app/composebasic/components/CheckBoxSample.kt: -------------------------------------------------------------------------------- 1 | package icu.bughub.app.composebasic.components 2 | 3 | 4 | import androidx.compose.foundation.layout.Column 5 | import androidx.compose.material.Checkbox 6 | import androidx.compose.runtime.* 7 | import androidx.compose.ui.tooling.preview.Preview 8 | 9 | 10 | @Composable 11 | fun CheckBoxSample() { 12 | 13 | // var checked by remember { 14 | // mutableStateOf(false) 15 | // } 16 | // 17 | // Checkbox(checked = checked, onCheckedChange = { 18 | // checked = it 19 | // }) 20 | 21 | var checkedList by remember { 22 | mutableStateOf(listOf(false, false)) 23 | } 24 | 25 | 26 | Column { 27 | 28 | checkedList.forEachIndexed { i, item -> 29 | 30 | Checkbox(checked = item, onCheckedChange = { 31 | 32 | checkedList = checkedList.mapIndexed { j, b -> 33 | 34 | if (i == j) { 35 | !b 36 | } else { 37 | b 38 | } 39 | 40 | } 41 | 42 | }) 43 | 44 | } 45 | 46 | } 47 | 48 | 49 | } 50 | 51 | @Preview 52 | @Composable 53 | fun CheckBoxSamplePreview() { 54 | CheckBoxSample() 55 | } 56 | 57 | -------------------------------------------------------------------------------- /ComposeBasic/app/src/main/java/icu/bughub/app/composebasic/components/ColumnSample.kt: -------------------------------------------------------------------------------- 1 | package icu.bughub.app.composebasic.components 2 | 3 | 4 | import androidx.compose.foundation.background 5 | import androidx.compose.foundation.layout.Arrangement 6 | import androidx.compose.foundation.layout.Column 7 | import androidx.compose.foundation.layout.size 8 | import androidx.compose.material.Text 9 | import androidx.compose.runtime.Composable 10 | import androidx.compose.ui.Modifier 11 | import androidx.compose.ui.graphics.Color 12 | import androidx.compose.ui.tooling.preview.Preview 13 | import androidx.compose.ui.unit.dp 14 | 15 | 16 | @Composable 17 | fun ColumnSample() { 18 | 19 | Column( 20 | modifier = Modifier 21 | .size(200.dp) 22 | .background(Color.Green), 23 | ) { 24 | Text( 25 | text = "Column First Item", 26 | modifier = Modifier.weight(1f) 27 | .background(Color.Red) 28 | ) 29 | Text( 30 | text = "Column Second Item", 31 | modifier = Modifier.weight(1f) 32 | ) 33 | } 34 | 35 | } 36 | 37 | @Preview 38 | @Composable 39 | fun ColumnSamplePreview() { 40 | ColumnSample() 41 | } 42 | 43 | -------------------------------------------------------------------------------- /ComposeBasic/app/src/main/java/icu/bughub/app/composebasic/components/CompositionLocalSample.kt: -------------------------------------------------------------------------------- 1 | package icu.bughub.app.composebasic.components 2 | 3 | 4 | import android.net.wifi.hotspot2.pps.HomeSp 5 | import androidx.compose.foundation.layout.Column 6 | import androidx.compose.material.Button 7 | import androidx.compose.material.Text 8 | import androidx.compose.runtime.Composable 9 | import androidx.compose.runtime.CompositionLocalProvider 10 | import androidx.compose.runtime.compositionLocalOf 11 | import androidx.compose.ui.tooling.preview.Preview 12 | import androidx.navigation.compose.NavHost 13 | import androidx.navigation.compose.composable 14 | import androidx.navigation.compose.rememberNavController 15 | 16 | 17 | @Composable 18 | fun CompositionLocalSample() { 19 | 20 | val navController = rememberNavController() 21 | val user = User("Test") 22 | 23 | CompositionLocalProvider(LocalActiveUser provides user) { 24 | NavHost(navController = navController, startDestination = "Home") { 25 | composable("Home") { 26 | HomeScreen { 27 | navController.navigate("Detail") 28 | } 29 | } 30 | 31 | composable("Detail") { 32 | DetailScreen() 33 | } 34 | } 35 | } 36 | } 37 | 38 | @Composable 39 | fun HomeScreen(onTap: () -> Unit) { 40 | Column { 41 | Text(text = "HomeScreen:${LocalActiveUser.current.name}") 42 | 43 | Button(onClick = { onTap() }) { 44 | Text(text = "Navigate to Detail") 45 | } 46 | } 47 | } 48 | 49 | @Composable 50 | fun DetailScreen() { 51 | Text(text = "DetailScreen:${LocalActiveUser.current.name}") 52 | } 53 | 54 | val LocalActiveUser = compositionLocalOf { error("user is null") } 55 | 56 | data class User(val name: String) 57 | 58 | @Preview 59 | @Composable 60 | fun CompositionLocalSamplePreview() { 61 | CompositionLocalSample() 62 | } 63 | 64 | -------------------------------------------------------------------------------- /ComposeBasic/app/src/main/java/icu/bughub/app/composebasic/components/ConstaintLayoutSample.kt: -------------------------------------------------------------------------------- 1 | package icu.bughub.app.composebasic.components 2 | 3 | 4 | import androidx.compose.foundation.background 5 | import androidx.compose.foundation.layout.fillMaxWidth 6 | import androidx.compose.foundation.layout.height 7 | import androidx.compose.material.Checkbox 8 | import androidx.compose.material.Icon 9 | import androidx.compose.material.Text 10 | import androidx.compose.material.icons.Icons 11 | import androidx.compose.material.icons.filled.AccountBox 12 | import androidx.compose.runtime.* 13 | import androidx.compose.ui.Modifier 14 | import androidx.compose.ui.graphics.Color 15 | import androidx.compose.ui.layout.layoutId 16 | import androidx.compose.ui.text.font.FontWeight 17 | import androidx.compose.ui.tooling.preview.Preview 18 | import androidx.compose.ui.unit.dp 19 | import androidx.compose.ui.unit.sp 20 | import androidx.constraintlayout.compose.ConstraintLayout 21 | import androidx.constraintlayout.compose.ConstraintSet 22 | 23 | 24 | @Composable 25 | fun ConstraintLayoutSample() { 26 | 27 | var checked by remember { 28 | mutableStateOf( 29 | false 30 | ) 31 | } 32 | 33 | val constraints = ConstraintSet { 34 | val icon = createRefFor("icon") 35 | val primaryText = createRefFor("primaryText") 36 | val secondlyText = createRefFor("secondlyText") 37 | val checkBox = createRefFor("checkBox") 38 | 39 | constrain(icon) { 40 | centerVerticallyTo(parent) 41 | start.linkTo(parent.start, margin = 8.dp) 42 | } 43 | 44 | constrain(primaryText) { 45 | start.linkTo(icon.end, margin = 8.dp) 46 | top.linkTo(parent.top, margin = 8.dp) 47 | } 48 | 49 | constrain(secondlyText) { 50 | start.linkTo(primaryText.start) 51 | top.linkTo(primaryText.bottom, margin = 8.dp) 52 | bottom.linkTo(parent.bottom, margin = 8.dp) 53 | } 54 | 55 | constrain(checkBox) { 56 | centerVerticallyTo(parent) 57 | end.linkTo(parent.end, margin = 8.dp) 58 | } 59 | } 60 | 61 | ConstraintLayout( 62 | modifier = Modifier 63 | .fillMaxWidth() 64 | .height(100.dp) 65 | .background(Color.Yellow), 66 | constraintSet = constraints 67 | ) { 68 | 69 | Icon( 70 | imageVector = Icons.Default.AccountBox, 71 | contentDescription = null, 72 | modifier = Modifier.layoutId("icon") 73 | ) 74 | 75 | Text( 76 | text = "Primary Text", 77 | fontWeight = FontWeight.Bold, 78 | fontSize = 16.sp, 79 | modifier = Modifier.layoutId("primaryText") 80 | ) 81 | 82 | Text( 83 | text = "Secondly Text", color = Color.Gray, 84 | modifier = Modifier.layoutId("secondlyText") 85 | ) 86 | 87 | Checkbox( 88 | checked = checked, 89 | onCheckedChange = { checked = it }, 90 | modifier = Modifier.layoutId("checkBox") 91 | ) 92 | } 93 | 94 | } 95 | 96 | @Preview 97 | @Composable 98 | fun ConstraintLayoutSamplePreview() { 99 | ConstraintLayoutSample() 100 | } 101 | 102 | -------------------------------------------------------------------------------- /ComposeBasic/app/src/main/java/icu/bughub/app/composebasic/components/ConstraintsSample.kt: -------------------------------------------------------------------------------- 1 | package icu.bughub.app.composebasic.components 2 | 3 | 4 | import androidx.compose.foundation.background 5 | import androidx.compose.foundation.layout.fillMaxWidth 6 | import androidx.compose.foundation.layout.height 7 | import androidx.compose.material.Button 8 | import androidx.compose.material.Checkbox 9 | import androidx.compose.material.Icon 10 | import androidx.compose.material.Text 11 | import androidx.compose.material.icons.Icons 12 | import androidx.compose.material.icons.filled.AccountBox 13 | import androidx.compose.runtime.* 14 | import androidx.compose.ui.Modifier 15 | import androidx.compose.ui.graphics.Color 16 | import androidx.compose.ui.text.font.FontWeight 17 | import androidx.compose.ui.tooling.preview.Preview 18 | import androidx.compose.ui.unit.dp 19 | import androidx.compose.ui.unit.sp 20 | import androidx.constraintlayout.compose.ConstraintLayout 21 | 22 | 23 | @Composable 24 | fun ConstraintsSample() { 25 | 26 | var checked by remember { 27 | mutableStateOf( 28 | false 29 | ) 30 | } 31 | 32 | ConstraintLayout( 33 | modifier = Modifier 34 | .fillMaxWidth() 35 | .height(100.dp) 36 | .background(Color.Yellow) 37 | ) { 38 | val (icon, primaryText, secondlyText, checkBox) = createRefs() 39 | 40 | Icon( 41 | imageVector = Icons.Default.AccountBox, 42 | contentDescription = null, 43 | modifier = Modifier.constrainAs(icon) { 44 | top.linkTo(parent.top) 45 | bottom.linkTo(parent.bottom) 46 | start.linkTo(parent.start, 8.dp) 47 | } 48 | ) 49 | 50 | Text( 51 | text = "Primary Text", 52 | fontWeight = FontWeight.Bold, 53 | fontSize = 16.sp, 54 | modifier = Modifier.constrainAs(primaryText) { 55 | start.linkTo(icon.end, margin = 8.dp) 56 | top.linkTo(parent.top) 57 | } 58 | ) 59 | 60 | Text(text = "Secondly Text", color = Color.Gray, 61 | modifier = Modifier.constrainAs(secondlyText) { 62 | start.linkTo(primaryText.start) 63 | top.linkTo(primaryText.bottom, margin = 8.dp) 64 | bottom.linkTo(parent.bottom) 65 | }) 66 | 67 | Checkbox( 68 | checked = checked, 69 | onCheckedChange = { checked = it }, 70 | modifier = Modifier.constrainAs(checkBox) { 71 | centerVerticallyTo(parent) 72 | end.linkTo(parent.end, margin = 8.dp) 73 | } 74 | ) 75 | } 76 | 77 | } 78 | 79 | @Composable 80 | fun ConstraintLayoutSample1() { 81 | ConstraintLayout( 82 | modifier = Modifier 83 | .fillMaxWidth() 84 | .height(100.dp) 85 | .background(Color.Yellow) 86 | ) { 87 | 88 | val (button, text) = createRefs() 89 | 90 | Button(onClick = { }, modifier = Modifier.constrainAs(button) { 91 | centerTo(parent) 92 | }) { 93 | Text(text = "Button") 94 | } 95 | 96 | Text(text = "Text", modifier = Modifier.constrainAs(text) { 97 | top.linkTo(button.bottom, 8.dp) 98 | start.linkTo(button.start) 99 | }) 100 | } 101 | } 102 | 103 | @Preview 104 | @Composable 105 | fun ConstraintsSamplePreview() { 106 | ConstraintLayoutSample1() 107 | } 108 | 109 | -------------------------------------------------------------------------------- /ComposeBasic/app/src/main/java/icu/bughub/app/composebasic/components/DialogSample.kt: -------------------------------------------------------------------------------- 1 | package icu.bughub.app.composebasic.components 2 | 3 | 4 | import androidx.compose.foundation.layout.Column 5 | import androidx.compose.foundation.layout.size 6 | import androidx.compose.material.* 7 | import androidx.compose.runtime.* 8 | import androidx.compose.ui.Modifier 9 | import androidx.compose.ui.graphics.Color 10 | import androidx.compose.ui.text.input.KeyboardType.Companion.Text 11 | import androidx.compose.ui.tooling.preview.Preview 12 | import androidx.compose.ui.unit.dp 13 | import androidx.compose.ui.window.Dialog 14 | 15 | 16 | @Composable 17 | fun DialogSample() { 18 | 19 | var showDialog by remember { 20 | mutableStateOf(false) 21 | } 22 | 23 | Column() { 24 | Button(onClick = { 25 | showDialog = true 26 | }) { 27 | Text("show dialog") 28 | } 29 | if (showDialog) { 30 | // Dialog( 31 | // onDismissRequest = { showDialog = false } 32 | // ) { 33 | // Surface( 34 | // color = Color.White, 35 | // modifier = Modifier.size( 36 | // 200.dp, 100.dp 37 | // ) 38 | // ) { 39 | // Column() { 40 | // Text("Dialog Content") 41 | // } 42 | // } 43 | // } 44 | 45 | AlertDialog( 46 | onDismissRequest = { showDialog = false }, 47 | title = { 48 | Text("Title") 49 | }, 50 | confirmButton = { 51 | TextButton(onClick = { showDialog = false }) { 52 | Text(text = "Confirm") 53 | } 54 | }, 55 | dismissButton = { 56 | TextButton(onClick = { showDialog = false }) { 57 | Text(text = "Cancel") 58 | } 59 | }, 60 | text = { 61 | Text("这是 Dialog 的内容") 62 | } 63 | ) 64 | 65 | } 66 | 67 | } 68 | 69 | } 70 | 71 | @Preview 72 | @Composable 73 | fun DialogSamplePreview() { 74 | DialogSample() 75 | } 76 | 77 | -------------------------------------------------------------------------------- /ComposeBasic/app/src/main/java/icu/bughub/app/composebasic/components/DividerSample.kt: -------------------------------------------------------------------------------- 1 | package icu.bughub.app.composebasic.components 2 | 3 | 4 | import androidx.compose.foundation.background 5 | import androidx.compose.foundation.layout.Column 6 | import androidx.compose.foundation.layout.Spacer 7 | import androidx.compose.foundation.layout.height 8 | import androidx.compose.foundation.layout.size 9 | import androidx.compose.material.Divider 10 | import androidx.compose.material.Text 11 | import androidx.compose.runtime.Composable 12 | import androidx.compose.ui.Modifier 13 | import androidx.compose.ui.graphics.Color 14 | import androidx.compose.ui.tooling.preview.Preview 15 | import androidx.compose.ui.unit.dp 16 | 17 | 18 | @Composable 19 | fun DividerSample() { 20 | 21 | Column( 22 | modifier = Modifier 23 | .size(200.dp) 24 | .background(Color.Green), 25 | ) { 26 | Text( 27 | text = "Column First Item", 28 | modifier = Modifier 29 | .background(Color.Red) 30 | ) 31 | 32 | Divider( 33 | startIndent = 4.dp, 34 | thickness = 10.dp, 35 | color = Color.Magenta 36 | ) 37 | 38 | Text( 39 | text = "Column Second Item", 40 | modifier = Modifier 41 | .background(Color.Yellow) 42 | ) 43 | } 44 | 45 | } 46 | 47 | @Preview 48 | @Composable 49 | fun DividerSamplePreview() { 50 | DividerSample() 51 | } 52 | 53 | -------------------------------------------------------------------------------- /ComposeBasic/app/src/main/java/icu/bughub/app/composebasic/components/DropdownMenuSample.kt: -------------------------------------------------------------------------------- 1 | package icu.bughub.app.composebasic.components 2 | 3 | 4 | import androidx.compose.foundation.layout.Column 5 | import androidx.compose.material.Button 6 | import androidx.compose.material.DropdownMenu 7 | import androidx.compose.material.DropdownMenuItem 8 | import androidx.compose.material.Text 9 | import androidx.compose.runtime.* 10 | import androidx.compose.ui.tooling.preview.Preview 11 | 12 | 13 | @Composable 14 | fun DropdownMenuSample() { 15 | 16 | var expanded by remember { 17 | mutableStateOf(false) 18 | } 19 | 20 | Column() { 21 | Button(onClick = { 22 | expanded = true 23 | }) { 24 | Text("快快点我吧") 25 | } 26 | 27 | DropdownMenu( 28 | expanded = expanded, 29 | onDismissRequest = { 30 | expanded = false 31 | } 32 | ) { 33 | DropdownMenuItem(onClick = { 34 | expanded = false 35 | }) { 36 | Text("Menu 0") 37 | } 38 | 39 | DropdownMenuItem(onClick = { 40 | expanded = false 41 | }) { 42 | Text("Menu 1") 43 | } 44 | 45 | DropdownMenuItem(onClick = { 46 | expanded = false 47 | }) { 48 | Text("Menu 2") 49 | } 50 | 51 | } 52 | } 53 | 54 | } 55 | 56 | @Preview 57 | @Composable 58 | fun DropdownMenuSamplePreview() { 59 | DropdownMenuSample() 60 | } 61 | 62 | -------------------------------------------------------------------------------- /ComposeBasic/app/src/main/java/icu/bughub/app/composebasic/components/IconSample.kt: -------------------------------------------------------------------------------- 1 | package icu.bughub.app.composebasic.components 2 | 3 | 4 | import androidx.compose.material.Icon 5 | import androidx.compose.material.IconButton 6 | import androidx.compose.material.icons.Icons 7 | import androidx.compose.material.icons.filled.AccountBox 8 | import androidx.compose.material.icons.filled.Translate 9 | import androidx.compose.runtime.Composable 10 | import androidx.compose.ui.graphics.Color 11 | import androidx.compose.ui.res.painterResource 12 | import androidx.compose.ui.tooling.preview.Preview 13 | import icu.bughub.app.composebasic.R 14 | 15 | 16 | @Composable 17 | fun IconSample() { 18 | // Icon(imageVector = Icons.Default.Translate, contentDescription = null, tint = Color.Red) 19 | 20 | IconButton(onClick = { }) { 21 | Icon( 22 | painter = painterResource(id = R.drawable.ic_android_black_24dp), 23 | contentDescription = null 24 | ) 25 | } 26 | } 27 | 28 | @Preview 29 | @Composable 30 | fun IconSamplePreview() { 31 | IconSample() 32 | } 33 | 34 | -------------------------------------------------------------------------------- /ComposeBasic/app/src/main/java/icu/bughub/app/composebasic/components/ImageSample.kt: -------------------------------------------------------------------------------- 1 | package icu.bughub.app.composebasic.components 2 | 3 | 4 | import androidx.compose.foundation.Image 5 | import androidx.compose.foundation.layout.size 6 | import androidx.compose.runtime.Composable 7 | import androidx.compose.ui.Modifier 8 | import androidx.compose.ui.graphics.BlendMode 9 | import androidx.compose.ui.graphics.Color 10 | import androidx.compose.ui.graphics.ColorFilter 11 | import androidx.compose.ui.layout.ContentScale 12 | import androidx.compose.ui.res.painterResource 13 | import androidx.compose.ui.tooling.preview.Preview 14 | import androidx.compose.ui.unit.dp 15 | import icu.bughub.app.composebasic.R 16 | 17 | 18 | @Composable 19 | fun ImageSample() { 20 | Image( 21 | painter = painterResource(id = R.drawable.newbanner4), 22 | contentDescription = null, 23 | modifier = Modifier.size(50.dp), 24 | colorFilter = ColorFilter.tint(Color.Red, blendMode = BlendMode.Color) 25 | ) 26 | } 27 | 28 | @Preview 29 | @Composable 30 | fun ImageSamplePreview() { 31 | ImageSample() 32 | } 33 | 34 | -------------------------------------------------------------------------------- /ComposeBasic/app/src/main/java/icu/bughub/app/composebasic/components/LazyColumnSample.kt: -------------------------------------------------------------------------------- 1 | package icu.bughub.app.composebasic.components 2 | 3 | 4 | import android.util.Log 5 | import androidx.compose.foundation.background 6 | import androidx.compose.foundation.clickable 7 | import androidx.compose.foundation.gestures.scrollBy 8 | import androidx.compose.foundation.gestures.scrollable 9 | import androidx.compose.foundation.layout.Column 10 | import androidx.compose.foundation.layout.fillMaxWidth 11 | import androidx.compose.foundation.layout.padding 12 | import androidx.compose.foundation.lazy.LazyColumn 13 | import androidx.compose.foundation.lazy.items 14 | import androidx.compose.foundation.lazy.rememberLazyListState 15 | import androidx.compose.foundation.rememberScrollState 16 | import androidx.compose.foundation.verticalScroll 17 | import androidx.compose.material.ExperimentalMaterialApi 18 | import androidx.compose.material.Icon 19 | import androidx.compose.material.ListItem 20 | import androidx.compose.material.Text 21 | import androidx.compose.material.icons.Icons 22 | import androidx.compose.material.icons.filled.AccountBox 23 | import androidx.compose.runtime.Composable 24 | import androidx.compose.runtime.DisposableEffect 25 | import androidx.compose.runtime.rememberCoroutineScope 26 | import androidx.compose.ui.Modifier 27 | import androidx.compose.ui.graphics.Color 28 | import androidx.compose.ui.tooling.preview.Preview 29 | import androidx.compose.ui.unit.dp 30 | import kotlinx.coroutines.launch 31 | 32 | 33 | @OptIn(ExperimentalMaterialApi::class) 34 | @Composable 35 | fun LazyColumnSample() { 36 | 37 | val data = listOf( 38 | 1, 2, 3, 4, 5, 39 | 6, 7, 8, 9, 10, 40 | 11, 12, 13, 14, 15, 41 | 16, 17, 18, 19, 20 42 | ) 43 | 44 | val scrollState = rememberScrollState() 45 | 46 | val coroutineScope = rememberCoroutineScope() 47 | 48 | Column( 49 | modifier = Modifier.verticalScroll(scrollState) 50 | ) { 51 | data.forEach { 52 | ListItem(icon = { 53 | Icon( 54 | imageVector = Icons.Default.AccountBox, 55 | contentDescription = null 56 | ) 57 | }, text = { 58 | Text("Title $it") 59 | }, secondaryText = { 60 | Text("secondaryText") 61 | }, modifier = Modifier.clickable { 62 | coroutineScope.launch { 63 | scrollState.scrollBy(100f) 64 | } 65 | }) 66 | DisposableEffect(Unit) { 67 | Log.d("====", "effect:$it") 68 | onDispose { 69 | Log.d("====", "onDispose:$it") 70 | } 71 | } 72 | } 73 | } 74 | 75 | } 76 | 77 | @OptIn(ExperimentalMaterialApi::class, androidx.compose.foundation.ExperimentalFoundationApi::class) 78 | @Composable 79 | fun LazyColumnSample1() { 80 | val data = listOf( 81 | 1, 2, 3, 4, 5, 82 | 6, 7, 8, 9, 10, 83 | 11, 12, 13, 14, 15, 84 | 16, 17, 18, 19, 20 85 | ) 86 | 87 | val lazyListState = rememberLazyListState() 88 | val coroutineScope = rememberCoroutineScope() 89 | 90 | LazyColumn(state = lazyListState) { 91 | stickyHeader { 92 | Text( 93 | "Sticky Header", 94 | modifier = Modifier 95 | .background(Color.Yellow) 96 | .fillMaxWidth() 97 | .padding(8.dp) 98 | ) 99 | } 100 | items(data) { 101 | ListItem(icon = { 102 | Icon( 103 | imageVector = Icons.Default.AccountBox, 104 | contentDescription = null 105 | ) 106 | }, text = { 107 | Text("Title $it") 108 | }, secondaryText = { 109 | Text("secondaryText") 110 | }, modifier = Modifier.clickable { 111 | coroutineScope.launch { 112 | lazyListState.animateScrollToItem(data.size - 1) 113 | } 114 | }) 115 | DisposableEffect(Unit) { 116 | Log.d("====", "effect:$it") 117 | onDispose { 118 | Log.d("====", "onDispose:$it") 119 | } 120 | } 121 | } 122 | } 123 | } 124 | 125 | @Preview 126 | @Composable 127 | fun LazyColumnSamplePreview() { 128 | LazyColumnSample1() 129 | } 130 | 131 | -------------------------------------------------------------------------------- /ComposeBasic/app/src/main/java/icu/bughub/app/composebasic/components/LazyRowSample.kt: -------------------------------------------------------------------------------- 1 | package icu.bughub.app.composebasic.components 2 | 3 | 4 | import androidx.compose.foundation.ExperimentalFoundationApi 5 | import androidx.compose.foundation.background 6 | import androidx.compose.foundation.layout.padding 7 | import androidx.compose.foundation.lazy.LazyRow 8 | import androidx.compose.foundation.lazy.items 9 | import androidx.compose.material.Text 10 | import androidx.compose.runtime.Composable 11 | import androidx.compose.ui.Modifier 12 | import androidx.compose.ui.graphics.Color 13 | import androidx.compose.ui.tooling.preview.Preview 14 | import androidx.compose.ui.unit.dp 15 | 16 | 17 | @OptIn(ExperimentalFoundationApi::class) 18 | @Composable 19 | fun LazyRowSample() { 20 | 21 | val data = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9) 22 | 23 | 24 | 25 | LazyRow() { 26 | 27 | stickyHeader { 28 | Text( 29 | text = "Header", 30 | modifier = Modifier.background(Color.Yellow) 31 | ) 32 | } 33 | 34 | items(data) { 35 | 36 | Text( 37 | "Row Item $it", 38 | modifier = Modifier.padding(8.dp) 39 | ) 40 | 41 | } 42 | 43 | } 44 | 45 | } 46 | 47 | @Preview 48 | @Composable 49 | fun LazyRowSamplePreview() { 50 | LazyRowSample() 51 | } 52 | 53 | -------------------------------------------------------------------------------- /ComposeBasic/app/src/main/java/icu/bughub/app/composebasic/components/LazyVerticalGridSample.kt: -------------------------------------------------------------------------------- 1 | package icu.bughub.app.composebasic.components 2 | 3 | 4 | import androidx.compose.foundation.ExperimentalFoundationApi 5 | import androidx.compose.foundation.layout.padding 6 | import androidx.compose.foundation.lazy.GridCells 7 | import androidx.compose.foundation.lazy.LazyVerticalGrid 8 | import androidx.compose.foundation.lazy.items 9 | import androidx.compose.material.Card 10 | import androidx.compose.material.Text 11 | import androidx.compose.runtime.Composable 12 | import androidx.compose.ui.Modifier 13 | import androidx.compose.ui.tooling.preview.Preview 14 | import androidx.compose.ui.unit.dp 15 | 16 | 17 | @OptIn(ExperimentalFoundationApi::class) 18 | @Composable 19 | fun LazyVerticalGridSample() { 20 | 21 | val data = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) 22 | 23 | LazyVerticalGrid(cells = GridCells.Adaptive(100.dp)) { 24 | items(data) { 25 | Card() { 26 | Text( 27 | "Grid Item $it", 28 | modifier = Modifier.padding(8.dp) 29 | ) 30 | } 31 | } 32 | } 33 | 34 | } 35 | 36 | @Preview 37 | @Composable 38 | fun LazyVerticalGridSamplePreview() { 39 | LazyVerticalGridSample() 40 | } 41 | 42 | -------------------------------------------------------------------------------- /ComposeBasic/app/src/main/java/icu/bughub/app/composebasic/components/LifecycleSample.kt: -------------------------------------------------------------------------------- 1 | package icu.bughub.app.composebasic.components 2 | 3 | 4 | import android.util.Log 5 | import androidx.compose.foundation.layout.Column 6 | import androidx.compose.material.Button 7 | import androidx.compose.material.Text 8 | import androidx.compose.runtime.* 9 | import androidx.compose.ui.platform.LocalLifecycleOwner 10 | import androidx.compose.ui.tooling.preview.Preview 11 | import androidx.lifecycle.Lifecycle 12 | import androidx.lifecycle.LifecycleEventObserver 13 | import kotlinx.coroutines.coroutineScope 14 | 15 | 16 | @Composable 17 | fun LifecycleSample() { 18 | 19 | var count by remember { 20 | mutableStateOf(0) 21 | } 22 | 23 | // LaunchedEffect(Unit) { 24 | // Log.i("======", "LaunchedEffect") 25 | // } 26 | // 27 | // Log.i("======", "LifecycleSample") 28 | 29 | val lifecycleOwner = LocalLifecycleOwner.current 30 | 31 | DisposableEffect(lifecycleOwner) { 32 | 33 | val lifecycleEventObserver = LifecycleEventObserver { _, event -> 34 | when (event) { 35 | Lifecycle.Event.ON_PAUSE -> { 36 | //进入后台,调用暂停方法 37 | } 38 | Lifecycle.Event.ON_RESUME -> { 39 | //回到前台 ,调用播放 40 | } 41 | else -> { 42 | 43 | } 44 | } 45 | } 46 | 47 | lifecycleOwner.lifecycle.addObserver(lifecycleEventObserver) 48 | 49 | onDispose { 50 | lifecycleOwner.lifecycle.removeObserver(lifecycleEventObserver) 51 | } 52 | } 53 | 54 | Column() { 55 | Text("我叫了${count}个小姐姐") 56 | 57 | Button(onClick = { 58 | count++ 59 | }) { 60 | Text("Button") 61 | } 62 | 63 | if (count == 3) { 64 | SubScreen(count) 65 | } 66 | } 67 | 68 | } 69 | 70 | @Composable 71 | fun SubScreen(count: Int) { 72 | 73 | DisposableEffect(Unit) { 74 | 75 | Log.i("======", "DisposableEffect") 76 | 77 | onDispose { 78 | Log.i("======", "DisposableEffect:onDispose") 79 | } 80 | } 81 | 82 | Text(text = "我儿子也叫了${count}个小姐姐") 83 | } 84 | 85 | @Preview 86 | @Composable 87 | fun LifecycleSamplePreview() { 88 | LifecycleSample() 89 | } 90 | 91 | -------------------------------------------------------------------------------- /ComposeBasic/app/src/main/java/icu/bughub/app/composebasic/components/ListItemSample.kt: -------------------------------------------------------------------------------- 1 | package icu.bughub.app.composebasic.components 2 | 3 | 4 | import android.util.Log 5 | import androidx.compose.foundation.layout.Column 6 | import androidx.compose.material.* 7 | import androidx.compose.material.icons.Icons 8 | import androidx.compose.material.icons.filled.AccountBox 9 | import androidx.compose.runtime.* 10 | import androidx.compose.ui.tooling.preview.Preview 11 | 12 | 13 | @OptIn(ExperimentalMaterialApi::class) 14 | @Composable 15 | fun ListItemSample() { 16 | 17 | var list by remember { 18 | mutableStateOf( 19 | listOf( 20 | ListItem("Item 0", true), 21 | ListItem("Item 1", false), 22 | ListItem("Item 2", false), 23 | ListItem("Item 3", false), 24 | ListItem("Item 4", false) 25 | ) 26 | ) 27 | } 28 | 29 | Log.i("======1","list:${list}") 30 | 31 | Column() { 32 | list.forEachIndexed { rawIndex, listItem -> 33 | 34 | ListItem(icon = { 35 | Icon( 36 | imageVector = Icons.Default.AccountBox, 37 | contentDescription = null 38 | ) 39 | }, text = { 40 | Text(text = listItem.title) 41 | }, secondaryText = { 42 | Text("Secondary Text") 43 | }, trailing = { 44 | Checkbox(checked = listItem.checked, onCheckedChange = { 45 | 46 | list = list.mapIndexed { newIndex, listItem -> 47 | val newItem = listItem.copy() 48 | if (rawIndex == newIndex) { 49 | newItem.checked = !listItem.checked 50 | } else { 51 | newItem.checked = listItem.checked 52 | } 53 | return@mapIndexed newItem 54 | } 55 | Log.i("======2","list:${list}") 56 | }) 57 | }) 58 | 59 | } 60 | } 61 | 62 | } 63 | 64 | data class ListItem(val title: String, var checked: Boolean) 65 | 66 | @Preview 67 | @Composable 68 | fun ListItemSamplePreview() { 69 | ListItemSample() 70 | } 71 | 72 | -------------------------------------------------------------------------------- /ComposeBasic/app/src/main/java/icu/bughub/app/composebasic/components/ModifierSample.kt: -------------------------------------------------------------------------------- 1 | package icu.bughub.app.composebasic.components 2 | 3 | 4 | import android.util.Log 5 | import androidx.compose.foundation.background 6 | import androidx.compose.foundation.border 7 | import androidx.compose.foundation.clickable 8 | import androidx.compose.foundation.layout.padding 9 | import androidx.compose.material.Text 10 | import androidx.compose.runtime.Composable 11 | import androidx.compose.ui.Modifier 12 | import androidx.compose.ui.graphics.Color 13 | import androidx.compose.ui.text.TextStyle 14 | import androidx.compose.ui.tooling.preview.Preview 15 | import androidx.compose.ui.unit.dp 16 | 17 | 18 | @Composable 19 | fun ModifierSample() { 20 | Text( 21 | text = "中国人不骗中国人", 22 | modifier = Modifier 23 | .border(1.dp, Color.Red) 24 | .background(Color.Yellow) 25 | .padding(8.dp) 26 | .clickable { 27 | Log.i("====","你点击到我了") 28 | } 29 | ) 30 | } 31 | 32 | @Preview 33 | @Composable 34 | fun ModifierSamplePreview() { 35 | ModifierSample() 36 | } 37 | 38 | -------------------------------------------------------------------------------- /ComposeBasic/app/src/main/java/icu/bughub/app/composebasic/components/NavSample.kt: -------------------------------------------------------------------------------- 1 | package icu.bughub.app.composebasic.components 2 | 3 | 4 | import androidx.compose.foundation.background 5 | import androidx.compose.foundation.layout.Arrangement 6 | import androidx.compose.foundation.layout.Column 7 | import androidx.compose.foundation.layout.fillMaxSize 8 | import androidx.compose.material.Button 9 | import androidx.compose.material.Text 10 | import androidx.compose.runtime.Composable 11 | import androidx.compose.ui.Alignment 12 | import androidx.compose.ui.Modifier 13 | import androidx.compose.ui.graphics.Color 14 | import androidx.compose.ui.tooling.preview.Preview 15 | import androidx.navigation.NavHostController 16 | import androidx.navigation.NavType 17 | import androidx.navigation.compose.NavHost 18 | import androidx.navigation.compose.composable 19 | import androidx.navigation.compose.rememberNavController 20 | import androidx.navigation.navArgument 21 | 22 | 23 | @Composable 24 | fun NavSample() { 25 | 26 | val navController = rememberNavController() 27 | 28 | NavHost( 29 | navController = navController, 30 | startDestination = Screen.First.route 31 | ) { 32 | 33 | composable(route = Screen.First.route) { 34 | FirstScreen() { 35 | navController.navigate("${Screen.Second.route}/韦爵爷/19") 36 | } 37 | } 38 | 39 | composable( 40 | route = "${Screen.Second.route}/{name}/{age}", arguments = listOf( 41 | navArgument("age") { 42 | type = NavType.IntType 43 | } 44 | ) 45 | ) { 46 | val name = it.arguments?.getString("name") 47 | val age = it.arguments?.getInt("age") ?: 18 48 | SecondScreen(name, age) { 49 | navController.navigate("Third?carName=五菱宏光 Mini") 50 | } 51 | } 52 | 53 | composable( 54 | route = "${Screen.Third.route}?carName={carName}", arguments = listOf( 55 | navArgument("carName") { 56 | nullable = true 57 | defaultValue = "卡宴" 58 | } 59 | ) 60 | ) { 61 | val carName = it.arguments?.getString("carName") 62 | 63 | ThirdScreen(carName) { 64 | navController.popBackStack( 65 | Screen.Second.route, 66 | inclusive = true 67 | ) 68 | } 69 | } 70 | 71 | } 72 | 73 | } 74 | 75 | sealed class Screen(val route: String) { 76 | object First : Screen("First") 77 | object Second : Screen("Second") 78 | object Third : Screen("Third") 79 | } 80 | 81 | @Composable 82 | fun FirstScreen(onNavigateToSecond: () -> Unit) { 83 | Column( 84 | modifier = Modifier 85 | .fillMaxSize() 86 | .background(Color.Red), 87 | horizontalAlignment = Alignment.CenterHorizontally, 88 | verticalArrangement = Arrangement.Center, 89 | ) { 90 | Text(text = "First Screen") 91 | 92 | Button(onClick = { 93 | onNavigateToSecond() 94 | }) { 95 | Text(text = "Navigate to second screen") 96 | } 97 | } 98 | } 99 | 100 | @Composable 101 | fun SecondScreen(name: String?, age: Int, onNavigateToThird: () -> Unit) { 102 | Column( 103 | modifier = Modifier 104 | .fillMaxSize() 105 | .background(Color.Green), 106 | horizontalAlignment = Alignment.CenterHorizontally, 107 | verticalArrangement = Arrangement.Center, 108 | ) { 109 | Text(text = "Second Screen:$name 今年${age}岁") 110 | 111 | Button(onClick = { 112 | onNavigateToThird() 113 | }) { 114 | Text(text = "Navigate to third screen") 115 | } 116 | } 117 | } 118 | 119 | @Composable 120 | fun ThirdScreen(carName: String?, onBackToRoot: () -> Unit) { 121 | Column( 122 | modifier = Modifier 123 | .fillMaxSize() 124 | .background(Color.Blue), 125 | horizontalAlignment = Alignment.CenterHorizontally, 126 | verticalArrangement = Arrangement.Center, 127 | ) { 128 | Text(text = "Third Screen:$carName") 129 | 130 | Button(onClick = { 131 | onBackToRoot() 132 | }) { 133 | Text(text = "Navigate back to root screen") 134 | } 135 | } 136 | } 137 | 138 | @Preview 139 | @Composable 140 | fun NavSamplePreview() { 141 | NavSample() 142 | } 143 | 144 | -------------------------------------------------------------------------------- /ComposeBasic/app/src/main/java/icu/bughub/app/composebasic/components/ProgressIndicatorSample.kt: -------------------------------------------------------------------------------- 1 | package icu.bughub.app.composebasic.components 2 | 3 | 4 | import androidx.compose.material.CircularProgressIndicator 5 | import androidx.compose.material.LinearProgressIndicator 6 | import androidx.compose.runtime.Composable 7 | import androidx.compose.ui.graphics.Color 8 | import androidx.compose.ui.tooling.preview.Preview 9 | 10 | 11 | @Composable 12 | fun ProgressIndicatorSample() { 13 | 14 | // CircularProgressIndicator( 15 | // color = Color.Red, 16 | // progress = 0.5f 17 | // ) 18 | 19 | LinearProgressIndicator( 20 | color = Color.Red, backgroundColor = Color.Green, progress = 0.5f 21 | ) 22 | 23 | } 24 | 25 | @Preview 26 | @Composable 27 | fun ProgressIndicatorSamplePreview() { 28 | ProgressIndicatorSample() 29 | } 30 | 31 | -------------------------------------------------------------------------------- /ComposeBasic/app/src/main/java/icu/bughub/app/composebasic/components/RadioButtonSample.kt: -------------------------------------------------------------------------------- 1 | package icu.bughub.app.composebasic.components 2 | 3 | 4 | import androidx.compose.foundation.layout.Column 5 | import androidx.compose.material.RadioButton 6 | import androidx.compose.runtime.* 7 | import androidx.compose.ui.tooling.preview.Preview 8 | 9 | 10 | @Composable 11 | fun RadioButtonSample() { 12 | 13 | //单个按钮 14 | // var selected by remember { 15 | // mutableStateOf(false) 16 | // } 17 | // 18 | // RadioButton(selected = selected, onClick = { 19 | // selected = !selected 20 | // }) 21 | 22 | //多个按钮 23 | 24 | var checkedList by remember { 25 | 26 | mutableStateOf( 27 | listOf( 28 | false, false 29 | ) 30 | ) 31 | } 32 | 33 | Column() { 34 | 35 | checkedList.forEachIndexed { i, item -> 36 | RadioButton(selected = item, onClick = { 37 | checkedList = checkedList.mapIndexed { j, _ -> 38 | i == j 39 | } 40 | 41 | }) 42 | } 43 | 44 | } 45 | 46 | } 47 | 48 | @Preview 49 | @Composable 50 | fun RadioButtonSamplePreview() { 51 | RadioButtonSample() 52 | } 53 | 54 | -------------------------------------------------------------------------------- /ComposeBasic/app/src/main/java/icu/bughub/app/composebasic/components/RowSample.kt: -------------------------------------------------------------------------------- 1 | package icu.bughub.app.composebasic.components 2 | 3 | 4 | import androidx.compose.foundation.background 5 | import androidx.compose.foundation.layout.Arrangement 6 | import androidx.compose.foundation.layout.Row 7 | import androidx.compose.foundation.layout.size 8 | import androidx.compose.material.Text 9 | import androidx.compose.runtime.Composable 10 | import androidx.compose.ui.Modifier 11 | import androidx.compose.ui.graphics.Color 12 | import androidx.compose.ui.tooling.preview.Preview 13 | import androidx.compose.ui.unit.dp 14 | 15 | 16 | @Composable 17 | fun RowSample() { 18 | 19 | Row( 20 | modifier = Modifier 21 | .size(400.dp) 22 | .background(Color.Green), horizontalArrangement = Arrangement.SpaceAround 23 | ) { 24 | Text(text = "Column First Item" ) 25 | Text(text = "Column Second Item") 26 | } 27 | 28 | } 29 | 30 | @Preview 31 | @Composable 32 | fun RowSamplePreview() { 33 | RowSample() 34 | } 35 | 36 | -------------------------------------------------------------------------------- /ComposeBasic/app/src/main/java/icu/bughub/app/composebasic/components/SliderSample.kt: -------------------------------------------------------------------------------- 1 | package icu.bughub.app.composebasic.components 2 | 3 | 4 | import androidx.compose.material.ExperimentalMaterialApi 5 | import androidx.compose.material.RangeSlider 6 | import androidx.compose.material.Slider 7 | import androidx.compose.runtime.* 8 | import androidx.compose.ui.tooling.preview.Preview 9 | 10 | 11 | @OptIn(ExperimentalMaterialApi::class) 12 | @Composable 13 | fun SliderSample() { 14 | 15 | // var value by remember { 16 | // mutableStateOf(0f) 17 | // } 18 | // 19 | // Slider(value = value, onValueChange = { 20 | // value = it 21 | // }, valueRange = 0f..100f, steps = 4) 22 | 23 | var values by remember { 24 | mutableStateOf(0.2f..0.5f) 25 | } 26 | 27 | RangeSlider(values = values, onValueChange = { values = it }) 28 | 29 | } 30 | 31 | @Preview 32 | @Composable 33 | fun SliderSamplePreview() { 34 | SliderSample() 35 | } 36 | 37 | -------------------------------------------------------------------------------- /ComposeBasic/app/src/main/java/icu/bughub/app/composebasic/components/SpacerSample.kt: -------------------------------------------------------------------------------- 1 | package icu.bughub.app.composebasic.components 2 | 3 | 4 | import androidx.compose.foundation.background 5 | import androidx.compose.foundation.layout.* 6 | import androidx.compose.material.Text 7 | import androidx.compose.runtime.Composable 8 | import androidx.compose.ui.Modifier 9 | import androidx.compose.ui.graphics.Color 10 | import androidx.compose.ui.tooling.preview.Preview 11 | import androidx.compose.ui.unit.dp 12 | 13 | 14 | @Composable 15 | fun SpacerSample() { 16 | 17 | Column( 18 | modifier = Modifier 19 | .size(200.dp) 20 | .background(Color.Green), 21 | ) { 22 | Text( 23 | text = "Column First Item", 24 | modifier = Modifier 25 | .background(Color.Red) 26 | ) 27 | 28 | Spacer(modifier = Modifier.height(50.dp)) 29 | 30 | Text( 31 | text = "Column Second Item", 32 | modifier = Modifier 33 | .background(Color.Yellow) 34 | ) 35 | } 36 | 37 | } 38 | 39 | @Preview 40 | @Composable 41 | fun SpacerSamplePreview() { 42 | SpacerSample() 43 | } 44 | 45 | -------------------------------------------------------------------------------- /ComposeBasic/app/src/main/java/icu/bughub/app/composebasic/components/StateSample.kt: -------------------------------------------------------------------------------- 1 | package icu.bughub.app.composebasic.components 2 | 3 | 4 | import android.annotation.SuppressLint 5 | import android.util.Log 6 | import androidx.compose.foundation.clickable 7 | import androidx.compose.foundation.layout.padding 8 | import androidx.compose.material.Text 9 | import androidx.compose.runtime.* 10 | import androidx.compose.ui.Modifier 11 | import androidx.compose.ui.tooling.preview.Preview 12 | import androidx.compose.ui.unit.dp 13 | 14 | 15 | @Composable 16 | fun StateSample() { 17 | 18 | var count by remember { 19 | mutableStateOf(1) 20 | } 21 | 22 | Log.d("====", "外面的值:${count}") 23 | 24 | Text(text = "我今天想叫${count}个小姐姐", modifier = Modifier 25 | .padding(8.dp) 26 | .clickable { 27 | count++ 28 | Log.d("====", "我进来了") 29 | }) 30 | 31 | 32 | } 33 | 34 | @Preview 35 | @Composable 36 | fun StateSamplePreview() { 37 | StateSample() 38 | } 39 | 40 | -------------------------------------------------------------------------------- /ComposeBasic/app/src/main/java/icu/bughub/app/composebasic/components/Stepper.kt: -------------------------------------------------------------------------------- 1 | package icu.bughub.app.composebasic.components 2 | 3 | 4 | import androidx.compose.foundation.background 5 | import androidx.compose.foundation.border 6 | import androidx.compose.foundation.clickable 7 | import androidx.compose.foundation.layout.* 8 | import androidx.compose.foundation.shape.RoundedCornerShape 9 | import androidx.compose.foundation.text.BasicTextField 10 | import androidx.compose.foundation.text.KeyboardOptions 11 | import androidx.compose.material.* 12 | import androidx.compose.material.icons.Icons 13 | import androidx.compose.material.icons.filled.ArrowLeft 14 | import androidx.compose.material.icons.filled.ArrowRight 15 | import androidx.compose.runtime.* 16 | import androidx.compose.ui.Alignment 17 | import androidx.compose.ui.Modifier 18 | import androidx.compose.ui.draw.clip 19 | import androidx.compose.ui.graphics.Color 20 | import androidx.compose.ui.text.TextStyle 21 | import androidx.compose.ui.text.input.KeyboardType 22 | import androidx.compose.ui.text.style.TextAlign 23 | import androidx.compose.ui.tooling.preview.Preview 24 | import androidx.compose.ui.unit.dp 25 | import androidx.compose.ui.unit.sp 26 | import kotlin.math.max 27 | 28 | 29 | @Composable 30 | fun Stepper(onStep: ((Int) -> Unit)? = null) { 31 | 32 | var step by remember { 33 | mutableStateOf("0") 34 | } 35 | 36 | Row( 37 | verticalAlignment = Alignment.CenterVertically, 38 | modifier = Modifier 39 | .border(0.5.dp, Color.Gray, shape = RoundedCornerShape(6.dp)) 40 | .clip(RoundedCornerShape(6.dp)) 41 | .background(Color.White) 42 | .height(IntrinsicSize.Min) 43 | .heightIn(30.dp) 44 | ) { 45 | 46 | Icon( 47 | imageVector = Icons.Default.ArrowLeft, 48 | contentDescription = null, 49 | tint = Color.White, 50 | modifier = Modifier 51 | .background(Color.Blue) 52 | .fillMaxHeight() 53 | .clickable { 54 | step = max(0, (step.toInt() - 1)).toString() 55 | } 56 | ) 57 | 58 | BasicTextField( 59 | value = step, 60 | onValueChange = { value -> 61 | step = value 62 | if (value.isNotEmpty()) 63 | onStep?.let { 64 | it(value.toInt()) 65 | } 66 | }, 67 | singleLine = true, 68 | keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number), 69 | textStyle = TextStyle( 70 | textAlign = TextAlign.Center, 71 | lineHeight = 14.sp, 72 | fontSize = 14.sp 73 | ), 74 | modifier = Modifier 75 | .background(Color.White) 76 | .padding(0.dp) 77 | .width(IntrinsicSize.Min) 78 | .widthIn(30.dp, 100.dp) 79 | ) 80 | 81 | Icon(imageVector = Icons.Default.ArrowRight, contentDescription = null, tint = Color.White, 82 | modifier = Modifier 83 | .background(Color.Blue) 84 | .fillMaxHeight() 85 | .clickable { 86 | step = (step.toInt() + 1).toString() 87 | }) 88 | } 89 | 90 | } 91 | 92 | 93 | @Preview 94 | @Composable 95 | fun StepperPreview() { 96 | Stepper() 97 | } 98 | 99 | -------------------------------------------------------------------------------- /ComposeBasic/app/src/main/java/icu/bughub/app/composebasic/components/SurfaceSample.kt: -------------------------------------------------------------------------------- 1 | package icu.bughub.app.composebasic.components 2 | 3 | 4 | import androidx.compose.foundation.BorderStroke 5 | import androidx.compose.foundation.Image 6 | import androidx.compose.foundation.layout.size 7 | import androidx.compose.foundation.shape.CircleShape 8 | import androidx.compose.foundation.shape.CutCornerShape 9 | import androidx.compose.foundation.shape.RoundedCornerShape 10 | import androidx.compose.material.Surface 11 | import androidx.compose.runtime.Composable 12 | import androidx.compose.ui.Modifier 13 | import androidx.compose.ui.graphics.Color 14 | import androidx.compose.ui.res.painterResource 15 | import androidx.compose.ui.tooling.preview.Preview 16 | import androidx.compose.ui.unit.dp 17 | import icu.bughub.app.composebasic.R 18 | 19 | 20 | @Composable 21 | fun SurfaceSample() { 22 | //RectangleShape 矩形 23 | //RoundedCornerShape 圆角 ==》 CircleShape 50%圆角形成胶囊状 24 | //CutCornerShape 切角 25 | Surface( 26 | // modifier = Modifier.size(100.dp, 20.dp), 27 | shape = CutCornerShape(20), 28 | color = Color.Yellow, 29 | border = BorderStroke(1.dp, Color.Green), 30 | elevation = 10.dp 31 | ) { 32 | Image( 33 | painter = painterResource(id = R.drawable.newbanner4), 34 | contentDescription = null 35 | ) 36 | } 37 | 38 | } 39 | 40 | @Preview 41 | @Composable 42 | fun SurfaceSamplePreview() { 43 | SurfaceSample() 44 | } 45 | 46 | -------------------------------------------------------------------------------- /ComposeBasic/app/src/main/java/icu/bughub/app/composebasic/components/SwipeToDismissSample.kt: -------------------------------------------------------------------------------- 1 | package icu.bughub.app.composebasic.components 2 | 3 | 4 | import androidx.compose.animation.animateColorAsState 5 | import androidx.compose.animation.core.animateDpAsState 6 | import androidx.compose.animation.core.animateFloatAsState 7 | import androidx.compose.foundation.background 8 | import androidx.compose.foundation.layout.Box 9 | import androidx.compose.foundation.layout.fillMaxSize 10 | import androidx.compose.foundation.layout.padding 11 | import androidx.compose.foundation.lazy.LazyColumn 12 | import androidx.compose.foundation.lazy.items 13 | import androidx.compose.material.* 14 | import androidx.compose.material.icons.Icons 15 | import androidx.compose.material.icons.filled.Delete 16 | import androidx.compose.material.icons.filled.Done 17 | import androidx.compose.runtime.* 18 | import androidx.compose.ui.Alignment 19 | import androidx.compose.ui.Modifier 20 | import androidx.compose.ui.draw.scale 21 | import androidx.compose.ui.graphics.Color 22 | import androidx.compose.ui.text.font.FontWeight 23 | import androidx.compose.ui.tooling.preview.Preview 24 | import androidx.compose.ui.unit.dp 25 | import icu.bughub.app.composebasic.components.ListItem 26 | 27 | 28 | @OptIn(ExperimentalMaterialApi::class) 29 | @Composable 30 | fun SwipeToDismissSample() { 31 | 32 | val items = listOf(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) 33 | 34 | LazyColumn() { 35 | items(items) { item -> 36 | 37 | var unread by remember { 38 | mutableStateOf(false) 39 | } 40 | 41 | val dismissState = rememberDismissState( 42 | confirmStateChange = { 43 | if (it == DismissValue.DismissedToEnd) unread = !unread 44 | it != DismissValue.DismissedToEnd 45 | } 46 | ) 47 | 48 | SwipeToDismiss(state = dismissState, modifier = Modifier.padding(vertical = 4.dp), 49 | directions = setOf( DismissDirection.EndToStart), 50 | background = { 51 | val direction = dismissState.dismissDirection ?: return@SwipeToDismiss 52 | val color by animateColorAsState( 53 | when (dismissState.targetValue) { 54 | DismissValue.Default -> Color.LightGray 55 | DismissValue.DismissedToEnd -> Color.Green 56 | DismissValue.DismissedToStart -> Color.Red 57 | } 58 | ) 59 | val alignment = when (direction) { 60 | DismissDirection.StartToEnd -> Alignment.CenterStart 61 | DismissDirection.EndToStart -> Alignment.CenterEnd 62 | } 63 | val icon = when (direction) { 64 | DismissDirection.StartToEnd -> Icons.Default.Done 65 | DismissDirection.EndToStart -> Icons.Default.Delete 66 | } 67 | val scale by animateFloatAsState( 68 | if (dismissState.targetValue == DismissValue.Default) 0.75f else 1f 69 | ) 70 | Box( 71 | Modifier 72 | .fillMaxSize() 73 | .background(color) 74 | .padding(horizontal = 20.dp), 75 | contentAlignment = alignment 76 | ) { 77 | Icon( 78 | icon, 79 | contentDescription = "Localized description", 80 | modifier = Modifier.scale(scale) 81 | ) 82 | } 83 | }) { 84 | Card( 85 | elevation = animateDpAsState( 86 | if (dismissState.dismissDirection != null) 4.dp else 0.dp 87 | ).value 88 | ) { 89 | ListItem( 90 | text = { 91 | Text( 92 | item.toString(), 93 | fontWeight = if (unread) FontWeight.Bold else null 94 | ) 95 | }, 96 | secondaryText = { Text("Swipe me left or right!") } 97 | ) 98 | } 99 | } 100 | 101 | } 102 | } 103 | 104 | } 105 | 106 | @androidx.compose.runtime.Composable 107 | fun SwipeSample() { 108 | 109 | 110 | } 111 | 112 | @Preview 113 | @Composable 114 | fun SwipeToDismissSamplePreview() { 115 | SwipeSample() 116 | } 117 | 118 | -------------------------------------------------------------------------------- /ComposeBasic/app/src/main/java/icu/bughub/app/composebasic/components/SwitchSample.kt: -------------------------------------------------------------------------------- 1 | package icu.bughub.app.composebasic.components 2 | 3 | 4 | import androidx.compose.material.Switch 5 | import androidx.compose.runtime.* 6 | import androidx.compose.ui.tooling.preview.Preview 7 | 8 | 9 | @Composable 10 | fun SwitchSample() { 11 | 12 | var checked by remember { mutableStateOf(false) } 13 | 14 | Switch(checked = checked, onCheckedChange = { 15 | checked = it 16 | }) 17 | 18 | } 19 | 20 | @Preview 21 | @Composable 22 | fun SwitchSamplePreview() { 23 | SwitchSample() 24 | } 25 | 26 | -------------------------------------------------------------------------------- /ComposeBasic/app/src/main/java/icu/bughub/app/composebasic/components/TabSample.kt: -------------------------------------------------------------------------------- 1 | package icu.bughub.app.composebasic.components 2 | 3 | 4 | import androidx.compose.foundation.clickable 5 | import androidx.compose.foundation.layout.Column 6 | import androidx.compose.foundation.layout.padding 7 | import androidx.compose.material.* 8 | import androidx.compose.material.icons.Icons 9 | import androidx.compose.material.icons.filled.AccountBox 10 | import androidx.compose.material.icons.filled.Image 11 | import androidx.compose.runtime.* 12 | import androidx.compose.ui.Modifier 13 | import androidx.compose.ui.graphics.Color 14 | import androidx.compose.ui.tooling.preview.Preview 15 | import androidx.compose.ui.unit.dp 16 | 17 | 18 | @OptIn(ExperimentalMaterialApi::class) 19 | @Composable 20 | fun TabSample() { 21 | 22 | var selectedTabIndex by remember { 23 | mutableStateOf(0) 24 | } 25 | 26 | Column { 27 | 28 | TabRow(selectedTabIndex = selectedTabIndex) { 29 | Tab( 30 | selected = selectedTabIndex == 0, 31 | onClick = { selectedTabIndex = 0 }, 32 | selectedContentColor = Color.Red, 33 | unselectedContentColor = Color.Gray 34 | ) { 35 | Text(text = "Tab0") 36 | } 37 | 38 | Tab( 39 | selected = selectedTabIndex == 1, 40 | onClick = { selectedTabIndex = 1 }, 41 | icon = { 42 | Icon( 43 | imageVector = Icons.Default.Image, 44 | contentDescription = null 45 | ) 46 | }, 47 | text = { 48 | Text("Tab1") 49 | } 50 | ) 51 | 52 | LeadingIconTab( 53 | selected = selectedTabIndex == 2, 54 | onClick = { selectedTabIndex = 2 }, 55 | icon = { 56 | Icon( 57 | imageVector = Icons.Default.AccountBox, 58 | contentDescription = null 59 | ) 60 | }, 61 | text = { 62 | Text("Tab2") 63 | } 64 | ) 65 | 66 | } 67 | 68 | Text("current index : $selectedTabIndex") 69 | 70 | } 71 | 72 | } 73 | 74 | @Preview 75 | @Composable 76 | fun TabSamplePreview() { 77 | TabSample() 78 | } 79 | 80 | -------------------------------------------------------------------------------- /ComposeBasic/app/src/main/java/icu/bughub/app/composebasic/components/TextFieldSample.kt: -------------------------------------------------------------------------------- 1 | package icu.bughub.app.composebasic.components 2 | 3 | 4 | import androidx.compose.foundation.text.KeyboardActions 5 | import androidx.compose.foundation.text.KeyboardOptions 6 | import androidx.compose.material.Icon 7 | import androidx.compose.material.Text 8 | import androidx.compose.material.TextField 9 | import androidx.compose.material.icons.Icons 10 | import androidx.compose.material.icons.filled.AccountBox 11 | import androidx.compose.runtime.* 12 | import androidx.compose.ui.text.input.ImeAction 13 | import androidx.compose.ui.text.input.KeyboardType 14 | import androidx.compose.ui.tooling.preview.Preview 15 | 16 | 17 | @Composable 18 | fun TextFieldSample() { 19 | 20 | var value by remember { 21 | mutableStateOf("") 22 | } 23 | 24 | TextField( 25 | value = value, 26 | onValueChange = { 27 | value = it 28 | }, 29 | label = { 30 | Text("姓名") 31 | }, 32 | placeholder = { 33 | Text("请在这里输入姓名") 34 | }, 35 | leadingIcon = { 36 | Icon(imageVector = Icons.Default.AccountBox, contentDescription = null) 37 | }, 38 | keyboardActions = KeyboardActions(onDone = { 39 | 40 | }), 41 | singleLine = true, 42 | keyboardOptions = KeyboardOptions( 43 | imeAction = ImeAction.Done, 44 | keyboardType = KeyboardType.Number 45 | ) 46 | 47 | ) 48 | 49 | } 50 | 51 | @Preview 52 | @Composable 53 | fun TextFieldSamplePreview() { 54 | TextFieldSample() 55 | } 56 | 57 | -------------------------------------------------------------------------------- /ComposeBasic/app/src/main/java/icu/bughub/app/composebasic/components/TextSample.kt: -------------------------------------------------------------------------------- 1 | package icu.bughub.app.composebasic.components 2 | 3 | import android.util.Log 4 | import androidx.compose.foundation.layout.Box 5 | import androidx.compose.foundation.text.ClickableText 6 | import androidx.compose.foundation.text.selection.SelectionContainer 7 | import androidx.compose.material.Text 8 | import androidx.compose.runtime.Composable 9 | import androidx.compose.ui.graphics.Color 10 | import androidx.compose.ui.res.stringResource 11 | import androidx.compose.ui.text.SpanStyle 12 | import androidx.compose.ui.text.TextStyle 13 | import androidx.compose.ui.text.buildAnnotatedString 14 | import androidx.compose.ui.text.style.TextAlign 15 | import androidx.compose.ui.text.style.TextDecoration 16 | import androidx.compose.ui.text.style.TextOverflow 17 | import androidx.compose.ui.text.withStyle 18 | import androidx.compose.ui.tooling.preview.Preview 19 | import androidx.compose.ui.unit.sp 20 | import icu.bughub.app.composebasic.R 21 | 22 | 23 | @Composable 24 | fun TextSample() { 25 | // Text(text = "我终于要学会Jetpack Compose 了!") 26 | 27 | val annotatedString = buildAnnotatedString { 28 | append("点击登录即代表您已知悉和同意") 29 | //往字符串中添加注解,tag 为标识,直到遇到 pop() 30 | pushStringAnnotation("protocol", "https://docs.bughub.icu/compose") 31 | withStyle(style = SpanStyle(Color.Blue, textDecoration = TextDecoration.Underline)) { 32 | append("用户协议") 33 | } 34 | pop() 35 | 36 | append("和") 37 | 38 | pushStringAnnotation("privacy", "https://github.com/RandyWei") 39 | withStyle(style = SpanStyle(Color.Blue, textDecoration = TextDecoration.Underline)) { 40 | append("隐私政策") 41 | } 42 | pop() 43 | } 44 | 45 | ClickableText(text = annotatedString, onClick = { offset -> 46 | //从字符串中根据 tag 查找注解 47 | annotatedString.getStringAnnotations("protocol", start = offset, end = offset).firstOrNull() 48 | ?.let { annotation -> 49 | Log.d("====", "你点击到${annotation.item}") 50 | } 51 | 52 | annotatedString.getStringAnnotations("privacy", start = offset, end = offset).firstOrNull() 53 | ?.let { annotation -> 54 | Log.d("====", "你点击到${annotation.item}") 55 | } 56 | 57 | }) 58 | 59 | } 60 | 61 | 62 | @Preview 63 | @Composable 64 | fun TextSamplePreview() { 65 | TextSample() 66 | } -------------------------------------------------------------------------------- /ComposeBasic/app/src/main/java/icu/bughub/app/composebasic/ui/theme/Color.kt: -------------------------------------------------------------------------------- 1 | package icu.bughub.app.composebasic.ui.theme 2 | 3 | import androidx.compose.ui.graphics.Color 4 | 5 | val Purple200 = Color(0xFFBB86FC) 6 | val Purple500 = Color(0xFF6200EE) 7 | val Purple700 = Color(0xFF3700B3) 8 | val Teal200 = Color(0xFF03DAC5) -------------------------------------------------------------------------------- /ComposeBasic/app/src/main/java/icu/bughub/app/composebasic/ui/theme/Shape.kt: -------------------------------------------------------------------------------- 1 | package icu.bughub.app.composebasic.ui.theme 2 | 3 | import androidx.compose.foundation.shape.RoundedCornerShape 4 | import androidx.compose.material.Shapes 5 | import androidx.compose.ui.unit.dp 6 | 7 | val Shapes = Shapes( 8 | small = RoundedCornerShape(4.dp), 9 | medium = RoundedCornerShape(4.dp), 10 | large = RoundedCornerShape(0.dp) 11 | ) -------------------------------------------------------------------------------- /ComposeBasic/app/src/main/java/icu/bughub/app/composebasic/ui/theme/Theme.kt: -------------------------------------------------------------------------------- 1 | package icu.bughub.app.composebasic.ui.theme 2 | 3 | import androidx.compose.foundation.isSystemInDarkTheme 4 | import androidx.compose.material.MaterialTheme 5 | import androidx.compose.material.darkColors 6 | import androidx.compose.material.lightColors 7 | import androidx.compose.runtime.Composable 8 | 9 | private val DarkColorPalette = darkColors( 10 | primary = Purple200, 11 | primaryVariant = Purple700, 12 | secondary = Teal200 13 | ) 14 | 15 | private val LightColorPalette = lightColors( 16 | primary = Purple500, 17 | primaryVariant = Purple700, 18 | secondary = Teal200 19 | 20 | /* Other default colors to override 21 | background = Color.White, 22 | surface = Color.White, 23 | onPrimary = Color.White, 24 | onSecondary = Color.Black, 25 | onBackground = Color.Black, 26 | onSurface = Color.Black, 27 | */ 28 | ) 29 | 30 | @Composable 31 | fun ComposeBasicTheme(darkTheme: Boolean = isSystemInDarkTheme(), content: @Composable () -> Unit) { 32 | val colors = if (darkTheme) { 33 | DarkColorPalette 34 | } else { 35 | LightColorPalette 36 | } 37 | 38 | MaterialTheme( 39 | colors = colors, 40 | typography = Typography, 41 | shapes = Shapes, 42 | content = content 43 | ) 44 | } -------------------------------------------------------------------------------- /ComposeBasic/app/src/main/java/icu/bughub/app/composebasic/ui/theme/Type.kt: -------------------------------------------------------------------------------- 1 | package icu.bughub.app.composebasic.ui.theme 2 | 3 | import androidx.compose.material.Typography 4 | import androidx.compose.ui.text.TextStyle 5 | import androidx.compose.ui.text.font.FontFamily 6 | import androidx.compose.ui.text.font.FontWeight 7 | import androidx.compose.ui.unit.sp 8 | 9 | // Set of Material typography styles to start with 10 | val Typography = Typography( 11 | body1 = TextStyle( 12 | fontFamily = FontFamily.Default, 13 | fontWeight = FontWeight.Normal, 14 | fontSize = 16.sp 15 | ) 16 | /* Other default text styles to override 17 | button = TextStyle( 18 | fontFamily = FontFamily.Default, 19 | fontWeight = FontWeight.W500, 20 | fontSize = 14.sp 21 | ), 22 | caption = TextStyle( 23 | fontFamily = FontFamily.Default, 24 | fontWeight = FontWeight.Normal, 25 | fontSize = 12.sp 26 | ) 27 | */ 28 | ) -------------------------------------------------------------------------------- /ComposeBasic/app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /ComposeBasic/app/src/main/res/drawable/ic_android_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | -------------------------------------------------------------------------------- /ComposeBasic/app/src/main/res/drawable/newbanner4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RandyWei/JetpackComposeCase/192eb509c12ea41eb8b9fef4ee69806a3a39afd7/ComposeBasic/app/src/main/res/drawable/newbanner4.png -------------------------------------------------------------------------------- /ComposeBasic/app/src/main/res/layout/main.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 11 | 12 | 13 | 17 | 18 | -------------------------------------------------------------------------------- /ComposeBasic/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /ComposeBasic/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /ComposeBasic/app/src/main/res/mipmap-hdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RandyWei/JetpackComposeCase/192eb509c12ea41eb8b9fef4ee69806a3a39afd7/ComposeBasic/app/src/main/res/mipmap-hdpi/ic_launcher.webp -------------------------------------------------------------------------------- /ComposeBasic/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RandyWei/JetpackComposeCase/192eb509c12ea41eb8b9fef4ee69806a3a39afd7/ComposeBasic/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /ComposeBasic/app/src/main/res/mipmap-mdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RandyWei/JetpackComposeCase/192eb509c12ea41eb8b9fef4ee69806a3a39afd7/ComposeBasic/app/src/main/res/mipmap-mdpi/ic_launcher.webp -------------------------------------------------------------------------------- /ComposeBasic/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RandyWei/JetpackComposeCase/192eb509c12ea41eb8b9fef4ee69806a3a39afd7/ComposeBasic/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /ComposeBasic/app/src/main/res/mipmap-xhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RandyWei/JetpackComposeCase/192eb509c12ea41eb8b9fef4ee69806a3a39afd7/ComposeBasic/app/src/main/res/mipmap-xhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /ComposeBasic/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RandyWei/JetpackComposeCase/192eb509c12ea41eb8b9fef4ee69806a3a39afd7/ComposeBasic/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /ComposeBasic/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RandyWei/JetpackComposeCase/192eb509c12ea41eb8b9fef4ee69806a3a39afd7/ComposeBasic/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /ComposeBasic/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RandyWei/JetpackComposeCase/192eb509c12ea41eb8b9fef4ee69806a3a39afd7/ComposeBasic/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /ComposeBasic/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RandyWei/JetpackComposeCase/192eb509c12ea41eb8b9fef4ee69806a3a39afd7/ComposeBasic/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /ComposeBasic/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RandyWei/JetpackComposeCase/192eb509c12ea41eb8b9fef4ee69806a3a39afd7/ComposeBasic/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /ComposeBasic/app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFBB86FC 4 | #FF6200EE 5 | #FF3700B3 6 | #FF03DAC5 7 | #FF018786 8 | #FF000000 9 | #FFFFFFFF 10 | -------------------------------------------------------------------------------- /ComposeBasic/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | ComposeBasic 3 | 这是一个从 strings.xml 里面来的文本 4 | -------------------------------------------------------------------------------- /ComposeBasic/app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | -------------------------------------------------------------------------------- /ComposeBasic/app/src/test/java/icu/bughub/app/composebasic/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package icu.bughub.app.composebasic 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } -------------------------------------------------------------------------------- /ComposeBasic/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext { 3 | compose_version = '1.0.5' 4 | } 5 | }// Top-level build file where you can add configuration options common to all sub-projects/modules. 6 | plugins { 7 | id 'com.android.application' version '7.1.0-rc01' apply false 8 | id 'com.android.library' version '7.1.0-rc01' apply false 9 | id 'org.jetbrains.kotlin.android' version '1.5.31' apply false 10 | } 11 | 12 | task clean(type: Delete) { 13 | delete rootProject.buildDir 14 | } -------------------------------------------------------------------------------- /ComposeBasic/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app"s APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Kotlin code style for this project: "official" or "obsolete": 19 | kotlin.code.style=official 20 | # Enables namespacing of each library's R class so that its R class includes only the 21 | # resources declared in the library itself and none from the library's dependencies, 22 | # thereby reducing the size of the R class for that library 23 | android.nonTransitiveRClass=true -------------------------------------------------------------------------------- /ComposeBasic/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RandyWei/JetpackComposeCase/192eb509c12ea41eb8b9fef4ee69806a3a39afd7/ComposeBasic/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /ComposeBasic/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Feb 08 06:53:47 CST 2022 2 | distributionBase=GRADLE_USER_HOME 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 7 | -------------------------------------------------------------------------------- /ComposeBasic/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /ComposeBasic/settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | gradlePluginPortal() 4 | google() 5 | mavenCentral() 6 | } 7 | } 8 | dependencyResolutionManagement { 9 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 10 | repositories { 11 | google() 12 | mavenCentral() 13 | } 14 | } 15 | rootProject.name = "ComposeBasic" 16 | include ':app' 17 | --------------------------------------------------------------------------------