a
This commit is contained in:
@@ -0,0 +1 @@
|
||||
/build
|
||||
@@ -0,0 +1,105 @@
|
||||
def isBuildModule = rootProject.ext.module.isBuildModule
|
||||
if (Boolean.valueOf(isBuildModule)) {
|
||||
apply plugin: 'com.android.application'
|
||||
} else {
|
||||
apply plugin: 'com.android.library'
|
||||
}
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'kotlin-kapt'
|
||||
apply plugin: 'kotlin-parcelize'
|
||||
apply plugin: 'com.google.dagger.hilt.android'
|
||||
|
||||
android {
|
||||
compileSdkVersion rootProject.ext.android.compileSdkVersion
|
||||
buildToolsVersion rootProject.ext.android.buildToolsVersion
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion rootProject.ext.android.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.android.targetSdkVersion
|
||||
versionCode rootProject.ext.android.versionCode
|
||||
versionName rootProject.ext.android.versionName
|
||||
multiDexEnabled true
|
||||
|
||||
javaCompileOptions {
|
||||
annotationProcessorOptions {
|
||||
arguments += [
|
||||
"room.schemaLocation":"$projectDir/schemas".toString(),
|
||||
"room.incremental":"true",
|
||||
"room.expandProjection":"true",
|
||||
AROUTER_MODULE_NAME: project.getName()
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled true
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = JavaVersion.VERSION_1_8.toString()
|
||||
}
|
||||
buildFeatures {
|
||||
viewBinding true
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
if (Boolean.valueOf(isBuildModule)) {
|
||||
manifest.srcFile 'src/main/module/AndroidManifest.xml'
|
||||
} else {
|
||||
manifest.srcFile 'src/main/AndroidManifest.xml'
|
||||
java {
|
||||
//排除java/debug文件夹下的所有文件
|
||||
exclude '*module'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lintOptions {
|
||||
checkReleaseBuilds false
|
||||
// Or, if you prefer, you can continue to check for errors in release builds,
|
||||
// but continue the build even when errors are found:
|
||||
abortOnError false
|
||||
}
|
||||
}
|
||||
|
||||
kapt {
|
||||
arguments {
|
||||
arg("AROUTER_MODULE_NAME", project.getName())
|
||||
}
|
||||
generateStubs = true
|
||||
useBuildCache = true
|
||||
javacOptions {
|
||||
option("-Xmaxerrs", 500)
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
implementation project(":common:common-base")
|
||||
implementation project(":common:common-service")
|
||||
|
||||
kapt rootProject.ext.compiler["arouterCompiler"]
|
||||
|
||||
implementation rootProject.ext.roomLibs
|
||||
kapt rootProject.ext.compiler["roomCompiler"]
|
||||
|
||||
compileOnly(rootProject.ext.jetpack["hilt"])
|
||||
kapt rootProject.ext.compiler["hiltAndroidCompiler"]
|
||||
|
||||
if (Boolean.valueOf(isBuildModule)) {
|
||||
implementation project(":modules:module-collect")
|
||||
implementation project(":modules:module-content")
|
||||
implementation project(":modules:module-login")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
package com.bbgo.module_project
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ExampleInstrumentedTest {
|
||||
@Test
|
||||
fun useAppContext() {
|
||||
// Context of the app under test.
|
||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
assertEquals("com.bbgo.module_project", appContext.packageName)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.bbgo.module_project">
|
||||
|
||||
|
||||
</manifest>
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
package com.bbgo.module_project
|
||||
|
||||
import com.bbgo.common_base.BaseApplication
|
||||
import dagger.hilt.android.HiltAndroidApp
|
||||
|
||||
/**
|
||||
* @Description:
|
||||
* @Author: wangyuebin
|
||||
* @Date: 2021/9/10 5:12 下午
|
||||
*/
|
||||
//@HiltAndroidApp
|
||||
class ProjectApp : BaseApplication() {
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
package com.bbgo.module_project.activity
|
||||
|
||||
import com.bbgo.common_base.base.BaseActivity
|
||||
import com.bbgo.module_project.R
|
||||
import com.bbgo.module_project.databinding.FragmentProjectBinding
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
|
||||
@AndroidEntryPoint
|
||||
class ProjectMainActivity : BaseActivity<FragmentProjectBinding>() {
|
||||
|
||||
override fun inflateViewBinding() = FragmentProjectBinding.inflate(layoutInflater)
|
||||
}
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
package com.bbgo.module_project.bean
|
||||
|
||||
import androidx.annotation.Keep
|
||||
import androidx.room.*
|
||||
import androidx.room.ForeignKey.CASCADE
|
||||
|
||||
@Keep
|
||||
@Entity(tableName = "project_tree")
|
||||
data class ProjectBean(
|
||||
@PrimaryKey var id: Int = 0,
|
||||
@Ignore var children: List<Any>? = null,
|
||||
var courseId: Int = 0,
|
||||
var name: String = "",
|
||||
var order: Int = 0,
|
||||
@ColumnInfo(name = "parent_chapter_id") var parentChapterId: Int = 0,
|
||||
@ColumnInfo(name = "user_control_set_top") var userControlSetTop: Boolean = false,
|
||||
var visible: Int = 0
|
||||
)
|
||||
|
||||
@Keep
|
||||
data class ArticleData(
|
||||
var curPage: Int,
|
||||
var datas: MutableList<ArticleDetail>,
|
||||
var offset: Int,
|
||||
var over: Boolean,
|
||||
var pageCount: Int,
|
||||
var size: Int,
|
||||
var total: Int
|
||||
)
|
||||
|
||||
@Keep
|
||||
@Entity(tableName = "article_detail")
|
||||
data class ArticleDetail(
|
||||
@PrimaryKey var id: Int = 0,
|
||||
@ColumnInfo(name = "apk_link") var apkLink: String = "",
|
||||
var audit: Int = 0,
|
||||
var author: String = "",
|
||||
@ColumnInfo(name = "can_edit") var canEdit: Boolean = false,
|
||||
@ColumnInfo(name = "chapter_id") var chapterId: Int = 0,
|
||||
@ColumnInfo(name = "chapter_name") var chapterName: String = "",
|
||||
var collect: Boolean = false,
|
||||
@ColumnInfo(name = "course_id") var courseId: Int = 0,
|
||||
var desc: String = "",
|
||||
@ColumnInfo(name = "desc_md") var descMd: String = "",
|
||||
@ColumnInfo(name = "envelope_pic") var envelopePic: String = "",
|
||||
var fresh: Boolean = false,
|
||||
var host: String = "",
|
||||
var link: String = "",
|
||||
@ColumnInfo(name = "nice_date") var niceDate: String = "",
|
||||
@ColumnInfo(name = "nice_share_date") var niceShareDate: String = "",
|
||||
var origin: String = "",
|
||||
var prefix: String = "",
|
||||
@ColumnInfo(name = "project_link") var projectLink: String = "",
|
||||
@ColumnInfo(name = "publish_time") var publishTime: Long = 0,
|
||||
@ColumnInfo(name = "real_super_chapter_id") var realSuperChapterId: Int = 0,
|
||||
@ColumnInfo(name = "self_visible") var selfVisible: Int = 0,
|
||||
@ColumnInfo(name = "share_date") var shareDate: Long = 0,
|
||||
@ColumnInfo(name = "share_user") var shareUser: String = "",
|
||||
@ColumnInfo(name = "super_chapter_id") var superChapterId: Int = 0,
|
||||
@ColumnInfo(name = "super_chapter_name") var superChapterName: String = "",
|
||||
@Ignore var tags: List<Tag>? = null,
|
||||
var title: String = "",
|
||||
var type: Int = 0,
|
||||
@ColumnInfo(name = "user_id") var userId: Int = 0,
|
||||
var visible: Int = 0,
|
||||
var zan: Int = 0,
|
||||
var top: String = "",
|
||||
@ColumnInfo(name = "local_path") var localPath: String = ""
|
||||
)
|
||||
|
||||
@Keep
|
||||
@Entity(tableName = "tag",
|
||||
foreignKeys = [
|
||||
ForeignKey(
|
||||
entity = ArticleDetail::class,
|
||||
parentColumns = arrayOf("id"),
|
||||
childColumns = arrayOf("article_id"),
|
||||
onDelete = CASCADE,)],
|
||||
indices = [Index(value = arrayOf("article_id"), unique = true)]
|
||||
)
|
||||
data class Tag(
|
||||
@PrimaryKey(autoGenerate = true)var id: Long,
|
||||
@ColumnInfo(name = "article_id") var artileId: Int,
|
||||
var name: String,
|
||||
var url: String
|
||||
)
|
||||
|
||||
/**
|
||||
* 连表查询,需要定义一个中间bean,具体用法详见
|
||||
* https://developer.android.google.cn/training/data-storage/room/relationships?hl=zh-cn
|
||||
*/
|
||||
@Keep
|
||||
data class ArticleDetailWithTag(
|
||||
@Embedded var articleDetail: ArticleDetail,
|
||||
@Relation(
|
||||
parentColumn = "id",
|
||||
entityColumn = "article_id"
|
||||
)
|
||||
var tags: List<Tag>?
|
||||
)
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
package com.bbgo.module_project.di
|
||||
|
||||
import android.app.Application
|
||||
import androidx.room.Room
|
||||
import com.bbgo.common_base.BaseApplication
|
||||
import com.bbgo.module_project.local.AppDatabase
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
/**
|
||||
* @Description:
|
||||
* @Author: wangyuebin
|
||||
* @Date: 2021/8/30 4:29 下午
|
||||
*/
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
class DataBaseModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideDataBase(app: Application) : AppDatabase {
|
||||
return Room.databaseBuilder(
|
||||
BaseApplication.getContext().applicationContext,
|
||||
AppDatabase::class.java, "project.db").build()
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideProjectTreeDao(database: AppDatabase) = database.projectTreeDao()
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideArticleDetailDao(database: AppDatabase) = database.articleDetailDao()
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideTagDao(database: AppDatabase) = database.tagDao()
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
package com.bbgo.module_project.local
|
||||
|
||||
import androidx.room.Database
|
||||
import androidx.room.RoomDatabase
|
||||
import com.bbgo.module_project.bean.ArticleDetail
|
||||
import com.bbgo.module_project.bean.ProjectBean
|
||||
import com.bbgo.module_project.bean.Tag
|
||||
import com.bbgo.module_project.local.dao.ArticleDetailDao
|
||||
import com.bbgo.module_project.local.dao.ProjectTreeDao
|
||||
import com.bbgo.module_project.local.dao.TagDao
|
||||
|
||||
/**
|
||||
* author: wangyb
|
||||
* date: 4/8/21 7:56 PM
|
||||
* description: todo
|
||||
*/
|
||||
@Database(entities = [ProjectBean::class, ArticleDetail::class, Tag::class], version = 2, exportSchema = false)
|
||||
abstract class AppDatabase : RoomDatabase() {
|
||||
abstract fun projectTreeDao(): ProjectTreeDao
|
||||
abstract fun articleDetailDao(): ArticleDetailDao
|
||||
abstract fun tagDao(): TagDao
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
package com.bbgo.module_project.local
|
||||
|
||||
import androidx.room.Room
|
||||
import androidx.room.migration.Migration
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase
|
||||
import com.bbgo.common_base.BaseApplication
|
||||
|
||||
/**
|
||||
* author: wangyb
|
||||
* date: 2021/5/24 5:56 下午
|
||||
* description: todo
|
||||
*/
|
||||
class DBUtil {
|
||||
|
||||
companion object {
|
||||
|
||||
@Volatile
|
||||
private var db: AppDatabase? = null
|
||||
|
||||
fun getInstance(): AppDatabase {
|
||||
return db ?: synchronized(AppDatabase::class.java) {
|
||||
val roomDB = Room.databaseBuilder(
|
||||
BaseApplication.getContext().applicationContext,
|
||||
AppDatabase::class.java, "project.db")
|
||||
.addMigrations(migration1_2)
|
||||
.build()
|
||||
db = roomDB
|
||||
roomDB
|
||||
}
|
||||
}
|
||||
|
||||
private val migration1_2 = object : Migration(1, 2) {
|
||||
override fun migrate(database: SupportSQLiteDatabase) {
|
||||
database.execSQL("ALTER TABLE article_detail ADD COLUMN local_path TEXT NOT NULL DEFAULT ''")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
package com.bbgo.module_project.local.dao
|
||||
|
||||
import androidx.room.*
|
||||
import com.bbgo.module_project.bean.ArticleDetail
|
||||
import com.bbgo.module_project.bean.ArticleDetailWithTag
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
/**
|
||||
* @Description:
|
||||
* @Author: wangyuebin
|
||||
* @Date: 2021/8/25 3:34 下午
|
||||
*/
|
||||
@Dao
|
||||
interface ArticleDetailDao {
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
fun insert(vararg data: ArticleDetail)
|
||||
|
||||
/**
|
||||
* 只能查询ArticleDetail表
|
||||
*/
|
||||
@Transaction
|
||||
@Query("SELECT * FROM ARTICLE_DETAIL")
|
||||
fun getArticleDetailList() : Flow<MutableList<ArticleDetail>>
|
||||
|
||||
/**
|
||||
* 可将ArticleDetail表和TAG表一起联合查询
|
||||
* 该方法需要 Room 运行两次查询,因此应向该方法添加 @Transaction 注释,以确保整个操作以原子方式执行
|
||||
*/
|
||||
@Transaction
|
||||
@Query("SELECT * FROM ARTICLE_DETAIL ORDER BY id ASC")
|
||||
fun getArticleDetailWithTag() : Flow<MutableList<ArticleDetailWithTag>>
|
||||
|
||||
@Transaction
|
||||
@Query("DELETE FROM ARTICLE_DETAIL WHERE id=:articleId")
|
||||
fun deleteArticleById(articleId: String)
|
||||
|
||||
@Delete
|
||||
fun deleteArticle(vararg articleDetail: ArticleDetail)
|
||||
|
||||
@Transaction
|
||||
@Query("UPDATE ARTICLE_DETAIL SET local_path =:localPath WHERE envelope_pic=:url")
|
||||
fun updatePathByUrl(localPath: String, url: String)
|
||||
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package com.bbgo.module_project.local.dao
|
||||
|
||||
import androidx.room.*
|
||||
import com.bbgo.module_project.bean.ProjectBean
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
/**
|
||||
* @Description:
|
||||
* @Author: wangyuebin
|
||||
* @Date: 2021/8/25 3:34 下午
|
||||
*/
|
||||
@Dao
|
||||
interface ProjectTreeDao {
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
fun insert(vararg data: ProjectBean)
|
||||
|
||||
@Transaction
|
||||
@Query("SELECT * FROM PROJECT_TREE")
|
||||
fun getProjectTree() : Flow<List<ProjectBean>>
|
||||
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
package com.bbgo.module_project.local.dao
|
||||
|
||||
import androidx.room.*
|
||||
import com.bbgo.module_project.bean.ProjectBean
|
||||
import com.bbgo.module_project.bean.Tag
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
/**
|
||||
* @Description:
|
||||
* @Author: wangyuebin
|
||||
* @Date: 2021/8/25 3:34 下午
|
||||
*/
|
||||
@Dao
|
||||
interface TagDao {
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
fun insert(vararg data: Tag)
|
||||
|
||||
@Transaction
|
||||
@Query("SELECT * FROM TAG")
|
||||
fun getTagList() : Flow<List<Tag>>
|
||||
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
package com.bbgo.module_project.net.api
|
||||
|
||||
import com.bbgo.common_base.bean.HttpResult
|
||||
import com.bbgo.module_project.bean.ArticleData
|
||||
import com.bbgo.module_project.bean.ProjectBean
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Path
|
||||
import retrofit2.http.Query
|
||||
|
||||
/**
|
||||
* author: wangyb
|
||||
* date: 4/7/21 9:24 PM
|
||||
* description: http api
|
||||
*/
|
||||
interface HttpProjectApiService {
|
||||
|
||||
/**
|
||||
* 项目数据
|
||||
* http://www.wanandroid.com/project/tree/json
|
||||
*/
|
||||
@GET("project/tree/json")
|
||||
fun getProjectTree(): Flow<HttpResult<List<ProjectBean>>>
|
||||
|
||||
/**
|
||||
* 项目列表数据
|
||||
* http://www.wanandroid.com/project/list/1/json?cid=294
|
||||
* @param page
|
||||
* @param cid
|
||||
*/
|
||||
@GET("project/list/{page}/json")
|
||||
fun getProjectList(@Path("page") page: Int, @Query("cid") cid: Int): Flow<HttpResult<ArticleData>>
|
||||
|
||||
}
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
package com.bbgo.module_project.repository
|
||||
|
||||
import android.util.Log
|
||||
import com.bbgo.common_base.net.download.DownloadListener
|
||||
import com.bbgo.common_base.net.download.FileDownloader
|
||||
import com.bbgo.module_project.bean.ArticleDetail
|
||||
import com.bbgo.module_project.bean.ArticleDetailWithTag
|
||||
import com.bbgo.module_project.bean.ProjectBean
|
||||
import com.bbgo.module_project.bean.Tag
|
||||
import com.bbgo.module_project.local.DBUtil
|
||||
import dagger.hilt.android.scopes.ActivityRetainedScoped
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import javax.inject.Inject
|
||||
|
||||
|
||||
/**
|
||||
* author: wangyb
|
||||
* date: 3/30/21 2:36 PM
|
||||
* description: todo
|
||||
*/
|
||||
@ActivityRetainedScoped
|
||||
class ProjectLocalRepository @Inject constructor() {
|
||||
|
||||
fun insertProjectTree(projectBeans: List<ProjectBean>) {
|
||||
DBUtil.getInstance().runInTransaction {
|
||||
projectBeans.forEach { projectBean ->
|
||||
DBUtil.getInstance().projectTreeDao().insert(projectBean)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getProjectTree() : Flow<List<ProjectBean>> {
|
||||
return DBUtil.getInstance().projectTreeDao().getProjectTree()
|
||||
}
|
||||
|
||||
fun insertProjectArticles(articleDetails: List<ArticleDetail>) {
|
||||
DBUtil.getInstance().runInTransaction {
|
||||
articleDetails.forEach { articleDetail ->
|
||||
DBUtil.getInstance().articleDetailDao().insert(articleDetail)
|
||||
articleDetail.tags?.forEach { tag ->
|
||||
tag.artileId = articleDetail.id
|
||||
insertTag(tag)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getProjectArticles() : Flow<MutableList<ArticleDetail>> {
|
||||
return DBUtil.getInstance().articleDetailDao().getArticleDetailList()
|
||||
}
|
||||
|
||||
private fun insertTag(tag: Tag) {
|
||||
DBUtil.getInstance().tagDao().insert(tag)
|
||||
}
|
||||
|
||||
fun getTags() : Flow<List<Tag>> {
|
||||
return DBUtil.getInstance().tagDao().getTagList()
|
||||
}
|
||||
|
||||
fun getArticleDetailWithTag() : Flow<MutableList<ArticleDetailWithTag>> {
|
||||
return DBUtil.getInstance().articleDetailDao().getArticleDetailWithTag()
|
||||
}
|
||||
|
||||
fun deleteArticleById(articleId: String) {
|
||||
DBUtil.getInstance().articleDetailDao().deleteArticleById(articleId)
|
||||
}
|
||||
|
||||
suspend fun downloadFile(url: String, path: String) {
|
||||
FileDownloader.create(url)
|
||||
.setPath(path)
|
||||
.setListener(object : DownloadListener {
|
||||
override fun onStart() {
|
||||
}
|
||||
|
||||
override fun onProgress(progress: Int, total: Float) {
|
||||
}
|
||||
|
||||
override fun onFinish(path: String, url: String) {
|
||||
Log.d("ProjectLocalRepository", "path = $path , url = $url , thread = ${Thread.currentThread().name}")
|
||||
DBUtil.getInstance().articleDetailDao().updatePathByUrl(path, url)
|
||||
}
|
||||
|
||||
override fun onError(msg: String?) {
|
||||
}
|
||||
})
|
||||
.start()
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
package com.bbgo.module_project.repository
|
||||
|
||||
import com.bbgo.common_base.bean.HttpResult
|
||||
import com.bbgo.common_base.net.ServiceCreators
|
||||
import com.bbgo.module_project.bean.ArticleData
|
||||
import com.bbgo.module_project.bean.ProjectBean
|
||||
import com.bbgo.module_project.net.api.HttpProjectApiService
|
||||
import dagger.hilt.android.scopes.ActivityRetainedScoped
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* author: wangyb
|
||||
* date: 3/30/21 2:35 PM
|
||||
* description: todo
|
||||
*/
|
||||
@ActivityRetainedScoped
|
||||
class ProjectRemoteRepository @Inject constructor(){
|
||||
|
||||
private val service = ServiceCreators.create(HttpProjectApiService::class.java)
|
||||
|
||||
fun getProjectTree() : Flow<HttpResult<List<ProjectBean>>> {
|
||||
return service.getProjectTree()
|
||||
}
|
||||
|
||||
fun getProjectList(id: Int, page: Int) : Flow<HttpResult<ArticleData>> {
|
||||
return service.getProjectList(page, id)
|
||||
}
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
package com.bbgo.module_project.repository
|
||||
|
||||
import com.bbgo.common_base.bean.HttpResult
|
||||
import com.bbgo.module_project.bean.*
|
||||
import dagger.hilt.android.scopes.ActivityRetainedScoped
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* author: wangyb
|
||||
* date: 3/29/21 9:32 PM
|
||||
* description: todo
|
||||
*/
|
||||
@ActivityRetainedScoped
|
||||
class ProjectRepository @Inject constructor(private val remoteRepository: ProjectRemoteRepository, private val localRepository: ProjectLocalRepository) {
|
||||
|
||||
fun getProjectTree() : Flow<HttpResult<List<ProjectBean>>> {
|
||||
return remoteRepository.getProjectTree()
|
||||
}
|
||||
|
||||
fun getProjectList(id: Int, page: Int) : Flow<HttpResult<ArticleData>> {
|
||||
return remoteRepository.getProjectList(id, page)
|
||||
}
|
||||
|
||||
fun insertProjectTree(projectBean: List<ProjectBean>) {
|
||||
localRepository.insertProjectTree(projectBean)
|
||||
}
|
||||
|
||||
fun getProjectTreeFromDB() : Flow<List<ProjectBean>> {
|
||||
return localRepository.getProjectTree()
|
||||
}
|
||||
|
||||
fun insertProjectArticles(articleDetail: List<ArticleDetail>) {
|
||||
localRepository.insertProjectArticles(articleDetail)
|
||||
}
|
||||
|
||||
fun getProjectArticlesFromDB() : Flow<MutableList<ArticleDetail>> {
|
||||
return localRepository.getProjectArticles()
|
||||
}
|
||||
|
||||
fun getTagsFromDB() : Flow<List<Tag>> {
|
||||
return localRepository.getTags()
|
||||
}
|
||||
|
||||
fun getArticleDetailWithTagFromDB() : Flow<MutableList<ArticleDetailWithTag>> {
|
||||
return localRepository.getArticleDetailWithTag()
|
||||
}
|
||||
|
||||
fun deleteArticleById(articleId: String) {
|
||||
localRepository.deleteArticleById(articleId)
|
||||
}
|
||||
|
||||
suspend fun downloadFile(url: String, path: String) {
|
||||
localRepository.downloadFile(url, path)
|
||||
}
|
||||
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
package com.bbgo.module_project.ui
|
||||
|
||||
import android.text.Html
|
||||
import android.text.TextUtils
|
||||
import android.view.View
|
||||
import android.widget.ImageView
|
||||
import com.bbgo.common_base.util.ImageLoader
|
||||
import com.bbgo.module_project.R
|
||||
import com.bbgo.module_project.bean.ArticleDetail
|
||||
import com.chad.library.adapter.base.BaseQuickAdapter
|
||||
import com.chad.library.adapter.base.viewholder.BaseViewHolder
|
||||
|
||||
|
||||
class ArticleListAdapter(datas: MutableList<ArticleDetail>)
|
||||
: BaseQuickAdapter<ArticleDetail, BaseViewHolder>(R.layout.item_article_list, datas) {
|
||||
|
||||
override fun convert(holder: BaseViewHolder, item: ArticleDetail) {
|
||||
val authorStr = if (item.author.isNotEmpty()) item.author else item.shareUser
|
||||
holder.setText(R.id.tv_article_title, Html.fromHtml(item.title))
|
||||
.setText(R.id.tv_article_author, authorStr)
|
||||
.setText(R.id.tv_article_date, item.niceDate)
|
||||
.setImageResource(R.id.iv_like,
|
||||
if (item.collect) R.drawable.ic_like else R.drawable.ic_like_not
|
||||
)
|
||||
val chapterName = when {
|
||||
item.superChapterName.isNotEmpty() and item.chapterName.isNotEmpty() ->
|
||||
"${item.superChapterName} / ${item.chapterName}"
|
||||
item.superChapterName.isNotEmpty() -> item.superChapterName
|
||||
item.chapterName.isNotEmpty() -> item.chapterName
|
||||
else -> ""
|
||||
}
|
||||
holder.setText(R.id.tv_article_chapterName, chapterName)
|
||||
|
||||
if (!TextUtils.isEmpty(item.envelopePic)) {
|
||||
holder.getView<ImageView>(R.id.iv_article_thumbnail)
|
||||
.visibility = View.VISIBLE
|
||||
ImageLoader.load(context, item.envelopePic, holder.getView(R.id.iv_article_thumbnail))
|
||||
} else {
|
||||
holder.getView<ImageView>(R.id.iv_article_thumbnail)
|
||||
.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+87
@@ -0,0 +1,87 @@
|
||||
package com.bbgo.module_project.ui
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import com.alibaba.android.arouter.facade.annotation.Route
|
||||
import com.bbgo.common_base.base.BaseFragment
|
||||
import com.bbgo.common_base.constants.RouterPath
|
||||
import com.bbgo.common_base.databinding.LayoutLoadingBinding
|
||||
import com.bbgo.common_base.ext.Resource
|
||||
import com.bbgo.common_base.ext.observe
|
||||
import com.bbgo.common_base.ext.showToast
|
||||
import com.bbgo.module_project.bean.ProjectBean
|
||||
import com.bbgo.module_project.databinding.FragmentProjectBinding
|
||||
import com.bbgo.module_project.viewmodel.ProjectViewModel
|
||||
import com.google.android.material.tabs.TabLayoutMediator
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
|
||||
/**
|
||||
* author: wangyb
|
||||
* date: 2021/5/20 3:04 下午
|
||||
* description: todo
|
||||
*/
|
||||
@Route(path = RouterPath.Project.PAGE_PROJECT)
|
||||
@AndroidEntryPoint
|
||||
class ProjectFragment : BaseFragment<FragmentProjectBinding>() {
|
||||
|
||||
private lateinit var loadingBinding: LayoutLoadingBinding
|
||||
|
||||
private val projectViewModel: ProjectViewModel by activityViewModels()
|
||||
|
||||
/**
|
||||
* datas
|
||||
*/
|
||||
private val projectDatas = mutableListOf<ProjectBean>()
|
||||
|
||||
/**
|
||||
* ViewPagerAdapter
|
||||
*/
|
||||
private val viewPagerAdapter: ProjectPagerAdapter by lazy {
|
||||
ProjectPagerAdapter(this)
|
||||
}
|
||||
|
||||
override fun lazyLoad() {
|
||||
projectViewModel.getProjectTree()
|
||||
}
|
||||
|
||||
override fun initView() {
|
||||
loadingBinding = LayoutLoadingBinding.bind(binding.root)
|
||||
binding.viewPager.adapter = viewPagerAdapter
|
||||
TabLayoutMediator(binding.tabLayout, binding.viewPager, true, true) { tab, position ->
|
||||
tab.text = projectDatas[position].name
|
||||
}.attach()
|
||||
}
|
||||
|
||||
override fun observe() {
|
||||
observe(projectViewModel.projectTreeLiveData, ::handleWxChapter)
|
||||
}
|
||||
|
||||
private fun handleWxChapter(status: Resource<List<ProjectBean>>) {
|
||||
when(status) {
|
||||
is Resource.Loading -> {
|
||||
loadingBinding.progressBar.visibility = View.VISIBLE
|
||||
}
|
||||
is Resource.Error -> {
|
||||
loadingBinding.progressBar.visibility = View.GONE
|
||||
showToast(status.exception.toString())
|
||||
}
|
||||
is Resource.Success -> {
|
||||
loadingBinding.progressBar.visibility = View.GONE
|
||||
projectDatas.addAll(status.data)
|
||||
viewPagerAdapter.setList(projectDatas)
|
||||
binding.viewPager.offscreenPageLimit = projectDatas.size
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "WeChatFragment"
|
||||
}
|
||||
|
||||
override fun inflateViewBinding(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?
|
||||
) = FragmentProjectBinding.inflate(inflater, container, false)
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
package com.bbgo.module_project.ui
|
||||
|
||||
import android.text.Html
|
||||
import android.text.TextUtils
|
||||
import android.view.View
|
||||
import android.widget.ImageView
|
||||
import com.bbgo.common_base.util.ImageLoader
|
||||
import com.bbgo.module_project.R
|
||||
import com.bbgo.module_project.bean.ArticleDetail
|
||||
import com.chad.library.adapter.base.BaseQuickAdapter
|
||||
import com.chad.library.adapter.base.viewholder.BaseViewHolder
|
||||
|
||||
/**
|
||||
* Created by chenxz on 2018/4/22.
|
||||
*/
|
||||
class ProjectListAdapter(datas: MutableList<ArticleDetail>)
|
||||
: BaseQuickAdapter<ArticleDetail, BaseViewHolder>(R.layout.item_article_list, datas) {
|
||||
|
||||
override fun convert(holder: BaseViewHolder, item: ArticleDetail) {
|
||||
val authorStr = if (item.author.isNotEmpty()) item.author else item.shareUser
|
||||
holder.setText(R.id.tv_article_title, Html.fromHtml(item.title))
|
||||
.setText(R.id.tv_article_author, authorStr)
|
||||
.setText(R.id.tv_article_date, item.niceDate)
|
||||
.setImageResource(R.id.iv_like,
|
||||
if (item.collect) R.drawable.ic_like else R.drawable.ic_like_not
|
||||
)
|
||||
val chapterName = when {
|
||||
item.superChapterName.isNotEmpty() and item.chapterName.isNotEmpty() ->
|
||||
"${item.superChapterName} / ${item.chapterName}"
|
||||
item.superChapterName.isNotEmpty() -> item.superChapterName
|
||||
item.chapterName.isNotEmpty() -> item.chapterName
|
||||
else -> ""
|
||||
}
|
||||
holder.setText(R.id.tv_article_chapterName, chapterName)
|
||||
|
||||
if (!TextUtils.isEmpty(item.envelopePic)) {
|
||||
holder.getView<ImageView>(R.id.iv_article_thumbnail)
|
||||
.visibility = View.VISIBLE
|
||||
ImageLoader.load(context, item.envelopePic, holder.getView(R.id.iv_article_thumbnail))
|
||||
} else {
|
||||
holder.getView<ImageView>(R.id.iv_article_thumbnail)
|
||||
.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+229
@@ -0,0 +1,229 @@
|
||||
package com.bbgo.module_project.ui
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.recyclerview.widget.DefaultItemAnimator
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
import com.alibaba.android.arouter.facade.annotation.Autowired
|
||||
import com.alibaba.android.arouter.launcher.ARouter
|
||||
import com.bbgo.common_base.base.BaseFragment
|
||||
import com.bbgo.common_base.bus.BusKey
|
||||
import com.bbgo.common_base.bus.LiveDataBus
|
||||
import com.bbgo.common_base.constants.Constants
|
||||
import com.bbgo.common_base.constants.RouterPath
|
||||
import com.bbgo.common_base.event.MessageEvent
|
||||
import com.bbgo.common_base.event.ScrollEvent
|
||||
import com.bbgo.common_base.ext.Resource
|
||||
import com.bbgo.common_base.ext.observe
|
||||
import com.bbgo.common_base.ext.showToast
|
||||
import com.bbgo.common_base.widget.SpaceItemDecoration
|
||||
import com.bbgo.common_service.collect.CollectService
|
||||
import com.bbgo.module_project.R
|
||||
import com.bbgo.module_project.bean.ArticleDetail
|
||||
import com.bbgo.module_project.databinding.FragmentProjectListBinding
|
||||
import com.bbgo.module_project.viewmodel.ProjectViewModel
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
|
||||
/**
|
||||
* Created by wangyb
|
||||
*/
|
||||
@AndroidEntryPoint
|
||||
class ProjectListFragment : BaseFragment<FragmentProjectListBinding>() {
|
||||
|
||||
companion object {
|
||||
fun getInstance(cid: Int): ProjectListFragment {
|
||||
val fragment = ProjectListFragment()
|
||||
val args = Bundle()
|
||||
args.putInt(Constants.CONTENT_CID_KEY, cid)
|
||||
fragment.arguments = args
|
||||
return fragment
|
||||
}
|
||||
}
|
||||
|
||||
@Autowired
|
||||
lateinit var collectService: CollectService
|
||||
|
||||
private lateinit var projectViewModel: ProjectViewModel
|
||||
|
||||
/**
|
||||
* cid
|
||||
*/
|
||||
private var cid: Int = 0
|
||||
|
||||
/**
|
||||
* 是否是下拉刷新
|
||||
*/
|
||||
private var isRefresh = true
|
||||
|
||||
/**
|
||||
* datas
|
||||
*/
|
||||
private val articleList = mutableListOf<ArticleDetail>()
|
||||
|
||||
/**
|
||||
* RecyclerView Divider
|
||||
*/
|
||||
private val recyclerViewItemDecoration by lazy {
|
||||
activity?.let {
|
||||
SpaceItemDecoration(it)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* LinearLayoutManager
|
||||
*/
|
||||
private val linearLayoutManager: LinearLayoutManager by lazy {
|
||||
LinearLayoutManager(activity)
|
||||
}
|
||||
|
||||
/**
|
||||
* Adapter
|
||||
*/
|
||||
private val mAdapter: ArticleListAdapter by lazy {
|
||||
ArticleListAdapter(articleList)
|
||||
}
|
||||
|
||||
/**
|
||||
* RefreshListener
|
||||
*/
|
||||
private val onRefreshListener = SwipeRefreshLayout.OnRefreshListener {
|
||||
binding.swipeRefreshLayout.isRefreshing = false
|
||||
isRefresh = true
|
||||
}
|
||||
|
||||
override fun initView() {
|
||||
ARouter.getInstance().inject(this)
|
||||
|
||||
cid = arguments?.getInt(Constants.CONTENT_CID_KEY) ?: 0
|
||||
binding.swipeRefreshLayout.setOnRefreshListener(onRefreshListener)
|
||||
binding.recyclerView.run {
|
||||
layoutManager = linearLayoutManager
|
||||
adapter = mAdapter
|
||||
itemAnimator = DefaultItemAnimator()
|
||||
recyclerViewItemDecoration?.let { addItemDecoration(it) }
|
||||
}
|
||||
|
||||
projectViewModel = ViewModelProvider(this).get(ProjectViewModel::class.java)
|
||||
|
||||
mAdapter.setOnItemClickListener { adapter, view, position ->
|
||||
val article = articleList[position]
|
||||
ARouter.getInstance().build(RouterPath.Content.PAGE_CONTENT)
|
||||
.withString(Constants.POSITION, position.toString())
|
||||
.withString(Constants.CONTENT_ID_KEY, article.id.toString())
|
||||
.withString(Constants.CONTENT_TITLE_KEY, article.title)
|
||||
.withString(Constants.CONTENT_URL_KEY, article.link)
|
||||
.navigation()
|
||||
}
|
||||
|
||||
mAdapter.run {
|
||||
setOnItemClickListener { adapter, view, position ->
|
||||
val article = articleList[position]
|
||||
ARouter.getInstance().build(RouterPath.Content.PAGE_CONTENT)
|
||||
.withString(Constants.POSITION, position.toString())
|
||||
.withString(Constants.CONTENT_ID_KEY, article.id.toString())
|
||||
.withString(Constants.CONTENT_TITLE_KEY, article.title)
|
||||
.withString(Constants.CONTENT_URL_KEY, article.link)
|
||||
.withString(Constants.COLLECT, article.collect.toString())
|
||||
.navigation()
|
||||
}
|
||||
addChildClickViewIds(R.id.iv_like)
|
||||
setOnItemChildClickListener { adapter, view, position ->
|
||||
if (view.id == R.id.iv_like) {
|
||||
val article = articleList[position]
|
||||
if (article.collect) {
|
||||
collectService.unCollect(
|
||||
Constants.FragmentIndex.PROJECT_INDEX,
|
||||
position,
|
||||
articleList[position].id
|
||||
)
|
||||
return@setOnItemChildClickListener
|
||||
}
|
||||
collectService.collect(
|
||||
Constants.FragmentIndex.PROJECT_INDEX,
|
||||
position,
|
||||
articleList[position].id
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
initBus()
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化事件总线,和eventbus效果相同
|
||||
*/
|
||||
private fun initBus() {
|
||||
LiveDataBus.get().with(BusKey.COLLECT, MessageEvent::class.java).observe(this) {
|
||||
if (it.indexPage == Constants.FragmentIndex.PROJECT_INDEX) {
|
||||
handleCollect(it)
|
||||
}
|
||||
}
|
||||
LiveDataBus.get().with(BusKey.SCROLL_TOP, ScrollEvent::class.java).observe(this) {
|
||||
if (it.index == 4) {
|
||||
scrollToTop()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun lazyLoad() {
|
||||
projectViewModel.getProjectList(cid, 0)
|
||||
}
|
||||
|
||||
override fun observe() {
|
||||
observe(projectViewModel.articlesLiveData, ::handleInfo)
|
||||
}
|
||||
|
||||
private fun handleInfo(status: Resource<MutableList<ArticleDetail>>) {
|
||||
if (status !is Resource.Success) return
|
||||
articleList.clear()
|
||||
articleList.addAll(status.data)
|
||||
mAdapter.run {
|
||||
if (isRefresh) {
|
||||
setList(articleList)
|
||||
} else {
|
||||
addData(articleList)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleCollect(event: MessageEvent) {
|
||||
when (event.type) {
|
||||
Constants.CollectType.UNKNOWN -> {
|
||||
ARouter.getInstance().build(RouterPath.LoginRegister.PAGE_LOGIN).navigation()
|
||||
}
|
||||
else -> {
|
||||
if (articleList.isEmpty()) {
|
||||
return
|
||||
}
|
||||
if (event.type == Constants.CollectType.COLLECT) {
|
||||
showToast(getString(R.string.collect_success))
|
||||
articleList[event.position].collect = true
|
||||
mAdapter.setList(articleList)
|
||||
return
|
||||
}
|
||||
articleList[event.position].collect = false
|
||||
mAdapter.setList(articleList)
|
||||
showToast(getString(R.string.cancel_collect_success))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun scrollToTop() {
|
||||
binding.recyclerView.run {
|
||||
if (linearLayoutManager.findFirstVisibleItemPosition() > 20) {
|
||||
scrollToPosition(0)
|
||||
} else {
|
||||
smoothScrollToPosition(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun inflateViewBinding(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?
|
||||
) = FragmentProjectListBinding.inflate(inflater, container, false)
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
package com.bbgo.module_project.ui
|
||||
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||
import com.bbgo.module_project.bean.ProjectBean
|
||||
|
||||
|
||||
class ProjectPagerAdapter(fm: Fragment)
|
||||
: FragmentStateAdapter(fm) {
|
||||
|
||||
private val fragments = mutableListOf<Fragment>()
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return fragments.size
|
||||
}
|
||||
|
||||
override fun createFragment(position: Int): Fragment {
|
||||
return fragments[position]
|
||||
}
|
||||
|
||||
fun setList(list: List<ProjectBean>) {
|
||||
fragments.clear()
|
||||
list.forEach {
|
||||
fragments.add(ProjectListFragment.getInstance(it.id))
|
||||
}
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+141
@@ -0,0 +1,141 @@
|
||||
package com.bbgo.module_project.viewmodel
|
||||
|
||||
import androidx.lifecycle.*
|
||||
import com.bbgo.common_base.BaseApplication
|
||||
import com.bbgo.common_base.ext.HTTP_REQUEST_ERROR
|
||||
import com.bbgo.common_base.ext.Resource
|
||||
import com.bbgo.common_base.util.FileUtils
|
||||
import com.bbgo.common_base.util.MD5Utils
|
||||
import com.bbgo.common_base.util.NetWorkUtil
|
||||
import com.bbgo.common_base.util.log.Logs
|
||||
import com.bbgo.module_project.bean.ArticleDetail
|
||||
import com.bbgo.module_project.bean.ProjectBean
|
||||
import com.bbgo.module_project.repository.ProjectRepository
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import java.io.File
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* author: wangyb
|
||||
* date: 3/29/21 9:31 PM
|
||||
* description: todo
|
||||
*/
|
||||
@HiltViewModel
|
||||
class ProjectViewModel @Inject constructor(private val repository: ProjectRepository) : ViewModel() {
|
||||
|
||||
val projectTreeLiveData = MutableLiveData<Resource<List<ProjectBean>>>()
|
||||
val articlesLiveData = MutableLiveData<Resource<MutableList<ArticleDetail>>>()
|
||||
private val articleList = mutableListOf<ArticleDetail>()
|
||||
|
||||
fun getProjectTree() = viewModelScope.launch {
|
||||
/**
|
||||
* 1.必须要有异常处理
|
||||
* 2.必须要有collect,否则map里面的代码不执行
|
||||
*/
|
||||
if (NetWorkUtil.isNetworkConnected(BaseApplication.getContext())) {
|
||||
repository.getProjectTree()
|
||||
.map {
|
||||
if (it.errorCode == HTTP_REQUEST_ERROR) {
|
||||
Resource.Error(Exception(it.errorMsg))
|
||||
} else {
|
||||
Resource.Success(it.data)
|
||||
}
|
||||
}
|
||||
.catch {
|
||||
}
|
||||
.collectLatest {
|
||||
projectTreeLiveData.value = it
|
||||
|
||||
insertProjectTree()
|
||||
}
|
||||
return@launch
|
||||
}
|
||||
/**
|
||||
* 无网络从DB中查询数据
|
||||
*/
|
||||
repository.getProjectTreeFromDB()
|
||||
.catch {
|
||||
|
||||
}
|
||||
.collectLatest {
|
||||
projectTreeLiveData.value = Resource.Success(it)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将从网络中获取的数据存入DB
|
||||
*/
|
||||
private fun insertProjectTree() = viewModelScope.launch(Dispatchers.IO) {
|
||||
if (projectTreeLiveData.value is Resource.Success) {
|
||||
val data = (projectTreeLiveData.value as Resource.Success<List<ProjectBean>>).data
|
||||
repository.insertProjectTree(data)
|
||||
// projectTreeLiveData.value?.data?.let {
|
||||
// repository.insertProjectTree(it)
|
||||
// }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun getProjectList(id: Int, page: Int) = viewModelScope.launch {
|
||||
/**
|
||||
* 1.必须要有异常处理
|
||||
* 2.必须要有collect,否则map里面的代码不执行
|
||||
*/
|
||||
if (NetWorkUtil.isNetworkConnected(BaseApplication.getContext())) {
|
||||
repository.getProjectList(id, page)
|
||||
.map {
|
||||
if (it.errorCode == HTTP_REQUEST_ERROR) {
|
||||
Resource.Error(Exception(it.errorMsg))
|
||||
} else {
|
||||
Resource.Success(it.data.datas)
|
||||
}
|
||||
}
|
||||
.catch {
|
||||
Logs.e(it, it.message)
|
||||
}
|
||||
.collectLatest {
|
||||
articlesLiveData.value = it
|
||||
|
||||
insertProjectArticle()
|
||||
}
|
||||
return@launch
|
||||
}
|
||||
|
||||
|
||||
repository.getArticleDetailWithTagFromDB()
|
||||
.map {
|
||||
articleList.clear()
|
||||
it.forEach { articleDetailWithTag ->
|
||||
articleDetailWithTag.articleDetail.tags = articleDetailWithTag.tags
|
||||
articleList.add(articleDetailWithTag.articleDetail)
|
||||
}
|
||||
Resource.Success(articleList)
|
||||
}
|
||||
.catch { }
|
||||
.flowOn(Dispatchers.IO)
|
||||
.collectLatest {
|
||||
articlesLiveData.value = it
|
||||
}
|
||||
}
|
||||
|
||||
private fun insertProjectArticle() {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
if (articlesLiveData.value is Resource.Success) {
|
||||
val data = (articlesLiveData.value as Resource.Success<MutableList<ArticleDetail>>).data
|
||||
repository.insertProjectArticles(data)
|
||||
|
||||
data.forEach { articleDetail ->
|
||||
repository.downloadFile(
|
||||
articleDetail.envelopePic,
|
||||
FileUtils.getExternalFilePath() + File.separator +
|
||||
MD5Utils.getMD5(articleDetail.envelopePic) + ".jpg"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.bbgo.module_project">
|
||||
|
||||
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> <!-- 配置权限,用来记录应用配置信息 -->
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <!-- 重力加速度传感器权限 -->
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <!-- Android8.0安装apk需要请求安装权限 -->
|
||||
<uses-permission android:name="android.permission.VIBRATE" /> <!-- 小米push -->
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
<uses-permission android:name="android.permission.REORDER_TASKS" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.READ_CONTACTS" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||
<uses-permission android:name="android.hardware.sensor.accelerometer" />
|
||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
||||
<uses-permission android:name="android.permission.GET_TASKS" />
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
<uses-permission android:name="android.permission.FLASHLIGHT" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS" />
|
||||
|
||||
<application
|
||||
android:name=".ProjectApp"
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme">
|
||||
|
||||
<activity
|
||||
android:name=".activity.ProjectMainActivity">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:endX="85.84757"
|
||||
android:endY="92.4963"
|
||||
android:startX="42.9492"
|
||||
android:startY="49.59793"
|
||||
android:type="linear">
|
||||
<item
|
||||
android:color="#44000000"
|
||||
android:offset="0.0" />
|
||||
<item
|
||||
android:color="#00000000"
|
||||
android:offset="1.0" />
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:fillType="nonZero"
|
||||
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
|
||||
android:strokeWidth="1"
|
||||
android:strokeColor="#00000000" />
|
||||
</vector>
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
|
||||
<stroke android:color="@color/Red"
|
||||
android:width="@dimen/dp_05"/>
|
||||
|
||||
<corners android:radius="@dimen/dp_2" />
|
||||
|
||||
</shape>
|
||||
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
|
||||
<stroke
|
||||
android:width="@dimen/dp_05"
|
||||
android:color="@color/colorAccent" />
|
||||
|
||||
<corners android:radius="@dimen/dp_2" />
|
||||
|
||||
</shape>
|
||||
@@ -0,0 +1,11 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="#00BCD4"
|
||||
android:alpha="0.8">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M12,17.27L18.18,21l-1.64,-7.03L22,9.24l-7.19,-0.61L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21z"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,11 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="#9E9E9E"
|
||||
android:alpha="0.8">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M22,9.24l-7.19,-0.62L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21 12,17.27 18.18,21l-1.63,-7.03L22,9.24zM12,15.4l-3.76,2.27 1,-4.28 -3.32,-2.88 4.38,-0.38L12,6.1l1.71,4.04 4.38,0.38 -3.32,2.88 1,4.28L12,15.4z"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:name="com.bbgo.module_project.ui.ProjectFragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
</fragment>
|
||||
@@ -0,0 +1,34 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/viewBackground"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.google.android.material.tabs.TabLayout
|
||||
android:id="@+id/tabLayout"
|
||||
style="@style/MyTabLayoutStyle"
|
||||
android:theme="@style/AppTheme.AppBarOverlay"
|
||||
android:layout_height="0dp"
|
||||
app:tabGravity="center"
|
||||
app:tabMode="scrollable"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintHeight_percent="0.06" />
|
||||
|
||||
<androidx.viewpager2.widget.ViewPager2
|
||||
android:id="@+id/viewPager"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:orientation="horizontal"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior"
|
||||
app:layout_constraintTop_toBottomOf="@id/tabLayout"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintHeight_percent="0.94"/>
|
||||
|
||||
<include layout="@layout/layout_loading" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
android:id="@+id/swipeRefreshLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/viewBackground">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recyclerView"
|
||||
style="@style/RecyclerViewStyle"
|
||||
tools:listitem="@layout/item_project_list" />
|
||||
|
||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -0,0 +1,100 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/cardView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:foreground="?attr/selectableItemBackground"
|
||||
app:cardBackgroundColor="@color/viewBackground"
|
||||
app:cardCornerRadius="@dimen/dp_1"
|
||||
app:cardElevation="@dimen/dp_1">
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="@dimen/item_content_padding"
|
||||
android:paddingRight="@dimen/item_content_padding"
|
||||
android:paddingBottom="@dimen/item_content_padding">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_article_author"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="@dimen/dp_10"
|
||||
android:textColor="@color/item_author"
|
||||
android:textSize="@dimen/item_tv_author"
|
||||
tools:text="@string/app_name" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_article_date"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentRight="true"
|
||||
android:textColor="@color/item_date"
|
||||
android:textSize="@dimen/item_tv_date"
|
||||
tools:text="@string/app_name" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_article_thumbnail"
|
||||
android:layout_width="@dimen/item_img_width"
|
||||
android:layout_height="@dimen/item_img_height"
|
||||
android:layout_below="@+id/tv_article_author"
|
||||
android:layout_marginLeft="@dimen/dp_10"
|
||||
android:layout_marginTop="@dimen/dp_8"
|
||||
android:scaleType="centerCrop"
|
||||
app:srcCompat="@drawable/bg_placeholder" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_article_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/tv_article_author"
|
||||
android:layout_marginLeft="@dimen/dp_10"
|
||||
android:layout_marginTop="@dimen/dp_8"
|
||||
android:layout_toRightOf="@+id/iv_article_thumbnail"
|
||||
android:ellipsize="end"
|
||||
android:gravity="top|start"
|
||||
android:lineSpacingExtra="2dp"
|
||||
android:maxLines="2"
|
||||
android:paddingBottom="@dimen/dp_6"
|
||||
android:textColor="@color/item_title"
|
||||
android:textSize="@dimen/item_tv_title" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_article_chapterName"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/tv_article_title"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_marginStart="@dimen/dp_10"
|
||||
android:layout_marginLeft="@dimen/dp_10"
|
||||
android:layout_marginTop="@dimen/dp_10"
|
||||
android:layout_marginEnd="@dimen/dp_10"
|
||||
android:layout_marginRight="@dimen/dp_10"
|
||||
android:layout_toRightOf="@+id/iv_article_thumbnail"
|
||||
android:gravity="center"
|
||||
android:textColor="@color/item_chapter"
|
||||
android:textSize="@dimen/item_tv_tag"
|
||||
tools:text="@string/app_name" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_like"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:src="@drawable/ic_like_not" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
</androidx.cardview.widget.CardView>
|
||||
@@ -0,0 +1,152 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/cardView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:foreground="?attr/selectableItemBackground"
|
||||
app:cardBackgroundColor="@color/viewBackground"
|
||||
app:cardCornerRadius="@dimen/dp_1"
|
||||
app:cardElevation="@dimen/dp_1">
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="@dimen/item_content_padding"
|
||||
android:paddingRight="@dimen/item_content_padding"
|
||||
android:paddingBottom="@dimen/item_content_padding">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_article_top"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="@dimen/dp_10"
|
||||
android:background="@drawable/bg_fresh"
|
||||
android:paddingLeft="@dimen/dp_4"
|
||||
android:paddingTop="@dimen/dp_2"
|
||||
android:paddingRight="@dimen/dp_4"
|
||||
android:paddingBottom="@dimen/dp_2"
|
||||
android:text="@string/top_tip"
|
||||
android:textColor="@color/Red"
|
||||
android:textSize="@dimen/sp_10"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_article_fresh"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="@dimen/dp_10"
|
||||
android:layout_toRightOf="@+id/tv_article_top"
|
||||
android:background="@drawable/bg_fresh"
|
||||
android:paddingLeft="@dimen/dp_4"
|
||||
android:paddingTop="@dimen/dp_2"
|
||||
android:paddingRight="@dimen/dp_4"
|
||||
android:paddingBottom="@dimen/dp_2"
|
||||
android:text="@string/new_fresh"
|
||||
android:textColor="@color/Red"
|
||||
android:textSize="@dimen/sp_10"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_article_tag"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="@dimen/dp_10"
|
||||
android:layout_toRightOf="@+id/tv_article_fresh"
|
||||
android:background="@drawable/bg_tag"
|
||||
android:paddingLeft="@dimen/dp_4"
|
||||
android:paddingTop="@dimen/dp_2"
|
||||
android:paddingRight="@dimen/dp_4"
|
||||
android:paddingBottom="@dimen/dp_2"
|
||||
android:textColor="@color/colorAccent"
|
||||
android:textSize="@dimen/sp_10"
|
||||
android:visibility="gone"
|
||||
tools:text="@string/app_name"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_article_author"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignBaseline="@+id/tv_article_top"
|
||||
android:layout_marginLeft="@dimen/dp_10"
|
||||
android:layout_toRightOf="@+id/tv_article_tag"
|
||||
android:textColor="@color/item_author"
|
||||
android:textSize="@dimen/item_tv_author"
|
||||
tools:text="@string/app_name" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_article_date"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignBaseline="@+id/tv_article_top"
|
||||
android:layout_alignParentRight="true"
|
||||
android:textColor="@color/item_date"
|
||||
android:textSize="@dimen/item_tv_date"
|
||||
tools:text="@string/app_name" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_article_thumbnail"
|
||||
android:layout_width="@dimen/item_img_width"
|
||||
android:layout_height="@dimen/item_img_height"
|
||||
android:layout_below="@+id/tv_article_author"
|
||||
android:layout_marginLeft="@dimen/dp_10"
|
||||
android:layout_marginTop="@dimen/dp_8"
|
||||
android:scaleType="centerCrop" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_article_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/tv_article_author"
|
||||
android:layout_marginLeft="@dimen/dp_10"
|
||||
android:layout_marginTop="@dimen/dp_8"
|
||||
android:layout_toRightOf="@+id/iv_article_thumbnail"
|
||||
android:ellipsize="end"
|
||||
android:gravity="top|start"
|
||||
android:lineSpacingExtra="2dp"
|
||||
android:maxLines="2"
|
||||
android:paddingBottom="@dimen/dp_6"
|
||||
android:textColor="@color/item_title"
|
||||
android:textSize="@dimen/item_tv_title" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/tv_article_chapterName"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/tv_article_title"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_marginStart="@dimen/dp_10"
|
||||
android:layout_marginLeft="@dimen/dp_10"
|
||||
android:layout_marginTop="@dimen/dp_10"
|
||||
android:layout_marginEnd="@dimen/dp_10"
|
||||
android:layout_marginRight="@dimen/dp_10"
|
||||
android:layout_toRightOf="@+id/iv_article_thumbnail"
|
||||
android:gravity="center"
|
||||
android:textColor="@color/item_chapter"
|
||||
android:textSize="@dimen/item_tv_tag"
|
||||
tools:text="@string/app_name" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/iv_like"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:src="@drawable/ic_like_not" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
</androidx.cardview.widget.CardView>
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/colorPrimary"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 2.1 KiB |
@@ -0,0 +1,16 @@
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<!-- Base application theme. -->
|
||||
<style name="Theme.WanAndroid" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
|
||||
<!-- Primary brand color. -->
|
||||
<item name="colorPrimary">@color/purple_200</item>
|
||||
<item name="colorPrimaryVariant">@color/purple_700</item>
|
||||
<item name="colorOnPrimary">@color/black</item>
|
||||
<!-- Secondary brand color. -->
|
||||
<item name="colorSecondary">@color/teal_200</item>
|
||||
<item name="colorSecondaryVariant">@color/teal_200</item>
|
||||
<item name="colorOnSecondary">@color/black</item>
|
||||
<!-- Status bar color. -->
|
||||
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
|
||||
<!-- Customize your theme here. -->
|
||||
</style>
|
||||
</resources>
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="purple_200">#FFBB86FC</color>
|
||||
<color name="purple_500">#FF6200EE</color>
|
||||
<color name="purple_700">#FF3700B3</color>
|
||||
<color name="teal_200">#FF03DAC5</color>
|
||||
<color name="teal_700">#FF018786</color>
|
||||
<color name="black">#FF000000</color>
|
||||
<color name="white">#FFFFFFFF</color>
|
||||
</resources>
|
||||
@@ -0,0 +1,7 @@
|
||||
<resources>
|
||||
<string name="app_name">项目模块</string>
|
||||
<string name="new_fresh">新</string>
|
||||
<string name="top_tip">置顶</string>
|
||||
<string name="collect_success">收藏成功</string>
|
||||
<string name="cancel_collect_success">已取消收藏</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,16 @@
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<!-- Base application theme. -->
|
||||
<style name="Theme.WanAndroid" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
|
||||
<!-- Primary brand color. -->
|
||||
<item name="colorPrimary">@color/purple_500</item>
|
||||
<item name="colorPrimaryVariant">@color/purple_700</item>
|
||||
<item name="colorOnPrimary">@color/white</item>
|
||||
<!-- Secondary brand color. -->
|
||||
<item name="colorSecondary">@color/teal_200</item>
|
||||
<item name="colorSecondaryVariant">@color/teal_700</item>
|
||||
<item name="colorOnSecondary">@color/black</item>
|
||||
<!-- Status bar color. -->
|
||||
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
|
||||
<!-- Customize your theme here. -->
|
||||
</style>
|
||||
</resources>
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
package com.bbgo.module_project
|
||||
|
||||
import org.junit.Test
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Example local unit test, which will execute on the development machine (host).
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
class ExampleUnitTest {
|
||||
@Test
|
||||
fun addition_isCorrect() {
|
||||
assertEquals(4, 2 + 2)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user