├── .gitignore ├── .idea ├── .gitignore ├── compiler.xml ├── deploymentTargetDropDown.xml ├── gradle.xml ├── inspectionProfiles │ └── Project_Default.xml └── misc.xml ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── fmt │ │ └── compose │ │ └── eyepetizer │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── fmt │ │ │ └── compose │ │ │ └── eyepetizer │ │ │ ├── App.kt │ │ │ ├── MainActivity.kt │ │ │ ├── MainViewModel.kt │ │ │ ├── db │ │ │ ├── CacheManager.kt │ │ │ ├── Video.kt │ │ │ └── VideoDao.kt │ │ │ ├── ext │ │ │ ├── AbsPagingViewModel.kt │ │ │ ├── GsonExt.kt │ │ │ ├── LazyPagingItemExt.kt │ │ │ ├── PxExt.kt │ │ │ └── ToastExt.kt │ │ │ ├── http │ │ │ ├── ApiService.kt │ │ │ └── IApi.kt │ │ │ ├── model │ │ │ ├── BaseApiResult.kt │ │ │ ├── Category.kt │ │ │ ├── Daily.kt │ │ │ ├── Follow.kt │ │ │ ├── Issue.kt │ │ │ ├── Item.kt │ │ │ ├── News.kt │ │ │ ├── Recommend.kt │ │ │ ├── TabListInfo.kt │ │ │ ├── Topic.kt │ │ │ └── TopicDetail.kt │ │ │ ├── pages │ │ │ ├── daily │ │ │ │ ├── DailyPage.kt │ │ │ │ ├── SearchVideoPage.kt │ │ │ │ ├── repository │ │ │ │ │ └── DailyPagingSource.kt │ │ │ │ └── viewmodel │ │ │ │ │ └── DailyViewModel.kt │ │ │ ├── detail │ │ │ │ ├── NewsDetailActivity.kt │ │ │ │ ├── TopicDetailActivity.kt │ │ │ │ ├── TopicDetailPage.kt │ │ │ │ ├── VideoDetailActivity.kt │ │ │ │ ├── VideoDetailPage.kt │ │ │ │ └── viewmodel │ │ │ │ │ ├── TopicDetailViewModel.kt │ │ │ │ │ └── VideoDetailModel.kt │ │ │ ├── discover │ │ │ │ ├── CategoryDetailActivity.kt │ │ │ │ ├── CategoryDetailPage.kt │ │ │ │ ├── CategoryPage.kt │ │ │ │ ├── DiscoverPage.kt │ │ │ │ ├── FollowPage.kt │ │ │ │ ├── NewsPage.kt │ │ │ │ ├── RecommendPage.kt │ │ │ │ ├── TopicPage.kt │ │ │ │ └── viewmodel │ │ │ │ │ ├── CategoryDetailViewModel.kt │ │ │ │ │ ├── CategoryViewModel.kt │ │ │ │ │ ├── DiscoverViewModel.kt │ │ │ │ │ ├── FollowViewModel.kt │ │ │ │ │ ├── NewsViewModel.kt │ │ │ │ │ ├── RecommendViewModel.kt │ │ │ │ │ └── TopicViewModel.kt │ │ │ ├── hot │ │ │ │ ├── HotPage.kt │ │ │ │ └── viewmodel │ │ │ │ │ ├── HotTabViewModel.kt │ │ │ │ │ └── HotViewModel.kt │ │ │ └── person │ │ │ │ ├── PersonPage.kt │ │ │ │ ├── WatchRecordActivity.kt │ │ │ │ ├── WatchRecordPage.kt │ │ │ │ └── viewmodel │ │ │ │ ├── PersonViewModel.kt │ │ │ │ └── WatchRecordViewModel.kt │ │ │ ├── ui │ │ │ └── theme │ │ │ │ ├── Color.kt │ │ │ │ ├── Shape.kt │ │ │ │ ├── Theme.kt │ │ │ │ └── Type.kt │ │ │ ├── util │ │ │ ├── DateUtils.kt │ │ │ └── ScreenUtils.kt │ │ │ └── view │ │ │ ├── Swipe.kt │ │ │ └── TopAppBar.kt │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ ├── ic_launcher_background.xml │ │ └── launch_layer_list.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── home__ic_mine_normal.png │ │ ├── home_ic_discovery_normal.png │ │ ├── home_ic_discovery_selected.png │ │ ├── home_ic_hot_normal.png │ │ ├── home_ic_hot_selected.png │ │ ├── home_ic_img_avatar.png │ │ ├── home_ic_mine_selected.png │ │ ├── home_ic_normal.png │ │ ├── home_ic_selected.png │ │ ├── home_launch_screen.jpg │ │ ├── ic_head_bg.webp │ │ ├── ic_launcher.png │ │ ├── ic_launcher_round.png │ │ └── ic_splash.jpg │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── themes.xml │ │ └── xml │ │ ├── backup_rules.xml │ │ └── data_extraction_rules.xml │ └── test │ └── java │ └── com │ └── fmt │ └── compose │ └── eyepetizer │ └── ExampleUnitTest.kt ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.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 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.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 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 18 | 19 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 37 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 fmtjava 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Compose_Eyepetizer 2 | 一款基于 Jetpack Compose 实现的精美仿开眼视频App(提供Kotlin、Flutter、React Native、小程序版本 😁 )

3 | Kotlin:[Jetpack_Kotlin_Eyepetizer](https://github.com/fmtjava/Jetpack_Kotlin_Eyepetizer)

4 | Flutter版:[flutter_eyepetizer](https://github.com/fmtjava/flutter_eyepetizer)

5 | ReactNative版:[ReactNative_Eyepetizer](https://github.com/fmtjava/ReactNative_Eyepetizer)

6 | 小程序版:[wx_eyepetizer](https://github.com/fmtjava/wx_eyepetizer)

7 | 8 | **如果喜欢的话希望给个 `Star` 或 `Fork` ^_^ ,谢谢** 9 | 10 | # 项目截图 11 |
12 |     13 |     14 | 15 |
16 | 17 |
18 | 19 |
20 |     21 |     22 | 23 |
24 |
25 | 26 |
27 |     28 |     29 | 30 |
31 |
32 | 33 |
34 |     35 |     36 | 37 |
38 | 39 |
40 |     41 |     42 | 43 |
44 | 45 |
46 | 47 |     48 |     49 |
50 | 51 | # 核心功能 52 |
53 | 54 |
55 | 56 | # 下载体验 57 | - 点击[![](https://img.shields.io/badge/Download-apk-green.svg)](https://www.pgyer.com/2hmCf5) 58 | - 下方二维码下载(每日上限100次,如达到上限,还是 clone 源码吧!✧(≖ ◡ ≖✿)))
59 | 60 | 61 | # 更新日志 62 | ### v1.0 63 | * 初始化项目,完成开眼视频App核心功能,目前实现首页、发现、热门、分类、我的、视频详情、视频播放等功能,后续持续学习 Compose 高级的内容,继续完善项目 64 | # Thanks 65 | - [coil](https://github.com/coil-kt/coil) 66 | - [JiaoZiVideoPlayer](https://github.com/Jzvd/JZVideo) 67 | - [compose-collapsing-toolbar](https://github.com/onebone/compose-collapsing-toolbar) 68 | - [retrofit2](https://github.com/square/retrofit) 69 | 70 | # 关于我 71 | - WX:fmtjava 72 | - QQ:2694746499 73 | - Email:2694746499@qq.com 74 | - Github:https://github.com/fmtjava 75 | 76 | # 声明 77 | 项目中的 API 均来自开眼视频,纯属学习交流使用,不得用于商业用途! 78 | 79 | # License 80 | 81 | Copyright (c) 2023 fmtjava 82 | 83 | Permission is hereby granted, free of charge, to any person obtaining a copy 84 | of this software and associated documentation files (the "Software"), to deal 85 | in the Software without restriction, including without limitation the rights 86 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 87 | copies of the Software, and to permit persons to whom the Software is 88 | furnished to do so, subject to the following conditions: 89 | 90 | The above copyright notice and this permission notice shall be included in all 91 | copies or substantial portions of the Software. 92 | 93 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 94 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 95 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 96 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 97 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 98 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 99 | SOFTWARE. 100 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | id 'org.jetbrains.kotlin.android' 4 | id 'kotlin-kapt' 5 | id 'kotlin-parcelize' 6 | } 7 | 8 | android { 9 | namespace 'com.fmt.compose.eyepetizer' 10 | compileSdk 33 11 | 12 | defaultConfig { 13 | applicationId "com.fmt.compose.eyepetizer" 14 | minSdk 21 15 | targetSdk 32 16 | versionCode 1 17 | versionName "1.0" 18 | 19 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 20 | vectorDrawables { 21 | useSupportLibrary true 22 | } 23 | } 24 | 25 | buildTypes { 26 | release { 27 | minifyEnabled false 28 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 29 | } 30 | } 31 | compileOptions { 32 | sourceCompatibility JavaVersion.VERSION_1_8 33 | targetCompatibility JavaVersion.VERSION_1_8 34 | } 35 | kotlinOptions { 36 | jvmTarget = '1.8' 37 | } 38 | buildFeatures { 39 | compose true 40 | } 41 | composeOptions { 42 | kotlinCompilerExtensionVersion '1.4.0' 43 | } 44 | packagingOptions { 45 | resources { 46 | excludes += '/META-INF/{AL2.0,LGPL2.1}' 47 | } 48 | } 49 | } 50 | 51 | dependencies { 52 | 53 | implementation 'androidx.core:core-ktx:1.10.1' 54 | implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.6.1' 55 | implementation 'androidx.activity:activity-compose:1.7.0' 56 | implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.6.1' 57 | implementation platform('androidx.compose:compose-bom:2023.01.00') 58 | implementation "androidx.compose.ui:ui" 59 | implementation "androidx.compose.ui:ui-tooling-preview" 60 | implementation 'androidx.compose.foundation:foundation' 61 | implementation 'androidx.compose.material:material' 62 | implementation 'androidx.compose.material:material-icons-core' 63 | implementation 'androidx.compose.material:material-icons-extended' 64 | 65 | implementation("androidx.paging:paging-compose:1.0.0-alpha18") 66 | 67 | implementation("io.coil-kt:coil-compose:2.4.0") 68 | 69 | implementation 'com.squareup.retrofit2:retrofit:2.9.0' 70 | implementation "com.squareup.retrofit2:converter-gson:2.9.0" 71 | 72 | implementation "androidx.room:room-ktx:2.5.2" 73 | implementation 'androidx.appcompat:appcompat:1.4.1' 74 | implementation 'com.google.android.material:material:1.4.+' 75 | implementation 'androidx.constraintlayout:constraintlayout:2.1.3' 76 | kapt "androidx.room:room-compiler:2.5.1" 77 | 78 | implementation 'cn.jzvd:jiaozivideoplayer:7.6.0' 79 | implementation "me.onebone:toolbar-compose:2.3.5" 80 | implementation 'com.github.chrisbanes:PhotoView:2.3.0' 81 | 82 | testImplementation 'junit:junit:4.13.2' 83 | androidTestImplementation 'androidx.test.ext:junit:1.1.3' 84 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' 85 | androidTestImplementation "androidx.compose.ui:ui-test-junit4" 86 | debugImplementation "androidx.compose.ui:ui-tooling" 87 | debugImplementation "androidx.compose.ui:ui-test-manifest" 88 | } -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /app/src/androidTest/java/com/fmt/compose/eyepetizer/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.fmt.compose.eyepetizer 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("com.fmt.compose.eyepetizer", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 19 | 24 | 29 | 34 | 39 | 44 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /app/src/main/java/com/fmt/compose/eyepetizer/App.kt: -------------------------------------------------------------------------------- 1 | package com.fmt.compose.eyepetizer 2 | 3 | import android.app.Application 4 | 5 | lateinit var mainApplication: Application 6 | 7 | class App : Application() { 8 | 9 | override fun onCreate() { 10 | super.onCreate() 11 | mainApplication = this 12 | } 13 | } -------------------------------------------------------------------------------- /app/src/main/java/com/fmt/compose/eyepetizer/MainActivity.kt: -------------------------------------------------------------------------------- 1 | @file:OptIn(ExperimentalFoundationApi::class) 2 | 3 | package com.fmt.compose.eyepetizer 4 | 5 | import android.os.Bundle 6 | import androidx.activity.ComponentActivity 7 | import androidx.activity.compose.setContent 8 | import androidx.annotation.DrawableRes 9 | import androidx.annotation.StringRes 10 | import androidx.compose.foundation.ExperimentalFoundationApi 11 | import androidx.compose.foundation.background 12 | import androidx.compose.foundation.layout.PaddingValues 13 | import androidx.compose.foundation.layout.fillMaxSize 14 | import androidx.compose.foundation.layout.padding 15 | import androidx.compose.foundation.layout.size 16 | import androidx.compose.foundation.pager.HorizontalPager 17 | import androidx.compose.foundation.pager.PagerState 18 | import androidx.compose.foundation.pager.rememberPagerState 19 | import androidx.compose.material.* 20 | import androidx.compose.runtime.* 21 | import androidx.compose.ui.Modifier 22 | import androidx.compose.ui.graphics.Color 23 | import androidx.compose.ui.res.painterResource 24 | import androidx.compose.ui.res.stringResource 25 | import androidx.compose.ui.unit.dp 26 | import androidx.core.view.WindowInsetsControllerCompat 27 | import androidx.lifecycle.viewmodel.compose.viewModel 28 | import com.fmt.compose.eyepetizer.pages.daily.DailyPage 29 | import com.fmt.compose.eyepetizer.pages.discover.DiscoverPage 30 | import com.fmt.compose.eyepetizer.pages.hot.HotPage 31 | import com.fmt.compose.eyepetizer.pages.person.PersonPage 32 | import com.fmt.compose.eyepetizer.ui.theme.SelectedItemColor 33 | import com.fmt.compose.eyepetizer.ui.theme.UnselectedItemColor 34 | import kotlinx.coroutines.launch 35 | 36 | class MainActivity : ComponentActivity() { 37 | 38 | override fun onCreate(savedInstanceState: Bundle?) { 39 | super.onCreate(savedInstanceState) 40 | window.statusBarColor = android.graphics.Color.WHITE 41 | WindowInsetsControllerCompat(window, window.decorView).apply { 42 | isAppearanceLightStatusBars = true 43 | } 44 | setContent { 45 | val pagerState = rememberPagerState() 46 | Scaffold(backgroundColor = Color.White, bottomBar = { 47 | BottomNavigationBar(pagerState) 48 | }) { padding -> 49 | ContentScreen(padding, pagerState) 50 | } 51 | } 52 | } 53 | } 54 | 55 | @Composable 56 | fun ContentScreen(padding: PaddingValues, pagerState: PagerState) { 57 | HorizontalPager(pageCount = 4, 58 | userScrollEnabled = false, 59 | state = pagerState, 60 | modifier = Modifier 61 | .fillMaxSize() 62 | .background(Color.White) 63 | .padding(padding)) { pageIndex -> 64 | when (pageIndex) { 65 | 0 -> DailyPage() 66 | 1 -> DiscoverPage() 67 | 2 -> HotPage() 68 | 3 -> PersonPage() 69 | } 70 | } 71 | } 72 | 73 | @Composable 74 | fun BottomNavigationBar(pagerState: PagerState) { 75 | val viewModel: MainViewModel = viewModel() 76 | val scope = rememberCoroutineScope() 77 | 78 | BottomNavigation(backgroundColor = Color.White) { 79 | viewModel.tabs.value.forEachIndexed { index, tabItem -> 80 | BottomNavigationItem(selected = viewModel.selectTabIndex.value == index, onClick = { 81 | viewModel.selectTabIndex.value = index 82 | scope.launch { 83 | pagerState.scrollToPage(index) 84 | } 85 | }, label = { 86 | Text(text = stringResource(id = tabItem.title), 87 | color = if (viewModel.selectTabIndex.value == index) SelectedItemColor else UnselectedItemColor) 88 | }, icon = { 89 | Icon(painter = painterResource(id = if (viewModel.selectTabIndex.value == index) tabItem.selectIcon else tabItem.normalIcon), 90 | contentDescription = null, 91 | modifier = Modifier.size(24.dp)) 92 | }) 93 | } 94 | } 95 | } 96 | 97 | data class TabItem( 98 | @StringRes val title: Int, 99 | @DrawableRes val normalIcon: Int, 100 | @DrawableRes val selectIcon: Int, 101 | ) 102 | -------------------------------------------------------------------------------- /app/src/main/java/com/fmt/compose/eyepetizer/MainViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.fmt.compose.eyepetizer 2 | 3 | import androidx.compose.runtime.mutableStateOf 4 | import androidx.lifecycle.ViewModel 5 | 6 | class MainViewModel : ViewModel() { 7 | 8 | val tabs = 9 | mutableStateOf(listOf( 10 | TabItem(R.string.daily_paper, R.mipmap.home_ic_normal, R.mipmap.home_ic_selected), 11 | TabItem(R.string.discover, 12 | R.mipmap.home_ic_discovery_normal, 13 | R.mipmap.home_ic_discovery_selected), 14 | TabItem(R.string.hot, R.mipmap.home_ic_hot_normal, R.mipmap.home_ic_hot_selected), 15 | TabItem(R.string.mime, R.mipmap.home__ic_mine_normal, R.mipmap.home_ic_mine_selected), 16 | )) 17 | 18 | var selectTabIndex = mutableStateOf(0) 19 | 20 | } -------------------------------------------------------------------------------- /app/src/main/java/com/fmt/compose/eyepetizer/db/CacheManager.kt: -------------------------------------------------------------------------------- 1 | package com.fmt.compose.eyepetizer.db 2 | 3 | import androidx.room.Database 4 | import androidx.room.Room 5 | import androidx.room.RoomDatabase 6 | import com.fmt.compose.eyepetizer.mainApplication 7 | 8 | @Database(entities = [Video::class], version = 1) 9 | abstract class CacheManager : RoomDatabase() { 10 | abstract val videoDao: VideoDao 11 | 12 | companion object { 13 | private val database = 14 | Room.databaseBuilder(mainApplication, CacheManager::class.java, "compose_cache") 15 | .build() 16 | 17 | @JvmStatic 18 | fun get(): CacheManager { 19 | return database 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /app/src/main/java/com/fmt/compose/eyepetizer/db/Video.kt: -------------------------------------------------------------------------------- 1 | package com.fmt.compose.eyepetizer.db 2 | 3 | import androidx.room.Entity 4 | import androidx.room.PrimaryKey 5 | 6 | @Entity(tableName = "video") 7 | data class Video(@PrimaryKey val videoId: Int, val content: String) -------------------------------------------------------------------------------- /app/src/main/java/com/fmt/compose/eyepetizer/db/VideoDao.kt: -------------------------------------------------------------------------------- 1 | package com.fmt.compose.eyepetizer.db 2 | 3 | import androidx.room.* 4 | 5 | @Dao 6 | interface VideoDao { 7 | 8 | @Insert(onConflict = OnConflictStrategy.REPLACE) 9 | suspend fun save(video: Video): Long 10 | 11 | @Query("select * from video where videoId=:videoId") 12 | suspend fun getVideo(videoId: Int): Video? 13 | 14 | @Query("select * from video") 15 | suspend fun getVideoList(): List