├── .gitignore
├── .idea
├── .gitignore
├── compiler.xml
├── deploymentTargetDropDown.xml
├── gradle.xml
├── inspectionProfiles
│ └── Project_Default.xml
├── jarRepositories.xml
├── kotlinScripting.xml
├── misc.xml
└── vcs.xml
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── mm
│ │ └── hamcompose
│ │ └── ExampleInstrumentedTest.kt
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── mm
│ │ │ └── hamcompose
│ │ │ ├── HamApp.kt
│ │ │ ├── IconsPreview.kt
│ │ │ ├── data
│ │ │ ├── bean
│ │ │ │ ├── ConstBean.kt
│ │ │ │ └── HttpBean.kt
│ │ │ ├── db
│ │ │ │ ├── DbConst.kt
│ │ │ │ ├── history
│ │ │ │ │ ├── HistoryDao.kt
│ │ │ │ │ └── HistoryDatabase.kt
│ │ │ │ ├── hotkey
│ │ │ │ │ ├── HotKeyDatabase.kt
│ │ │ │ │ └── HotkeysDao.kt
│ │ │ │ └── user
│ │ │ │ │ ├── UserInfoDao.kt
│ │ │ │ │ └── UserInfoDatabase.kt
│ │ │ ├── http
│ │ │ │ ├── ApiCall.kt
│ │ │ │ ├── HttpResult.kt
│ │ │ │ ├── HttpService.kt
│ │ │ │ ├── interceptor
│ │ │ │ │ ├── CacheCookieInterceptor.kt
│ │ │ │ │ ├── LogInterceptor.kt
│ │ │ │ │ └── SetCookieInterceptor.kt
│ │ │ │ └── paging
│ │ │ │ │ ├── BasePagingSource.kt
│ │ │ │ │ ├── GirlPhotoPagingSource.kt
│ │ │ │ │ └── PagingFactory.kt
│ │ │ └── store
│ │ │ │ └── DataStoreUtils.kt
│ │ │ ├── di
│ │ │ ├── module
│ │ │ │ ├── DatabaseModule.kt
│ │ │ │ └── NetworkModule.kt
│ │ │ └── scope
│ │ │ │ ├── ActivityScope.kt
│ │ │ │ ├── ApplicationScope.kt
│ │ │ │ └── FragmentScope.kt
│ │ │ ├── repository
│ │ │ ├── HttpRepository.kt
│ │ │ └── HttpRepositoryImpl.kt
│ │ │ ├── theme
│ │ │ ├── Color.kt
│ │ │ ├── Dimens.kt
│ │ │ ├── Shape.kt
│ │ │ ├── Theme.kt
│ │ │ └── Type.kt
│ │ │ ├── ui
│ │ │ ├── HomeActivity.kt
│ │ │ ├── HomeEntry.kt
│ │ │ ├── page
│ │ │ │ ├── base
│ │ │ │ │ ├── BaseCollectViewModel.kt
│ │ │ │ │ ├── BaseRepository.kt
│ │ │ │ │ ├── BaseViewModel.kt
│ │ │ │ │ ├── CacheHistoryViewModel.kt
│ │ │ │ │ └── HamScaffold.kt
│ │ │ │ ├── girls
│ │ │ │ │ ├── info
│ │ │ │ │ │ └── GirlInfoPage.kt
│ │ │ │ │ └── list
│ │ │ │ │ │ ├── GirlPhotoPage.kt
│ │ │ │ │ │ └── GirlPhotoViewModel.kt
│ │ │ │ ├── main
│ │ │ │ │ ├── category
│ │ │ │ │ │ ├── CategoryPage.kt
│ │ │ │ │ │ ├── CategoryViewModel.kt
│ │ │ │ │ │ ├── navigation
│ │ │ │ │ │ │ ├── NaviPage.kt
│ │ │ │ │ │ │ └── NaviViewModel.kt
│ │ │ │ │ │ ├── pubaccount
│ │ │ │ │ │ │ ├── author
│ │ │ │ │ │ │ │ ├── PublicAccountAuthor.kt
│ │ │ │ │ │ │ │ └── PublicAccountAuthorViewModel.kt
│ │ │ │ │ │ │ ├── category
│ │ │ │ │ │ │ │ ├── PublicAccountPage.kt
│ │ │ │ │ │ │ │ └── PublicAccountViewModel.kt
│ │ │ │ │ │ │ └── search
│ │ │ │ │ │ │ │ ├── PublicAccountSearch.kt
│ │ │ │ │ │ │ │ └── PublicAccountSearchViewModel.kt
│ │ │ │ │ │ ├── share
│ │ │ │ │ │ │ ├── ShareArticlePage.kt
│ │ │ │ │ │ │ └── ShareArticleViewModel.kt
│ │ │ │ │ │ └── structure
│ │ │ │ │ │ │ ├── list
│ │ │ │ │ │ │ ├── StructureListPage.kt
│ │ │ │ │ │ │ └── StructureListViewModel.kt
│ │ │ │ │ │ │ └── tree
│ │ │ │ │ │ │ ├── StructureTreePage.kt
│ │ │ │ │ │ │ └── StructureTreeViewModel.kt
│ │ │ │ │ ├── collection
│ │ │ │ │ │ ├── CollectionPage.kt
│ │ │ │ │ │ ├── CollectionViewModel.kt
│ │ │ │ │ │ └── edit
│ │ │ │ │ │ │ ├── WebSiteEditPage.kt
│ │ │ │ │ │ │ └── WebSiteEditViewModel.kt
│ │ │ │ │ ├── home
│ │ │ │ │ │ ├── HomePage.kt
│ │ │ │ │ │ ├── HomeViewModel.kt
│ │ │ │ │ │ ├── index
│ │ │ │ │ │ │ ├── IndexPage.kt
│ │ │ │ │ │ │ └── IndexViewModel.kt
│ │ │ │ │ │ ├── project
│ │ │ │ │ │ │ ├── ProjectPage.kt
│ │ │ │ │ │ │ └── ProjectViewModel.kt
│ │ │ │ │ │ ├── search
│ │ │ │ │ │ │ ├── SearchPage.kt
│ │ │ │ │ │ │ └── SearchViewModel.kt
│ │ │ │ │ │ ├── square
│ │ │ │ │ │ │ ├── SquarePage.kt
│ │ │ │ │ │ │ └── SquareViewModel.kt
│ │ │ │ │ │ └── wenda
│ │ │ │ │ │ │ ├── WenDaPage.kt
│ │ │ │ │ │ │ └── WenDaViewModel.kt
│ │ │ │ │ └── profile
│ │ │ │ │ │ ├── ProfilePage.kt
│ │ │ │ │ │ ├── ProfileViewModel.kt
│ │ │ │ │ │ ├── history
│ │ │ │ │ │ ├── HistoryPage.kt
│ │ │ │ │ │ └── HistoryViewModel.kt
│ │ │ │ │ │ ├── message
│ │ │ │ │ │ ├── MessagePage.kt
│ │ │ │ │ │ └── MessageViewModel.kt
│ │ │ │ │ │ ├── points
│ │ │ │ │ │ ├── PointsRankingViewModel.kt
│ │ │ │ │ │ └── PointsRankingsPage.kt
│ │ │ │ │ │ ├── settings
│ │ │ │ │ │ ├── SettingsPage.kt
│ │ │ │ │ │ └── SettingsViewModel.kt
│ │ │ │ │ │ ├── sharer
│ │ │ │ │ │ ├── SharerPage.kt
│ │ │ │ │ │ └── SharerViewModel.kt
│ │ │ │ │ │ └── user
│ │ │ │ │ │ ├── LoginPage.kt
│ │ │ │ │ │ ├── RegisterPage.kt
│ │ │ │ │ │ └── UserViewModel.kt
│ │ │ │ ├── splash
│ │ │ │ │ └── SplashPage.kt
│ │ │ │ └── webview
│ │ │ │ │ ├── WebView.kt
│ │ │ │ │ └── WebViewCtrl.kt
│ │ │ ├── route
│ │ │ │ ├── BottomNavRoute.kt
│ │ │ │ ├── RouteName.kt
│ │ │ │ └── RouteUtils.kt
│ │ │ └── widget
│ │ │ │ ├── Animations.kt
│ │ │ │ ├── AsyncImage.kt
│ │ │ │ ├── Banner.kt
│ │ │ │ ├── Buttons.kt
│ │ │ │ ├── Common.kt
│ │ │ │ ├── Dialog.kt
│ │ │ │ ├── EditView.kt
│ │ │ │ ├── ListItemView.kt
│ │ │ │ ├── SnackBar.kt
│ │ │ │ ├── SpecIcons.kt
│ │ │ │ └── Title.kt
│ │ │ └── util
│ │ │ ├── CacheDataManager.kt
│ │ │ ├── Navigation.kt
│ │ │ └── RegexUtils.kt
│ └── res
│ │ ├── drawable-v24
│ │ └── ic_launcher_foreground.xml
│ │ ├── drawable
│ │ ├── horizontal_progressbar.xml
│ │ ├── ic_add.xml
│ │ ├── ic_arrow_more.xml
│ │ ├── ic_article.xml
│ │ ├── ic_author.xml
│ │ ├── ic_camera.xml
│ │ ├── ic_close.xml
│ │ ├── ic_community.xml
│ │ ├── ic_data.xml
│ │ ├── ic_delete.xml
│ │ ├── ic_drawer.xml
│ │ ├── ic_exit_app.xml
│ │ ├── ic_feedback.xml
│ │ ├── ic_help.xml
│ │ ├── ic_history_record.xml
│ │ ├── ic_hot.xml
│ │ ├── ic_launcher_background.xml
│ │ ├── ic_menu_settings.xml
│ │ ├── ic_menu_welfare.xml
│ │ ├── ic_message.xml
│ │ ├── ic_ranking.xml
│ │ ├── ic_search.xml
│ │ ├── ic_share.xml
│ │ ├── ic_star.xml
│ │ ├── ic_star_border.xml
│ │ ├── ic_theme.xml
│ │ ├── ic_time.xml
│ │ ├── icon_back.xml
│ │ ├── icon_back_white.xml
│ │ ├── no_banner.png
│ │ └── wukong.jpeg
│ │ ├── 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
│ │ ├── splash_image01.jpg
│ │ ├── splash_image02.jpg
│ │ ├── splash_image03.jpg
│ │ ├── splash_image04.jpg
│ │ └── splash_image05.jpg
│ │ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xxxhdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── values-night
│ │ └── themes.xml
│ │ ├── values
│ │ ├── colors.xml
│ │ ├── strings.xml
│ │ └── themes.xml
│ │ └── xml
│ │ └── network_security_config.xml
│ └── test
│ └── java
│ └── com
│ └── mm
│ └── hamcompose
│ └── ExampleUnitTest.kt
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── screenshot
├── Screenshot_20210927_111453_com.mm.hamcompose.jpg
├── Screenshot_20210927_111526_com.mm.hamcompose.jpg
├── Screenshot_20210927_111602_com.mm.hamcompose.jpg
├── Screenshot_20210927_111619_com.mm.hamcompose.jpg
├── Screenshot_20210927_111628_com.mm.hamcompose.jpg
├── Screenshot_20210927_111722_com.mm.hamcompose.jpg
├── Screenshot_20210927_111736_com.mm.hamcompose.jpg
├── Screenshot_20210927_111821_com.mm.hamcompose.jpg
└── Screenshot_20210927_111932_com.mm.hamcompose.jpg
└── 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 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
20 |
21 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/.idea/jarRepositories.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 |
29 |
30 |
--------------------------------------------------------------------------------
/.idea/kotlinScripting.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # WanAndroidCompose版本
2 |
3 | #### 介绍
4 | 此WanAndroid app客户端项目使用Android官方的Jetpack Compose完成,
5 | 遵循MVVM架构思路,以下为本项目用到的框架:
6 | jetpack compose, viewModel, retrofit, okhttp3, coroutine/flow, paging3,
7 | room, accompanist, hilt, gson, glide/picasso, navigation.
8 |
9 | 项目模块:
10 | 首页(推荐、广场、项目、问答),
11 | 分类(体系、导航、公众号,分享文章),
12 | 收藏(网址、文章),
13 | 我的(我的文章、积分排行、历史浏览、添加文章、设置、消息、主题色、清缓存等)
14 | 登录、登出、注册
15 |
16 |
17 | #### 软件架构
18 | Mvvm, Composable + viewModel + repository
19 |
20 | #### ScreenShot
21 | https://github.com/manqianzhuang/HamApp/tree/origin/screenshot
22 |
23 |
24 | #### 关于项目
25 |
26 | 1. 项目地址: https://github.com/manqianzhuang/HamApp.git
27 | 2. apk地址: https://www.pgyer.com/F9NX
28 | 3. 联系方式: ganzhuangman@gmail.com
29 | 4. API提供: 鸿洋(WanAndroid开放api)
30 |
31 | #### 使用说明
32 |
33 | 1. 此项目仅提供学习用途,未经允许不得用于商业项目
34 | 2. 感谢鸿洋大佬提供的WanAndroid网站,让我们可以学习到很多的android/flutter/前端等技术
35 | 3. 欢迎各位提PR,我会抽时间不断优化代码和修复bug。如有请教,请邮件联系
36 |
37 | #### TODO
38 | 1. 添加动画 = WAIT
39 | 2. 我的消息开发 = WAIT
40 |
41 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/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/mm/hamcompose/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.mm.hamcompose
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.mm.hamcompose", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
9 |
17 |
18 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mm/hamcompose/HamApp.kt:
--------------------------------------------------------------------------------
1 | package com.mm.hamcompose
2 |
3 | import android.annotation.SuppressLint
4 | import android.app.Application
5 | import android.content.Context
6 | import com.mm.hamcompose.data.store.DataStoreUtils
7 | import dagger.hilt.android.HiltAndroidApp
8 |
9 | /**
10 | * 1. 所有使用 Hilt 的 App 必须包含 一个使用 @HiltAndroidApp 注解的 Application
11 | * 2. @HiltAndroidApp 将会触发 Hilt 代码的生成,包括用作应用程序依赖项容器的基类
12 | * 3. 生成的 Hilt 组件依附于 Application 的生命周期,它也是 App 的父组件,提供其他组件访问的依赖
13 | * 4. 在 Application 中设置好 @HiltAndroidApp 之后,就可以使用 Hilt 提供的组件了,
14 | * Hilt 提供的 @AndroidEntryPoint 注解用于提供 Android 类的依赖(Activity、Fragment、View、Service、BroadcastReceiver)等等
15 | * Application 使用 @HiltAndroidApp 注解
16 | */
17 | @HiltAndroidApp
18 | class HamApp: Application() {
19 | companion object {
20 | @SuppressLint("StaticFieldLeak")
21 | lateinit var CONTEXT: Context
22 | }
23 |
24 | override fun onCreate() {
25 | super.onCreate()
26 | CONTEXT = this
27 | DataStoreUtils.init(this)
28 | }
29 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mm/hamcompose/data/bean/ConstBean.kt:
--------------------------------------------------------------------------------
1 | package com.mm.hamcompose.data.bean
2 |
3 | import androidx.room.Entity
4 | import androidx.room.PrimaryKey
5 | import com.mm.hamcompose.data.db.DbConst
6 |
7 | const val MY_USER_ID = -999
8 |
9 | data class MenuTitle(
10 | val title: String,
11 | val iconRes: Int?
12 | )
13 |
14 | data class TabTitle(
15 | val id: Int,
16 | val text: String,
17 | var cachePosition: Int = 0,
18 | var selected: Boolean = false
19 | )
20 |
21 | @Entity(tableName = DbConst.history)
22 | data class HistoryRecord(
23 | @PrimaryKey var id: Int,
24 | var title: String,
25 | var link: String,
26 | var niceDate: String,
27 | var shareUser: String,
28 | var userId: Int,
29 | var author: String,
30 | var superChapterId: Int,
31 | var superChapterName: String,
32 | var chapterId: Int,
33 | var chapterName: String,
34 | var desc: String,
35 | )
--------------------------------------------------------------------------------
/app/src/main/java/com/mm/hamcompose/data/db/DbConst.kt:
--------------------------------------------------------------------------------
1 | package com.mm.hamcompose.data.db
2 |
3 | object DbConst {
4 | const val dbVersion = 1
5 | const val hotKeyDbName = "hot_key_db"
6 | const val userDbName = "user_db"
7 | const val historyDbName = "history_db"
8 | const val hotKey = "hot_key"
9 | const val userInfo = "user_info"
10 | const val history = "history"
11 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mm/hamcompose/data/db/history/HistoryDao.kt:
--------------------------------------------------------------------------------
1 | package com.mm.hamcompose.data.db.history
2 |
3 | import androidx.room.Dao
4 | import androidx.room.Insert
5 | import androidx.room.OnConflictStrategy
6 | import androidx.room.Query
7 | import com.mm.hamcompose.data.bean.HistoryRecord
8 |
9 | @Dao
10 | interface HistoryDao {
11 |
12 | @Insert(onConflict = OnConflictStrategy.REPLACE)
13 | suspend fun insertHistory(vararg history: HistoryRecord)
14 |
15 | @Query("SELECT * FROM history")
16 | suspend fun queryAll(): Array
17 |
18 | @Query("DELETE FROM history")
19 | suspend fun deleteAll()
20 |
21 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mm/hamcompose/data/db/history/HistoryDatabase.kt:
--------------------------------------------------------------------------------
1 | package com.mm.hamcompose.data.db.history
2 |
3 | import androidx.room.Database
4 | import androidx.room.RoomDatabase
5 | import com.mm.hamcompose.data.bean.HistoryRecord
6 | import com.mm.hamcompose.data.db.DbConst
7 |
8 | @Database(entities = [HistoryRecord::class], version = DbConst.dbVersion)
9 | abstract class HistoryDatabase: RoomDatabase() {
10 | abstract fun historyDao(): HistoryDao
11 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mm/hamcompose/data/db/hotkey/HotKeyDatabase.kt:
--------------------------------------------------------------------------------
1 | package com.mm.hamcompose.data.db.hotkey
2 |
3 | import androidx.room.Database
4 | import androidx.room.RoomDatabase
5 | import com.mm.hamcompose.data.bean.Hotkey
6 | import com.mm.hamcompose.data.db.DbConst
7 |
8 | @Database(entities = [Hotkey::class], version = DbConst.dbVersion)
9 | abstract class HotkeyDatabase: RoomDatabase() {
10 | abstract fun hotkeyDao(): HotkeysDao
11 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mm/hamcompose/data/db/hotkey/HotkeysDao.kt:
--------------------------------------------------------------------------------
1 | package com.mm.hamcompose.data.db.hotkey
2 |
3 | import androidx.room.*
4 | import com.mm.hamcompose.data.bean.Hotkey
5 |
6 | @Dao
7 | interface HotkeysDao {
8 |
9 | @Insert(onConflict = OnConflictStrategy.REPLACE)
10 | suspend fun insertHotkeys(vararg keys: Hotkey)
11 |
12 | @Update
13 | suspend fun updateHotkeys(vararg keys: Hotkey)
14 |
15 | @Delete
16 | suspend fun deleteKeys(vararg keys: Hotkey)
17 |
18 | @Query("SELECT * FROM hot_key")
19 | suspend fun loadAllKeys(): Array
20 |
21 | @Query("DELETE FROM hot_key")
22 | suspend fun deleteAll()
23 |
24 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mm/hamcompose/data/db/user/UserInfoDao.kt:
--------------------------------------------------------------------------------
1 | package com.mm.hamcompose.data.db.user
2 |
3 | import androidx.room.*
4 | import com.mm.hamcompose.data.bean.UserInfo
5 | import com.mm.hamcompose.data.db.DbConst
6 | import retrofit2.http.DELETE
7 |
8 | @Dao
9 | interface UserInfoDao {
10 |
11 | @Insert(onConflict = OnConflictStrategy.REPLACE)
12 | suspend fun insertUserInfo(userInfo: UserInfo)
13 |
14 | @Update
15 | suspend fun updateUserInfo(userInfo: UserInfo)
16 |
17 | @Query("SELECT * FROM user_info")
18 | suspend fun queryUserInfo(): List
19 |
20 | @Delete(entity = UserInfo::class)
21 | suspend fun deleteUserInfo(vararg userInfo: UserInfo): Int
22 |
23 | @Query("DELETE FROM user_info")
24 | suspend fun deleteAllUserInfo()
25 |
26 |
27 |
28 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mm/hamcompose/data/db/user/UserInfoDatabase.kt:
--------------------------------------------------------------------------------
1 | package com.mm.hamcompose.data.db.user
2 |
3 | import androidx.room.Database
4 | import androidx.room.RoomDatabase
5 | import androidx.room.TypeConverters
6 | import com.mm.hamcompose.data.bean.IntTypeConverter
7 | import com.mm.hamcompose.data.bean.UserInfo
8 | import com.mm.hamcompose.data.db.DbConst
9 |
10 | @Database(entities = [UserInfo::class], version = DbConst.dbVersion)
11 | @TypeConverters(IntTypeConverter::class)
12 | abstract class UserInfoDatabase: RoomDatabase() {
13 | abstract fun userInfoDao(): UserInfoDao
14 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mm/hamcompose/data/http/ApiCall.kt:
--------------------------------------------------------------------------------
1 | package com.mm.hamcompose.data.http
2 |
3 | import android.annotation.SuppressLint
4 | import com.jakewharton.retrofit2.adapter.kotlin.coroutines.CoroutineCallAdapterFactory
5 | import com.mm.hamcompose.data.http.interceptor.CacheCookieInterceptor
6 | import com.mm.hamcompose.data.http.interceptor.LogInterceptor
7 | import com.mm.hamcompose.data.http.interceptor.SetCookieInterceptor
8 | import okhttp3.OkHttpClient
9 | import retrofit2.Retrofit
10 | import retrofit2.converter.gson.GsonConverterFactory
11 | import java.security.SecureRandom
12 | import java.security.cert.X509Certificate
13 | import java.util.concurrent.TimeUnit
14 | import javax.net.ssl.*
15 |
16 | /**
17 | * Created by Superman. 19/5/27
18 | */
19 | object ApiCall {
20 |
21 | /**
22 | * 请求超时时间
23 | */
24 | private const val DEFAULT_TIMEOUT = 30000
25 | private lateinit var SERVICE: HttpService
26 |
27 | //手动创建一个OkHttpClient并设置超时时间
28 | val retrofit: HttpService
29 | get() {
30 | if (!ApiCall::SERVICE.isInitialized) {
31 | SERVICE = Retrofit.Builder()
32 | .client(okHttp)
33 | .addConverterFactory(GsonConverterFactory.create())
34 | .addCallAdapterFactory(CoroutineCallAdapterFactory.invoke())
35 | .baseUrl(HttpService.url)
36 | .build()
37 | .create(HttpService::class.java)
38 | }
39 | return SERVICE
40 | }
41 |
42 | //手动创建一个OkHttpClient并设置超时时间
43 | val okHttp: OkHttpClient
44 | get() {
45 | return OkHttpClient.Builder().run {
46 | connectTimeout(DEFAULT_TIMEOUT.toLong(), TimeUnit.MILLISECONDS)
47 | readTimeout(DEFAULT_TIMEOUT.toLong(), TimeUnit.MILLISECONDS)
48 | writeTimeout(DEFAULT_TIMEOUT.toLong(), TimeUnit.MILLISECONDS)
49 | addInterceptor(SetCookieInterceptor())
50 | addInterceptor(CacheCookieInterceptor())
51 | addInterceptor(LogInterceptor())
52 | //不验证证书
53 | sslSocketFactory(createSSLSocketFactory())
54 | hostnameVerifier(TrustAllNameVerifier())
55 | build()
56 | }
57 | }
58 |
59 | private fun createSSLSocketFactory(): SSLSocketFactory {
60 | lateinit var ssfFactory: SSLSocketFactory
61 | try {
62 | val sslFactory = SSLContext.getInstance("TLS")
63 | sslFactory.init(null, arrayOf(TrustAllCerts()), SecureRandom());
64 | ssfFactory = sslFactory.socketFactory
65 | } catch (e: Exception) {
66 | print("SSL错误:${e.message}")
67 | }
68 | return ssfFactory
69 | }
70 |
71 | }
72 |
73 | class TrustAllNameVerifier: HostnameVerifier {
74 | @SuppressLint("BadHostnameVerifier")
75 | override fun verify(hostname: String?, session: SSLSession?): Boolean = true
76 | }
77 |
78 | @SuppressLint("CustomX509TrustManager")
79 | class TrustAllCerts : X509TrustManager {
80 |
81 | @SuppressLint("TrustAllX509TrustManager")
82 | override fun checkClientTrusted(chain: Array?, authType: String?) {}
83 |
84 | @SuppressLint("TrustAllX509TrustManager")
85 | override fun checkServerTrusted(chain: Array?, authType: String?) {}
86 |
87 | override fun getAcceptedIssuers(): Array = arrayOf()
88 | }
89 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mm/hamcompose/data/http/HttpResult.kt:
--------------------------------------------------------------------------------
1 | package com.mm.hamcompose.data.http
2 |
3 | import java.lang.Exception
4 |
5 | sealed class HttpResult {
6 |
7 | data class Success(val result: T): HttpResult()
8 | data class Error(val exception: Exception): HttpResult()
9 |
10 | }
11 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mm/hamcompose/data/http/interceptor/CacheCookieInterceptor.kt:
--------------------------------------------------------------------------------
1 | package com.mm.hamcompose.data.http.interceptor
2 |
3 | import com.mm.hamcompose.data.store.DataStoreUtils
4 | import okhttp3.Interceptor
5 | import okhttp3.Response
6 |
7 | class CacheCookieInterceptor: Interceptor {
8 |
9 | private val loginUrl = "user/login"
10 | private val registerUrl = "user/register"
11 | private val SET_COOKIE_KEY = "set-cookie"
12 |
13 | override fun intercept(chain: Interceptor.Chain): Response {
14 | val request = chain.request()
15 | val response = chain.proceed(request)
16 | val requestUrl = request.url().toString()
17 | val domain = request.url().host()
18 | if (aboutUser(requestUrl)) {
19 | val cookies = response.headers(SET_COOKIE_KEY)
20 | if (cookies.isNotEmpty()) {
21 | //cookie可能有多个,都保存下来
22 | DataStoreUtils.putSyncData(domain, encodeCookie(cookies))
23 | }
24 | }
25 | return response
26 | }
27 |
28 | private fun aboutUser(url: String): Boolean = url.contains(loginUrl) or url.contains(registerUrl)
29 | }
30 |
31 | /**
32 | * 整理cookie
33 | */
34 | private fun encodeCookie(cookies: List): String {
35 | val sb = StringBuilder()
36 | val set = HashSet()
37 | cookies
38 | .map { cookie ->
39 | cookie.split(";".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
40 | }
41 | .forEach { it ->
42 | it.filterNot { set.contains(it) }.forEach { set.add(it) }
43 | }
44 |
45 | val ite = set.iterator()
46 | while (ite.hasNext()) {
47 | val cookie = ite.next()
48 | sb.append(cookie).append(";")
49 | }
50 |
51 | val last = sb.lastIndexOf(";")
52 | if (sb.length - 1 == last) {
53 | sb.deleteCharAt(last)
54 | }
55 |
56 | return sb.toString()
57 | }
58 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mm/hamcompose/data/http/interceptor/SetCookieInterceptor.kt:
--------------------------------------------------------------------------------
1 | package com.mm.hamcompose.data.http.interceptor
2 |
3 | import com.mm.hamcompose.data.store.DataStoreUtils
4 | import okhttp3.Interceptor
5 | import okhttp3.Response
6 |
7 | class SetCookieInterceptor: Interceptor {
8 |
9 | override fun intercept(chain: Interceptor.Chain): Response {
10 | val request = chain.request()
11 | val builder = request.newBuilder()
12 | val domain = request.url().host()
13 | //获取domain内的cookie
14 | if (domain.isNotEmpty()) {
15 | val cookie: String = DataStoreUtils.readStringData(domain, "")
16 | if (cookie.isNotEmpty()) {
17 | builder.addHeader("Cookie", cookie)
18 | }
19 | }
20 | return chain.proceed(builder.build())
21 | }
22 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mm/hamcompose/data/http/paging/BasePagingSource.kt:
--------------------------------------------------------------------------------
1 | package com.mm.hamcompose.data.http.paging
2 |
3 | import androidx.paging.PagingSource
4 | import androidx.paging.PagingState
5 | import com.blankj.utilcode.util.LogUtils
6 | import com.mm.hamcompose.data.bean.BasicBean
7 | import com.mm.hamcompose.data.bean.ListWrapper
8 | import com.mm.hamcompose.data.http.HttpResult
9 |
10 | class BasePagingSource constructor(
11 | private val callDataFromRemoteServer: suspend (page: Int)-> HttpResult>>
12 | ): PagingSource() {
13 |
14 | private var page: Int = -1
15 |
16 | override fun getRefreshKey(state: PagingState): Int? {
17 | return state.anchorPosition?.let {
18 | val anchorPage = state.closestPageToPosition(it)
19 | anchorPage?.prevKey?.plus(1) ?: anchorPage?.nextKey?.minus(1)
20 | }
21 | }
22 |
23 | override suspend fun load(params: LoadParams): LoadResult {
24 | println("当前页 ${params.key}")
25 | page = params.key ?: 0
26 | return when (val response = callDataFromRemoteServer(page)) {
27 | is HttpResult.Success -> {
28 | val data = response.result.data
29 | val hasNotNext = (data!!.datas.size < params.loadSize) && (data.over)
30 | LoadResult.Page(
31 | data = response.result.data!!.datas,
32 | prevKey = if (page - 1 > 0) page - 1 else null,
33 | nextKey = if (hasNotNext) null else page+1
34 | )
35 | }
36 | is HttpResult.Error -> {
37 | LogUtils.e("网络请求异常: ${response.exception.message}")
38 | LoadResult.Error(response.exception)
39 | }
40 | }
41 | }
42 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mm/hamcompose/data/http/paging/GirlPhotoPagingSource.kt:
--------------------------------------------------------------------------------
1 | package com.mm.hamcompose.data.http.paging
2 |
3 | import androidx.paging.PagingSource
4 | import androidx.paging.PagingState
5 | import com.blankj.utilcode.util.LogUtils
6 | import com.mm.hamcompose.data.bean.WelfareData
7 | import com.mm.hamcompose.data.http.HttpService
8 | import javax.inject.Inject
9 |
10 | class GirlPhotoPagingSource @Inject constructor(
11 | private val apiService: HttpService,
12 | ): PagingSource() {
13 |
14 | override fun getRefreshKey(state: PagingState): Int? = null
15 |
16 | override suspend fun load(params: LoadParams): LoadResult {
17 | return try {
18 | LogUtils.e("currentPage= ${params.key}, size=${params.loadSize}")
19 | val page = params.key?: 0
20 | val response = apiService.getWelfareList("Girl", "Girl", page, params.loadSize)
21 | val isNextPage = response.data!!.isNotEmpty()
22 | LoadResult.Page(
23 | data = response.data!!,
24 | prevKey = if (page>0) page-1 else null,
25 | nextKey = if (isNextPage) page+1 else null
26 | )
27 | } catch (e: Exception) {
28 | LogUtils.e("网络请求异常: ${e.message}")
29 | LoadResult.Error(e)
30 | }
31 | }
32 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mm/hamcompose/data/http/paging/PagingFactory.kt:
--------------------------------------------------------------------------------
1 | package com.mm.hamcompose.data.http.paging
2 |
3 | import androidx.paging.PagingConfig
4 |
5 | class PagingFactory {
6 |
7 | val pagingConfig = PagingConfig(
8 |
9 | // 每页显示的数据的大小
10 | pageSize = 20,
11 | //开启占位符
12 | enablePlaceholders = true,
13 | //预刷新的距离,距离最后一个 item 多远时加载数据
14 | prefetchDistance = 4,
15 | //初始化加载数量,默认为 pageSize * 3
16 | initialLoadSize = 20
17 | )
18 |
19 |
20 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mm/hamcompose/di/module/DatabaseModule.kt:
--------------------------------------------------------------------------------
1 | package com.mm.hamcompose.di.module
2 |
3 | import androidx.room.Room
4 | import com.mm.hamcompose.HamApp
5 | import com.mm.hamcompose.data.db.DbConst
6 | import com.mm.hamcompose.data.db.history.HistoryDatabase
7 | import com.mm.hamcompose.data.db.hotkey.HotkeyDatabase
8 | import com.mm.hamcompose.data.db.user.UserInfoDatabase
9 | import dagger.Module
10 | import dagger.Provides
11 | import dagger.hilt.InstallIn
12 | import dagger.hilt.components.SingletonComponent
13 | import javax.inject.Singleton
14 |
15 | @Module
16 | @InstallIn(SingletonComponent::class)
17 | class DatabaseModule {
18 |
19 | @Singleton
20 | @Provides
21 | fun provideHotkeyDataBase(): HotkeyDatabase {
22 | return Room.databaseBuilder(HamApp.CONTEXT, HotkeyDatabase::class.java, DbConst.hotKeyDbName)
23 | .build()
24 | }
25 |
26 | @Singleton
27 | @Provides
28 | fun provideUserInfoDataBase(): UserInfoDatabase {
29 | return Room.databaseBuilder(HamApp.CONTEXT, UserInfoDatabase::class.java, DbConst.userDbName)
30 | .build()
31 | }
32 |
33 | @Singleton
34 | @Provides
35 | fun provideHistoryDataBase(): HistoryDatabase {
36 | return Room.databaseBuilder(HamApp.CONTEXT, HistoryDatabase::class.java, DbConst.historyDbName)
37 | .build()
38 | }
39 |
40 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mm/hamcompose/di/module/NetworkModule.kt:
--------------------------------------------------------------------------------
1 | package com.mm.hamcompose.di.module
2 |
3 | import com.mm.hamcompose.data.http.ApiCall
4 | import com.mm.hamcompose.data.http.HttpService
5 | import com.mm.hamcompose.data.http.interceptor.LogInterceptor
6 | import com.mm.hamcompose.repository.HttpRepository
7 | import com.mm.hamcompose.repository.HttpRepositoryImpl
8 | import dagger.Module
9 | import dagger.Provides
10 | import dagger.hilt.InstallIn
11 | import dagger.hilt.components.SingletonComponent
12 | import okhttp3.Interceptor
13 | import okhttp3.OkHttpClient
14 | import javax.inject.Singleton
15 |
16 | //这里使用了SingletonComponent,因此 NetworkModule 绑定到 Application 的生命周期
17 | @Module
18 | @InstallIn(SingletonComponent::class)
19 | class NetworkModule {
20 |
21 | @Singleton
22 | @Provides
23 | fun provideApiService(): HttpService = ApiCall.retrofit
24 |
25 | @Singleton
26 | @Provides
27 | fun provideOkHttp(): OkHttpClient = ApiCall.okHttp
28 |
29 | @Singleton
30 | @Provides
31 | fun provideLogInterceptor(): Interceptor = LogInterceptor()
32 |
33 | @Provides
34 | fun provideRepository(apiService: HttpService): HttpRepository {
35 | return HttpRepositoryImpl(apiService)
36 | }
37 |
38 | // @Singleton
39 | // @Provides
40 | // fun provideRepo(apiService: HttpService): HttpRepository {
41 | // return HttpRepository(apiService)
42 | // }
43 |
44 |
45 |
46 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mm/hamcompose/di/scope/ActivityScope.kt:
--------------------------------------------------------------------------------
1 | package com.mm.hamcompose.di.scope
2 |
3 | import javax.inject.Scope
4 |
5 | @Scope
6 | @MustBeDocumented
7 | annotation class ActivityScope()
8 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mm/hamcompose/di/scope/ApplicationScope.kt:
--------------------------------------------------------------------------------
1 | package com.mm.hamcompose.di.scope
2 |
3 | import javax.inject.Scope
4 |
5 | @Scope
6 | @MustBeDocumented
7 | annotation class ApplicationScope()
8 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mm/hamcompose/di/scope/FragmentScope.kt:
--------------------------------------------------------------------------------
1 | package com.mm.hamcompose.di.scope
2 |
3 | import javax.inject.Scope
4 |
5 | @Scope
6 | @MustBeDocumented
7 | annotation class FragmentScope()
8 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mm/hamcompose/repository/HttpRepository.kt:
--------------------------------------------------------------------------------
1 | package com.mm.hamcompose.repository
2 |
3 | import androidx.paging.PagingData
4 | import com.mm.hamcompose.data.bean.*
5 | import com.mm.hamcompose.data.http.HttpResult
6 | import kotlinx.coroutines.flow.Flow
7 |
8 | //类型别名,用于定义较长的泛型类型
9 | typealias BANNER = Flow>>
10 | typealias ARTICLE = Flow>>
11 | typealias HOTKEY = Flow>>
12 | typealias PARENT = Flow>>
13 | typealias NAVIGATION = Flow>>
14 | typealias USERINFO = Flow>
15 | typealias POINTS = Flow>
16 | typealias ANY = Flow>
17 | typealias COLLECT = Flow>
18 | typealias SHARER = Flow>>
19 | typealias ONE_PARENT = Flow>
20 | typealias BASIC_USERINFO = Flow>
21 |
22 | typealias WELFARE = Flow>
23 |
24 | typealias PagingAny = Flow>
25 | typealias PagingPoints = Flow>
26 | typealias PagingCollect = Flow>
27 | typealias PagingArticle = Flow>
28 | typealias PagingWelfare = Flow>
29 |
30 | interface HttpRepository {
31 | //普通请求
32 | suspend fun getBanners(): BANNER
33 | suspend fun getTopArticles(): ARTICLE
34 | suspend fun getHotkeys(): HOTKEY
35 | suspend fun getStructureList(): PARENT
36 | suspend fun getNavigationList(): NAVIGATION
37 | suspend fun getPublicInformation(): PARENT
38 | suspend fun getProjectCategory(): PARENT
39 | suspend fun register(userName: String, password: String, repassword: String): USERINFO
40 | suspend fun login(userName: String, password: String): USERINFO
41 | suspend fun logout(): ANY
42 | suspend fun getMyPointsRanking(): POINTS
43 | suspend fun getMessageCount(): Flow>
44 | suspend fun getCollectUrls(): PARENT
45 | suspend fun collectInnerArticle(id: Int): ANY
46 | suspend fun uncollectInnerArticle(id: Int): ANY
47 | suspend fun uncollectArticleById(id: Int, originId: Int): ANY
48 | suspend fun addNewWebsiteCollect(title: String, linkUrl: String): ONE_PARENT
49 | suspend fun addNewArticleCollect(title: String, linkUrl: String, author: String): COLLECT
50 | suspend fun deleteWebsite(id: Int): ANY
51 | suspend fun editCollectWebsite(id: Int, title: String, linkUrl: String): ANY
52 | suspend fun getMyShareArticles(page: Int): SHARER
53 | suspend fun getAuthorShareArticles(userId: Int, page: Int): SHARER
54 | suspend fun deleteMyShareArticle(articleId: Int): ANY
55 | suspend fun addMyShareArticle(title: String, link: String, shareUser: String): ANY
56 | suspend fun getBasicUserInfo(): BASIC_USERINFO
57 |
58 | //干货 gank.io的妹纸福利列表
59 | suspend fun getWelfareData(page: Int, pageSize: Int): WELFARE
60 |
61 | //分页请求
62 | fun getIndexData(): PagingArticle
63 | fun getSquareData(): PagingArticle
64 | fun getWendaData(): PagingArticle
65 | fun getProjects(cId: Int): PagingArticle
66 | fun getPublicArticles(publicId: Int): PagingArticle
67 | fun getStructureArticles(param: Any): PagingArticle
68 | fun searchArticleWithKey(publicId: Int, key: String): PagingArticle
69 | fun queryArticle(key: String): PagingArticle
70 | fun getPointsRankings(): PagingPoints
71 | fun getPointsRecords(): PagingPoints
72 | fun getCollectionList(): PagingCollect
73 | fun getUnreadMessages(): PagingAny
74 | fun getReadedMessages(): PagingAny
75 | fun getWelfareData(key: String): PagingWelfare
76 |
77 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mm/hamcompose/theme/Color.kt:
--------------------------------------------------------------------------------
1 | package com.mm.hamcompose.theme
2 |
3 | import androidx.compose.ui.graphics.Color
4 | import com.mm.hamcompose.HamApp
5 | import com.mm.hamcompose.R
6 |
7 | val Transparent = Color(0x00000000)
8 |
9 | val themeColors = arrayOf(
10 | Color(HamApp.CONTEXT.resources.getColor(R.color.primary)),
11 | Color(HamApp.CONTEXT.resources.getColor(R.color.purple_200)),
12 | Color(HamApp.CONTEXT.resources.getColor(R.color.purple_500)),
13 | Color(HamApp.CONTEXT.resources.getColor(R.color.purple_700)),
14 | Color(HamApp.CONTEXT.resources.getColor(R.color.teal_700)),
15 | Color(HamApp.CONTEXT.resources.getColor(R.color.navajo_white)),
16 | Color(HamApp.CONTEXT.resources.getColor(R.color.medium_blue)),
17 | Color(HamApp.CONTEXT.resources.getColor(R.color.hot_pink)),
18 | Color(HamApp.CONTEXT.resources.getColor(R.color.chocolate)),
19 | Color(HamApp.CONTEXT.resources.getColor(R.color.dark_orange)),
20 | Color(HamApp.CONTEXT.resources.getColor(R.color.orange)),
21 | Color(HamApp.CONTEXT.resources.getColor(R.color.gold)),
22 | Color(HamApp.CONTEXT.resources.getColor(R.color.yellow)),
23 | Color(HamApp.CONTEXT.resources.getColor(R.color.fire_red)),
24 | Color(HamApp.CONTEXT.resources.getColor(R.color.light_green)),
25 | Color(HamApp.CONTEXT.resources.getColor(R.color.sprint_green)),
26 | //Color(HamApp.CONTEXT.resources.getColor(R.color.azure)),
27 | )
28 |
29 | val splashText = Color(0x25000000)
30 | val white = Color(0xFFFFFFFF)
31 | val white1 = Color(0xFFF7F7F7)
32 | val white2 = Color(0xFFEDEDED)
33 | val white3 = Color(0xFFE5E5E5)
34 | val white4 = Color(0xFFD5D5D5)
35 | val white5 = Color(0xFFCCCCCC)
36 | val black = Color(0xFF000000)
37 | val black1 = Color(0xFF1E1E1E)
38 | val black2 = Color(0xFF111111)
39 | val black3 = Color(0xFF191919)
40 | val black4 = Color(0xFF252525)
41 | val black5 = Color(0xFF2C2C2C)
42 | val black6 = Color(0xFF07130A)
43 | val black7 = Color(0xFF292929)
44 | val grey1 = Color(0xFF888888)
45 | val grey2 = Color(0xFFCCC7BF)
46 | val grey3 = Color(0xFF767676)
47 | val grey4 = Color(0xFFB2B2B2)
48 | val grey5 = Color(0xFF5E5E5E)
49 | val green1 = Color(0xFFB0EB6E)
50 | val green2 = Color(0xFF6DB476)
51 | val green3 = Color(0xFF67BF63)
52 | val red = Color(0xFFFF0000)
53 | val red1 = Color(0xFFDF5554)
54 | val red2 = Color(0xFFDD302E)
55 | val red3 = Color(0xFFF77B7A)
56 | val red4 = Color(0xFFD42220)
57 | val red5 = Color(0xFFC51614)
58 | val red6 = Color(0xFFF74D4B)
59 | val red7 = Color(0xFFDC514E)
60 | val red8 = Color(0xFFCBC7BF)
61 | val yellow1 = Color(0xFFF6CA23)
62 | val blue = Color(0xFF0000FF)
63 | val info = Color(0xFF018786)
64 | val warn = Color(0xFFD87831)
--------------------------------------------------------------------------------
/app/src/main/java/com/mm/hamcompose/theme/Dimens.kt:
--------------------------------------------------------------------------------
1 | package com.mm.hamcompose.theme
2 |
3 | import androidx.compose.ui.unit.dp
4 | import androidx.compose.ui.unit.sp
5 |
6 | val ToolBarHeight = 48.dp
7 | val TabBarHeight = 48.dp
8 | val SearchBarHeight = 42.dp
9 | val BottomNavBarHeight = 56.dp
10 | val ListTitleHeight = 30.dp
11 |
12 | val PrimaryButtonHeight = 36.dp
13 | val MediumButtonHeight = 28.dp
14 | val SmallButtonHeight = 28.dp
15 |
16 |
17 | val H1 = 48.sp //超大号标题
18 | val H2 = 36.sp //大号标题
19 | val H3 = 24.sp //主标题
20 | val H4 = 20.sp //普通标题
21 | val H5 = 16.sp //内容文本
22 | val H6 = 14.sp //普通文字尺寸
23 | val H7 = 12.sp //提示语尺寸
24 |
25 | val ToolBarTitleSize = 18.sp
26 |
27 | val cardCorner = 5.dp //卡片的圆角
28 | val buttonCorner = 3.dp //按钮的圆角
29 | val buttonHeight = 36.dp //按钮的高度
30 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mm/hamcompose/theme/Shape.kt:
--------------------------------------------------------------------------------
1 | package com.mm.hamcompose.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 HamShapes = Shapes(
8 | small = RoundedCornerShape(4.dp),
9 | medium = RoundedCornerShape(4.dp),
10 | large = RoundedCornerShape(0.dp)
11 | )
--------------------------------------------------------------------------------
/app/src/main/java/com/mm/hamcompose/theme/Type.kt:
--------------------------------------------------------------------------------
1 | package com.mm.hamcompose.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/src/main/java/com/mm/hamcompose/ui/HomeActivity.kt:
--------------------------------------------------------------------------------
1 | package com.mm.hamcompose.ui
2 |
3 | import android.os.Bundle
4 | import androidx.activity.ComponentActivity
5 | import androidx.activity.compose.setContent
6 | import androidx.core.view.WindowCompat
7 | import com.blankj.utilcode.util.LogUtils
8 | import com.blankj.utilcode.util.ToastUtils
9 | import com.mm.hamcompose.R
10 | import dagger.hilt.android.AndroidEntryPoint
11 | import kotlin.system.exitProcess
12 |
13 | @AndroidEntryPoint
14 | class HomeActivity : ComponentActivity() {
15 |
16 | override fun onCreate(savedInstanceState: Bundle?) {
17 | super.onCreate(savedInstanceState)
18 |
19 | WindowCompat.setDecorFitsSystemWindows(window, false)
20 | window.navigationBarColor = resources.getColor(R.color.transparent)
21 | // window.setFlags(
22 | // WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION,
23 | // WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION
24 | // )
25 | setContent { HomeEntry(onBackPressedDispatcher) }
26 | }
27 |
28 | private var cacheMills: Long = 0L
29 | override fun onBackPressed() {
30 | LogUtils.e("是否可以回退 ${onBackPressedDispatcher.hasEnabledCallbacks()}")
31 | if (!onBackPressedDispatcher.hasEnabledCallbacks()) {
32 | if (System.currentTimeMillis() - cacheMills > 1000L) {
33 | cacheMills = System.currentTimeMillis()
34 | ToastUtils.showShort("连按两次退出app")
35 | } else {
36 | this.finish()
37 | exitProcess(0)
38 | }
39 | }
40 | else super.onBackPressed()
41 | }
42 | }
43 |
44 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mm/hamcompose/ui/HomeEntry.kt:
--------------------------------------------------------------------------------
1 | package com.mm.hamcompose.ui
2 |
3 | import androidx.activity.OnBackPressedDispatcher
4 | import androidx.compose.runtime.*
5 | import com.mm.hamcompose.theme.HamTheme
6 | import com.mm.hamcompose.ui.page.base.HamScaffold
7 | import com.mm.hamcompose.ui.page.splash.SplashPage
8 |
9 | @Composable
10 | fun HomeEntry(backDispatcher: OnBackPressedDispatcher) {
11 |
12 | //是否闪屏页
13 | var isSplash by remember { mutableStateOf(true) }
14 | if (isSplash) {
15 | SplashPage { isSplash = false }
16 | } else {
17 | HamTheme { HamScaffold() }
18 | }
19 |
20 | }
21 |
22 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mm/hamcompose/ui/page/base/BaseCollectViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.mm.hamcompose.ui.page.base
2 |
3 | import com.mm.hamcompose.data.http.HttpResult
4 | import com.mm.hamcompose.repository.HttpRepository
5 | import kotlinx.coroutines.flow.collectLatest
6 |
7 | abstract class BaseCollectViewModel constructor(
8 | private val httpRepo: HttpRepository
9 | ) : CacheHistoryViewModel() {
10 |
11 | fun collectArticleById(id: Int) {
12 | async {
13 | httpRepo.collectInnerArticle(id).collectLatest { response ->
14 | when (response) {
15 | is HttpResult.Success -> { }
16 | is HttpResult.Error -> {
17 | //收藏接口,不走success判断分支
18 | val nullNotice = "the result of remote's request is null"
19 | if (response.exception.message==nullNotice) {
20 | println("收藏成功(id=$id)")
21 | message.value = "收藏成功"
22 | } else {
23 | message.value = response.exception.message ?: "未知异常"
24 | }
25 | }
26 | }
27 | }
28 | }
29 | }
30 |
31 | fun uncollectArticleById(id: Int) {
32 | async {
33 | httpRepo.uncollectInnerArticle(id).collectLatest { response ->
34 | when (response) {
35 | is HttpResult.Success -> { }
36 | is HttpResult.Error -> {
37 | //收藏接口,不走success判断分支
38 | val nullNotice = "the result of remote's request is null"
39 | if (response.exception.message==nullNotice) {
40 | println("取消收藏(id=$id)")
41 | message.value = "取消收藏"
42 | } else {
43 | message.value = response.exception.message ?: "未知异常"
44 | }
45 | }
46 | }
47 | }
48 | }
49 | }
50 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mm/hamcompose/ui/page/base/BaseRepository.kt:
--------------------------------------------------------------------------------
1 | package com.mm.hamcompose.ui.page.base
2 |
3 | import androidx.paging.Pager
4 | import androidx.paging.PagingConfig
5 | import androidx.paging.PagingData
6 | import com.mm.hamcompose.data.bean.BasicBean
7 | import com.mm.hamcompose.data.bean.ListWrapper
8 | import com.mm.hamcompose.data.http.HttpResult
9 | import com.mm.hamcompose.data.http.paging.BasePagingSource
10 | import com.mm.hamcompose.data.http.paging.PagingFactory
11 | import kotlinx.coroutines.Dispatchers
12 | import kotlinx.coroutines.flow.Flow
13 | import kotlinx.coroutines.flow.flow
14 | import kotlinx.coroutines.flow.flowOn
15 |
16 | open class BaseRepository {
17 |
18 | fun flowable(call: suspend ()-> BasicBean): Flow> {
19 | return flow {
20 | val result = try {
21 | val response = call()
22 | if (response.errorCode==0) {
23 | if (response.data!=null) {
24 | HttpResult.Success(response.data!!)
25 | } else {
26 | throw Exception("the result of remote's request is null")
27 | }
28 | } else {
29 | throw Exception(response.errorMsg)
30 | }
31 | } catch (ex: Exception) {
32 | HttpResult.Error(ex)
33 | }
34 | emit(result)
35 | }.flowOn(Dispatchers.IO)
36 | }
37 |
38 | fun pager(
39 | initKey: Int = 0,
40 | baseConfig: PagingConfig = PagingFactory().pagingConfig,
41 | callAction: suspend (page: Int)-> BasicBean>
42 | ): Flow> {
43 |
44 | // config = 加载分页数据的配置项
45 | // initialKey = 设置默认的初始页
46 | // pagingSourceFactory = 加载分页的驱动器
47 | return Pager(
48 | config = baseConfig,
49 | initialKey = initKey,
50 | pagingSourceFactory = {
51 | BasePagingSource {
52 | try {
53 | HttpResult.Success(callAction(it))
54 | } catch (e: Exception) {
55 | HttpResult.Error(e)
56 | }
57 | }
58 | }).flow
59 | }
60 |
61 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mm/hamcompose/ui/page/base/BaseViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.mm.hamcompose.ui.page.base
2 |
3 | import androidx.compose.runtime.mutableStateOf
4 | import androidx.lifecycle.ViewModel
5 | import androidx.lifecycle.viewModelScope
6 | import kotlinx.coroutines.launch
7 |
8 | abstract class BaseViewModel : ViewModel() {
9 |
10 | //分类列表(装非分页加载的容器)
11 | var list = mutableStateOf(mutableListOf())
12 |
13 | var currentListIndex = mutableStateOf(0)
14 |
15 | var loading = mutableStateOf(false)
16 |
17 | private var _isInited = mutableStateOf(false)
18 |
19 | var message = mutableStateOf("")
20 |
21 | private val isInited: Boolean
22 | get() = _isInited.value
23 |
24 | private fun requestInitialized() {
25 | _isInited.value = true
26 | }
27 |
28 | fun resetListIndex() {
29 | currentListIndex.value = 0
30 | }
31 |
32 | fun resetInitState() {
33 | _isInited.value = false
34 | }
35 |
36 | fun async(block: suspend ()-> Unit) {
37 | viewModelScope.launch { block() }
38 | }
39 |
40 | abstract fun start()
41 |
42 | fun initThat(block: () -> Unit) {
43 | if (!isInited) {
44 | block.invoke()
45 | requestInitialized()
46 | }
47 | }
48 |
49 | fun savePosition(index: Int) {
50 | currentListIndex.value = index
51 | println("## save position = $index ##")
52 | }
53 |
54 | fun stopLoading() {
55 | loading.value = false
56 | }
57 |
58 | fun startLoading() {
59 | loading.value = true
60 | }
61 |
62 | open fun loadContent() { }
63 |
64 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mm/hamcompose/ui/page/base/CacheHistoryViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.mm.hamcompose.ui.page.base
2 |
3 | import com.mm.hamcompose.data.bean.Article
4 | import com.mm.hamcompose.data.bean.HistoryRecord
5 | import com.mm.hamcompose.data.db.history.HistoryDatabase
6 | import kotlinx.coroutines.Dispatchers
7 | import kotlinx.coroutines.withContext
8 |
9 | abstract class CacheHistoryViewModel: BaseViewModel() {
10 |
11 | fun cacheHistory(db: HistoryDatabase, article: Article) {
12 | async {
13 | val history = toMapData(article)
14 | withContext(Dispatchers.IO) {
15 | db.historyDao().insertHistory(history)
16 | println("成功储存到历史记录")
17 | }
18 | }
19 | }
20 |
21 | private fun toMapData(article: Article): HistoryRecord {
22 | return with(article) {
23 | HistoryRecord(
24 | id = id,
25 | title = title ?: "",
26 | link = link ?: "",
27 | niceDate = niceDate ?: "",
28 | shareUser = shareUser ?: "",
29 | userId = userId,
30 | author = author ?: "",
31 | superChapterId = superChapterId,
32 | superChapterName = superChapterName ?: "",
33 | chapterId = chapterId,
34 | chapterName = chapterName ?: "",
35 | desc = desc ?: ""
36 | )
37 | }
38 | }
39 |
40 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mm/hamcompose/ui/page/girls/info/GirlInfoPage.kt:
--------------------------------------------------------------------------------
1 | package com.mm.hamcompose.ui.page.girls.info
2 |
3 | import androidx.compose.foundation.Image
4 | import androidx.compose.foundation.background
5 | import androidx.compose.foundation.layout.*
6 | import androidx.compose.material.ScaffoldState
7 | import androidx.compose.runtime.Composable
8 | import androidx.compose.ui.Alignment
9 | import androidx.compose.ui.Modifier
10 | import androidx.compose.ui.graphics.Brush
11 | import androidx.compose.ui.layout.ContentScale
12 | import androidx.compose.ui.unit.dp
13 | import androidx.navigation.NavHostController
14 | import coil.compose.rememberImagePainter
15 | import com.google.accompanist.pager.ExperimentalPagerApi
16 | import com.mm.hamcompose.R
17 | import com.mm.hamcompose.data.bean.WelfareData
18 | import com.mm.hamcompose.theme.HamTheme
19 | import com.mm.hamcompose.ui.route.RouteUtils.back
20 | import com.mm.hamcompose.ui.widget.HamToolBar
21 | import com.mm.hamcompose.ui.widget.MainTitle
22 | import com.mm.hamcompose.ui.widget.TextContent
23 |
24 | @OptIn(ExperimentalPagerApi::class)
25 | @Composable
26 | fun GirlInfoPage(
27 | welfare: WelfareData,
28 | navCtrl: NavHostController,
29 | scaffoldState: ScaffoldState,
30 | ) {
31 | Column {
32 | HamToolBar(title = welfare.title!!, onBack = { navCtrl.back() })
33 | PhotoView(welfare = welfare)
34 | }
35 | }
36 |
37 | @Composable
38 | private fun PhotoView(welfare: WelfareData) {
39 | Box {
40 | Image(
41 | painter = rememberImagePainter(
42 | data = welfare.url,
43 | builder = {
44 | crossfade(true)
45 | placeholder(R.drawable.no_banner)
46 |
47 | },
48 | ),
49 | contentDescription = welfare.author,
50 | contentScale = ContentScale.FillHeight,
51 | modifier = Modifier.fillMaxSize()
52 | )
53 | MainTitle(
54 | title = welfare.author!!,
55 | modifier = Modifier
56 | .padding(10.dp)
57 | .align(Alignment.TopStart)
58 | )
59 | TextContent(
60 | text = welfare.desc!!,
61 | modifier = Modifier
62 | .padding(10.dp)
63 | .wrapContentSize()
64 | .align(Alignment.BottomCenter)
65 | .background(
66 | brush = Brush.horizontalGradient(
67 | listOf(HamTheme.colors.placeholder, HamTheme.colors.placeholder)
68 | ),
69 | alpha = 0.3f
70 | )
71 | .padding(horizontal = 10.dp)
72 | ,
73 | color = HamTheme.colors.mainColor
74 | )
75 | }
76 |
77 | }
78 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mm/hamcompose/ui/page/girls/list/GirlPhotoViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.mm.hamcompose.ui.page.girls.list
2 |
3 | import androidx.compose.runtime.mutableStateOf
4 | import com.mm.hamcompose.data.bean.WelfareData
5 | import com.mm.hamcompose.data.http.HttpResult
6 | import com.mm.hamcompose.repository.HttpRepository
7 | import com.mm.hamcompose.ui.page.base.BaseViewModel
8 | import dagger.hilt.android.lifecycle.HiltViewModel
9 | import kotlinx.coroutines.flow.collectLatest
10 | import javax.inject.Inject
11 |
12 | @HiltViewModel
13 | class GirlPhotoViewModel @Inject constructor(
14 | private var repo: HttpRepository
15 | ) : BaseViewModel() {
16 |
17 | val pageSize = 40
18 | var page = mutableStateOf(1)
19 | var hasNext = mutableStateOf(false)
20 |
21 | val photoData = mutableStateOf(mutableListOf())
22 |
23 | override fun start() {
24 | initThat { loadContent() }
25 | }
26 |
27 | fun loadMore() {
28 | if (hasNext.value) {
29 | page.value += 1
30 | loadContent()
31 | }
32 | }
33 |
34 | override fun loadContent() {
35 | async {
36 | repo.getWelfareData(page.value, pageSize).collectLatest { response ->
37 | when (response) {
38 | is HttpResult.Success -> {
39 | val photos = response.result.data
40 | if (!photos.isNullOrEmpty()) {
41 | hasNext.value = true
42 | if (photoData.value.isEmpty()) {
43 | photoData.value = photos as MutableList
44 | } else {
45 | photoData.value.addAll(photos)
46 | }
47 | } else {
48 | hasNext.value = false
49 | }
50 | }
51 | is HttpResult.Error -> {
52 | println(response.exception.message)
53 | }
54 | }
55 | }
56 | }
57 | }
58 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mm/hamcompose/ui/page/main/category/CategoryPage.kt:
--------------------------------------------------------------------------------
1 | package com.mm.hamcompose.ui.page.main.category
2 |
3 | import androidx.compose.foundation.layout.Box
4 | import androidx.compose.foundation.layout.Column
5 | import androidx.compose.foundation.layout.Row
6 | import androidx.compose.foundation.layout.padding
7 | import androidx.compose.runtime.Composable
8 | import androidx.compose.runtime.getValue
9 | import androidx.compose.runtime.remember
10 | import androidx.compose.runtime.rememberCoroutineScope
11 | import androidx.compose.ui.Modifier
12 | import androidx.hilt.navigation.compose.hiltViewModel
13 | import androidx.navigation.NavHostController
14 | import com.google.accompanist.pager.ExperimentalPagerApi
15 | import com.google.accompanist.pager.HorizontalPager
16 | import com.google.accompanist.pager.rememberPagerState
17 | import com.mm.hamcompose.theme.BottomNavBarHeight
18 | import com.mm.hamcompose.ui.page.main.category.navigation.NaviPage
19 | import com.mm.hamcompose.ui.page.main.category.pubaccount.category.PublicAccountPage
20 | import com.mm.hamcompose.ui.page.main.category.structure.tree.StructurePage
21 | import com.mm.hamcompose.ui.route.RouteName
22 | import com.mm.hamcompose.ui.route.RouteUtils
23 | import com.mm.hamcompose.ui.widget.TextTabBar
24 | import kotlinx.coroutines.launch
25 |
26 | @OptIn(ExperimentalPagerApi::class)
27 | @Composable
28 | fun CategoryPage(
29 | navCtrl: NavHostController,
30 | categoryIndex: Int = 0,
31 | viewModel: CategoryViewModel = hiltViewModel(),
32 | onPageSelected: (position: Int) -> Unit,
33 | ) {
34 |
35 | val titles by remember { viewModel.titles }
36 | Box(modifier = Modifier.padding(bottom = BottomNavBarHeight)) {
37 | Column {
38 | val pagerState = rememberPagerState(
39 | pageCount = titles.size,
40 | initialPage = categoryIndex,
41 | initialOffscreenLimit = titles.size
42 | )
43 | val scopeState = rememberCoroutineScope()
44 |
45 | Row {
46 | TextTabBar(
47 | index = pagerState.currentPage,
48 | tabTexts = titles,
49 | modifier = Modifier.weight(1f),
50 | onTabSelected = { index ->
51 | scopeState.launch {
52 | pagerState.scrollToPage(index)
53 | }
54 | },
55 | withAdd = true,
56 | onAddClick = {
57 | RouteUtils.navTo(navCtrl, RouteName.SHARE_ARTICLE)
58 | }
59 | )
60 | }
61 |
62 | HorizontalPager(state = pagerState) { page ->
63 | onPageSelected(pagerState.currentPage)
64 | when (page) {
65 | 0 -> StructurePage(navCtrl)
66 | 1 -> NaviPage(navCtrl)
67 | 2 -> PublicAccountPage(navCtrl)
68 | }
69 | }
70 | }
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mm/hamcompose/ui/page/main/category/CategoryViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.mm.hamcompose.ui.page.main.category
2 |
3 | import androidx.compose.runtime.mutableStateOf
4 | import com.mm.hamcompose.data.bean.ParentBean
5 | import com.mm.hamcompose.data.bean.TabTitle
6 | import com.mm.hamcompose.ui.page.base.BaseViewModel
7 | import dagger.hilt.android.lifecycle.HiltViewModel
8 | import javax.inject.Inject
9 |
10 | @HiltViewModel
11 | class CategoryViewModel @Inject constructor() : BaseViewModel() {
12 |
13 | val titles = mutableStateOf(
14 | mutableListOf(
15 | TabTitle(201, "体系"),
16 | TabTitle(202, "导航"),
17 | TabTitle(203, "公众号"),
18 | )
19 | )
20 |
21 |
22 | override fun start() {
23 |
24 | }
25 |
26 |
27 | override fun onCleared() {
28 | super.onCleared()
29 | println("CategoryViewModel ==> onClear")
30 | }
31 |
32 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mm/hamcompose/ui/page/main/category/navigation/NaviPage.kt:
--------------------------------------------------------------------------------
1 | package com.mm.hamcompose.ui.page.main.category.navigation
2 |
3 | import androidx.compose.foundation.ExperimentalFoundationApi
4 | import androidx.compose.foundation.background
5 | import androidx.compose.foundation.layout.*
6 | import androidx.compose.foundation.lazy.LazyColumn
7 | import androidx.compose.foundation.lazy.itemsIndexed
8 | import androidx.compose.foundation.lazy.rememberLazyListState
9 | import androidx.compose.material.Divider
10 | import androidx.compose.material.Text
11 | import androidx.compose.runtime.*
12 | import androidx.compose.ui.Modifier
13 | import androidx.compose.ui.text.font.FontWeight
14 | import androidx.compose.ui.unit.dp
15 | import androidx.hilt.navigation.compose.hiltViewModel
16 | import androidx.navigation.NavHostController
17 | import com.google.accompanist.flowlayout.FlowRow
18 | import com.mm.hamcompose.data.bean.NaviWrapper
19 | import com.mm.hamcompose.data.bean.WebData
20 | import com.mm.hamcompose.theme.HamTheme
21 | import com.mm.hamcompose.ui.route.RouteUtils
22 | import com.mm.hamcompose.ui.route.RouteName
23 |
24 | import com.mm.hamcompose.ui.widget.LabelTextButton
25 | import com.mm.hamcompose.ui.widget.ListTitle
26 |
27 | @OptIn(ExperimentalFoundationApi::class)
28 | @Composable
29 | fun NaviPage(
30 | navCtrl: NavHostController,
31 | viewModel: NaviViewModel = hiltViewModel()
32 | ) {
33 | viewModel.start()
34 | val naviData by remember { viewModel.list }
35 | val isLoading by remember { viewModel.loading }
36 | val currentPosition by remember { viewModel.currentListIndex }
37 | val listState = rememberLazyListState(currentPosition)
38 |
39 | LazyColumn(
40 | modifier = Modifier.fillMaxSize(),
41 | state = listState,
42 | contentPadding = PaddingValues(vertical = 10.dp)
43 | ) {
44 | if (isLoading) {
45 | items(6) {
46 | NaviItem(
47 | wrapper = NaviWrapper(null, -1, ""),
48 | isLoading = isLoading,
49 | )
50 | }
51 | } else {
52 | naviData.forEachIndexed { index, naviBean ->
53 | stickyHeader { ListTitle(title = naviBean.name ?: "标题") }
54 | item {
55 | NaviItem(naviBean, onSelected = {
56 | viewModel.savePosition(listState.firstVisibleItemIndex)
57 | RouteUtils.navTo(navCtrl, RouteName.WEB_VIEW, it)
58 | })
59 | if (index <= naviData.size - 1) {
60 | Divider(
61 | startIndent = 10.dp,
62 | color = HamTheme.colors.divider,
63 | thickness = 0.8f.dp
64 | )
65 | }
66 | Spacer(modifier = Modifier.height(10.dp))
67 | }
68 | }
69 | }
70 | }
71 | }
72 |
73 | @Composable
74 | fun NaviItem(
75 | wrapper: NaviWrapper,
76 | isLoading: Boolean = false,
77 | onSelected: (WebData) -> Unit = {}
78 | ) {
79 | Column(
80 | modifier = Modifier
81 | .fillMaxWidth()
82 | .padding(horizontal = 10.dp)
83 | ) {
84 | if (isLoading) {
85 | ListTitle(title = "我是标题")
86 | FlowRow(
87 | modifier = Modifier.padding(top = 10.dp)
88 | ) {
89 | for (i in 0..7) {
90 | LabelTextButton(
91 | text = "android",
92 | modifier = Modifier.padding(start = 5.dp, bottom = 5.dp),
93 | isLoading = true
94 | )
95 | }
96 | }
97 | Spacer(modifier = Modifier.height(10.dp))
98 | } else {
99 | if (!wrapper.articles.isNullOrEmpty()) {
100 | FlowRow(
101 | modifier = Modifier.padding(top = 10.dp)
102 | ) {
103 | for (item in wrapper.articles!!) {
104 | LabelTextButton(
105 | text = item.title ?: "android",
106 | modifier = Modifier.padding(start = 5.dp, bottom = 5.dp),
107 | onClick = {
108 | val webData = WebData(item.title, item.link!!)
109 | onSelected(webData)
110 | }
111 | )
112 | }
113 | }
114 | Spacer(modifier = Modifier.height(10.dp))
115 | }
116 | }
117 |
118 | }
119 | }
120 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mm/hamcompose/ui/page/main/category/navigation/NaviViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.mm.hamcompose.ui.page.main.category.navigation
2 |
3 | import com.mm.hamcompose.data.bean.NaviWrapper
4 | import com.mm.hamcompose.data.http.HttpResult
5 | import com.mm.hamcompose.repository.HttpRepository
6 | import com.mm.hamcompose.ui.page.base.BaseViewModel
7 | import dagger.hilt.android.lifecycle.HiltViewModel
8 | import kotlinx.coroutines.flow.collectLatest
9 | import javax.inject.Inject
10 |
11 | @HiltViewModel
12 | class NaviViewModel @Inject constructor(
13 | private val httpRepo: HttpRepository
14 | ): BaseViewModel() {
15 |
16 | override fun loadContent() {
17 | async {
18 | httpRepo.getNavigationList().collectLatest { response ->
19 | when (response) {
20 | is HttpResult.Success -> {
21 | list.value = response.result
22 | }
23 | is HttpResult.Error -> {
24 |
25 | }
26 | }
27 | }
28 | }
29 | }
30 |
31 | override fun start() {
32 | initThat { loadContent() }
33 | }
34 |
35 |
36 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mm/hamcompose/ui/page/main/category/pubaccount/author/PublicAccountAuthorViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.mm.hamcompose.ui.page.main.category.pubaccount.author
2 |
3 | import androidx.compose.runtime.mutableStateOf
4 | import androidx.lifecycle.MutableLiveData
5 | import androidx.lifecycle.viewModelScope
6 | import androidx.paging.cachedIn
7 | import com.blankj.utilcode.util.LogUtils
8 | import com.mm.hamcompose.data.bean.Article
9 | import com.mm.hamcompose.data.db.history.HistoryDatabase
10 | import com.mm.hamcompose.repository.HttpRepository
11 | import com.mm.hamcompose.repository.PagingArticle
12 | import com.mm.hamcompose.ui.page.base.BaseCollectViewModel
13 | import dagger.hilt.android.lifecycle.HiltViewModel
14 | import javax.inject.Inject
15 |
16 | @HiltViewModel
17 | class PublicAccountAuthorViewModel @Inject constructor(
18 | private var repo: HttpRepository,
19 | private val db: HistoryDatabase,
20 | ): BaseCollectViewModel(repo) {
21 |
22 | /**
23 | * 某个技术公众号的列表
24 | */
25 | var publicData = MutableLiveData(null)
26 | var isRefreshing = mutableStateOf(false)
27 | private var authorId = mutableStateOf(-1)
28 |
29 | override fun start() {
30 | initThat { initPublicArticles() }
31 | }
32 |
33 | fun setPublicId(id: Int) {
34 | authorId.value = id
35 | }
36 |
37 | fun clearCache() {
38 | isRefreshing.value = true
39 | publicData.value = null
40 | }
41 |
42 | fun initPublicArticles() {
43 | if (publicData.value==null) {
44 | publicData.value = getPublicArticles()
45 | isRefreshing.value = publicData.value==null
46 | }
47 | }
48 |
49 | private fun getPublicArticles() = repo.getPublicArticles(authorId.value).cachedIn(viewModelScope)
50 |
51 | override fun onCleared() {
52 | LogUtils.e("ViewModel执行onCleared()")
53 | super.onCleared()
54 | }
55 |
56 | fun saveDataToHistory(article: Article) {
57 | cacheHistory(db, article)
58 | }
59 |
60 |
61 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mm/hamcompose/ui/page/main/category/pubaccount/category/PublicAccountViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.mm.hamcompose.ui.page.main.category.pubaccount.category
2 |
3 | import androidx.compose.runtime.mutableStateOf
4 | import com.mm.hamcompose.data.bean.ParentBean
5 | import com.mm.hamcompose.data.http.HttpResult
6 | import com.mm.hamcompose.repository.HttpRepository
7 | import com.mm.hamcompose.ui.page.base.BaseViewModel
8 | import dagger.hilt.android.lifecycle.HiltViewModel
9 | import kotlinx.coroutines.flow.collectLatest
10 | import javax.inject.Inject
11 |
12 | @HiltViewModel
13 | class PublicAccountViewModel @Inject constructor(
14 | private var httpRepo: HttpRepository
15 | ): BaseViewModel() {
16 |
17 | //公众号ID
18 | private var publicId = mutableStateOf(-1)
19 |
20 | override fun start() {
21 | initThat { loadContent() }
22 | }
23 |
24 | override fun loadContent() {
25 | async {
26 | httpRepo.getPublicInformation().collectLatest { response ->
27 | when (response) {
28 | is HttpResult.Success -> {
29 | list.value = response.result
30 | }
31 | is HttpResult.Error -> {
32 |
33 | }
34 | }
35 | }
36 | }
37 | }
38 |
39 |
40 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mm/hamcompose/ui/page/main/category/pubaccount/search/PublicAccountSearchViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.mm.hamcompose.ui.page.main.category.pubaccount.search
2 |
3 | import androidx.compose.runtime.mutableStateOf
4 | import androidx.lifecycle.MutableLiveData
5 | import androidx.lifecycle.viewModelScope
6 | import androidx.paging.cachedIn
7 | import com.mm.hamcompose.data.bean.Article
8 | import com.mm.hamcompose.data.db.history.HistoryDatabase
9 | import com.mm.hamcompose.repository.HttpRepository
10 | import com.mm.hamcompose.repository.PagingArticle
11 | import com.mm.hamcompose.ui.page.base.BaseCollectViewModel
12 | import dagger.hilt.android.lifecycle.HiltViewModel
13 | import javax.inject.Inject
14 |
15 | @HiltViewModel
16 | class PublicAccountSearchViewModel @Inject constructor(
17 | private val repo: HttpRepository,
18 | private val db: HistoryDatabase,
19 | ): BaseCollectViewModel(repo) {
20 |
21 | /**
22 | * 在某个公众号下,搜索关键字
23 | */
24 | //var searchText = MutableLiveData("")
25 | var isRefreshing = mutableStateOf(false)
26 | var searchContent = mutableStateOf("")
27 | val searchResult = MutableLiveData(null)
28 | private var publicId = mutableStateOf(-1)
29 |
30 |
31 | override fun start() {
32 |
33 | }
34 |
35 | fun setPublicId(id: Int) {
36 | this.publicId.value = id
37 | }
38 |
39 | private fun searchArticleWithKey(key: String) =
40 | repo.searchArticleWithKey(publicId.value, key).cachedIn(viewModelScope)
41 |
42 | fun refreshSearch(key: String) {
43 | resetListIndex()
44 | search(key)
45 | }
46 |
47 | fun search(key: String) {
48 | searchResult.value = null
49 | searchResult.value = searchArticleWithKey(key)
50 | isRefreshing.value = searchResult.value==null
51 | }
52 |
53 | fun saveDataToHistory(article: Article) {
54 | cacheHistory(db, article)
55 | }
56 |
57 |
58 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mm/hamcompose/ui/page/main/category/share/ShareArticlePage.kt:
--------------------------------------------------------------------------------
1 | package com.mm.hamcompose.ui.page.main.category.share
2 |
3 | import androidx.compose.foundation.layout.Column
4 | import androidx.compose.foundation.layout.fillMaxSize
5 | import androidx.compose.foundation.layout.padding
6 | import androidx.compose.foundation.lazy.LazyColumn
7 | import androidx.compose.material.ScaffoldState
8 | import androidx.compose.runtime.*
9 | import androidx.compose.ui.ExperimentalComposeUiApi
10 | import androidx.compose.ui.Modifier
11 | import androidx.compose.ui.platform.LocalSoftwareKeyboardController
12 | import androidx.compose.ui.res.stringResource
13 | import androidx.compose.ui.unit.dp
14 | import androidx.hilt.navigation.compose.hiltViewModel
15 | import androidx.navigation.NavHostController
16 | import com.mm.hamcompose.R
17 | import com.mm.hamcompose.ui.route.RouteUtils.back
18 | import com.mm.hamcompose.ui.widget.*
19 |
20 | @OptIn(ExperimentalComposeUiApi::class)
21 | @Composable
22 | fun ShareArticlePage(
23 | navCtrl: NavHostController,
24 | scaffoldState: ScaffoldState,
25 | viewModel: ShareArticleViewModel = hiltViewModel()
26 | ) {
27 |
28 | val title by remember { viewModel.title }
29 | val shareUser by remember { viewModel.shareUser }
30 | val linkUrl by remember { viewModel.linkUrl }
31 | var errorMsg by remember { viewModel.errorMessage }
32 | var snackLabel by remember { mutableStateOf(SNACK_WARN) }
33 | val keyboardController = LocalSoftwareKeyboardController.current
34 |
35 |
36 | if (errorMsg.isNotEmpty()) {
37 | popupSnackBar(
38 | scope = rememberCoroutineScope(),
39 | scaffoldState = scaffoldState,
40 | label = if (errorMsg == "分享成功") SNACK_SUCCESS else snackLabel,
41 | message = errorMsg
42 | )
43 | errorMsg = ""
44 | }
45 |
46 | Column {
47 | HamToolBar(
48 | title = "分享文章",
49 | onBack = { navCtrl.back() },
50 | rightText = "保存",
51 | onRightClick = {
52 | keyboardController?.hide()
53 | if (title.isNullOrEmpty()) {
54 | snackLabel = SNACK_WARN
55 | errorMsg = "标题不能为空"
56 | return@HamToolBar
57 | }
58 | if (linkUrl.isNullOrEmpty()) {
59 | snackLabel = SNACK_WARN
60 | errorMsg = "链接不能为空"
61 | return@HamToolBar
62 | }
63 | viewModel.addShareArticle()
64 | }
65 | )
66 |
67 | LazyColumn(
68 | modifier = Modifier.weight(1f)
69 | ) {
70 | item {
71 | LabelEditView(
72 | modifier = Modifier.padding(horizontal = 10.dp, vertical = 20.dp),
73 | text = title ?: "",
74 | labelText = stringResource(id = R.string.title),
75 | hintText = "请输入标题(限100字以内)",
76 | onValueChanged = {
77 | viewModel.title.value = it
78 | },
79 | onDeleteClick = { viewModel.title.value = "" },
80 | )
81 | }
82 | item {
83 | LabelEditView(
84 | modifier = Modifier.padding(horizontal = 10.dp, vertical = 20.dp),
85 | text = shareUser ?: "",
86 | labelText = stringResource(id = R.string.author),
87 | hintText = "默认使用昵称,没有昵称则使用用户名",
88 | onValueChanged = {
89 | viewModel.shareUser.value = it
90 | },
91 | onDeleteClick = { viewModel.shareUser.value = "" },
92 | )
93 | }
94 | item {
95 | LabelEditView(
96 | modifier = Modifier.padding(horizontal = 10.dp, vertical = 20.dp),
97 | text = linkUrl ?: "",
98 | labelText = stringResource(id = R.string.link),
99 | hintText = stringResource(id = R.string.hint_text_website_url),
100 | onValueChanged = {
101 | viewModel.linkUrl.value = it
102 | },
103 | onDeleteClick = { viewModel.linkUrl.value = "" },
104 | )
105 | }
106 | }
107 | }
108 |
109 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mm/hamcompose/ui/page/main/category/share/ShareArticleViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.mm.hamcompose.ui.page.main.category.share
2 |
3 | import androidx.compose.runtime.mutableStateOf
4 | import com.mm.hamcompose.data.bean.Article
5 | import com.mm.hamcompose.data.db.user.UserInfoDatabase
6 | import com.mm.hamcompose.data.http.HttpResult
7 | import com.mm.hamcompose.repository.HttpRepository
8 | import com.mm.hamcompose.ui.page.base.BaseViewModel
9 | import dagger.hilt.android.lifecycle.HiltViewModel
10 | import kotlinx.coroutines.flow.collectLatest
11 | import javax.inject.Inject
12 |
13 | @HiltViewModel
14 | class ShareArticleViewModel @Inject constructor(
15 | private val repo: HttpRepository,
16 | private val db: UserInfoDatabase
17 | ): BaseViewModel() {
18 |
19 | var title = mutableStateOf(null)
20 | var shareUser = mutableStateOf(null)
21 | var linkUrl = mutableStateOf(null)
22 | var errorMessage = mutableStateOf("")
23 |
24 | override fun start() {
25 |
26 | }
27 |
28 | fun addShareArticle() {
29 | async {
30 | if (shareUser.value.isNullOrEmpty()) {
31 | val users = db.userInfoDao().queryUserInfo()
32 | if (!users.isNullOrEmpty()) {
33 | with(users[0]!!) {
34 | shareUser.value = if (nickname.isEmpty()) username else nickname
35 | }
36 | }
37 | }
38 | repo.addMyShareArticle(title.value!!, linkUrl.value!!, shareUser.value!!)
39 | .collectLatest { response ->
40 | when(response) {
41 | is HttpResult.Success -> { }
42 | is HttpResult.Error -> {
43 | val isShare = response.exception.message == "the result of remote's request is null"
44 | if (isShare) {
45 | errorMessage.value = "分享成功"
46 | }
47 | }
48 | }
49 | }
50 |
51 | }
52 | }
53 |
54 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mm/hamcompose/ui/page/main/category/structure/list/StructureListViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.mm.hamcompose.ui.page.main.category.structure.list
2 |
3 | import androidx.compose.runtime.mutableStateOf
4 | import androidx.lifecycle.MutableLiveData
5 | import androidx.lifecycle.viewModelScope
6 | import androidx.paging.cachedIn
7 | import com.mm.hamcompose.data.bean.Article
8 | import com.mm.hamcompose.data.db.history.HistoryDatabase
9 | import com.mm.hamcompose.repository.HttpRepository
10 | import com.mm.hamcompose.repository.PagingArticle
11 | import com.mm.hamcompose.ui.page.base.BaseCollectViewModel
12 | import dagger.hilt.android.lifecycle.HiltViewModel
13 | import javax.inject.Inject
14 |
15 | @HiltViewModel
16 | class StructureListViewModel @Inject constructor(
17 | private val repo: HttpRepository,
18 | private val db: HistoryDatabase,
19 | ): BaseCollectViewModel(repo) {
20 |
21 | private var cid = -1
22 | //某个文章的列表
23 | var articles = MutableLiveData(null)
24 | var isRefreshing = mutableStateOf(true)
25 | var authorName = mutableStateOf("")
26 |
27 | fun setId(id: Int) {
28 | cid = id
29 | }
30 |
31 | override fun start() {
32 | initThat { initArticles() }
33 | }
34 |
35 | fun refresh(author: String) {
36 | resetListIndex()
37 | isRefreshing.value = true
38 | articles.value = null
39 | if (author.isEmpty()) {
40 | initArticles()
41 | } else {
42 | searchByAuthor(author)
43 | }
44 | }
45 |
46 | private fun initArticles() {
47 | if (articles.value==null) {
48 | articles.value = getStructureArticles()
49 | isRefreshing.value = articles.value==null
50 | }
51 | }
52 |
53 | private fun getStructureArticles() = repo.getStructureArticles(cid).cachedIn(viewModelScope)
54 |
55 | fun searchByAuthor(author: String) {
56 | isRefreshing.value = true
57 | articles.value = null
58 | articles.value = repo.getStructureArticles(author).cachedIn(viewModelScope)
59 | isRefreshing.value = articles.value==null
60 | }
61 |
62 |
63 | fun saveDataToHistory(article: Article) {
64 | cacheHistory(db, article)
65 | }
66 |
67 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mm/hamcompose/ui/page/main/category/structure/tree/StructureTreePage.kt:
--------------------------------------------------------------------------------
1 | package com.mm.hamcompose.ui.page.main.category.structure.tree
2 |
3 | import androidx.compose.foundation.ExperimentalFoundationApi
4 | import androidx.compose.foundation.background
5 | import androidx.compose.foundation.layout.*
6 | import androidx.compose.foundation.lazy.LazyColumn
7 | import androidx.compose.foundation.lazy.itemsIndexed
8 | import androidx.compose.foundation.lazy.rememberLazyListState
9 | import androidx.compose.material.Divider
10 | import androidx.compose.material.Text
11 | import androidx.compose.runtime.Composable
12 | import androidx.compose.runtime.getValue
13 | import androidx.compose.runtime.remember
14 | import androidx.compose.ui.Modifier
15 | import androidx.compose.ui.text.font.FontWeight
16 | import androidx.compose.ui.unit.dp
17 | import androidx.hilt.navigation.compose.hiltViewModel
18 | import androidx.navigation.NavHostController
19 | import com.google.accompanist.flowlayout.FlowRow
20 | import com.mm.hamcompose.data.bean.ParentBean
21 | import com.mm.hamcompose.theme.HamTheme
22 | import com.mm.hamcompose.ui.route.RouteName
23 | import com.mm.hamcompose.ui.route.RouteUtils
24 | import com.mm.hamcompose.ui.widget.LabelTextButton
25 | import com.mm.hamcompose.ui.widget.ListTitle
26 |
27 | @OptIn(ExperimentalFoundationApi::class)
28 | @Composable
29 | fun StructurePage(
30 | navCtrl: NavHostController,
31 | viewModel: StructureViewModel = hiltViewModel()
32 | ) {
33 |
34 | viewModel.start()
35 | val systemData by remember { viewModel.list }
36 | val isLoading by remember { viewModel.loading }
37 | val currentPosition by remember { viewModel.currentListIndex }
38 | val listState = rememberLazyListState(currentPosition)
39 |
40 | LazyColumn(
41 | modifier = Modifier
42 | .fillMaxWidth()
43 | .fillMaxHeight()
44 | .background(HamTheme.colors.background),
45 | state = listState,
46 | contentPadding = PaddingValues(vertical = 10.dp)
47 | ) {
48 |
49 | if (isLoading) {
50 | items(5) {
51 | StructureItem(ParentBean(null), isLoading = true)
52 | }
53 | } else {
54 | systemData.forEachIndexed { position, chapter1 ->
55 | stickyHeader { ListTitle(title = chapter1.name ?: "标题") }
56 | item {
57 | StructureItem(chapter1, onSelect = { parent->
58 | viewModel.savePosition(listState.firstVisibleItemIndex)
59 | RouteUtils.navTo(navCtrl, RouteName.STRUCTURE_LIST, parent)
60 | })
61 | if (position <= systemData.size - 1) {
62 | Divider(startIndent = 10.dp, color = HamTheme.colors.divider, thickness = 0.8f.dp)
63 | }
64 | Spacer(modifier = Modifier.height(10.dp))
65 | }
66 | }
67 | }
68 | }
69 | }
70 |
71 | @Composable
72 | fun StructureItem(
73 | bean: ParentBean,
74 | isLoading: Boolean = false,
75 | onSelect: (parent: ParentBean) -> Unit = {},
76 | ) {
77 | Column(
78 | modifier = Modifier.fillMaxWidth().padding(top = 10.dp)
79 | ) {
80 | if (isLoading) {
81 | ListTitle(title = "我都标题", isLoading = true)
82 | FlowRow(
83 | modifier = Modifier.padding(horizontal = 10.dp)
84 | ) {
85 | for (i in 0..7) {
86 | LabelTextButton(
87 | text = "android",
88 | modifier = Modifier.padding(start = 5.dp, bottom = 5.dp),
89 | isLoading = true
90 | )
91 | }
92 | }
93 | Spacer(modifier = Modifier.height(10.dp))
94 | } else {
95 | if (!bean.children.isNullOrEmpty()) {
96 | FlowRow(
97 | modifier = Modifier.padding(horizontal = 10.dp)
98 | ) {
99 | for (item in bean.children!!) {
100 | LabelTextButton(
101 | text = item.name ?: "android",
102 | modifier = Modifier.padding(start = 5.dp, bottom = 5.dp),
103 | onClick = {
104 | onSelect(item)
105 | }
106 | )
107 | }
108 | }
109 | Spacer(modifier = Modifier.height(10.dp))
110 | }
111 | }
112 |
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mm/hamcompose/ui/page/main/category/structure/tree/StructureTreeViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.mm.hamcompose.ui.page.main.category.structure.tree
2 |
3 | import com.mm.hamcompose.data.bean.ParentBean
4 | import com.mm.hamcompose.data.http.HttpResult
5 | import com.mm.hamcompose.repository.HttpRepository
6 | import com.mm.hamcompose.ui.page.base.BaseViewModel
7 | import dagger.hilt.android.lifecycle.HiltViewModel
8 | import kotlinx.coroutines.flow.collectLatest
9 | import javax.inject.Inject
10 |
11 | private const val TAG = "StructureViewModel ==> "
12 |
13 | @HiltViewModel
14 | class StructureViewModel @Inject constructor(
15 | private var repo: HttpRepository,
16 | ): BaseViewModel() {
17 |
18 | override fun loadContent() {
19 | startLoading()
20 | async {
21 | repo.getStructureList().collectLatest { response ->
22 | when (response) {
23 | is HttpResult.Success -> {
24 | list.value = response.result
25 | }
26 | is HttpResult.Error -> {
27 | println(TAG + response.exception.message)
28 | }
29 | }
30 | stopLoading()
31 | }
32 | }
33 | }
34 |
35 | override fun start() {
36 | initThat { loadContent() }
37 | }
38 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mm/hamcompose/ui/page/main/collection/CollectionViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.mm.hamcompose.ui.page.main.collection
2 |
3 | import android.annotation.SuppressLint
4 | import androidx.compose.runtime.mutableStateOf
5 | import androidx.lifecycle.MutableLiveData
6 | import androidx.lifecycle.viewModelScope
7 | import androidx.paging.cachedIn
8 | import com.mm.hamcompose.data.bean.ParentBean
9 | import com.mm.hamcompose.data.bean.TabTitle
10 | import com.mm.hamcompose.data.db.user.UserInfoDatabase
11 | import com.mm.hamcompose.data.http.HttpResult
12 | import com.mm.hamcompose.repository.HttpRepository
13 | import com.mm.hamcompose.repository.PagingCollect
14 | import com.mm.hamcompose.ui.page.base.BaseViewModel
15 | import dagger.hilt.android.lifecycle.HiltViewModel
16 | import kotlinx.coroutines.Dispatchers
17 | import kotlinx.coroutines.flow.*
18 | import javax.inject.Inject
19 |
20 | @HiltViewModel
21 | class CollectionViewModel @Inject constructor(
22 | private val repo: HttpRepository,
23 | private val db: UserInfoDatabase
24 | ) : BaseViewModel() {
25 |
26 | val titles = mutableStateOf(
27 | mutableListOf(
28 | TabTitle(301, "文章列表"),
29 | TabTitle(302, "我的网址"),
30 | )
31 | )
32 |
33 | var collectArticles = MutableLiveData(null)
34 | var webUrlList = mutableStateOf?>(null)
35 | var isRefreshing = mutableStateOf(false)
36 | var isLogin = mutableStateOf(false)
37 |
38 | override fun start() {
39 | checkLoginState()
40 | }
41 |
42 | private fun checkLoginState() {
43 | async {
44 | flow { emit(db.userInfoDao().queryUserInfo()) }
45 | .flowOn(Dispatchers.IO)
46 | .collectLatest { users ->
47 | isLogin.value = users.isNotEmpty()
48 | if (isLogin.value && isNotInit()) {
49 | initData()
50 | }
51 | }
52 | }
53 | }
54 |
55 | private fun isNotInit() = collectArticles.value == null && webUrlList.value == null
56 |
57 | private fun initData() {
58 | getCollectUrlList()
59 | getArticles()
60 | }
61 |
62 | fun refresh() {
63 | isRefreshing.value = true
64 | webUrlList.value = null
65 | collectArticles.value = null
66 | checkLoginState()
67 | }
68 |
69 | private fun getArticles() {
70 | collectArticles.value = collectList()
71 | isRefreshing.value = collectArticles.value == null
72 | }
73 |
74 | private fun getCollectUrlList() {
75 | async {
76 | repo.getCollectUrls().collectLatest { response ->
77 | when (response) {
78 | is HttpResult.Success -> {
79 | webUrlList.value = response.result
80 | }
81 | is HttpResult.Error -> {
82 |
83 | }
84 | }
85 | }
86 | }
87 | }
88 |
89 | private fun collectList() = repo.getCollectionList().cachedIn(viewModelScope)
90 |
91 | fun uncollectArticle(id: Int, originId: Int) {
92 | async {
93 | repo.uncollectArticleById(id, originId).collectLatest { response ->
94 | when (response) {
95 | is HttpResult.Success -> {
96 | }
97 | is HttpResult.Error -> {
98 | //收藏接口,不走success判断分支
99 | val deleted = response.exception.message == "the result of remote's request is null"
100 | if (deleted) {
101 | println("取消收藏(id=$id)")
102 | getArticles()
103 | message.value = "已取消收藏"
104 | }
105 | }
106 | }
107 | }
108 | }
109 | }
110 |
111 | @SuppressLint("NewApi")
112 | fun deleteWebsite(id: Int) {
113 | async {
114 | repo.deleteWebsite(id).collectLatest { response ->
115 | when (response) {
116 | is HttpResult.Success -> {
117 | }
118 | is HttpResult.Error -> {
119 | if (response.exception.message == "the result of remote's request is null") {
120 | webUrlList.value?.remove(webUrlList.value?.find { it.id == id })
121 | message.value = "删除成功"
122 | }
123 | }
124 | }
125 | }
126 | }
127 | }
128 |
129 | override fun onCleared() {
130 | super.onCleared()
131 | println("CollectionViewModel ==> onClear")
132 | }
133 |
134 |
135 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mm/hamcompose/ui/page/main/collection/edit/WebSiteEditViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.mm.hamcompose.ui.page.main.collection.edit
2 |
3 | import androidx.compose.runtime.mutableStateOf
4 | import com.mm.hamcompose.data.bean.TabTitle
5 | import com.mm.hamcompose.data.http.HttpResult
6 | import com.mm.hamcompose.repository.HttpRepository
7 | import com.mm.hamcompose.ui.page.base.BaseViewModel
8 | import dagger.hilt.android.lifecycle.HiltViewModel
9 | import kotlinx.coroutines.flow.collectLatest
10 | import javax.inject.Inject
11 |
12 | @HiltViewModel
13 | class WebSiteEditViewModel @Inject constructor(
14 | private val repo: HttpRepository
15 | ): BaseViewModel() {
16 |
17 | var webSiteTitle = mutableStateOf(null)
18 | var linkUrl = mutableStateOf(null)
19 | var author = mutableStateOf(null)
20 | var isSaved = mutableStateOf(false)
21 | var errorMessage = mutableStateOf("")
22 | val titles = mutableStateOf(mutableListOf(
23 | TabTitle(601, "网站"),
24 | TabTitle(602, "文章"),
25 | ))
26 |
27 | override fun start() {
28 |
29 | }
30 |
31 | /**
32 | * type: 0 = 添加新网站, 1 = 添加新文章 , -1 = 编辑网站
33 | * id: 仅编辑网站时候用到此参数
34 | */
35 | fun saveNewCollect(type: Int, id: Int = 0) {
36 | async {
37 | when (type) {
38 | 0 -> {
39 | repo.addNewWebsiteCollect(webSiteTitle.value!!, linkUrl.value!!)
40 | .collectLatest { response ->
41 | when (response) {
42 | is HttpResult.Success -> {
43 | isSaved.value = true
44 | }
45 | is HttpResult.Error -> {
46 | println(response.exception.message)
47 | errorMessage.value = response.exception.message ?: "请求异常"
48 | }
49 | }
50 | }
51 | }
52 | 1 -> {
53 | repo.addNewArticleCollect(webSiteTitle.value!!, linkUrl.value!!, author.value!!)
54 | .collectLatest { response ->
55 | when (response) {
56 | is HttpResult.Success -> {
57 | isSaved.value = true
58 | }
59 | is HttpResult.Error -> {
60 | println(response.exception.message)
61 | errorMessage.value = response.exception.message ?: "请求异常"
62 | }
63 | }
64 | }
65 | }
66 | else -> {
67 | repo.editCollectWebsite(id, webSiteTitle.value!!, linkUrl.value!!)
68 | .collectLatest { response ->
69 | when (response) {
70 | is HttpResult.Success -> {
71 | isSaved.value = true
72 | }
73 | is HttpResult.Error -> {
74 | println(response.exception.message)
75 | errorMessage.value = response.exception.message ?: "请求异常"
76 | }
77 | }
78 | }
79 | }
80 | }
81 | }
82 | }
83 |
84 | private fun setResponseState(errorInfo: String?) {
85 | isSaved.value = errorInfo == "the result of remote's request is null"
86 | }
87 |
88 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mm/hamcompose/ui/page/main/home/HomeViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.mm.hamcompose.ui.page.main.home
2 |
3 | import androidx.compose.runtime.mutableStateOf
4 | import com.mm.hamcompose.R
5 | import com.mm.hamcompose.data.bean.HomeThemeBean
6 | import com.mm.hamcompose.data.bean.MenuTitle
7 | import com.mm.hamcompose.data.bean.TabTitle
8 | import com.mm.hamcompose.theme.HamTheme
9 | import com.mm.hamcompose.ui.page.base.BaseViewModel
10 | import dagger.hilt.android.lifecycle.HiltViewModel
11 | import javax.inject.Inject
12 |
13 | @HiltViewModel
14 | class HomeViewModel @Inject constructor() : BaseViewModel() {
15 |
16 | var theme = mutableStateOf(HamTheme.Theme.Light)
17 | var menuItems = mutableListOf(
18 | MenuTitle("主页", null),
19 | MenuTitle("福利", R.drawable.ic_menu_welfare),
20 | MenuTitle("收藏", R.drawable.ic_star),
21 | MenuTitle("设置", R.drawable.ic_menu_settings),
22 | )
23 |
24 | var isShowSearchBar = mutableStateOf(true)
25 | val titles = mutableStateOf(
26 | mutableListOf(
27 | TabTitle(101, "推荐"),
28 | TabTitle(102, "广场"),
29 | TabTitle(103, "项目"),
30 | TabTitle(104, "问答")
31 | )
32 | )
33 |
34 | fun setCachePosition(tabIndex: Int, newPosition: Int) {
35 | titles.value[tabIndex].cachePosition = newPosition
36 | //val oldPosition = titles.value[tabIndex].cachePosition
37 | //isShowSearchBar.value = newPosition <= 1
38 | //LogUtils.w("newPosition = $newPosition isShowSearch = ${isShowSearchBar.value}")
39 | }
40 |
41 | override fun start() {
42 |
43 | }
44 |
45 | override fun onCleared() {
46 | super.onCleared()
47 | println("HomeViewModel ==> onClear")
48 | }
49 |
50 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mm/hamcompose/ui/page/main/home/index/IndexViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.mm.hamcompose.ui.page.main.home.index
2 |
3 | import androidx.compose.runtime.mutableStateListOf
4 | import androidx.compose.runtime.mutableStateOf
5 | import androidx.lifecycle.MutableLiveData
6 | import androidx.lifecycle.viewModelScope
7 | import androidx.paging.cachedIn
8 | import com.blankj.utilcode.util.LogUtils
9 | import com.mm.hamcompose.data.bean.Article
10 | import com.mm.hamcompose.data.bean.BannerBean
11 | import com.mm.hamcompose.data.db.history.HistoryDatabase
12 | import com.mm.hamcompose.data.http.HttpResult
13 | import com.mm.hamcompose.repository.HttpRepository
14 | import com.mm.hamcompose.repository.PagingArticle
15 | import com.mm.hamcompose.ui.page.base.BaseCollectViewModel
16 | import com.mm.hamcompose.ui.widget.BannerData
17 | import dagger.hilt.android.lifecycle.HiltViewModel
18 | import kotlinx.coroutines.flow.collectLatest
19 | import javax.inject.Inject
20 |
21 |
22 | @HiltViewModel
23 | class IndexViewModel @Inject constructor(
24 | private var repo: HttpRepository,
25 | private val historyDb: HistoryDatabase
26 | ) : BaseCollectViewModel(repo) {
27 |
28 | var pagingData = MutableLiveData(null)
29 | val imageList = mutableStateOf(mutableListOf())
30 | var isRefreshing = mutableStateOf(false)
31 | var topArticles = mutableStateListOf()
32 |
33 | //列表:使用paging3分页加载框架
34 | private fun homeData() = repo.getIndexData().cachedIn(viewModelScope)
35 |
36 | private fun loadBanners() {
37 | async {
38 | repo.getBanners().collectLatest { response ->
39 | when (response) {
40 | is HttpResult.Success -> {
41 | imageList.value = response.result.map {
42 | BannerData(
43 | imageUrl = it.imagePath ?: "",
44 | linkUrl = it.url ?: "",
45 | title = it.title ?: ""
46 | )
47 | } as MutableList
48 | }
49 | is HttpResult.Error -> {
50 | imageList.value.clear()
51 | }
52 | }
53 | }
54 | }
55 | }
56 |
57 | private fun loadTopArticles() {
58 | async {
59 | repo.getTopArticles().collectLatest { response ->
60 | when (response) {
61 | is HttpResult.Success -> {
62 | topArticles.clear()
63 | topArticles.addAll(response.result)
64 | }
65 | is HttpResult.Error -> {
66 | topArticles.clear()
67 | }
68 | }
69 | }
70 | }
71 | }
72 |
73 | private fun refreshBanner() {
74 | imageList.value.clear()
75 | loadBanners()
76 | }
77 |
78 | private fun refreshHots() {
79 | topArticles.clear()
80 | loadTopArticles()
81 | }
82 |
83 | private fun getHomesList() {
84 | pagingData.value = null
85 | pagingData.value = homeData()
86 | isRefreshing.value = pagingData.value == null
87 | }
88 |
89 | fun refresh() {
90 | resetListIndex()
91 | isRefreshing.value = true
92 | refreshBanner()
93 | refreshHots()
94 | getHomesList()
95 | }
96 |
97 | override fun start() {
98 | initThat {
99 | loadBanners()
100 | loadTopArticles()
101 | getHomesList()
102 | }
103 | }
104 |
105 | override fun onCleared() {
106 | LogUtils.e("IndexViewModel ===> ViewModel执行onCleared()")
107 | super.onCleared()
108 | }
109 |
110 | fun saveDataToHistory(article: Article) {
111 | cacheHistory(historyDb, article)
112 | }
113 |
114 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mm/hamcompose/ui/page/main/home/project/ProjectViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.mm.hamcompose.ui.page.main.home.project
2 |
3 | import androidx.compose.runtime.mutableStateOf
4 | import androidx.lifecycle.MutableLiveData
5 | import androidx.lifecycle.viewModelScope
6 | import androidx.paging.cachedIn
7 | import com.mm.hamcompose.data.bean.ParentBean
8 | import com.mm.hamcompose.data.http.HttpResult
9 | import com.mm.hamcompose.repository.HttpRepository
10 | import com.mm.hamcompose.repository.PagingArticle
11 | import com.mm.hamcompose.ui.page.base.BaseCollectViewModel
12 | import dagger.hilt.android.lifecycle.HiltViewModel
13 | import kotlinx.coroutines.flow.catch
14 | import kotlinx.coroutines.flow.collectLatest
15 | import kotlinx.coroutines.flow.onCompletion
16 | import javax.inject.Inject
17 |
18 | @HiltViewModel
19 | class ProjectViewModel @Inject constructor(
20 | private var httpRepo: HttpRepository
21 | ): BaseCollectViewModel(httpRepo) {
22 |
23 | var tabIndex = mutableStateOf(-1)
24 | var isRefreshing = mutableStateOf(true)
25 | var pagingData = MutableLiveData(null)
26 | var projectId = mutableStateOf(-1)
27 | var currentRowIndex = mutableStateOf(0)
28 |
29 | override fun start() {
30 | initThat {
31 | loadCategory()
32 | loadContent()
33 | }
34 |
35 | }
36 |
37 | fun setupProjectId(id: Int) {
38 | this.projectId.value = id
39 | }
40 |
41 | /**
42 | * 触发刷新机制
43 | */
44 | fun triggerRefresh() {
45 | isRefreshing.value = true
46 | }
47 |
48 | fun setTabIndex(index: Int) {
49 | tabIndex.value = index
50 | }
51 |
52 | private fun loadCategory() {
53 | async {
54 | httpRepo.getProjectCategory()
55 | .collectLatest { response ->
56 | when (response) {
57 | is HttpResult.Success -> {
58 | list.value = response.result
59 | tabIndex.value = 0
60 | }
61 | is HttpResult.Error -> {
62 | println(response.exception.message)
63 | }
64 | }
65 | }
66 | }
67 | }
68 |
69 | private fun getProjects() = httpRepo.getProjects(projectId.value).cachedIn(viewModelScope)
70 |
71 | fun refresh() {
72 | pagingData.value = null
73 | loadContent()
74 | }
75 |
76 | override fun loadContent() {
77 | pagingData.value = getProjects()
78 | isRefreshing.value = pagingData.value==null
79 | }
80 |
81 | fun saveRowPosition(position: Int) {
82 | currentRowIndex.value = position
83 | }
84 |
85 | }
86 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mm/hamcompose/ui/page/main/home/search/SearchViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.mm.hamcompose.ui.page.main.home.search
2 |
3 | import androidx.compose.runtime.mutableStateOf
4 | import androidx.lifecycle.MutableLiveData
5 | import androidx.lifecycle.viewModelScope
6 | import androidx.paging.cachedIn
7 | import com.blankj.utilcode.util.LogUtils
8 | import com.mm.hamcompose.data.bean.Article
9 | import com.mm.hamcompose.data.bean.Hotkey
10 | import com.mm.hamcompose.data.db.history.HistoryDatabase
11 | import com.mm.hamcompose.data.db.hotkey.HotkeyDatabase
12 | import com.mm.hamcompose.data.http.HttpResult
13 | import com.mm.hamcompose.repository.HttpRepository
14 | import com.mm.hamcompose.repository.PagingArticle
15 | import com.mm.hamcompose.ui.page.base.BaseCollectViewModel
16 | import dagger.hilt.android.lifecycle.HiltViewModel
17 | import kotlinx.coroutines.Dispatchers
18 | import kotlinx.coroutines.flow.collectLatest
19 | import kotlinx.coroutines.withContext
20 | import javax.inject.Inject
21 |
22 | @HiltViewModel
23 | class SearchViewModel @Inject constructor(
24 | private val repo: HttpRepository,
25 | private val hotkeyDatabase: HotkeyDatabase,
26 | private val historyDatabase: HistoryDatabase,
27 | ): BaseCollectViewModel(repo) {
28 |
29 | //搜索列表
30 | val searches = MutableLiveData(null)
31 | //搜索词的历史记录
32 | val history = mutableStateOf(mutableListOf())
33 | //搜索的热词
34 | val hotkeys = mutableStateOf(mutableListOf())
35 | val searchContent = mutableStateOf("")
36 |
37 | override fun start() {
38 | initThat {
39 | getHotkey()
40 | getHistory()
41 | }
42 | }
43 |
44 | fun search(key: String) {
45 | searches.value = repo.queryArticle(key).cachedIn(viewModelScope)
46 | }
47 |
48 | //插入数据
49 | fun insertKey(key: String) {
50 | if (hasTheSame(key))
51 | return
52 | async {
53 | val bean = Hotkey(link = "", name = key, order = 1, visible = 0)
54 | hotkeyDatabase.hotkeyDao().insertHotkeys(bean)
55 | update()
56 | }
57 | }
58 |
59 | //判断是否已经存在搜索词
60 | private fun hasTheSame(key: String): Boolean {
61 | val same = history.value.indexOfFirst { it.name.equals(key, ignoreCase = true) } != -1
62 | LogUtils.e("是否相同搜索词 = $same")
63 | return same
64 | }
65 |
66 | //删除单条数据
67 | fun deleteKey(key: Hotkey) {
68 | async {
69 | withContext(Dispatchers.IO) {
70 | hotkeyDatabase.hotkeyDao().deleteKeys(key)
71 | }
72 | update()
73 | }
74 | }
75 |
76 | //删除所有数据
77 | fun deleteAll() {
78 | async {
79 | hotkeyDatabase.hotkeyDao().deleteAll()
80 | update()
81 | }
82 | }
83 |
84 | private fun update() = getHistory()
85 |
86 | //查询所有数据
87 | fun getHistory() {
88 | async {
89 | val result = hotkeyDatabase.hotkeyDao().loadAllKeys()
90 | withContext(Dispatchers.Main) {
91 | history.value = result.toMutableList()
92 | }
93 | }
94 | }
95 |
96 | fun getHotkey() {
97 | async {
98 | repo.getHotkeys().collectLatest { response ->
99 | when (response) {
100 | is HttpResult.Success -> {
101 | hotkeys.value = response.result
102 | }
103 | is HttpResult.Error -> {
104 |
105 | }
106 | }
107 | }
108 | }
109 | }
110 |
111 | fun saveDataToHistory(article: Article) {
112 | cacheHistory(historyDatabase, article)
113 | }
114 |
115 |
116 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mm/hamcompose/ui/page/main/home/square/SquarePage.kt:
--------------------------------------------------------------------------------
1 | package com.mm.hamcompose.ui.page.main.home.square
2 |
3 | import androidx.compose.foundation.layout.PaddingValues
4 | import androidx.compose.foundation.layout.fillMaxSize
5 | import androidx.compose.foundation.lazy.LazyColumn
6 | import androidx.compose.foundation.lazy.rememberLazyListState
7 | import androidx.compose.material.ScaffoldState
8 | import androidx.compose.runtime.Composable
9 | import androidx.compose.runtime.getValue
10 | import androidx.compose.runtime.remember
11 | import androidx.compose.runtime.rememberCoroutineScope
12 | import androidx.compose.ui.Modifier
13 | import androidx.compose.ui.unit.dp
14 | import androidx.hilt.navigation.compose.hiltViewModel
15 | import androidx.navigation.NavHostController
16 | import androidx.paging.compose.collectAsLazyPagingItems
17 | import androidx.paging.compose.itemsIndexed
18 | import com.google.accompanist.swiperefresh.SwipeRefresh
19 | import com.google.accompanist.swiperefresh.rememberSwipeRefreshState
20 | import com.mm.hamcompose.data.bean.Article
21 | import com.mm.hamcompose.ui.route.RouteName
22 | import com.mm.hamcompose.ui.route.RouteUtils
23 | import com.mm.hamcompose.ui.widget.EmptyView
24 | import com.mm.hamcompose.ui.widget.MultiStateItemView
25 | import com.mm.hamcompose.ui.widget.SNACK_INFO
26 | import com.mm.hamcompose.ui.widget.popupSnackBar
27 |
28 | @Composable
29 | fun SquarePage(
30 | navCtrl: NavHostController,
31 | scaffoldState: ScaffoldState,
32 | viewModel: SquareViewModel = hiltViewModel(),
33 | onScrollChangeListener: (position: Int) -> Unit,
34 | ) {
35 |
36 | viewModel.start()
37 | val squareData = viewModel.pagingData.value?.collectAsLazyPagingItems()
38 | val isLoaded = squareData?.loadState?.prepend?.endOfPaginationReached ?: false
39 | val refreshing: Boolean by remember { viewModel.isRefreshing }
40 | val swipeRefreshState = rememberSwipeRefreshState(refreshing)
41 | val currentPosition by remember { viewModel.currentListIndex }
42 | val message by remember { viewModel.message }
43 | val listState = rememberLazyListState(currentPosition)
44 | val coroutineScope = rememberCoroutineScope()
45 |
46 | if (message.isNotEmpty()) {
47 | popupSnackBar(coroutineScope, scaffoldState, SNACK_INFO, message)
48 | viewModel.message.value = ""
49 | }
50 |
51 |
52 | SwipeRefresh(
53 | state = swipeRefreshState,
54 | onRefresh = { viewModel.refresh() }
55 | ) {
56 |
57 | LazyColumn(
58 | modifier = Modifier.fillMaxSize(),
59 | state = listState,
60 | contentPadding = PaddingValues(top = 10.dp)
61 | ) {
62 |
63 | if (isLoaded) {
64 | if (squareData!!.itemCount > 0) {
65 | itemsIndexed(squareData) { index, item ->
66 | MultiStateItemView(
67 | data = item!!,
68 | onSelected = {
69 | viewModel.saveDataToHistory(item)
70 | viewModel.savePosition(listState.firstVisibleItemIndex)
71 | RouteUtils.navTo(navCtrl, RouteName.WEB_VIEW, it)
72 | },
73 | onCollectClick = {
74 | if (item.collect) {
75 | viewModel.uncollectArticleById(it)
76 | squareData.peek(index)?.collect = false
77 | } else {
78 | viewModel.collectArticleById(it)
79 | squareData.peek(index)?.collect = true
80 | }
81 |
82 | },
83 | onUserClick = { userId ->
84 | RouteUtils.navTo(navCtrl, RouteName.SHARER, userId)
85 | })
86 | }
87 | onScrollChangeListener(listState.firstVisibleItemIndex)
88 | } else {
89 | item { EmptyView() }
90 | }
91 | } else {
92 | items(5) {
93 | MultiStateItemView(
94 | data = Article(),
95 | isLoading = true
96 | )
97 | }
98 | }
99 |
100 | }
101 | }
102 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mm/hamcompose/ui/page/main/home/square/SquareViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.mm.hamcompose.ui.page.main.home.square
2 |
3 | import androidx.compose.runtime.mutableStateOf
4 | import androidx.lifecycle.MutableLiveData
5 | import androidx.lifecycle.viewModelScope
6 | import androidx.paging.cachedIn
7 | import com.blankj.utilcode.util.LogUtils
8 | import com.mm.hamcompose.data.bean.Article
9 | import com.mm.hamcompose.data.db.history.HistoryDatabase
10 | import com.mm.hamcompose.repository.HttpRepository
11 | import com.mm.hamcompose.repository.PagingArticle
12 | import com.mm.hamcompose.ui.page.base.BaseCollectViewModel
13 | import dagger.hilt.android.lifecycle.HiltViewModel
14 | import javax.inject.Inject
15 |
16 | @HiltViewModel
17 | class SquareViewModel @Inject constructor(
18 | private var repo: HttpRepository,
19 | private val db: HistoryDatabase,
20 | ): BaseCollectViewModel(repo) {
21 |
22 | var pagingData = MutableLiveData(null)
23 | var isRefreshing = mutableStateOf(false)
24 |
25 | override fun start() {
26 | initThat {
27 | pagingData.value = squareData()
28 | }
29 | }
30 |
31 | fun refresh() {
32 | resetListIndex()
33 | isRefreshing.value = true
34 | pagingData.value = null
35 | pagingData.value = squareData()
36 | isRefreshing.value = pagingData.value==null
37 | }
38 |
39 | private fun squareData() = repo.getSquareData().cachedIn(viewModelScope)
40 |
41 | fun saveDataToHistory(article: Article) {
42 | cacheHistory(db, article)
43 | }
44 |
45 | override fun onCleared() {
46 | LogUtils.e("SquareViewModel ===> ViewModel执行onCleared()")
47 | super.onCleared()
48 | }
49 |
50 |
51 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mm/hamcompose/ui/page/main/home/wenda/WenDaViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.mm.hamcompose.ui.page.main.home.wenda
2 |
3 | import androidx.compose.runtime.mutableStateOf
4 | import androidx.lifecycle.MutableLiveData
5 | import androidx.lifecycle.viewModelScope
6 | import androidx.paging.cachedIn
7 | import com.mm.hamcompose.data.bean.Article
8 | import com.mm.hamcompose.repository.HttpRepository
9 | import com.mm.hamcompose.repository.PagingArticle
10 | import com.mm.hamcompose.ui.page.base.BaseViewModel
11 | import dagger.hilt.android.lifecycle.HiltViewModel
12 | import javax.inject.Inject
13 |
14 | @HiltViewModel
15 | class WenDaViewModel @Inject constructor(
16 | private var repo: HttpRepository
17 | ): BaseViewModel() {
18 |
19 | var pagingData = MutableLiveData(null)
20 | var isRefreshing = mutableStateOf(false)
21 |
22 | override fun start() {
23 | initThat { pagingData.value = squareData() }
24 | }
25 |
26 | fun refresh() {
27 | resetListIndex()
28 | isRefreshing.value = true
29 | pagingData.value = null
30 | pagingData.value = squareData()
31 | isRefreshing.value = pagingData.value==null
32 | }
33 |
34 | private fun squareData() = repo.getWendaData().cachedIn(viewModelScope)
35 |
36 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mm/hamcompose/ui/page/main/profile/ProfileViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.mm.hamcompose.ui.page.main.profile
2 |
3 | import androidx.compose.runtime.mutableStateOf
4 | import com.mm.hamcompose.data.bean.Article
5 | import com.mm.hamcompose.data.bean.PointsBean
6 | import com.mm.hamcompose.data.bean.UserInfo
7 | import com.mm.hamcompose.data.db.user.UserInfoDatabase
8 | import com.mm.hamcompose.data.http.HttpResult
9 | import com.mm.hamcompose.repository.HttpRepository
10 | import com.mm.hamcompose.ui.page.base.BaseViewModel
11 | import dagger.hilt.android.lifecycle.HiltViewModel
12 | import kotlinx.coroutines.Dispatchers
13 | import kotlinx.coroutines.flow.collectLatest
14 | import kotlinx.coroutines.flow.flow
15 | import kotlinx.coroutines.flow.flowOn
16 | import kotlinx.coroutines.withContext
17 | import javax.inject.Inject
18 |
19 | @HiltViewModel
20 | class ProfileViewModel @Inject constructor(
21 | private val repo: HttpRepository,
22 | private val db: UserInfoDatabase
23 | ) : BaseViewModel() {
24 |
25 | val isLogin = mutableStateOf(false)
26 | val isRefresh = mutableStateOf(false)
27 | val messageCount = mutableStateOf(0)
28 | var userInfo = mutableStateOf(null)
29 | var page = mutableStateOf(1)
30 | var myPoints = mutableStateOf(null)
31 | val myArticles = mutableStateOf(mutableListOf())
32 |
33 | override fun start() {
34 | checkLoginState()
35 | }
36 |
37 | private fun initUserRemoteData() {
38 | initMessageCount()
39 | initBasicUserInfo()
40 | getMyShareArticles()
41 | }
42 |
43 | fun refresh() {
44 | isRefresh.value = true
45 | checkLoginState()
46 | }
47 |
48 | private fun initBasicUserInfo() {
49 | async {
50 | repo.getBasicUserInfo().collectLatest { response ->
51 | when (response) {
52 | is HttpResult.Success -> {
53 | myPoints.value = response.result.coinInfo
54 | userInfo.value = response.result.userInfo
55 | insertNewestUserInfo(response.result.userInfo)
56 | }
57 | is HttpResult.Error -> {
58 | println(response.exception.message)
59 | }
60 | }
61 | }
62 | }
63 | }
64 |
65 | private suspend fun insertNewestUserInfo(user: UserInfo) {
66 | withContext(Dispatchers.IO) {
67 | db.userInfoDao().insertUserInfo(user)
68 | }
69 | }
70 |
71 | private fun initMessageCount() {
72 | async {
73 | repo.getMessageCount().collectLatest { response ->
74 | when (response) {
75 | is HttpResult.Success -> {
76 | messageCount.value = response.result
77 | }
78 | is HttpResult.Error -> {
79 | println(response.exception.message)
80 | }
81 | }
82 | }
83 | }
84 | }
85 |
86 | private fun checkLoginState() {
87 | async {
88 | flow { emit(db.userInfoDao().queryUserInfo()) }
89 | .flowOn(Dispatchers.IO)
90 | .collectLatest { users ->
91 | isLogin.value = users.isNotEmpty()
92 | if (users.isNotEmpty()) {
93 | userInfo.value = users[0]
94 | initUserRemoteData()
95 | }
96 | }
97 | }
98 | }
99 |
100 | private fun isNotInit() = userInfo.value==null && myPoints.value==null
101 |
102 | private fun getMyShareArticles() {
103 | async {
104 | repo.getMyShareArticles(page.value).collectLatest { response ->
105 | isRefresh.value = false
106 | when (response) {
107 | is HttpResult.Success -> {
108 | myPoints.value = response.result.coinInfo
109 | myArticles.value = response.result.shareArticles.datas
110 | }
111 | is HttpResult.Error -> {
112 | println(response.exception.message)
113 | }
114 | }
115 | }
116 | }
117 | }
118 |
119 | override fun onCleared() {
120 | super.onCleared()
121 | println("ProfileViewModel ==> onClear")
122 | }
123 |
124 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mm/hamcompose/ui/page/main/profile/history/HistoryPage.kt:
--------------------------------------------------------------------------------
1 | package com.mm.hamcompose.ui.page.main.profile.history
2 |
3 | import androidx.compose.foundation.ExperimentalFoundationApi
4 | import androidx.compose.foundation.clickable
5 | import androidx.compose.foundation.layout.Column
6 | import androidx.compose.foundation.layout.fillMaxSize
7 | import androidx.compose.foundation.layout.padding
8 | import androidx.compose.foundation.lazy.LazyColumn
9 | import androidx.compose.foundation.lazy.items
10 | import androidx.compose.foundation.lazy.itemsIndexed
11 | import androidx.compose.material.ScaffoldState
12 | import androidx.compose.material.icons.Icons
13 | import androidx.compose.material.icons.filled.Info
14 | import androidx.compose.runtime.Composable
15 | import androidx.compose.runtime.getValue
16 | import androidx.compose.runtime.remember
17 | import androidx.compose.runtime.rememberCoroutineScope
18 | import androidx.compose.ui.Modifier
19 | import androidx.compose.ui.unit.dp
20 | import androidx.hilt.navigation.compose.hiltViewModel
21 | import androidx.navigation.NavHostController
22 | import com.mm.hamcompose.data.bean.WebData
23 | import com.mm.hamcompose.ui.route.RouteName
24 | import com.mm.hamcompose.ui.route.RouteUtils
25 | import com.mm.hamcompose.ui.route.RouteUtils.back
26 | import com.mm.hamcompose.ui.widget.*
27 |
28 | @OptIn(ExperimentalFoundationApi::class)
29 | @Composable
30 | fun HistoryPage(
31 | navCtrl: NavHostController,
32 | scaffoldState: ScaffoldState,
33 | viewModel: HistoryViewModel = hiltViewModel()
34 | ) {
35 |
36 | viewModel.start()
37 | val historyList by remember { viewModel.list }
38 | val isClear by remember { viewModel.isClear }
39 | val asyncScope = rememberCoroutineScope()
40 |
41 | if (isClear) {
42 | popupSnackBar(asyncScope, scaffoldState, SNACK_INFO, "历史记录已清空")
43 | viewModel.isClear.value = false
44 | }
45 |
46 | Column(
47 | modifier = Modifier.fillMaxSize()
48 | ) {
49 | HamToolBar(
50 | title = "历史浏览记录",
51 | rightText = "清除所有",
52 | onBack = { navCtrl.back() },
53 | onRightClick = {
54 | viewModel.clearAllHistory()
55 | }
56 | )
57 | ListTitle(title = "最近在看", modifier = Modifier.padding(top = 12.dp, bottom = 5.dp))
58 | if (historyList.isNotEmpty()) {
59 | LazyColumn {
60 | itemsIndexed(historyList) { index, item ->
61 | TextContent(
62 | text = "${index + 1}. ${item.title}",
63 | modifier = Modifier
64 | .padding(horizontal = 10.dp, vertical = 5.dp)
65 | .clickable {
66 | RouteUtils.navTo(
67 | navCtrl = navCtrl,
68 | destinationName = RouteName.WEB_VIEW,
69 | args = WebData(item.title, item.link)
70 | )
71 | },
72 | maxLines = 2,
73 | )
74 | }
75 |
76 | }
77 | } else {
78 | EmptyView(tips = "暂无浏览记录", imageVector = Icons.Default.Info)
79 | }
80 |
81 | }
82 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mm/hamcompose/ui/page/main/profile/history/HistoryViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.mm.hamcompose.ui.page.main.profile.history
2 |
3 | import androidx.compose.runtime.mutableStateOf
4 | import com.mm.hamcompose.data.bean.HistoryRecord
5 | import com.mm.hamcompose.data.db.history.HistoryDatabase
6 | import com.mm.hamcompose.ui.page.base.BaseViewModel
7 | import dagger.hilt.android.lifecycle.HiltViewModel
8 | import kotlinx.coroutines.Dispatchers
9 | import kotlinx.coroutines.withContext
10 | import javax.inject.Inject
11 |
12 | @HiltViewModel
13 | class HistoryViewModel @Inject constructor(private val db: HistoryDatabase): BaseViewModel() {
14 |
15 | var isClear = mutableStateOf(false)
16 |
17 | override fun start() {
18 | initThat { getHistoryList() }
19 | }
20 |
21 | private fun getHistoryList() {
22 | async {
23 | val history = withContext(Dispatchers.IO) { db.historyDao().queryAll() }
24 | withContext(Dispatchers.Main) {
25 | list.value = history.toMutableList()
26 | }
27 | }
28 | }
29 |
30 | fun clearAllHistory() {
31 | async {
32 | withContext(Dispatchers.IO) { db.historyDao().deleteAll() }
33 | withContext(Dispatchers.Main) {
34 | list.value = mutableListOf()
35 | isClear.value = true
36 | }
37 | }
38 | }
39 |
40 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mm/hamcompose/ui/page/main/profile/message/MessagePage.kt:
--------------------------------------------------------------------------------
1 | package com.mm.hamcompose.ui.page.main.profile.message
2 |
3 | import androidx.compose.foundation.layout.Column
4 | import androidx.compose.foundation.lazy.LazyColumn
5 | import androidx.compose.material.icons.Icons
6 | import androidx.compose.material.icons.filled.Refresh
7 | import androidx.compose.runtime.*
8 | import androidx.compose.ui.unit.dp
9 | import androidx.hilt.navigation.compose.hiltViewModel
10 | import androidx.navigation.NavHostController
11 | import androidx.paging.compose.LazyPagingItems
12 | import androidx.paging.compose.collectAsLazyPagingItems
13 | import com.google.accompanist.pager.ExperimentalPagerApi
14 | import com.google.accompanist.pager.HorizontalPager
15 | import com.google.accompanist.pager.rememberPagerState
16 | import com.mm.hamcompose.ui.route.RouteUtils.back
17 | import com.mm.hamcompose.ui.widget.EmptyView
18 | import com.mm.hamcompose.ui.widget.HamToolBar
19 | import com.mm.hamcompose.ui.widget.SwitchTabBar
20 | import kotlinx.coroutines.launch
21 |
22 | @OptIn(ExperimentalPagerApi::class)
23 | @Composable
24 | fun MessagePage(
25 | navCtrl: NavHostController,
26 | viewModel: MessageViewModel = hiltViewModel()
27 | ) {
28 |
29 | val titles by remember { viewModel.titles }
30 | val tabIndex by remember { viewModel.tabIndex }
31 | val unreadMessages = viewModel.pagingUnread.value?.collectAsLazyPagingItems()
32 | val readedMessages = viewModel.pagingReaded.value?.collectAsLazyPagingItems()
33 |
34 | Column {
35 |
36 | HamToolBar(title = "我的消息", onBack = { navCtrl.back() })
37 |
38 | val coroutineScope = rememberCoroutineScope()
39 | val pagerState = rememberPagerState(
40 | pageCount = titles.size,
41 | initialPage = tabIndex,
42 | initialOffscreenLimit = titles.size
43 | )
44 |
45 | SwitchTabBar(
46 | titles = titles,
47 | selectIndex = tabIndex,
48 | ) {
49 | viewModel.tabIndex.value = it
50 | coroutineScope.launch {
51 | pagerState.scrollToPage(tabIndex)
52 | }
53 | }
54 |
55 | HorizontalPager(state = pagerState) { page ->
56 | viewModel.tabIndex.value = pagerState.currentPage
57 | when (page) {
58 | 0 -> MessageScreen(unreadMessages, false) {
59 | viewModel.refreshUnreadData()
60 | }
61 | 1 -> MessageScreen(readedMessages, true) {
62 | viewModel.refreshReadedData()
63 | }
64 | }
65 | }
66 | }
67 | }
68 |
69 | @Composable
70 | private fun MessageScreen(data: LazyPagingItems?, isReaded: Boolean, onRefresh: ()-> Unit) {
71 | if (data == null) {
72 | EmptyView(
73 | tips = if (isReaded) "没有已读消息" else "没有未读消息",
74 | imageVector = Icons.Default.Refresh,
75 | onClick = onRefresh)
76 | } else {
77 | LazyColumn {
78 | //TODO 未知消息的数据json
79 | }
80 | }
81 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mm/hamcompose/ui/page/main/profile/message/MessageViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.mm.hamcompose.ui.page.main.profile.message
2 |
3 | import androidx.compose.runtime.mutableStateOf
4 | import androidx.lifecycle.MutableLiveData
5 | import androidx.lifecycle.viewModelScope
6 | import androidx.paging.cachedIn
7 | import com.mm.hamcompose.data.bean.TabTitle
8 | import com.mm.hamcompose.repository.HttpRepository
9 | import com.mm.hamcompose.repository.PagingAny
10 | import com.mm.hamcompose.ui.page.base.BaseViewModel
11 | import dagger.hilt.android.lifecycle.HiltViewModel
12 | import javax.inject.Inject
13 |
14 | @HiltViewModel
15 | class MessageViewModel @Inject constructor(private val repo: HttpRepository): BaseViewModel() {
16 |
17 |
18 | var tabIndex = mutableStateOf(0)
19 | var errorMessage = mutableStateOf(null)
20 | var pagingUnread = MutableLiveData(null)
21 | var pagingReaded = MutableLiveData(null)
22 | val titles = mutableStateOf(mutableListOf(
23 | TabTitle(401, "未读消息"),
24 | TabTitle(402, "已读消息"),
25 | ))
26 |
27 |
28 | override fun start() {
29 | initThat {
30 | pagingUnread.value = unread()
31 | pagingReaded.value = readed()
32 | }
33 | }
34 |
35 | fun refreshUnreadData() {
36 | pagingUnread.value = null
37 | pagingUnread.value = unread()
38 | }
39 |
40 | fun refreshReadedData() {
41 | pagingReaded.value = null
42 | pagingReaded.value = readed()
43 | }
44 |
45 | private fun unread() = repo.getUnreadMessages().cachedIn(viewModelScope)
46 |
47 | private fun readed() = repo.getReadedMessages().cachedIn(viewModelScope)
48 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mm/hamcompose/ui/page/main/profile/points/PointsRankingViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.mm.hamcompose.ui.page.main.profile.points
2 |
3 | import androidx.compose.runtime.mutableStateOf
4 | import androidx.lifecycle.MutableLiveData
5 | import androidx.lifecycle.viewModelScope
6 | import androidx.paging.cachedIn
7 | import com.mm.hamcompose.data.bean.PointsBean
8 | import com.mm.hamcompose.data.bean.TabTitle
9 | import com.mm.hamcompose.data.http.HttpResult
10 | import com.mm.hamcompose.repository.HttpRepository
11 | import com.mm.hamcompose.repository.PagingPoints
12 | import com.mm.hamcompose.ui.page.base.BaseViewModel
13 | import dagger.hilt.android.lifecycle.HiltViewModel
14 | import kotlinx.coroutines.flow.collectLatest
15 | import javax.inject.Inject
16 |
17 | @HiltViewModel
18 | class PointsRankingViewModel @Inject constructor(
19 | private val repo: HttpRepository,
20 | ) : BaseViewModel() {
21 |
22 | val pagingRanking = MutableLiveData(null)
23 | val pagingRecords = MutableLiveData(null)
24 | val personalPoints = mutableStateOf(null)
25 | var tabIndex = mutableStateOf(0)
26 | var errorMessage = mutableStateOf(null)
27 | val titles = mutableStateOf(
28 | mutableListOf(
29 | TabTitle(501, "排行榜"),
30 | TabTitle(502, "我的积分"),
31 | )
32 | )
33 |
34 | override fun start() {
35 | initThat {
36 | fetchData()
37 | }
38 | }
39 |
40 | private fun fetchData() {
41 | if (personalPoints.value == null) {
42 | requestPersonPoints()
43 | }
44 | if (pagingRanking.value == null) {
45 | pagingRanking.value = ranking()
46 | }
47 | if (pagingRecords.value == null) {
48 | pagingRecords.value = records()
49 | }
50 | }
51 |
52 | private fun requestPersonPoints() {
53 | async {
54 | repo.getMyPointsRanking()
55 | .collectLatest { response ->
56 | when (response) {
57 | is HttpResult.Success -> {
58 | personalPoints.value = response.result
59 | }
60 | is HttpResult.Error -> {
61 | errorMessage.value = response.exception.message
62 | }
63 | }
64 | }
65 | }
66 | }
67 |
68 | private fun ranking(): PagingPoints {
69 | return repo.getPointsRankings().cachedIn(viewModelScope)
70 | }
71 |
72 |
73 | private fun records() = repo.getPointsRecords().cachedIn(viewModelScope)
74 |
75 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mm/hamcompose/ui/page/main/profile/settings/SettingsViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.mm.hamcompose.ui.page.main.profile.settings
2 |
3 | import androidx.compose.runtime.mutableStateOf
4 | import androidx.compose.ui.graphics.Color
5 | import com.blankj.utilcode.util.SPUtils
6 | import com.mm.hamcompose.data.bean.UserInfo
7 | import com.mm.hamcompose.data.db.user.UserInfoDatabase
8 | import com.mm.hamcompose.data.http.HttpResult
9 | import com.mm.hamcompose.data.store.DataStoreUtils
10 | import com.mm.hamcompose.repository.HttpRepository
11 | import com.mm.hamcompose.theme.THEME_COLOR_KEY
12 | import com.mm.hamcompose.ui.page.base.BaseViewModel
13 | import dagger.hilt.android.lifecycle.HiltViewModel
14 | import kotlinx.coroutines.Dispatchers
15 | import kotlinx.coroutines.delay
16 | import kotlinx.coroutines.flow.collectLatest
17 | import kotlinx.coroutines.withContext
18 | import javax.inject.Inject
19 |
20 | @HiltViewModel
21 | class SettingsViewModel @Inject constructor(
22 | private val repo: HttpRepository,
23 | private val db: UserInfoDatabase
24 | ): BaseViewModel() {
25 |
26 | val cacheSize = mutableStateOf(0)
27 |
28 | var logout = mutableStateOf(false)
29 | var themeIndex = mutableStateOf(SPUtils.getInstance().getInt(THEME_COLOR_KEY, 0))
30 | var selectTheme = mutableStateOf(null)
31 |
32 |
33 | override fun start() {
34 | initThat { }
35 | }
36 |
37 | fun logout() {
38 | async {
39 | repo.logout().collectLatest {
40 | when(it) {
41 | is HttpResult.Success -> { }
42 | is HttpResult.Error -> {
43 | //退出登录的情况下,不走success判断分支
44 | val nullNotice = "the result of remote's request is null"
45 | if (it.exception.message==nullNotice) {
46 | clearUserInfo()
47 | }
48 | }
49 | }
50 | }
51 | }
52 | }
53 |
54 | private fun clearUserInfo() {
55 | async {
56 | withContext(Dispatchers.IO) {
57 | db.userInfoDao().deleteAllUserInfo()
58 | DataStoreUtils.clear()
59 | delay(10)
60 | logout.value = true
61 | }
62 | }
63 | }
64 |
65 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mm/hamcompose/ui/page/main/profile/sharer/SharerViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.mm.hamcompose.ui.page.main.profile.sharer
2 |
3 | import androidx.compose.runtime.mutableStateListOf
4 | import androidx.compose.runtime.mutableStateOf
5 | import com.mm.hamcompose.data.bean.Article
6 | import com.mm.hamcompose.data.bean.MY_USER_ID
7 | import com.mm.hamcompose.data.bean.PointsBean
8 | import com.mm.hamcompose.data.http.HttpResult
9 | import com.mm.hamcompose.repository.HttpRepository
10 | import com.mm.hamcompose.ui.page.base.BaseViewModel
11 | import dagger.hilt.android.lifecycle.HiltViewModel
12 | import kotlinx.coroutines.flow.collectLatest
13 | import javax.inject.Inject
14 |
15 | @HiltViewModel
16 | class SharerViewModel @Inject constructor(private val repo: HttpRepository): BaseViewModel() {
17 |
18 | private var page = mutableStateOf(1)
19 | var points = mutableStateOf(null)
20 | var articles = mutableStateListOf()
21 | private val userId = mutableStateOf(-1)
22 | var isLoadingMore = mutableStateOf(false)
23 | var hasMore = mutableStateOf(false)
24 | var errorMessage = mutableStateOf("")
25 |
26 | fun setupUserId(id: Int) {
27 | this.userId.value = id
28 | }
29 |
30 | override fun start() {
31 | initThat {
32 | startLoading()
33 | getShareData()
34 | }
35 | }
36 |
37 | fun nextPage() {
38 | if (hasMore.value) {
39 | page.value += 1
40 | getShareData()
41 | }
42 | }
43 |
44 | private fun getShareData() {
45 | async {
46 | isLoadingMore.value = true
47 | val call = if (userId.value == MY_USER_ID) {
48 | repo.getMyShareArticles(page.value)
49 | } else {
50 | repo.getAuthorShareArticles(userId.value, page.value)
51 | }
52 | call.collectLatest { response ->
53 | when (response) {
54 | is HttpResult.Success -> {
55 |
56 | points.value = response.result.coinInfo
57 | response.result.shareArticles.datas.run {
58 | if (!isNullOrEmpty()) {
59 | articles.addAll(this)
60 | }
61 | errorMessage.value = if (isNullOrEmpty()) "啥都没有~" else ""
62 | hasMore.value = !response.result.shareArticles.over && size >= 0
63 | }
64 |
65 | }
66 | is HttpResult.Error -> {
67 | println(response.exception.message)
68 | errorMessage.value = response.exception.message ?: "未知异常"
69 | }
70 | }
71 | resetStatus()
72 | }
73 | }
74 | }
75 |
76 | private fun resetStatus() {
77 | stopLoading()
78 | isLoadingMore.value = false
79 | }
80 |
81 |
82 | fun deleteMyArticle(id: Int) {
83 | async {
84 | repo.deleteMyShareArticle(id).collectLatest { response ->
85 | when (response) {
86 | is HttpResult.Success -> {
87 | }
88 | is HttpResult.Error -> {
89 | println(response.exception.message)
90 | val error = response.exception.message
91 | if (error == "the result of remote's request is null") {
92 | val deleteItem = articles.find { it.id == id }
93 | articles.remove(deleteItem)
94 | }
95 | }
96 | }
97 | }
98 | }
99 | }
100 |
101 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mm/hamcompose/ui/page/main/profile/user/UserViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.mm.hamcompose.ui.page.main.profile.user
2 |
3 | import androidx.compose.runtime.mutableStateOf
4 | import com.blankj.utilcode.util.LogUtils
5 | import com.mm.hamcompose.data.bean.UserInfo
6 | import com.mm.hamcompose.data.http.HttpResult
7 | import com.mm.hamcompose.repository.HttpRepository
8 | import com.mm.hamcompose.data.db.user.UserInfoDatabase
9 | import com.mm.hamcompose.ui.page.base.BaseViewModel
10 | import dagger.hilt.android.lifecycle.HiltViewModel
11 | import kotlinx.coroutines.Dispatchers
12 | import kotlinx.coroutines.flow.collectLatest
13 | import kotlinx.coroutines.withContext
14 | import javax.inject.Inject
15 |
16 | @HiltViewModel
17 | class UserViewModel @Inject constructor(
18 | private val repo: HttpRepository,
19 | private val userDb: UserInfoDatabase
20 | ) : BaseViewModel() {
21 |
22 | var errorMessage = mutableStateOf(null)
23 | var isRegister = mutableStateOf(false)
24 | var isLogin = mutableStateOf(false)
25 |
26 | override fun start() {}
27 |
28 | fun register(account: String, password: String, repassword: String) {
29 | async {
30 | repo.register(account, password, repassword)
31 | .collectLatest { response ->
32 | when (response) {
33 | is HttpResult.Success -> {
34 | isRegister.value = true
35 | }
36 | is HttpResult.Error -> {
37 | errorMessage.value = response.exception.message
38 | //ToastUtils.showShort(errorMessage.value)
39 | }
40 | }
41 | }
42 | }
43 | }
44 |
45 | fun login(account: String, password: String) {
46 | async {
47 | repo.login(account, password)
48 | .collectLatest { response ->
49 | when (response) {
50 | is HttpResult.Success -> {
51 | saveUserInfo(response.result)
52 | isLogin.value = true
53 | }
54 | is HttpResult.Error -> {
55 | errorMessage.value = response.exception.message
56 | }
57 | }
58 | }
59 | }
60 | }
61 |
62 | override fun onCleared() {
63 | super.onCleared()
64 | LogUtils.e("invoke onCleared of ViewModel")
65 | }
66 |
67 | private fun saveUserInfo(userInfo: UserInfo) {
68 | async {
69 | withContext(Dispatchers.IO) {
70 | userDb.userInfoDao().insertUserInfo(userInfo)
71 | }
72 | }
73 | }
74 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mm/hamcompose/ui/page/webview/WebView.kt:
--------------------------------------------------------------------------------
1 | package com.mm.hamcompose.ui.page.webview
2 |
3 | import android.annotation.SuppressLint
4 | import android.content.res.ColorStateList
5 | import android.view.ViewGroup
6 | import android.webkit.WebView
7 | import android.widget.FrameLayout
8 | import android.widget.ProgressBar
9 | import androidx.compose.foundation.layout.Box
10 | import androidx.compose.foundation.layout.fillMaxSize
11 | import androidx.compose.foundation.layout.padding
12 | import androidx.compose.runtime.*
13 | import androidx.compose.ui.Modifier
14 | import androidx.compose.ui.viewinterop.AndroidView
15 | import androidx.navigation.NavHostController
16 | import com.blankj.utilcode.util.SizeUtils
17 | import com.google.accompanist.swiperefresh.rememberSwipeRefreshState
18 | import com.mm.hamcompose.R
19 | import com.mm.hamcompose.data.bean.WebData
20 | import com.mm.hamcompose.theme.ToolBarHeight
21 | import com.mm.hamcompose.ui.route.RouteUtils.back
22 | import com.mm.hamcompose.ui.widget.HamToolBar
23 |
24 | @SuppressLint("UseCompatLoadingForDrawables")
25 | @Composable
26 | fun WebViewPage(
27 | webData: WebData,
28 | navCtrl: NavHostController
29 | ) {
30 | var ctrl: WebViewCtrl? by remember { mutableStateOf(null) }
31 | Box {
32 | var isRefreshing: Boolean by remember { mutableStateOf(false) }
33 | val refreshState = rememberSwipeRefreshState(isRefreshing)
34 | AndroidView(
35 | modifier = Modifier
36 | .padding(top = ToolBarHeight)
37 | .fillMaxSize(),
38 | factory = { context ->
39 | FrameLayout(context).apply {
40 | layoutParams = FrameLayout.LayoutParams(
41 | FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT
42 | )
43 | val progressView = ProgressBar(context).apply {
44 | layoutParams = ViewGroup.LayoutParams(
45 | ViewGroup.LayoutParams.MATCH_PARENT,
46 | SizeUtils.dp2px(2f)
47 | )
48 | progressDrawable =
49 | context.resources.getDrawable(R.drawable.horizontal_progressbar)
50 | indeterminateTintList =
51 | ColorStateList.valueOf(context.resources.getColor(R.color.teal_200))
52 | }
53 | val webView = WebView(context).apply {
54 | layoutParams = ViewGroup.LayoutParams(
55 | ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT
56 | )
57 | }
58 | addView(webView)
59 | addView(progressView)
60 | ctrl = WebViewCtrl(this, webData.url, onWebCall = { isFinish ->
61 | isRefreshing = !isFinish
62 | })
63 | ctrl?.initSettings()
64 | }
65 |
66 | },
67 | update = {
68 |
69 | }
70 | )
71 |
72 | HamToolBar(title = webData.title ?: "标题", onBack = {
73 | ctrl?.onDestroy()
74 | navCtrl.back()
75 | })
76 | }
77 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mm/hamcompose/ui/page/webview/WebViewCtrl.kt:
--------------------------------------------------------------------------------
1 | package com.mm.hamcompose.ui.page.webview
2 |
3 | import android.graphics.Bitmap
4 | import android.net.http.SslError
5 | import android.os.Build
6 | import android.view.View
7 | import android.webkit.*
8 | import android.widget.FrameLayout
9 | import android.widget.ProgressBar
10 | import androidx.annotation.RequiresApi
11 |
12 | class WebViewCtrl(
13 | private val mView: FrameLayout,
14 | private var linkUrl: String,
15 | private val onWebCall: (isFinish: Boolean) -> Unit
16 | ) {
17 |
18 | private val webView by lazy { mView.getChildAt(0) as WebView }
19 | private val progressBar by lazy { mView.getChildAt(1) as ProgressBar }
20 |
21 | fun initSettings() {
22 | onWebCall(false)
23 | setWebSettings()
24 | setupWebClient()
25 | }
26 |
27 | fun onDestroy() {
28 | mView.removeAllViews()
29 | webView.destroy()
30 | }
31 |
32 | private fun setWebSettings() {
33 | val webSettings = webView.settings
34 | //如果访问的页面中要与Javascript交互,则webview必须设置支持Javascript
35 | webSettings.javaScriptEnabled = false
36 | //设置自适应屏幕,两者合用
37 | webSettings.useWideViewPort = true //将图片调整到适合webview的大小
38 | webSettings.loadWithOverviewMode = true // 缩放至屏幕的大小
39 | //缩放操作
40 | webSettings.setSupportZoom(true) //支持缩放,默认为true。是下面那个的前提。
41 | webSettings.builtInZoomControls = true //设置内置的缩放控件。若为false,则该WebView不可缩放
42 | webSettings.displayZoomControls = false //隐藏原生的缩放控件
43 |
44 | //其他细节操作
45 | webSettings.cacheMode = WebSettings.LOAD_CACHE_ELSE_NETWORK //关闭webview中缓存
46 | webSettings.allowFileAccess = true //设置可以访问文件
47 | webSettings.javaScriptCanOpenWindowsAutomatically = true //支持通过JS打开新窗口
48 | webSettings.loadsImagesAutomatically = true //支持自动加载图片
49 | webSettings.defaultTextEncodingName = "UTF-8"//设置编码格式
50 | }
51 |
52 |
53 | private fun setupWebClient() {
54 | webView.webViewClient = NewWebViewClient()
55 | webView.webChromeClient = ProgressWebViewChromeClient()
56 | refresh()
57 | }
58 |
59 | fun refresh() {
60 | webView.loadUrl(linkUrl)
61 | }
62 |
63 |
64 | inner class ProgressWebViewChromeClient : WebChromeClient() {
65 | override fun onProgressChanged(view: WebView?, newProgress: Int) {
66 | super.onProgressChanged(view, newProgress)
67 | progressBar.progress = newProgress
68 | }
69 |
70 | override fun onReceivedTitle(view: WebView?, title: String?) {
71 | super.onReceivedTitle(view, title)
72 | }
73 | }
74 |
75 |
76 | inner class NewWebViewClient : WebViewClient() {
77 |
78 | @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
79 | override fun shouldOverrideUrlLoading(
80 | view: WebView?,
81 | request: WebResourceRequest?
82 | ): Boolean {
83 | linkUrl = request?.url.toString()
84 | return super.shouldOverrideUrlLoading(view, request)
85 | }
86 |
87 | override fun shouldOverrideUrlLoading(view: WebView?, url: String?): Boolean {
88 | linkUrl = url?:"NullUrlString"
89 | return super.shouldOverrideUrlLoading(view, url)
90 | }
91 |
92 | override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
93 | progressBar.visibility = View.VISIBLE
94 | super.onPageStarted(view, url, favicon)
95 | }
96 |
97 | override fun onPageFinished(view: WebView?, url: String?) {
98 | progressBar.visibility = View.GONE
99 | onWebCall(true)
100 | super.onPageFinished(view, url)
101 | }
102 |
103 | override fun onReceivedSslError(
104 | view: WebView?,
105 | handler: SslErrorHandler?,
106 | error: SslError?
107 | ) {
108 | handler?.proceed()
109 | }
110 | }
111 |
112 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mm/hamcompose/ui/route/BottomNavRoute.kt:
--------------------------------------------------------------------------------
1 | package com.mm.hamcompose.ui.route
2 |
3 | import androidx.annotation.StringRes
4 | import androidx.compose.material.icons.Icons
5 | import androidx.compose.material.icons.filled.*
6 | import androidx.compose.ui.graphics.vector.ImageVector
7 | import com.mm.hamcompose.R
8 |
9 | sealed class BottomNavRoute(
10 | var routeName: String,
11 | @StringRes var stringId: Int,
12 | var icon: ImageVector
13 | ) {
14 | object Home: BottomNavRoute(RouteName.HOME, R.string.home, Icons.Default.Home)
15 | object Category: BottomNavRoute(RouteName.CATEGORY, R.string.category, Icons.Default.Menu)
16 | object Collection: BottomNavRoute(RouteName.COLLECTION, R.string.collection, Icons.Default.Favorite)
17 | object Profile: BottomNavRoute(RouteName.PROFILE, R.string.profile, Icons.Default.Person)
18 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mm/hamcompose/ui/route/RouteName.kt:
--------------------------------------------------------------------------------
1 | package com.mm.hamcompose.ui.route
2 |
3 | object RouteName {
4 | const val HOME = "home"
5 | const val CATEGORY = "category"
6 | const val COLLECTION = "collection"
7 | const val PROFILE = "profile"
8 |
9 | const val STRUCTURE_LIST = "structure_list"
10 | const val ARTICLE_SEARCH = "article_search"
11 | const val PUB_ACCOUNT_DETAIL = "pub_account_detail"
12 | const val PUB_ACCOUNT_SEARCH = "pub_account_search"
13 | const val WEB_VIEW = "web_view"
14 |
15 | const val LOGIN = "login"
16 | const val REGISTER = "register"
17 | const val RANKING = "ranking"
18 | const val MESSAGE = "message"
19 | const val SETTINGS = "settings"
20 | const val EDIT_WEBSITE = "edit_website"
21 | const val SHARER = "sharer"
22 | const val SHARE_ARTICLE = "share_article"
23 | const val HISTORY = "history"
24 |
25 | const val GIRL_PHOTO = "girl_photo"
26 | const val GIRL_INFO = "girl_info"
27 |
28 |
29 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mm/hamcompose/ui/route/RouteUtils.kt:
--------------------------------------------------------------------------------
1 | package com.mm.hamcompose.ui.route
2 |
3 | import android.os.Bundle
4 | import android.os.Parcelable
5 | import androidx.navigation.NavGraph.Companion.findStartDestination
6 | import androidx.navigation.NavHostController
7 |
8 | /**
9 | * 路由名称
10 | */
11 | object RouteUtils {
12 |
13 | const val STEAD_SYMBOL = "^0^"
14 |
15 | //初始化Bundle参数
16 | fun initBundle(params: Parcelable) = Bundle().apply { putParcelable(ARGS, params) }
17 |
18 | /**
19 | * 导航到某个页面
20 | */
21 | fun navTo(
22 | navCtrl: NavHostController,
23 | destinationName: String,
24 | args: Any? = null,
25 | backStackRouteName: String? = null,
26 | isLaunchSingleTop: Boolean = true,
27 | needToRestoreState: Boolean = true,
28 | ) {
29 |
30 | var singleArgument = ""
31 | if (args!=null) {
32 | when(args) {
33 | is Parcelable -> {
34 | navCtrl.currentBackStackEntry?.replaceArguments(initBundle(args))
35 | }
36 | is String -> {
37 | singleArgument = String.format("/%s", args)
38 | }
39 | is Int -> {
40 | singleArgument = String.format("/%s", args)
41 | }
42 | is Float -> {
43 | singleArgument = String.format("/%s", args)
44 | }
45 | is Double -> {
46 | singleArgument = String.format("/%s", args)
47 | }
48 | is Boolean -> {
49 | singleArgument = String.format("/%s", args)
50 | }
51 | is Long -> {
52 | singleArgument = String.format("/%s", args)
53 | }
54 | }
55 | } else {
56 | navCtrl.previousBackStackEntry?.arguments = null
57 | navCtrl.currentBackStackEntry?.arguments = null
58 | }
59 | println("导航到: $destinationName")
60 | navCtrl.navigate("$destinationName$singleArgument") {
61 | if (backStackRouteName != null) {
62 | popUpTo(backStackRouteName) { saveState = true }
63 | }
64 | launchSingleTop = isLaunchSingleTop
65 | restoreState = needToRestoreState
66 | }
67 | }
68 |
69 | fun NavHostController.back() {
70 | navigateUp()
71 | }
72 |
73 | private fun getPopUpId(navCtrl: NavHostController, routeName: String?): Int {
74 | val defaultId = navCtrl.graph.findStartDestination().id
75 | return if (routeName == null) {
76 | defaultId
77 | } else {
78 | navCtrl.findDestination(routeName)?.id ?: defaultId
79 | }
80 | }
81 |
82 | fun getArguments(navCtrl: NavHostController): T? {
83 | return navCtrl.previousBackStackEntry?.arguments?.getParcelable(ARGS)
84 | }
85 |
86 | /**
87 | * 各个序列化的参数类的key名
88 | */
89 | private const val ARGS = "args"
90 |
91 |
92 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mm/hamcompose/ui/widget/Animations.kt:
--------------------------------------------------------------------------------
1 | package com.mm.hamcompose.ui.widget
2 |
3 | import androidx.compose.animation.*
4 | import androidx.compose.animation.AnimatedVisibility
5 | import androidx.compose.runtime.Composable
6 |
7 | @OptIn(ExperimentalAnimationApi::class)
8 | @Composable
9 | fun FadeAnim(
10 | isVisible: Boolean,
11 | content: @Composable ()-> Unit
12 | ) {
13 | AnimatedVisibility(
14 | visible = isVisible,
15 | enter = fadeIn(),
16 | exit = fadeOut()
17 | ) {
18 | content
19 | }
20 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mm/hamcompose/ui/widget/AsyncImage.kt:
--------------------------------------------------------------------------------
1 | package com.mm.hamcompose.ui.widget
2 |
3 | import androidx.compose.foundation.Image
4 | import androidx.compose.runtime.Composable
5 | import androidx.compose.ui.Modifier
6 | import androidx.compose.ui.layout.ContentScale
7 | import coil.compose.rememberImagePainter
8 | import com.google.accompanist.placeholder.material.placeholder
9 | import com.mm.hamcompose.R
10 | import com.mm.hamcompose.theme.HamTheme
11 |
12 | @Composable
13 | fun NetworkImage(
14 | url: String,
15 | isLoading: Boolean = true,
16 | contentDesc: String? = null,
17 | contentScale: ContentScale = ContentScale.Crop,
18 | modifier: Modifier = Modifier,
19 | ) {
20 | Image(
21 | painter = rememberImagePainter(
22 | data = url,
23 | builder = {
24 | crossfade(true)
25 | placeholder(R.drawable.no_banner)
26 | }
27 | ),
28 | contentDescription = contentDesc,
29 | contentScale = ContentScale.FillBounds,
30 | modifier = modifier
31 | .placeholder(
32 | visible = isLoading,
33 | color = HamTheme.colors.placeholder
34 | )
35 | )
36 | }
37 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mm/hamcompose/ui/widget/Buttons.kt:
--------------------------------------------------------------------------------
1 | package com.mm.hamcompose.ui.widget
2 |
3 | import androidx.compose.foundation.ExperimentalFoundationApi
4 | import androidx.compose.foundation.background
5 | import androidx.compose.foundation.clickable
6 | import androidx.compose.foundation.combinedClickable
7 | import androidx.compose.foundation.layout.Box
8 | import androidx.compose.foundation.layout.fillMaxWidth
9 | import androidx.compose.foundation.layout.height
10 | import androidx.compose.foundation.layout.padding
11 | import androidx.compose.foundation.shape.RoundedCornerShape
12 | import androidx.compose.material.Text
13 | import androidx.compose.runtime.Composable
14 | import androidx.compose.ui.Alignment
15 | import androidx.compose.ui.Modifier
16 | import androidx.compose.ui.draw.clip
17 | import androidx.compose.ui.graphics.Color
18 | import androidx.compose.ui.text.style.TextAlign
19 | import androidx.compose.ui.text.style.TextOverflow
20 | import androidx.compose.ui.unit.Dp
21 | import androidx.compose.ui.unit.dp
22 | import androidx.compose.ui.unit.sp
23 | import com.google.accompanist.placeholder.material.placeholder
24 | import com.mm.hamcompose.theme.HamTheme
25 | import com.mm.hamcompose.theme.buttonCorner
26 | import com.mm.hamcompose.theme.buttonHeight
27 | import com.mm.hamcompose.theme.white1
28 | import org.jetbrains.annotations.NotNull
29 |
30 |
31 | @Composable
32 | fun HamButton(
33 | text: String,
34 | modifier: Modifier = Modifier,
35 | bgColor: Color = HamTheme.colors.secondBtnBg,
36 | textColor: Color = HamTheme.colors.textPrimary,
37 | onClick: () -> Unit
38 | ) {
39 | Box(
40 | modifier = modifier
41 | .fillMaxWidth()
42 | .height(buttonHeight)
43 | .background(color = bgColor, shape = RoundedCornerShape(buttonCorner))
44 | .clickable {
45 | onClick()
46 | }
47 | ) {
48 | TextContent(text = text, color = textColor, modifier = Modifier.align(Alignment.Center))
49 | }
50 | }
51 |
52 |
53 | @Composable
54 | fun PrimaryButton(
55 | text: String,
56 | modifier: Modifier = Modifier,
57 | onClick: () -> Unit
58 | ) {
59 | HamButton(
60 | text = text,
61 | modifier = modifier,
62 | textColor = HamTheme.colors.textPrimary,
63 | onClick = onClick,
64 | bgColor = HamTheme.colors.themeUi
65 | )
66 | }
67 |
68 | @Composable
69 | fun SecondlyButton(
70 | text: String,
71 | modifier: Modifier = Modifier,
72 | onClick: () -> Unit
73 | ) {
74 | HamButton(
75 | text = text,
76 | modifier = modifier,
77 | textColor = HamTheme.colors.textSecondary,
78 | onClick = onClick
79 | )
80 | }
81 |
82 | @OptIn(ExperimentalFoundationApi::class)
83 | @Composable
84 | fun LabelTextButton(
85 | @NotNull text: String,
86 | modifier: Modifier = Modifier,
87 | isSelect: Boolean = true,
88 | specTextColor: Color? = null,
89 | cornerValue: Dp = 25.dp / 2,
90 | isLoading: Boolean = false,
91 | onClick: (() -> Unit)? = null,
92 | onLongClick: (() -> Unit)? = null
93 | ) {
94 | Text(
95 | text = text,
96 | modifier = modifier
97 | .height(25.dp)
98 | .clip(shape = RoundedCornerShape(cornerValue))
99 | .background(
100 | color = if (isSelect && !isLoading) HamTheme.colors.themeUi else HamTheme.colors.secondBtnBg,
101 | )
102 | .padding(
103 | horizontal = 10.dp,
104 | vertical = 3.dp
105 | )
106 | .combinedClickable(
107 | enabled = !isLoading,
108 | onClick = { onClick?.invoke() },
109 | onLongClick = { onLongClick?.invoke() }
110 | )
111 | .placeholder(
112 | visible = isLoading,
113 | color = HamTheme.colors.placeholder
114 | ),
115 | fontSize = 13.sp,
116 | textAlign = TextAlign.Center,
117 | color = specTextColor ?: if (isSelect) white1 else HamTheme.colors.textSecondary,
118 | overflow = TextOverflow.Ellipsis,
119 | maxLines = 1,
120 | )
121 | }
122 |
123 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mm/hamcompose/ui/widget/SnackBar.kt:
--------------------------------------------------------------------------------
1 | package com.mm.hamcompose.ui.widget
2 |
3 | import androidx.compose.material.ScaffoldState
4 | import androidx.compose.material.Snackbar
5 | import androidx.compose.material.SnackbarData
6 | import androidx.compose.runtime.Composable
7 | import com.mm.hamcompose.theme.HamTheme
8 | import kotlinx.coroutines.CoroutineScope
9 | import kotlinx.coroutines.delay
10 | import kotlinx.coroutines.launch
11 |
12 | const val SNACK_INFO = ""
13 | const val SNACK_WARN = " "
14 | const val SNACK_ERROR = " "
15 | const val SNACK_SUCCESS = "OK"
16 |
17 | @Composable
18 | fun HamSnackBar(data: SnackbarData) {
19 | Snackbar(
20 | snackbarData = data,
21 | backgroundColor = when (data.actionLabel) {
22 | SNACK_INFO -> HamTheme.colors.themeUi
23 | SNACK_WARN -> HamTheme.colors.warn
24 | SNACK_ERROR -> HamTheme.colors.error
25 | SNACK_SUCCESS -> HamTheme.colors.success
26 | else -> HamTheme.colors.themeUi
27 | },
28 | actionColor = HamTheme.colors.textPrimary,
29 | contentColor = HamTheme.colors.textPrimary,
30 | )
31 | }
32 |
33 | fun popupSnackBar(
34 | scope: CoroutineScope,
35 | scaffoldState: ScaffoldState,
36 | label: String,
37 | message: String,
38 | onDismissCallback: () -> Unit = {}
39 | ) {
40 | scope.launch {
41 | scaffoldState.snackbarHostState.showSnackbar(actionLabel = label, message = message)
42 | onDismissCallback.invoke()
43 | }
44 |
45 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mm/hamcompose/ui/widget/Title.kt:
--------------------------------------------------------------------------------
1 | package com.mm.hamcompose.ui.widget
2 |
3 | import androidx.compose.foundation.text.selection.SelectionContainer
4 | import androidx.compose.material.Text
5 | import androidx.compose.runtime.Composable
6 | import androidx.compose.ui.Modifier
7 | import androidx.compose.ui.graphics.Color
8 | import androidx.compose.ui.text.font.FontWeight
9 | import androidx.compose.ui.text.style.TextAlign
10 | import androidx.compose.ui.text.style.TextOverflow
11 | import androidx.compose.ui.unit.TextUnit
12 | import com.google.accompanist.placeholder.material.placeholder
13 | import com.mm.hamcompose.theme.*
14 |
15 | @Composable
16 | fun LargeTitle(
17 | title: String,
18 | modifier: Modifier = Modifier,
19 | color: Color? = null,
20 | isLoading: Boolean = false
21 | ) {
22 | Title(
23 | title = title,
24 | modifier = modifier,
25 | fontSize = H3,
26 | color = color ?: HamTheme.colors.textPrimary,
27 | fontWeight = FontWeight.Bold,
28 | isLoading = isLoading
29 | )
30 | }
31 |
32 | @Composable
33 | fun MainTitle(
34 | title: String,
35 | modifier: Modifier = Modifier,
36 | maxLine: Int = 1,
37 | textAlign: TextAlign = TextAlign.Start,
38 | color: Color = HamTheme.colors.textPrimary,
39 | isLoading: Boolean = false
40 | ) {
41 | Title(
42 | title = title,
43 | modifier = modifier,
44 | fontSize = H4,
45 | color = color,
46 | fontWeight = FontWeight.SemiBold,
47 | maxLine = maxLine,
48 | textAlign = textAlign,
49 | isLoading = isLoading
50 | )
51 | }
52 |
53 | @Composable
54 | fun MediumTitle(
55 | title: String,
56 | modifier: Modifier = Modifier,
57 | color: Color = HamTheme.colors.textPrimary,
58 | textAlign: TextAlign = TextAlign.Start,
59 | isLoading: Boolean = false
60 | ) {
61 | Title(
62 | title = title,
63 | fontSize = H5,
64 | modifier = modifier,
65 | color = color,
66 | textAlign = textAlign,
67 | isLoading = isLoading
68 | )
69 | }
70 |
71 | @Composable
72 | fun TextContent(
73 | text: String,
74 | modifier: Modifier = Modifier,
75 | color: Color = HamTheme.colors.textSecondary,
76 | maxLines: Int = 99,
77 | textAlign: TextAlign = TextAlign.Start,
78 | canCopy: Boolean = false,
79 | isLoading: Boolean = false
80 | ) {
81 | if (canCopy) {
82 | SelectionContainer {
83 | Title(
84 | title = text,
85 | modifier = modifier,
86 | fontSize = H6,
87 | color = color,
88 | maxLine = maxLines,
89 | textAlign = textAlign,
90 | isLoading = isLoading
91 | )
92 | }
93 | } else {
94 | Title(
95 | title = text,
96 | modifier = modifier,
97 | fontSize = H6,
98 | color = color,
99 | maxLine = maxLines,
100 | textAlign = textAlign,
101 | isLoading = isLoading
102 | )
103 | }
104 |
105 | }
106 |
107 | @Composable
108 | fun MiniTitle(
109 | text: String,
110 | modifier: Modifier = Modifier,
111 | color: Color = HamTheme.colors.textSecondary,
112 | maxLines: Int = 1,
113 | textAlign: TextAlign = TextAlign.Start,
114 | isLoading: Boolean = false
115 | ) {
116 | Title(
117 | title = text,
118 | modifier = modifier,
119 | fontSize = H7,
120 | color = color,
121 | maxLine = maxLines,
122 | textAlign = textAlign,
123 | isLoading = isLoading,
124 | )
125 | }
126 |
127 | @Composable
128 | fun Title(
129 | title: String,
130 | modifier: Modifier = Modifier,
131 | fontSize: TextUnit,
132 | color: Color = HamTheme.colors.textSecondary,
133 | fontWeight: FontWeight = FontWeight.Normal,
134 | maxLine: Int = 1,
135 | textAlign: TextAlign = TextAlign.Start,
136 | isLoading: Boolean = false
137 | ) {
138 | Text(
139 | text = title,
140 | modifier = modifier
141 | .placeholder(
142 | visible = isLoading,
143 | color = HamTheme.colors.placeholder
144 | ),
145 | fontSize = fontSize,
146 | color = color,
147 | maxLines = maxLine,
148 | overflow = TextOverflow.Ellipsis,
149 | textAlign = textAlign
150 | )
151 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mm/hamcompose/util/CacheDataManager.kt:
--------------------------------------------------------------------------------
1 | package com.mm.hamcompose.util
2 |
3 | import android.content.Context
4 | import android.os.Environment
5 | import java.io.File
6 | import java.math.BigDecimal
7 |
8 | object CacheDataManager {
9 |
10 | /**
11 | * 获取App缓存大小
12 | */
13 | fun getTotalCacheSize(context: Context): String {
14 |
15 | var cacheSize = getFolderSize(context.cacheDir)
16 | if (Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED) {
17 | cacheSize += getFolderSize(context.externalCacheDir)
18 | }
19 | return getFormatSize(cacheSize.toDouble())
20 | }
21 |
22 | /**
23 | * 清理缓存
24 | */
25 | fun clearAllCache(context: Context): Boolean {
26 | with(context) {
27 | deleteDir(cacheDir)
28 | if (Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED) {
29 | if (externalCacheDir == null) {
30 | println("清理缓存失败")
31 | }
32 | return false
33 | }
34 |
35 | if (externalCacheDir != null) {
36 | return deleteFile(externalCacheDir?.absolutePath)
37 | }
38 | return false
39 | }
40 | }
41 |
42 | }
43 |
44 | private fun deleteDir(dir: File): Boolean {
45 | if (dir.isDirectory) {
46 | val children = dir.list()
47 | for (i in children.indices) {
48 | val success = deleteDir(File(dir, children[i]))
49 | if (!success) {
50 | return false
51 | }
52 | }
53 | }
54 | return dir.delete()
55 | }
56 |
57 | /**
58 | * 获取文件
59 | * Context.getExternalFilesDir() --> SDCard/Android/data/你的应用的包名/files/
60 | * 目录,一般放一些长时间保存的数据
61 | * Context.getExternalCacheDir() -->
62 | * SDCard/Android/data/你的应用包名/cache/目录,一般存放临时缓存数据
63 | */
64 | fun getFolderSize(file: File?): Long {
65 | var size: Long = 0
66 | file?.run {
67 | try {
68 | val fileList = listFiles()
69 | for (i in fileList.indices) {
70 | // 如果下面还有文件
71 | size += if (fileList[i].isDirectory) {
72 | getFolderSize(fileList[i])
73 | } else {
74 | fileList[i].length()
75 | }
76 | }
77 | } catch (e: Exception) {
78 | e.printStackTrace()
79 | }
80 | }
81 | return size
82 | }
83 |
84 | /**
85 | * 格式化单位
86 | */
87 | fun getFormatSize(size: Double): String {
88 |
89 | val kiloByte = size / 1024
90 | if (kiloByte < 1) {
91 | return size.toString() + "Byte"
92 | }
93 |
94 | val megaByte = kiloByte / 1024
95 |
96 | if (megaByte < 1) {
97 | val result1 = BigDecimal(kiloByte.toString())
98 | return result1.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString() + "KB"
99 | }
100 |
101 | val gigaByte = megaByte / 1024
102 |
103 | if (gigaByte < 1) {
104 | val result2 = BigDecimal(megaByte.toString())
105 | return result2.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString() + "MB"
106 | }
107 |
108 | val teraBytes = gigaByte / 1024
109 |
110 | if (teraBytes < 1) {
111 | val result3 = BigDecimal(gigaByte.toString())
112 | return result3.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString() + "GB"
113 | }
114 |
115 | val result4 = BigDecimal(teraBytes)
116 | return result4.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString() + "TB"
117 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/mm/hamcompose/util/Navigation.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.mm.hamcompose.util
18 |
19 | import android.os.Parcelable
20 | import androidx.activity.OnBackPressedCallback
21 | import androidx.activity.OnBackPressedDispatcher
22 | import androidx.compose.runtime.saveable.listSaver
23 | import androidx.compose.runtime.toMutableStateList
24 |
25 | /**
26 | * A simple navigator which maintains a back stack.
27 | */
28 | class Navigator private constructor(
29 | initialBackStack: List,
30 | backDispatcher: OnBackPressedDispatcher
31 | ) {
32 | constructor(
33 | initial: T,
34 | backDispatcher: OnBackPressedDispatcher
35 | ) : this(listOf(initial), backDispatcher)
36 |
37 | //回退栈
38 | private val backStack = initialBackStack.toMutableStateList()
39 | //回退栈的回调
40 | private val backCallback = object : OnBackPressedCallback(canGoBack()) {
41 | override fun handleOnBackPressed() {
42 | back()
43 | }
44 | }.also { callback ->
45 | backDispatcher.addCallback(callback)
46 | }
47 | //栈顶
48 | val current: T get() = backStack.last()
49 |
50 | //
51 | fun back() {
52 | backStack.removeAt(backStack.lastIndex)
53 | backCallback.isEnabled = canGoBack()
54 | }
55 |
56 | fun navigate(destination: T) {
57 | backStack += destination
58 | backCallback.isEnabled = canGoBack()
59 | }
60 |
61 | private fun canGoBack(): Boolean = backStack.size > 1
62 |
63 | companion object {
64 | /**
65 | * Serialize the back stack to save to instance state.
66 | */
67 | fun saver(backDispatcher: OnBackPressedDispatcher) =
68 | listSaver, T>(
69 | save = { navigator -> navigator.backStack.toList() },
70 | restore = { backstack -> Navigator(backstack, backDispatcher) }
71 | )
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/app/src/main/java/com/mm/hamcompose/util/RegexUtils.kt:
--------------------------------------------------------------------------------
1 | package com.mm.hamcompose.util
2 |
3 | import java.text.SimpleDateFormat
4 |
5 | class RegexUtils {
6 |
7 | fun symbolClear(text: String?): String {
8 | // val pattern = Pattern.compile()
9 | if (text.isNullOrEmpty()) {
10 | return ""
11 | }
12 | val regex = Regex("<[a-z]+>|[a-z]+>|<[a-z]+/>")
13 | if (text.contains(regex)) {
14 | return text.replace(regex, "")
15 | }
16 | return text
17 | }
18 |
19 | fun timestamp(time: String?): String? {
20 | time ?: return null
21 | return kotlin.runCatching {
22 | SimpleDateFormat("yyyy-MM-dd HH:mm").parse(time)
23 | time.substring(0, time.indexOf(" "))
24 | }.getOrDefault(time)
25 | }
26 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
30 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/horizontal_progressbar.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
4 |
5 |
6 |
7 |
8 | -
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_add.xml:
--------------------------------------------------------------------------------
1 |
3 |
6 |
9 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_arrow_more.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_article.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_author.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_camera.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_close.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_community.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_data.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_delete.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_drawer.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_exit_app.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_feedback.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_help.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_history_record.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_hot.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_menu_settings.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_menu_welfare.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_message.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_ranking.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_search.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_share.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_star.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_star_border.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_theme.xml:
--------------------------------------------------------------------------------
1 |
7 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_time.xml:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_back.xml:
--------------------------------------------------------------------------------
1 |
6 |
11 |
16 |
17 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/icon_back_white.xml:
--------------------------------------------------------------------------------
1 |
6 |
11 |
16 |
17 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/no_banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/manqianzhuang/HamApp/edfd54f8cf8f3b319751d450fdf79f8c4abf5a42/app/src/main/res/drawable/no_banner.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/wukong.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/manqianzhuang/HamApp/edfd54f8cf8f3b319751d450fdf79f8c4abf5a42/app/src/main/res/drawable/wukong.jpeg
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/manqianzhuang/HamApp/edfd54f8cf8f3b319751d450fdf79f8c4abf5a42/app/src/main/res/mipmap-hdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/manqianzhuang/HamApp/edfd54f8cf8f3b319751d450fdf79f8c4abf5a42/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/manqianzhuang/HamApp/edfd54f8cf8f3b319751d450fdf79f8c4abf5a42/app/src/main/res/mipmap-mdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/manqianzhuang/HamApp/edfd54f8cf8f3b319751d450fdf79f8c4abf5a42/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/manqianzhuang/HamApp/edfd54f8cf8f3b319751d450fdf79f8c4abf5a42/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/manqianzhuang/HamApp/edfd54f8cf8f3b319751d450fdf79f8c4abf5a42/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/splash_image01.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/manqianzhuang/HamApp/edfd54f8cf8f3b319751d450fdf79f8c4abf5a42/app/src/main/res/mipmap-xhdpi/splash_image01.jpg
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/splash_image02.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/manqianzhuang/HamApp/edfd54f8cf8f3b319751d450fdf79f8c4abf5a42/app/src/main/res/mipmap-xhdpi/splash_image02.jpg
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/splash_image03.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/manqianzhuang/HamApp/edfd54f8cf8f3b319751d450fdf79f8c4abf5a42/app/src/main/res/mipmap-xhdpi/splash_image03.jpg
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/splash_image04.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/manqianzhuang/HamApp/edfd54f8cf8f3b319751d450fdf79f8c4abf5a42/app/src/main/res/mipmap-xhdpi/splash_image04.jpg
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/splash_image05.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/manqianzhuang/HamApp/edfd54f8cf8f3b319751d450fdf79f8c4abf5a42/app/src/main/res/mipmap-xhdpi/splash_image05.jpg
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/manqianzhuang/HamApp/edfd54f8cf8f3b319751d450fdf79f8c4abf5a42/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/manqianzhuang/HamApp/edfd54f8cf8f3b319751d450fdf79f8c4abf5a42/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/manqianzhuang/HamApp/edfd54f8cf8f3b319751d450fdf79f8c4abf5a42/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/manqianzhuang/HamApp/edfd54f8cf8f3b319751d450fdf79f8c4abf5a42/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/values-night/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #00BFFF
4 | #BB86FC
5 | #6200EE
6 | #3700B3
7 | #03DAC5
8 | #018786
9 | #000000
10 | #FFFFFF
11 | #00000000
12 | #cdcdcd
13 | #bfbfbf
14 | #8a8a8a
15 | #FEFEFE
16 | #FAFAFA
17 | #F8F8F8
18 | #DBDBDB
19 | #e6e6e6
20 | #2c2c2c
21 |
22 | #FFDEAD
23 | #0000CD
24 | #FF69B4
25 | #8B4513
26 | #FF7F00
27 | #FFA500
28 | #FFD700
29 | #FFFF00
30 | #FF3030
31 | #90EE90
32 | #00FF7F
33 | #F0FFFF
34 |
35 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | HamCompose
3 | WebView
4 | MainActivity
5 | 首页
6 | 分类
7 | 收藏
8 | 我的
9 | 标题
10 | 作者
11 | 网站
12 | 链接
13 | 用户
14 | 保存
15 | 登录
16 | 注册
17 | 请输入标题
18 | 请输入名称
19 | 请输入内容
20 | 请输入用户名
21 | 请输入帐号
22 | 请输入密码
23 | 请再次输入密码
24 | 请输入邀请码
25 | 请输入网址
26 |
--------------------------------------------------------------------------------
/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
18 |
19 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/network_security_config.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/test/java/com/mm/hamcompose/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.mm.hamcompose
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 | }
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 | buildscript {
3 | ext {
4 | //android
5 | androidCompileSdk = 30
6 | androidBuildToolsVersion = "30.0.3"
7 | minSdkVersion = 21
8 | targetSdkVersion = 30
9 | currentVersionCode = 1
10 | currentVersionName = "1.0"
11 |
12 | //lib
13 | composeVersion = "1.0.2"
14 | coreKtxVersion = "1.7.0-alpha01"
15 | appcompatVersion = "1.3.0"
16 | materialVersion = "1.4.0"
17 | activityComposeVersion = "1.3.1"
18 | accompanistVersion = "0.18.0"
19 | constraintComposeVersion = "1.0.0-alpha08"
20 | lifecycleVersion = "2.3.0"
21 | viewModelComposeVersion = "1.0.0-alpha07"
22 | coroutineVersion = "1.4.3"
23 | hiltVersion = "2.38.1"
24 | hiltCompilerVersion = "1.0.0"
25 | hiltComposeVersion = "1.0.0-alpha03"
26 | retrofitVersion = "2.9.0"
27 | coroutineAdapterVersion = "0.9.2"
28 | gsonVersion = "2.8.6"
29 | blankjToolVersion = "1.23.7"
30 | glideVersion = "4.12.0"
31 | picassoVersion = "2.71828"
32 | pagingVersion = "3.0.0-beta02"
33 | pagingComposeVersion = "1.0.0-alpha11"
34 | roomVersion = "2.3.0"
35 | roomPagingVersion = "2.4.0-alpha04"
36 | navigationComposeVersion = "2.4.0-alpha05"
37 | datastoreVersion = "1.0.0-rc02"
38 | //unit or ui test
39 | junitVersion = "4.13.2"
40 | extJunitVersion = "1.1.3"
41 | espressoVersion = "3.4.0"
42 | }
43 | repositories {
44 | google()
45 | mavenCentral()
46 | jcenter()
47 | // maven { url 'https://oss.sonatype.org/content/repositories/snapshots' }
48 | // maven { url "https://androidx.dev/snapshots/builds/0.7.2-SNAPSHOT/artifacts/repository/" }
49 |
50 | }
51 | dependencies {
52 | classpath 'com.android.tools.build:gradle:7.0.1'
53 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.21"
54 | classpath "com.google.dagger:hilt-android-gradle-plugin:2.38.1"
55 | classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.3.5"
56 |
57 | // NOTE: Do not place your application dependencies here; they belong
58 | // in the individual module build.gradle.kts files
59 | }
60 | }
61 |
62 | allprojects {
63 | repositories {
64 | google()
65 | mavenCentral()
66 | jcenter()
67 | }
68 | }
69 |
70 |
71 | tasks.register("clean", Delete) {
72 | delete rootProject.buildDir
73 | }
--------------------------------------------------------------------------------
/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 | # Automatically convert third-party libraries to use AndroidX
19 | android.enableJetifier=true
20 | # Kotlin code style for this project: "official" or "obsolete":
21 | kotlin.code.style=official
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/manqianzhuang/HamApp/edfd54f8cf8f3b319751d450fdf79f8c4abf5a42/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Fri Sep 03 14:36:11 CST 2021
2 | distributionBase=GRADLE_USER_HOME
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip
4 | distributionPath=wrapper/dists
5 | zipStorePath=wrapper/dists
6 | zipStoreBase=GRADLE_USER_HOME
7 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/screenshot/Screenshot_20210927_111453_com.mm.hamcompose.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/manqianzhuang/HamApp/edfd54f8cf8f3b319751d450fdf79f8c4abf5a42/screenshot/Screenshot_20210927_111453_com.mm.hamcompose.jpg
--------------------------------------------------------------------------------
/screenshot/Screenshot_20210927_111526_com.mm.hamcompose.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/manqianzhuang/HamApp/edfd54f8cf8f3b319751d450fdf79f8c4abf5a42/screenshot/Screenshot_20210927_111526_com.mm.hamcompose.jpg
--------------------------------------------------------------------------------
/screenshot/Screenshot_20210927_111602_com.mm.hamcompose.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/manqianzhuang/HamApp/edfd54f8cf8f3b319751d450fdf79f8c4abf5a42/screenshot/Screenshot_20210927_111602_com.mm.hamcompose.jpg
--------------------------------------------------------------------------------
/screenshot/Screenshot_20210927_111619_com.mm.hamcompose.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/manqianzhuang/HamApp/edfd54f8cf8f3b319751d450fdf79f8c4abf5a42/screenshot/Screenshot_20210927_111619_com.mm.hamcompose.jpg
--------------------------------------------------------------------------------
/screenshot/Screenshot_20210927_111628_com.mm.hamcompose.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/manqianzhuang/HamApp/edfd54f8cf8f3b319751d450fdf79f8c4abf5a42/screenshot/Screenshot_20210927_111628_com.mm.hamcompose.jpg
--------------------------------------------------------------------------------
/screenshot/Screenshot_20210927_111722_com.mm.hamcompose.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/manqianzhuang/HamApp/edfd54f8cf8f3b319751d450fdf79f8c4abf5a42/screenshot/Screenshot_20210927_111722_com.mm.hamcompose.jpg
--------------------------------------------------------------------------------
/screenshot/Screenshot_20210927_111736_com.mm.hamcompose.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/manqianzhuang/HamApp/edfd54f8cf8f3b319751d450fdf79f8c4abf5a42/screenshot/Screenshot_20210927_111736_com.mm.hamcompose.jpg
--------------------------------------------------------------------------------
/screenshot/Screenshot_20210927_111821_com.mm.hamcompose.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/manqianzhuang/HamApp/edfd54f8cf8f3b319751d450fdf79f8c4abf5a42/screenshot/Screenshot_20210927_111821_com.mm.hamcompose.jpg
--------------------------------------------------------------------------------
/screenshot/Screenshot_20210927_111932_com.mm.hamcompose.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/manqianzhuang/HamApp/edfd54f8cf8f3b319751d450fdf79f8c4abf5a42/screenshot/Screenshot_20210927_111932_com.mm.hamcompose.jpg
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | rootProject.name = "HamCompose"
2 | include ":app"
3 |
--------------------------------------------------------------------------------