a
This commit is contained in:
@@ -0,0 +1 @@
|
||||
/build
|
||||
@@ -0,0 +1,141 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'kotlin-android'
|
||||
id "kotlin-kapt"
|
||||
// Parcelable 实现生成器
|
||||
id "kotlin-parcelize"
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdk compile_sdk_version
|
||||
|
||||
defaultConfig {
|
||||
applicationId application_id
|
||||
minSdk min_sdk_version
|
||||
targetSdk target_sdk_version
|
||||
versionCode version_code
|
||||
versionName version_name
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
vectorDrawables {
|
||||
useSupportLibrary true
|
||||
}
|
||||
|
||||
//只打包默认资源与英文资源
|
||||
resConfigs "en"
|
||||
|
||||
ndk {
|
||||
//只适配armeabi-v7a 架构
|
||||
abiFilters "armeabi-v7a"
|
||||
}
|
||||
|
||||
//room
|
||||
javaCompileOptions {
|
||||
annotationProcessorOptions {
|
||||
arguments += [
|
||||
//配置并启用将数据库架构导出到给定目录中的json文件的功能
|
||||
"room.schemaLocation" : "$projectDir/schemas".toString(),
|
||||
//启用gradle增量注解处理器
|
||||
"room.incremental" : "true",
|
||||
//配置room以重写查询,使其顶部星形投影在展开后包含DAO方法返回类型中定义的列
|
||||
"room.expandProjection": "true"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = java_version
|
||||
useIR = true
|
||||
//NetRspKtx 使用了实验特性
|
||||
freeCompilerArgs += ["-Xopt-in=kotlin.RequiresOptIn"]
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
compose true
|
||||
}
|
||||
|
||||
signingConfigs {
|
||||
debug {
|
||||
storeFile file('../linxdebug.keystore')
|
||||
storePassword 'linxiang'
|
||||
keyAlias 'jiaru'
|
||||
keyPassword 'linxiang'
|
||||
}
|
||||
release {
|
||||
storeFile file('../linx.keystore')
|
||||
storePassword 'linxxx'
|
||||
keyAlias 'jiaru'
|
||||
keyPassword 'linxxx'
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
debug {
|
||||
minifyEnabled true
|
||||
shrinkResources true
|
||||
zipAlignEnabled true
|
||||
signingConfig signingConfigs.debug
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
|
||||
release {
|
||||
//开启混淆
|
||||
minifyEnabled true
|
||||
//去除无用资源
|
||||
shrinkResources true
|
||||
zipAlignEnabled true
|
||||
signingConfig signingConfigs.release
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
|
||||
composeOptions {
|
||||
kotlinCompilerExtensionVersion compose_version
|
||||
kotlinCompilerVersion kotlin_version
|
||||
}
|
||||
|
||||
applicationVariants.all { variant ->
|
||||
variant.outputs.all {
|
||||
outputFileName = "${applicationId}_${getBuildDate()}_${variant.versionName}.apk"
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility java_version
|
||||
targetCompatibility java_version
|
||||
}
|
||||
|
||||
packagingOptions {
|
||||
resources {
|
||||
excludes += '/META-INF/{AL2.0,LGPL2.1}'
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取编译日期
|
||||
*/
|
||||
static String getBuildDate() {
|
||||
Date date = new Date()
|
||||
String dateStr = date.format("yyyy-MM-dd")
|
||||
return dateStr
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api project(path: ':common')
|
||||
|
||||
// 内存泄露检测
|
||||
debugImplementation "com.squareup.leakcanary:leakcanary-android:$leakcanary_version"
|
||||
|
||||
testImplementation 'junit:junit:4.+'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
|
||||
androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version"
|
||||
debugImplementation "androidx.compose.ui:ui-tooling:$compose_version"
|
||||
|
||||
//room
|
||||
testImplementation "androidx.room:room-testing:$room_version"
|
||||
kapt "androidx.room:room-compiler:$room_version"
|
||||
|
||||
}
|
||||
Vendored
+38
@@ -0,0 +1,38 @@
|
||||
# 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
|
||||
|
||||
#androidx
|
||||
-keep class com.google.android.material.** {*;}
|
||||
-keep class androidx.** {*;}
|
||||
-keep public class * extends androidx.**
|
||||
-keep interface androidx.** {*;}
|
||||
-dontwarn com.google.android.material.**
|
||||
-dontnote com.google.android.material.**
|
||||
-dontwarn androidx.**
|
||||
|
||||
#OkHttp3
|
||||
-keep class okhttp3.** { *; }
|
||||
-keep interface okhttp3.** { *; }
|
||||
-dontwarn okhttp3.**
|
||||
|
||||
-keep class okio.** { *; }
|
||||
-dontwarn okio.**
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.linx.playAndroid
|
||||
|
||||
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.linx.wanandroid", appContext.packageName)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="com.linx.playAndroid">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
||||
<application
|
||||
android:allowBackup="false"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="false"
|
||||
android:theme="@style/Theme.WanAndroid">
|
||||
|
||||
<activity
|
||||
android:name="com.linx.playAndroid.MainActivity"
|
||||
android:exported="true"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/Theme.WanAndroid.NoActionBar">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<!-- StartUp配置 -->
|
||||
<provider
|
||||
android:name="androidx.startup.InitializationProvider"
|
||||
android:authorities="${applicationId}.androidx-startup"
|
||||
android:exported="false"
|
||||
tools:node="merge">
|
||||
<!-- 初始化Retrofit -->
|
||||
<meta-data
|
||||
android:name="com.linx.playAndroid.initializer.NetWorkInitializer"
|
||||
android:value="androidx.startup" />
|
||||
|
||||
<!--初始化mmkv-->
|
||||
<meta-data
|
||||
android:name="com.linx.playAndroid.initializer.MmkvIniaializer"
|
||||
android:value="androidx.startup" />
|
||||
|
||||
<!--初始化工具类-->
|
||||
<meta-data
|
||||
android:name="com.linx.playAndroid.initializer.WidgetInitializer"
|
||||
android:value="androidx.startup" />
|
||||
|
||||
</provider>
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.linx.playAndroid
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.KeyEvent
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.ui.graphics.toArgb
|
||||
import androidx.core.view.WindowCompat
|
||||
import coil.annotation.ExperimentalCoilApi
|
||||
import com.google.accompanist.insets.ProvideWindowInsets
|
||||
import com.google.accompanist.pager.ExperimentalPagerApi
|
||||
import com.linx.common.baseData.themeTypeState
|
||||
import com.linx.playAndroid.ui.theme.CustomThemeManager
|
||||
import com.linx.common.widget.TwoBackFinish
|
||||
import com.linx.playAndroid.composable.MainCompose
|
||||
|
||||
class MainActivity : ComponentActivity() {
|
||||
|
||||
@ExperimentalCoilApi
|
||||
@ExperimentalPagerApi
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContent {
|
||||
|
||||
val themeState = themeTypeState.value
|
||||
|
||||
//设置为沉浸式状态栏
|
||||
WindowCompat.setDecorFitsSystemWindows(window, false)
|
||||
|
||||
//主题包裹
|
||||
CustomThemeManager.WanAndroidTheme(themeState) {
|
||||
//可以获取状态栏高度
|
||||
ProvideWindowInsets {
|
||||
//主界面
|
||||
MainCompose(onFinish = { finish() })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,273 @@
|
||||
package com.linx.playAndroid
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.compose.navArgument
|
||||
import androidx.navigation.navigation
|
||||
import coil.annotation.ExperimentalCoilApi
|
||||
import com.google.accompanist.pager.ExperimentalPagerApi
|
||||
import com.linx.common.baseData.Nav
|
||||
import com.linx.common.widget.TwoBackFinish
|
||||
import com.linx.playAndroid.composable.*
|
||||
import com.linx.playAndroid.widget.StatsBarUtil
|
||||
|
||||
/**
|
||||
* 内容 导航
|
||||
*/
|
||||
@ExperimentalCoilApi
|
||||
@ExperimentalPagerApi
|
||||
@Composable
|
||||
fun NavigationHost(
|
||||
navHostController: NavHostController,
|
||||
paddingValues: PaddingValues = PaddingValues(0.dp),
|
||||
onFinish: () -> Unit
|
||||
) {
|
||||
|
||||
val context = LocalContext.current
|
||||
|
||||
NavHost(
|
||||
navHostController,
|
||||
startDestination = KeyNavigationRoute.MAIN.route,
|
||||
//todo 只有主界面需要这个padding
|
||||
modifier = Modifier.padding(paddingValues),
|
||||
builder = {
|
||||
|
||||
//主页面
|
||||
navigation(
|
||||
route = KeyNavigationRoute.MAIN.route,
|
||||
startDestination = Nav.BottomNavScreen.HomeScreen.route
|
||||
) {
|
||||
//首页
|
||||
composable(Nav.BottomNavScreen.HomeScreen.route) {
|
||||
|
||||
//系统颜色的状态栏
|
||||
StatsBarUtil().StatsBarColor(false)
|
||||
|
||||
HomeCompose(navHostController)
|
||||
|
||||
//点击两次返回才关闭app
|
||||
BackHandler {
|
||||
TwoBackFinish().execute(context, onFinish)
|
||||
}
|
||||
}
|
||||
//项目页面
|
||||
composable(Nav.BottomNavScreen.ProjectScreen.route) {
|
||||
|
||||
//系统颜色的状态栏
|
||||
StatsBarUtil().StatsBarColor(false)
|
||||
|
||||
ProjectCompose(navHostController)
|
||||
|
||||
//点击两次返回才关闭app
|
||||
BackHandler {
|
||||
TwoBackFinish().execute(context, onFinish)
|
||||
}
|
||||
}
|
||||
//广场页面
|
||||
composable(Nav.BottomNavScreen.SquareScreen.route) {
|
||||
|
||||
//系统颜色的状态栏
|
||||
StatsBarUtil().StatsBarColor(false)
|
||||
|
||||
SquareCompose(navHostController)
|
||||
|
||||
//点击两次返回才关闭app
|
||||
BackHandler {
|
||||
TwoBackFinish().execute(context, onFinish)
|
||||
}
|
||||
}
|
||||
//公众号页面
|
||||
composable(Nav.BottomNavScreen.PublicNumScreen.route) {
|
||||
|
||||
//系统颜色的状态栏
|
||||
StatsBarUtil().StatsBarColor(false)
|
||||
|
||||
PublicNumCompose(navHostController)
|
||||
|
||||
//点击两次返回才关闭app
|
||||
BackHandler {
|
||||
TwoBackFinish().execute(context, onFinish)
|
||||
}
|
||||
}
|
||||
//学习页面
|
||||
composable(Nav.BottomNavScreen.LearnScreen.route) {
|
||||
//系统颜色的状态栏
|
||||
StatsBarUtil().StatsBarColor(false)
|
||||
|
||||
LearnCompose(navHostController)
|
||||
|
||||
//点击两次返回才关闭app
|
||||
BackHandler {
|
||||
TwoBackFinish().execute(context, onFinish)
|
||||
}
|
||||
}
|
||||
//我的页面
|
||||
composable(Nav.BottomNavScreen.MineScreen.route) {
|
||||
|
||||
//系统颜色的状态栏
|
||||
StatsBarUtil().StatsBarColor(false)
|
||||
|
||||
MineCompose(navHostController)
|
||||
|
||||
//点击两次返回才关闭app
|
||||
BackHandler {
|
||||
TwoBackFinish().execute(context, onFinish)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//登录页面
|
||||
composable(route = KeyNavigationRoute.LOGIN.route) {
|
||||
//透明/沉浸式状态栏
|
||||
StatsBarUtil().StatsBarColor(true)
|
||||
|
||||
LoginCompose(navHostController)
|
||||
|
||||
BackHandler { navHostController.navigateUp() }
|
||||
}
|
||||
|
||||
//注册页面
|
||||
composable(route = KeyNavigationRoute.REGISTER.route) {
|
||||
//透明/沉浸式状态栏
|
||||
StatsBarUtil().StatsBarColor(true)
|
||||
|
||||
RegisterCompose(navHostController)
|
||||
|
||||
BackHandler { navHostController.navigateUp() }
|
||||
}
|
||||
|
||||
//积分排行页面
|
||||
composable(route = KeyNavigationRoute.INTEGRAL_RANK.route) {
|
||||
//系统颜色的状态栏
|
||||
StatsBarUtil().StatsBarColor(false)
|
||||
|
||||
IntegralRankCompose(navHostController)
|
||||
|
||||
BackHandler { navHostController.navigateUp() }
|
||||
}
|
||||
|
||||
//我的收藏页面
|
||||
composable(route = KeyNavigationRoute.MY_COLLECT.route) {
|
||||
//系统颜色的状态栏
|
||||
StatsBarUtil().StatsBarColor(false)
|
||||
|
||||
MyCollectCompose(navHostController)
|
||||
|
||||
BackHandler { navHostController.navigateUp() }
|
||||
}
|
||||
|
||||
//我分享的文章页面
|
||||
composable(route = KeyNavigationRoute.MY_SHARE_ARTICLES.route) {
|
||||
//系统颜色的状态栏
|
||||
StatsBarUtil().StatsBarColor(false)
|
||||
|
||||
MyShareArticlesCompose(navHostController)
|
||||
|
||||
BackHandler { navHostController.navigateUp() }
|
||||
}
|
||||
|
||||
//设置页面
|
||||
composable(route = KeyNavigationRoute.SETTING.route) {
|
||||
//系统颜色的状态栏
|
||||
StatsBarUtil().StatsBarColor(false)
|
||||
|
||||
SettingCompose(navHostController)
|
||||
|
||||
BackHandler { navHostController.navigateUp() }
|
||||
}
|
||||
|
||||
//H5页面
|
||||
composable(
|
||||
route = "${KeyNavigationRoute.WEBVIEW.route}?url={url}", arguments = listOf(
|
||||
navArgument("url") { defaultValue = "https://www.wanandroid.com/" })
|
||||
) { backStackEntry ->
|
||||
|
||||
//系统颜色的状态栏
|
||||
StatsBarUtil().StatsBarColor(false)
|
||||
|
||||
WebViewCompose(
|
||||
navHostController,
|
||||
backStackEntry.arguments?.getString("url") ?: "https://www.wanandroid.com"
|
||||
)
|
||||
|
||||
BackHandler { navHostController.navigateUp() }
|
||||
}
|
||||
|
||||
//搜索页面
|
||||
composable(route = KeyNavigationRoute.SEARCH.route) {
|
||||
//系统颜色的状态栏
|
||||
StatsBarUtil().StatsBarColor(false)
|
||||
|
||||
SearchCompose(navHostController)
|
||||
|
||||
BackHandler { navHostController.navigateUp() }
|
||||
}
|
||||
|
||||
//学习 - 动画页面
|
||||
composable(KeyNavigationRoute.LEARN_ANIMATION.route) {
|
||||
//系统颜色的状态栏
|
||||
StatsBarUtil().StatsBarColor(false)
|
||||
|
||||
AnimationCompose(navHostController = navHostController)
|
||||
|
||||
BackHandler { navHostController.navigateUp() }
|
||||
}
|
||||
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 页面跳转关键类
|
||||
*/
|
||||
enum class KeyNavigationRoute(
|
||||
val route: String
|
||||
) {
|
||||
//主页面
|
||||
MAIN("main"),
|
||||
|
||||
//登录页面
|
||||
LOGIN("login"),
|
||||
|
||||
//注册页面
|
||||
REGISTER("register"),
|
||||
|
||||
//积分排行
|
||||
INTEGRAL_RANK("integral_rank"),
|
||||
|
||||
//我的收藏
|
||||
MY_COLLECT("my_collect"),
|
||||
|
||||
//我的文章
|
||||
MY_SHARE_ARTICLES("my_share_articles"),
|
||||
|
||||
//设置
|
||||
SETTING("setting"),
|
||||
|
||||
//H5
|
||||
WEBVIEW("webview"),
|
||||
|
||||
//搜索
|
||||
SEARCH("search"),
|
||||
|
||||
//学习 - 动画
|
||||
LEARN_ANIMATION("learn_animation")
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,719 @@
|
||||
package com.linx.playAndroid.composable
|
||||
|
||||
import android.util.Log
|
||||
import androidx.compose.animation.*
|
||||
import androidx.compose.animation.core.*
|
||||
import androidx.compose.foundation.Canvas
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.PlayArrow
|
||||
import androidx.compose.material.icons.filled.Star
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.draw.rotate
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.geometry.Size
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.drawscope.Stroke
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.IntOffset
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.navigation.NavHostController
|
||||
import com.linx.common.baseData.themeTypeState
|
||||
import com.linx.playAndroid.R
|
||||
import com.linx.playAndroid.public.BaseScreen
|
||||
import com.linx.playAndroid.public.ColorPicker
|
||||
import com.linx.playAndroid.public.SubtitleText
|
||||
import com.linx.playAndroid.ui.theme.*
|
||||
import com.linx.playAndroid.ui.theme.CustomThemeManager.getThemeColor
|
||||
|
||||
@Composable
|
||||
fun AnimationCompose(navHostController: NavHostController) {
|
||||
BaseScreen {
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBar(title = {
|
||||
Text(text = "Animation")
|
||||
}, navigationIcon = {
|
||||
IconButton(onClick = {
|
||||
navHostController.navigateUp()
|
||||
}) {
|
||||
Icon(imageVector = Icons.Default.ArrowBack, contentDescription = null)
|
||||
}
|
||||
}, backgroundColor = getThemeColor(themeType = themeTypeState.value).primary)
|
||||
},
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
LazyColumn(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
item {
|
||||
AnimationScreen()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AnimationScreen() {
|
||||
SubtitleText(subtitle = "点击改变颜色 - animateColorAsState")
|
||||
SimpleColorStateAnimation()
|
||||
Divider()
|
||||
|
||||
SubtitleText(subtitle = "点击改变Button大小 - animateDpAsState")
|
||||
SimpleDpStateAnimation()
|
||||
Divider()
|
||||
|
||||
SubtitleText(subtitle = "点击改变透明度 - animateFloatAsState")
|
||||
SimpleFloatStateAnimation()
|
||||
Divider()
|
||||
|
||||
SubtitleText(subtitle = "点击改变控件位置 - animateOffsetAsState")
|
||||
SimpleOffsetStateAnimation()
|
||||
Divider()
|
||||
|
||||
SubtitleText(subtitle = "自定义动画")
|
||||
SimpleAnimateCustomState()
|
||||
Divider()
|
||||
|
||||
SubtitleText(subtitle = "动画实战 - 图片展示")
|
||||
DrawLayerWithAnimation()
|
||||
Divider()
|
||||
|
||||
SubtitleText(subtitle = "实验性动画 - AnimatedVisibility")
|
||||
AnimatedVisibilityAnimated()
|
||||
Divider()
|
||||
|
||||
SubtitleText(subtitle = "改变多状态动画")
|
||||
MultiStateColorPositionAnimation()
|
||||
Divider()
|
||||
|
||||
SubtitleText(subtitle = "改变多状态动画 - 无限 rememberInfiniteTransition")
|
||||
MultiStateInfiniteTransition()
|
||||
Divider()
|
||||
|
||||
SubtitleText(subtitle = "Canvas绘制圆 无限动画 - 放大缩小")
|
||||
MultiStateAnimateCircleFilledCanvas()
|
||||
Divider()
|
||||
|
||||
SubtitleText(subtitle = "Canvas绘制圆环 无限动画 - 转圈")
|
||||
MultiStateAnimationCircleStrokeCanvas()
|
||||
Divider()
|
||||
|
||||
SubtitleText(subtitle = "颜色选择器")
|
||||
Spacer(modifier = Modifier.size(20.dp))
|
||||
ColorPicker { color: Color ->
|
||||
Log.i("xxx", "选择的颜色是 $color")
|
||||
}
|
||||
Spacer(modifier = Modifier.size(20.dp))
|
||||
Divider()
|
||||
}
|
||||
|
||||
/**
|
||||
* 点击改变颜色
|
||||
*/
|
||||
@Composable
|
||||
private fun SimpleColorStateAnimation() {
|
||||
|
||||
var enabled by remember {
|
||||
mutableStateOf(true)
|
||||
}
|
||||
|
||||
val themeColor = getThemeColor(themeTypeState.value)
|
||||
|
||||
val animatedColor =
|
||||
animateColorAsState(targetValue = if (enabled) themeColor.primary else c_b66731)
|
||||
|
||||
val buttonColors = ButtonDefaults.buttonColors(
|
||||
backgroundColor = animatedColor.value
|
||||
)
|
||||
|
||||
Button(
|
||||
onClick = { enabled = !enabled },
|
||||
colors = buttonColors,
|
||||
modifier = Modifier
|
||||
.padding(16.dp)
|
||||
.fillMaxWidth()
|
||||
) {
|
||||
Text(text = "点击改变颜色")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 点击改变大小
|
||||
*/
|
||||
@Composable
|
||||
private fun SimpleDpStateAnimation() {
|
||||
|
||||
var enabled by remember {
|
||||
mutableStateOf(true)
|
||||
}
|
||||
|
||||
val animaHeight = animateDpAsState(targetValue = if (enabled) 40.dp else 60.dp)
|
||||
val animaWidth = animateDpAsState(targetValue = if (enabled) 150.dp else 300.dp)
|
||||
|
||||
Button(
|
||||
onClick = { enabled = !enabled },
|
||||
modifier = Modifier
|
||||
.padding(16.dp)
|
||||
.height(animaHeight.value)
|
||||
.width(animaWidth.value)
|
||||
) {
|
||||
Text(text = "点击改变大小")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 点击改变透明度
|
||||
*/
|
||||
@Composable
|
||||
private fun SimpleFloatStateAnimation() {
|
||||
|
||||
var enable by remember {
|
||||
mutableStateOf(true)
|
||||
}
|
||||
|
||||
val animaFloat = animateFloatAsState(targetValue = if (enable) 1f else 0.2f)
|
||||
|
||||
Button(
|
||||
onClick = { enable = !enable },
|
||||
modifier = Modifier
|
||||
.padding(16.dp)
|
||||
.alpha(animaFloat.value)
|
||||
) {
|
||||
Text(text = "点击改变透明度")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 点击改变控件位置
|
||||
*/
|
||||
@Composable
|
||||
private fun SimpleOffsetStateAnimation() {
|
||||
|
||||
var enable by remember {
|
||||
mutableStateOf(true)
|
||||
}
|
||||
|
||||
val animaOffset by
|
||||
animateOffsetAsState(targetValue = if (enable) Offset(0f, 0f) else Offset(50f, 40f))
|
||||
|
||||
Row {
|
||||
Image(
|
||||
painter = painterResource(id = R.mipmap.ic_shuoshuo_1),
|
||||
contentScale = ContentScale.FillBounds,
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.size(100.dp)
|
||||
.padding(16.dp)
|
||||
.offset(x = Dp(animaOffset.x), y = Dp(animaOffset.y))
|
||||
.clickable { enable = !enable })
|
||||
Image(
|
||||
painter = painterResource(id = R.mipmap.ic_shuoshuo_2),
|
||||
contentScale = ContentScale.FillBounds,
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.size(100.dp)
|
||||
.padding(16.dp)
|
||||
.offset(x = -Dp(animaOffset.x), y = -Dp(animaOffset.y))
|
||||
.clickable { enable = !enable })
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
data class CustomAnimationState(val width: Dp, val height: Dp, val rotation: Float)
|
||||
|
||||
/**
|
||||
* 自定义动画
|
||||
*/
|
||||
@Composable
|
||||
private fun SimpleAnimateCustomState() {
|
||||
|
||||
var enabled by remember {
|
||||
mutableStateOf(true)
|
||||
}
|
||||
|
||||
val initUiState = CustomAnimationState(200.dp, 40.dp, 0f)
|
||||
val targetUiState = CustomAnimationState(300.dp, 60.dp, 15f)
|
||||
|
||||
val uiState = if (enabled) initUiState else targetUiState
|
||||
|
||||
val animatedUiState by animateValueAsState(
|
||||
targetValue = uiState,
|
||||
typeConverter = TwoWayConverter(convertToVector = {
|
||||
AnimationVector3D(it.width.value, it.height.value, it.rotation)
|
||||
}, convertFromVector = {
|
||||
CustomAnimationState(it.v1.dp, it.v2.dp, it.v3)
|
||||
}),
|
||||
animationSpec = tween(600)
|
||||
)
|
||||
|
||||
Button(
|
||||
onClick = { enabled = !enabled },
|
||||
modifier = Modifier
|
||||
.padding(16.dp)
|
||||
.width(animatedUiState.width)
|
||||
.height(animatedUiState.height)
|
||||
.rotate(animatedUiState.rotation)
|
||||
) {
|
||||
Text(text = "自定义的动画")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 动画实战 - 用于图片
|
||||
*/
|
||||
@Composable
|
||||
private fun DrawLayerWithAnimation() {
|
||||
|
||||
var draw2 by remember {
|
||||
mutableStateOf(false)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.padding(10.dp))
|
||||
|
||||
Box {
|
||||
Image(
|
||||
painter = painterResource(id = R.mipmap.ic_shuoshuo_1),
|
||||
contentScale = ContentScale.FillBounds,
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.size(100.dp)
|
||||
.graphicsLayer(
|
||||
translationY = animateFloatAsState(targetValue = if (draw2) 0f else -20f).value,
|
||||
//横坐标
|
||||
translationX = animateFloatAsState(targetValue = if (draw2) 300.dp.value else 0f).value,
|
||||
//阴影高度
|
||||
shadowElevation = animateFloatAsState(targetValue = if (draw2) 30f else 5f).value
|
||||
)
|
||||
.clickable {
|
||||
draw2 = !draw2
|
||||
}
|
||||
)
|
||||
|
||||
Image(
|
||||
painter = painterResource(id = R.mipmap.ic_shuoshuo_2),
|
||||
contentScale = ContentScale.FillBounds,
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.size(100.dp)
|
||||
.graphicsLayer(
|
||||
translationY = 0f,
|
||||
shadowElevation = animateFloatAsState(targetValue = if (draw2) 20f else 5f).value,
|
||||
translationX = 0f
|
||||
)
|
||||
.clickable { draw2 = !draw2 }
|
||||
)
|
||||
|
||||
Image(
|
||||
painter = painterResource(id = R.mipmap.ic_shuoshuo_3),
|
||||
contentScale = ContentScale.FillBounds,
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.size(100.dp)
|
||||
.graphicsLayer(
|
||||
translationY = animateFloatAsState(targetValue = if (draw2) 0f else 20f).value,
|
||||
//横坐标
|
||||
translationX = animateFloatAsState(targetValue = if (draw2) -300.dp.value else 0f).value,
|
||||
//阴影高度
|
||||
shadowElevation = animateFloatAsState(targetValue = if (draw2) 10f else 5f).value
|
||||
)
|
||||
.clickable {
|
||||
draw2 = !draw2
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.padding(20.dp))
|
||||
|
||||
Box {
|
||||
Image(
|
||||
painter = painterResource(id = R.mipmap.ic_shuoshuo_1),
|
||||
contentScale = ContentScale.FillBounds,
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.size(100.dp)
|
||||
.graphicsLayer(
|
||||
translationY = animateFloatAsState(targetValue = if (draw2) 0f else -20f).value,
|
||||
//横坐标
|
||||
translationX = animateFloatAsState(targetValue = if (draw2) 300.dp.value else 0f).value,
|
||||
//阴影高度
|
||||
shadowElevation = animateFloatAsState(targetValue = if (draw2) 30f else 3f).value,
|
||||
rotationY = animateFloatAsState(targetValue = if (draw2) 45f else 0f).value
|
||||
)
|
||||
.clickable {
|
||||
draw2 = !draw2
|
||||
}
|
||||
)
|
||||
|
||||
Image(
|
||||
painter = painterResource(id = R.mipmap.ic_shuoshuo_2),
|
||||
contentScale = ContentScale.FillBounds,
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.size(100.dp)
|
||||
.graphicsLayer(
|
||||
translationY = 0f,
|
||||
shadowElevation = animateFloatAsState(targetValue = if (draw2) 20f else 5f).value,
|
||||
translationX = 0f,
|
||||
rotationY = animateFloatAsState(targetValue = if (draw2) 45f else 0f).value
|
||||
)
|
||||
.clickable { draw2 = !draw2 }
|
||||
)
|
||||
|
||||
Image(
|
||||
painter = painterResource(id = R.mipmap.ic_shuoshuo_3),
|
||||
contentScale = ContentScale.FillBounds,
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.size(100.dp)
|
||||
.graphicsLayer(
|
||||
translationY = animateFloatAsState(targetValue = if (draw2) 0f else 20f).value,
|
||||
//横坐标
|
||||
translationX = animateFloatAsState(targetValue = if (draw2) -300.dp.value else 0f).value,
|
||||
//阴影高度
|
||||
shadowElevation = animateFloatAsState(targetValue = if (draw2) 10f else 5f).value,
|
||||
rotationY = animateFloatAsState(targetValue = if (draw2) 45f else 0f).value
|
||||
)
|
||||
.clickable {
|
||||
draw2 = !draw2
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.padding(20.dp))
|
||||
|
||||
Box {
|
||||
Image(
|
||||
painter = painterResource(id = R.mipmap.ic_shuoshuo_1),
|
||||
contentScale = ContentScale.FillBounds,
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.size(100.dp)
|
||||
.graphicsLayer(
|
||||
translationY = animateFloatAsState(targetValue = if (draw2) 0f else -20f).value,
|
||||
//横坐标
|
||||
translationX = animateFloatAsState(targetValue = if (draw2) 300.dp.value else 0f).value,
|
||||
//阴影高度
|
||||
shadowElevation = animateFloatAsState(targetValue = if (draw2) 30f else 5f).value,
|
||||
rotationZ = animateFloatAsState(targetValue = if (draw2) 45f else 0f).value
|
||||
)
|
||||
.clickable {
|
||||
draw2 = !draw2
|
||||
}
|
||||
)
|
||||
|
||||
Image(
|
||||
painter = painterResource(id = R.mipmap.ic_shuoshuo_2),
|
||||
contentScale = ContentScale.FillBounds,
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.size(100.dp)
|
||||
.graphicsLayer(
|
||||
translationY = 0f,
|
||||
shadowElevation = animateFloatAsState(targetValue = if (draw2) 20f else 5f).value,
|
||||
translationX = 0f,
|
||||
rotationZ = animateFloatAsState(targetValue = if (draw2) 45f else 0f).value
|
||||
)
|
||||
.clickable { draw2 = !draw2 }
|
||||
)
|
||||
|
||||
Image(
|
||||
painter = painterResource(id = R.mipmap.ic_shuoshuo_3),
|
||||
contentScale = ContentScale.FillBounds,
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.size(100.dp)
|
||||
.graphicsLayer(
|
||||
translationY = animateFloatAsState(targetValue = if (draw2) 0f else 20f).value,
|
||||
//横坐标
|
||||
translationX = animateFloatAsState(targetValue = if (draw2) -300.dp.value else 0f).value,
|
||||
//阴影高度
|
||||
shadowElevation = animateFloatAsState(targetValue = if (draw2) 10f else 5f).value,
|
||||
rotationZ = animateFloatAsState(targetValue = if (draw2) 45f else 0f).value
|
||||
)
|
||||
.clickable {
|
||||
draw2 = !draw2
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.padding(20.dp))
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 实验性动画 - AnimatedVisibility
|
||||
*/
|
||||
@OptIn(ExperimentalAnimationApi::class)
|
||||
@Composable
|
||||
private fun AnimatedVisibilityAnimated() {
|
||||
|
||||
var expanded by remember {
|
||||
mutableStateOf(true)
|
||||
}
|
||||
|
||||
//收藏按钮
|
||||
FloatingActionButton(onClick = { expanded = !expanded }, modifier = Modifier.padding(16.dp)) {
|
||||
Row(modifier = Modifier.padding(start = 16.dp, end = 16.dp)) {
|
||||
Icon(imageVector = Icons.Default.Star, contentDescription = null)
|
||||
AnimatedVisibility(visible = expanded) {
|
||||
Text(text = "收藏", modifier = Modifier.padding(start = 8.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.size(20.dp))
|
||||
|
||||
//向下移动隐藏/向上移动显示
|
||||
Row(modifier = Modifier
|
||||
.background(Default200)
|
||||
.width(200.dp)
|
||||
.height(60.dp)
|
||||
.clickable { expanded = !expanded }) {
|
||||
AnimatedVisibility(
|
||||
visible = expanded,
|
||||
//进入
|
||||
enter = slideIn(
|
||||
{
|
||||
IntOffset(0, 60.dp.value.toInt())
|
||||
},
|
||||
tween(
|
||||
easing = LinearOutSlowInEasing,
|
||||
durationMillis = 500
|
||||
)
|
||||
),
|
||||
//退出
|
||||
exit = slideOut({
|
||||
IntOffset(0, 60.dp.value.toInt())
|
||||
}, tween(easing = FastOutSlowInEasing, durationMillis = 500))
|
||||
) {
|
||||
Text(text = "slideIn/ slideOut", fontSize = 12.sp)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.size(20.dp))
|
||||
|
||||
//左右隐藏/显示
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.background(Default200)
|
||||
.width(200.dp)
|
||||
.height(60.dp)
|
||||
.clickable { expanded = !expanded },
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
AnimatedVisibility(
|
||||
visible = expanded,
|
||||
enter = expandIn(expandFrom = Alignment.CenterStart),
|
||||
exit = shrinkOut(shrinkTowards = Alignment.CenterEnd)
|
||||
) {
|
||||
Text(text = "expandIn/ shrinkOut", fontSize = 12.sp)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.size(20.dp))
|
||||
|
||||
//动态增加数量
|
||||
var count by remember {
|
||||
mutableStateOf(1)
|
||||
}
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.animateContentSize()
|
||||
.clickable {
|
||||
if (count < 10) count += 3 else count = 1
|
||||
}
|
||||
) {
|
||||
(0..count).forEach { _ ->
|
||||
Icon(imageVector = Icons.Default.PlayArrow, contentDescription = null)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.size(20.dp))
|
||||
|
||||
}
|
||||
|
||||
enum class MyAnimationState {
|
||||
START, MID, END
|
||||
}
|
||||
|
||||
/**
|
||||
* 改变多状态动画
|
||||
*/
|
||||
@Composable
|
||||
private fun MultiStateColorPositionAnimation() {
|
||||
|
||||
Spacer(modifier = Modifier.size(100.dp))
|
||||
|
||||
var animationState by remember {
|
||||
mutableStateOf(MyAnimationState.START)
|
||||
}
|
||||
|
||||
val transition = updateTransition(targetState = animationState, label = "transition")
|
||||
|
||||
val animatedColor by transition.animateColor(
|
||||
transitionSpec = { tween(500) },
|
||||
label = "animatedColor"
|
||||
) { state ->
|
||||
when (state) {
|
||||
MyAnimationState.START -> Default200
|
||||
MyAnimationState.MID -> Theme1_200
|
||||
MyAnimationState.END -> Theme2_200
|
||||
}
|
||||
}
|
||||
|
||||
val position by transition.animateDp(
|
||||
label = "position",
|
||||
transitionSpec = { tween(500) }) { state ->
|
||||
when (state) {
|
||||
MyAnimationState.START -> 0.dp
|
||||
MyAnimationState.MID -> 80.dp
|
||||
MyAnimationState.END -> (-80).dp
|
||||
}
|
||||
}
|
||||
|
||||
FloatingActionButton(onClick = {
|
||||
animationState = when (animationState) {
|
||||
MyAnimationState.START -> MyAnimationState.MID
|
||||
MyAnimationState.MID -> MyAnimationState.END
|
||||
MyAnimationState.END -> MyAnimationState.START
|
||||
}
|
||||
}, modifier = Modifier.offset(x = position, y = position), backgroundColor = animatedColor) {
|
||||
Icon(imageVector = Icons.Default.PlayArrow, contentDescription = null)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.size(100.dp))
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 改变多状态动画 - 无限
|
||||
*/
|
||||
@Composable
|
||||
private fun MultiStateInfiniteTransition() {
|
||||
|
||||
//可以无限运行子动画
|
||||
val transition = rememberInfiniteTransition()
|
||||
|
||||
val animatedColor by transition.animateColor(
|
||||
initialValue = Default200,
|
||||
targetValue = Theme2_200,
|
||||
animationSpec = infiniteRepeatable(tween(800), RepeatMode.Reverse)
|
||||
)
|
||||
|
||||
val position by transition.animateFloat(
|
||||
initialValue = -80f,
|
||||
targetValue = 80f,
|
||||
animationSpec = infiniteRepeatable(tween(800), RepeatMode.Reverse)
|
||||
)
|
||||
|
||||
FloatingActionButton(
|
||||
modifier = Modifier.offset(x = position.dp),
|
||||
backgroundColor = animatedColor,
|
||||
onClick = {}
|
||||
) {
|
||||
Icon(imageVector = Icons.Default.PlayArrow, contentDescription = null)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Canvas绘制圆 无限动画 - 放大缩小
|
||||
*/
|
||||
@Composable
|
||||
private fun MultiStateAnimateCircleFilledCanvas(
|
||||
color: Color = Theme2_500,
|
||||
radiusEnd: Float = 200f
|
||||
) {
|
||||
|
||||
val transition = rememberInfiniteTransition()
|
||||
|
||||
val floatAnim by transition.animateFloat(
|
||||
initialValue = 10f,
|
||||
targetValue = radiusEnd,
|
||||
animationSpec = infiniteRepeatable(tween(1200), RepeatMode.Reverse)
|
||||
)
|
||||
|
||||
val centerOffset = Offset(0f, 10f)
|
||||
|
||||
Canvas(
|
||||
modifier = Modifier
|
||||
.padding(100f.dp)
|
||||
) {
|
||||
drawCircle(
|
||||
color = color.copy(alpha = 0.9f),
|
||||
center = centerOffset,
|
||||
radius = floatAnim
|
||||
)
|
||||
drawCircle(
|
||||
color = color.copy(alpha = 0.5f),
|
||||
center = centerOffset,
|
||||
radius = floatAnim / 2
|
||||
)
|
||||
drawCircle(
|
||||
color = color.copy(alpha = 0.2f),
|
||||
center = centerOffset,
|
||||
radius = floatAnim / 4
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Canvas绘制圆环 无限动画 - 转圈
|
||||
*/
|
||||
@Composable
|
||||
private fun MultiStateAnimationCircleStrokeCanvas() {
|
||||
|
||||
val transition = rememberInfiniteTransition()
|
||||
|
||||
val animatedFloat by transition.animateFloat(
|
||||
initialValue = 0f,
|
||||
targetValue = 360f,
|
||||
animationSpec = infiniteRepeatable(tween(800), repeatMode = RepeatMode.Restart)
|
||||
)
|
||||
|
||||
val stroke = Stroke(8f)
|
||||
val primary = getThemeColor(themeType = themeTypeState.value).primary
|
||||
|
||||
Canvas(modifier = Modifier
|
||||
.padding(16.dp)
|
||||
.size(100.dp)) {
|
||||
//宽度和高度的大小中较小的
|
||||
val diameter = size.minDimension
|
||||
val radius = diameter / 2
|
||||
val insideRadius = radius - stroke.width
|
||||
val topLeftOffset = Offset(0f, 10f)
|
||||
val size = Size(insideRadius * 2, insideRadius * 2)
|
||||
|
||||
drawArc(
|
||||
primary,
|
||||
//大小
|
||||
size = size,
|
||||
//是否画圆心
|
||||
useCenter = false,
|
||||
style = stroke,
|
||||
//长度
|
||||
sweepAngle = 160f,
|
||||
topLeft = topLeftOffset,
|
||||
//起始角度
|
||||
startAngle = animatedFloat
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package com.linx.playAndroid.composable
|
||||
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.paging.compose.collectAsLazyPagingItems
|
||||
import coil.annotation.ExperimentalCoilApi
|
||||
import com.google.accompanist.pager.ExperimentalPagerApi
|
||||
import com.linx.playAndroid.KeyNavigationRoute
|
||||
import com.linx.playAndroid.public.*
|
||||
import com.linx.playAndroid.viewModel.HomeViewModel
|
||||
|
||||
/**
|
||||
* 首页页面
|
||||
*/
|
||||
@ExperimentalCoilApi
|
||||
@ExperimentalPagerApi
|
||||
@Composable
|
||||
fun HomeCompose(navHostController: NavHostController) {
|
||||
|
||||
val homeViewModel: HomeViewModel = viewModel()
|
||||
|
||||
val homeListData = homeViewModel.homeListData.collectAsLazyPagingItems()
|
||||
|
||||
homeViewModel.getBannerData()
|
||||
|
||||
val bannerListData = homeViewModel.bannerListData.observeAsState()
|
||||
|
||||
//获取置顶数据列表
|
||||
homeViewModel.getArticleTopListData()
|
||||
|
||||
val articleTopData = homeViewModel.articleTopList.observeAsState()
|
||||
|
||||
//首页页面的内容
|
||||
SwipeRefreshContent(
|
||||
homeViewModel,
|
||||
homeListData,
|
||||
state = homeViewModel.homeLazyListState,
|
||||
itemContent = {
|
||||
item {
|
||||
//轮播图
|
||||
Banner(bannerListData.value) { link ->
|
||||
navHostController.navigate("${KeyNavigationRoute.WEBVIEW.route}?url=$link")
|
||||
}
|
||||
}
|
||||
|
||||
//置顶数据
|
||||
articleTopData.value?.let { listData ->
|
||||
items(listData) { data ->
|
||||
SimpleCard(cardHeight = 120.dp) {
|
||||
data.apply {
|
||||
HomeCardItemContent(
|
||||
getAuthor(author, shareUser),
|
||||
fresh,
|
||||
true,
|
||||
niceDate ?: "刚刚",
|
||||
title ?: "",
|
||||
superChapterName ?: "未知",
|
||||
collect
|
||||
) {
|
||||
navHostController.navigate("${KeyNavigationRoute.WEBVIEW.route}?url=$link")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}) { index, data ->
|
||||
data.apply {
|
||||
HomeCardItemContent(
|
||||
getAuthor(author, shareUser),
|
||||
fresh,
|
||||
false,
|
||||
niceDate ?: "刚刚",
|
||||
title ?: "",
|
||||
superChapterName ?: "未知",
|
||||
collect
|
||||
) {
|
||||
navHostController.navigate("${KeyNavigationRoute.WEBVIEW.route}?url=$link")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
package com.linx.playAndroid.composable
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Scaffold
|
||||
import androidx.compose.material.Surface
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowBack
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.paging.compose.LazyPagingItems
|
||||
import androidx.paging.compose.collectAsLazyPagingItems
|
||||
import com.google.accompanist.insets.navigationBarsPadding
|
||||
import com.linx.playAndroid.model.CoinRankData
|
||||
import com.linx.playAndroid.public.AppBar
|
||||
import com.linx.playAndroid.public.BaseScreen
|
||||
import com.linx.playAndroid.public.SwipeRefreshContent
|
||||
import com.linx.playAndroid.viewModel.IntegralRankViewModel
|
||||
|
||||
/**
|
||||
* 积分排行
|
||||
*/
|
||||
@Composable
|
||||
fun IntegralRankCompose(navHostController: NavHostController) {
|
||||
|
||||
val integralRankViewModel: IntegralRankViewModel = viewModel()
|
||||
|
||||
//积分排行数据
|
||||
val coinRankData = integralRankViewModel.coinRankListData.collectAsLazyPagingItems()
|
||||
|
||||
LaunchedEffect(coinRankData.itemCount) {
|
||||
if (!integralRankViewModel.isRequestUserInfoData && coinRankData.itemCount > 0) {
|
||||
//标记已经请求过个人积分数据
|
||||
integralRankViewModel.isRequestUserInfoData = true
|
||||
//获取个人积分数据
|
||||
integralRankViewModel.getUserInfoIntegral()
|
||||
}
|
||||
}
|
||||
|
||||
//布局
|
||||
IntegralScreen(navHostController, integralRankViewModel, coinRankData)
|
||||
}
|
||||
|
||||
/**
|
||||
* 积分排行布局
|
||||
*/
|
||||
@Composable
|
||||
private fun IntegralScreen(
|
||||
navHostController: NavHostController,
|
||||
integralRankViewModel: IntegralRankViewModel,
|
||||
coinRankData: LazyPagingItems<CoinRankData>
|
||||
) {
|
||||
|
||||
BaseScreen {
|
||||
Scaffold(
|
||||
topBar = {
|
||||
AppBar("积分排行", leftIcon = Icons.Default.ArrowBack, onLeftClick = {
|
||||
navHostController.navigateUp()
|
||||
})
|
||||
},
|
||||
bottomBar = {
|
||||
val userInfoIntegralData = integralRankViewModel.userInfoIntegral.observeAsState()
|
||||
userInfoIntegralData.value?.let { data ->
|
||||
BottomBarScreen(
|
||||
data.rank ?: "0",
|
||||
data.username ?: "",
|
||||
data.coinCount.toString()
|
||||
)
|
||||
}
|
||||
},
|
||||
content = { paddingValues: PaddingValues ->
|
||||
//带刷新头的列表
|
||||
SwipeRefreshContent(
|
||||
integralRankViewModel,
|
||||
coinRankData,
|
||||
cardHeight = 65.dp
|
||||
) { index: Int, data: CoinRankData ->
|
||||
CardContent(
|
||||
(index + 1).toString(),
|
||||
data.username ?: "",
|
||||
data.coinCount.toString()
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 卡片内的内容
|
||||
*/
|
||||
@Composable
|
||||
private fun CardContent(
|
||||
//排名
|
||||
rank: String,
|
||||
//用户名
|
||||
userName: String,
|
||||
//积分数量
|
||||
integralNum: String
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.padding(start = 20.dp, end = 20.dp).fillMaxSize(),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
|
||||
TextCustom(rank, color = MaterialTheme.colors.secondaryVariant)
|
||||
|
||||
TextCustom(
|
||||
userName,
|
||||
modifier = Modifier.padding(start = 20.dp),
|
||||
fontWeight = FontWeight.Normal,
|
||||
color = Color.Gray
|
||||
)
|
||||
|
||||
TextCustom(
|
||||
integralNum,
|
||||
modifier = Modifier.weight(1f, true),
|
||||
color = Color.Gray,
|
||||
textAlign = TextAlign.End
|
||||
)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 底部栏
|
||||
*/
|
||||
@Composable
|
||||
private fun BottomBarScreen(
|
||||
//排名
|
||||
rank: String,
|
||||
//用户名
|
||||
userName: String,
|
||||
//积分数量
|
||||
integralNum: String
|
||||
) {
|
||||
Surface(
|
||||
elevation = 4.dp
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.background(MaterialTheme.colors.background)
|
||||
.padding(start = 30.dp, end = 30.dp).height(70.dp).navigationBarsPadding()
|
||||
.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
TextCustom(rank)
|
||||
TextCustom(userName, modifier = Modifier.padding(start = 20.dp))
|
||||
TextCustom(
|
||||
integralNum, //靠右对齐
|
||||
textAlign = TextAlign.End,
|
||||
modifier = Modifier.weight(1f, true)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 文本控件
|
||||
*/
|
||||
@Composable
|
||||
private fun TextCustom(
|
||||
text: String,
|
||||
modifier: Modifier = Modifier,
|
||||
textAlign: TextAlign? = null,
|
||||
color: Color = MaterialTheme.colors.primary,
|
||||
fontWeight: FontWeight = FontWeight.Bold
|
||||
) {
|
||||
Text(
|
||||
text, fontWeight = fontWeight, color = color,
|
||||
fontSize = 20.sp,
|
||||
modifier = modifier,
|
||||
textAlign = textAlign
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package com.linx.playAndroid.composable
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.material.Button
|
||||
import androidx.compose.material.Divider
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.navigation.NavHostController
|
||||
import com.linx.playAndroid.KeyNavigationRoute
|
||||
|
||||
/**
|
||||
* 学习页面
|
||||
*/
|
||||
@Composable
|
||||
fun LearnCompose(navHostController: NavHostController) {
|
||||
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
||||
item {
|
||||
ListItemScreen(navHostController, "动画")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 只有一个按钮的控件
|
||||
*/
|
||||
@Composable
|
||||
private fun ListItemScreen(navHostController: NavHostController, str: String) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Button(
|
||||
onClick = {
|
||||
navHostController.navigate(KeyNavigationRoute.LEARN_ANIMATION.route)
|
||||
},
|
||||
modifier = Modifier
|
||||
.padding(top = 30.dp, bottom = 30.dp)
|
||||
.size(200.dp, 40.dp)
|
||||
) {
|
||||
Text(
|
||||
text = str,
|
||||
fontSize = 15.sp
|
||||
)
|
||||
}
|
||||
}
|
||||
Divider()
|
||||
}
|
||||
@@ -0,0 +1,366 @@
|
||||
package com.linx.playAndroid.composable
|
||||
|
||||
import android.widget.Toast
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.text.input.PasswordVisualTransformation
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.compose.ui.text.input.VisualTransformation
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.navigation.NavHostController
|
||||
import com.linx.common.baseData.CommonConstant
|
||||
import com.linx.common.baseData.refreshUserMessageData
|
||||
import com.linx.common.ext.toast
|
||||
import com.linx.common.widget.SpUtilsMMKV
|
||||
import com.linx.playAndroid.KeyNavigationRoute
|
||||
import com.linx.playAndroid.R
|
||||
import com.linx.playAndroid.ui.theme.c_80F
|
||||
import com.linx.playAndroid.ui.theme.c_B3F
|
||||
import com.linx.playAndroid.viewModel.LoginViewModel
|
||||
|
||||
/**
|
||||
* 登录页面
|
||||
*/
|
||||
@Composable
|
||||
fun LoginCompose(navHostController: NavHostController) {
|
||||
|
||||
val loginViewModel: LoginViewModel = viewModel()
|
||||
|
||||
val context = LocalContext.current
|
||||
|
||||
loginViewModel.apply {
|
||||
//toast
|
||||
mToast.observeAsState().value?.toast(context)
|
||||
|
||||
val userLogin= userLoginData.observeAsState()
|
||||
//登录成功
|
||||
LaunchedEffect(userLogin.value) {
|
||||
if (userLogin.value == "登录成功") {
|
||||
mToast.value = "登录成功"
|
||||
//保存登录数据
|
||||
SpUtilsMMKV.put(CommonConstant.IS_LOGIN, true)
|
||||
//通知刷新个人信息
|
||||
refreshUserMessageData.value = System.currentTimeMillis().toString()
|
||||
navHostController.navigateUp()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//登录页面布局
|
||||
LoginScreen(navHostController, loginViewModel)
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 登录页面 布局
|
||||
*/
|
||||
@Composable
|
||||
private fun LoginScreen(navHostController: NavHostController, loginViewModel: LoginViewModel) {
|
||||
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
//背景图
|
||||
Image(
|
||||
painter = painterResource(R.mipmap.login_bg),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentScale = ContentScale.FillBounds
|
||||
)
|
||||
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
item {
|
||||
|
||||
//返回上个页面和找回密码
|
||||
BackAndFinPassWordComposable() {
|
||||
//关闭页面
|
||||
navHostController.navigateUp()
|
||||
}
|
||||
|
||||
Image(
|
||||
modifier = Modifier.padding(top = 50.dp).size(100.dp)
|
||||
.align(Alignment.Center),
|
||||
painter = painterResource(R.mipmap.ic_account),
|
||||
contentDescription = null
|
||||
)
|
||||
|
||||
loginViewModel.apply {
|
||||
//用户名和密码
|
||||
UserNameAndPasswordComposable(userName, passWord, passwordVisible)
|
||||
}
|
||||
|
||||
//登录按钮 用户注册 作者登录
|
||||
LoginBtnAndTextComposable(loginClick = {
|
||||
//登录
|
||||
loginViewModel.getUserLoginData()
|
||||
}, registerClick = {
|
||||
//跳转到注册页面
|
||||
navHostController.navigate(KeyNavigationRoute.REGISTER.route)
|
||||
})
|
||||
//第三方登录
|
||||
OtherLoginComposable()
|
||||
|
||||
//用户协议
|
||||
AgreementComposable()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户协议
|
||||
*/
|
||||
@Composable
|
||||
private fun AgreementComposable() {
|
||||
val context = LocalContext.current
|
||||
Spacer(
|
||||
modifier = Modifier.padding(top = 30.dp).background(color = c_80F).height(2.dp)
|
||||
.fillMaxWidth()
|
||||
)
|
||||
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().padding(top = 15.dp),
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(
|
||||
"登录或注册即同意开通",
|
||||
color = Color.LightGray,
|
||||
fontSize = 12.sp
|
||||
)
|
||||
Text(
|
||||
"用户服务协议",
|
||||
color = Color.White,
|
||||
fontSize = 17.sp,
|
||||
modifier = Modifier.clickable {
|
||||
Toast.makeText(context, "点击用户服务协议", Toast.LENGTH_SHORT).show()
|
||||
}.padding(4.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 第三方登录
|
||||
*/
|
||||
@Composable
|
||||
private fun OtherLoginComposable() {
|
||||
val modifier =
|
||||
Modifier.clip(CircleShape).background(MaterialTheme.colors.background).padding(10.dp)
|
||||
.size(30.dp)
|
||||
Row(
|
||||
modifier = Modifier.padding(start = 40.dp, end = 40.dp, top = 90.dp)
|
||||
.fillMaxWidth(),
|
||||
//平分控件
|
||||
horizontalArrangement = Arrangement.SpaceEvenly
|
||||
) {
|
||||
//微信登录
|
||||
Icon(
|
||||
painterResource(R.mipmap.ic_action_share_wechat_grey),
|
||||
contentDescription = null,
|
||||
modifier = modifier,
|
||||
tint = MaterialTheme.colors.secondaryVariant
|
||||
)
|
||||
//微博登录
|
||||
Icon(
|
||||
painterResource(R.mipmap.ic_action_share_weibo_grey),
|
||||
contentDescription = null,
|
||||
modifier = modifier,
|
||||
tint = MaterialTheme.colors.secondaryVariant
|
||||
)
|
||||
//QQ登录
|
||||
Icon(
|
||||
painterResource(R.mipmap.ic_action_share_qq_grey),
|
||||
contentDescription = null,
|
||||
modifier = modifier,
|
||||
tint = MaterialTheme.colors.secondaryVariant
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 登录按钮
|
||||
* 用户注册 作者登录
|
||||
*/
|
||||
@Composable
|
||||
private fun LoginBtnAndTextComposable(loginClick: () -> Unit, registerClick: () -> Unit) {
|
||||
|
||||
//登录
|
||||
Button(
|
||||
onClick = loginClick,
|
||||
modifier = Modifier
|
||||
.padding(start = 50.dp, end = 50.dp, top = 30.dp)
|
||||
.fillMaxWidth(),
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
//透明的背景
|
||||
backgroundColor = c_B3F,
|
||||
contentColor = Color.Black
|
||||
)
|
||||
) {
|
||||
Text("登录", fontSize = 14.sp)
|
||||
}
|
||||
|
||||
//用户注册 作者登录
|
||||
Row(
|
||||
modifier = Modifier.padding(start = 50.dp, end = 50.dp, top = 30.dp).fillMaxWidth(),
|
||||
//平分控件
|
||||
horizontalArrangement = Arrangement.SpaceEvenly
|
||||
) {
|
||||
Text("用户注册", color = Color.White, modifier = Modifier.clickable(onClick = registerClick).padding(10.dp))
|
||||
Text("找回密码", color = Color.White, modifier = Modifier.padding(10.dp))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户名和密码
|
||||
*/
|
||||
@Composable
|
||||
fun UserNameAndPasswordComposable(
|
||||
//用户名
|
||||
userName: MutableState<TextFieldValue>,
|
||||
//密码
|
||||
passWord: MutableState<TextFieldValue>,
|
||||
//密码是否可见
|
||||
passWordVisible: MutableState<Boolean>
|
||||
) {
|
||||
|
||||
val userNameModifier = Modifier.padding(start = 50.dp, end = 50.dp, top = 50.dp).fillMaxWidth()
|
||||
.height(56.dp)
|
||||
|
||||
val passWordModifier =
|
||||
Modifier.padding(start = 50.dp, end = 50.dp, top = 20.dp).fillMaxWidth().height(56.dp)
|
||||
|
||||
//用户名
|
||||
OutlinedTextField(
|
||||
userName.value,
|
||||
onValueChange = { newValue ->
|
||||
userName.value = newValue
|
||||
},
|
||||
label = {
|
||||
Text("请输入手机号/邮箱地址", fontSize = 13.sp)
|
||||
},
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
painterResource(R.mipmap.ic_account_login_user_name),
|
||||
contentDescription = null
|
||||
)
|
||||
},
|
||||
colors = TextFieldDefaults.textFieldColors(
|
||||
unfocusedLabelColor = Color.White,
|
||||
textColor = Color.White,
|
||||
backgroundColor = Color.Transparent,
|
||||
focusedLabelColor = Color.White,
|
||||
leadingIconColor = Color.White,
|
||||
focusedIndicatorColor = Color.White,
|
||||
unfocusedIndicatorColor = Color.White,
|
||||
//光标
|
||||
cursorColor = Color.White
|
||||
),
|
||||
singleLine = true,
|
||||
modifier = userNameModifier,
|
||||
textStyle = TextStyle(
|
||||
fontSize = 13.sp
|
||||
)
|
||||
)
|
||||
|
||||
//密码
|
||||
OutlinedTextField(
|
||||
passWord.value, onValueChange = { newValue ->
|
||||
passWord.value = newValue
|
||||
},
|
||||
label = {
|
||||
Text("请输入密码", fontSize = 13.sp)
|
||||
},
|
||||
leadingIcon = {
|
||||
Icon(
|
||||
painterResource(R.mipmap.ic_account_login_password),
|
||||
contentDescription = null
|
||||
)
|
||||
},
|
||||
trailingIcon = {
|
||||
IconButton(onClick = {
|
||||
passWordVisible.value = !passWordVisible.value
|
||||
}) {
|
||||
Icon(
|
||||
painter = if (passWordVisible.value) painterResource(R.mipmap.ic_password_view) else painterResource(
|
||||
R.mipmap.ic_password_view_off
|
||||
), contentDescription = null,
|
||||
modifier = Modifier.size(20.dp),
|
||||
tint = Color.White
|
||||
)
|
||||
}
|
||||
},
|
||||
colors = TextFieldDefaults.textFieldColors(
|
||||
unfocusedLabelColor = Color.White,
|
||||
textColor = Color.White,
|
||||
backgroundColor = Color.Transparent,
|
||||
focusedLabelColor = Color.White,
|
||||
leadingIconColor = Color.White,
|
||||
focusedIndicatorColor = Color.White,
|
||||
unfocusedIndicatorColor = Color.White,
|
||||
//光标
|
||||
cursorColor = Color.White
|
||||
),
|
||||
singleLine = true,
|
||||
modifier = passWordModifier,
|
||||
textStyle = TextStyle(
|
||||
fontSize = 13.sp
|
||||
),
|
||||
//输入类型设置为密码
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password),
|
||||
//转化为不可见的密码
|
||||
visualTransformation = if (!passWordVisible.value) PasswordVisualTransformation() else VisualTransformation.None
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回上个页面和跳过
|
||||
*/
|
||||
@Composable
|
||||
fun BackAndFinPassWordComposable(
|
||||
back: () -> Unit,
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().padding(top = 40.dp),
|
||||
//第一个和最后一个按钮分别靠左边界和右边界
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(R.mipmap.ic_tab_strip_icon_follow),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.clickable(onClick = back).padding(10.dp),
|
||||
tint = Color.White
|
||||
)
|
||||
|
||||
Text(
|
||||
text = "跳过",
|
||||
fontSize = 13.sp,
|
||||
color = Color.White,
|
||||
modifier = Modifier.padding(10.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,291 @@
|
||||
package com.linx.playAndroid.composable
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.navigation.NavGraph.Companion.findStartDestination
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.currentBackStackEntryAsState
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import coil.annotation.ExperimentalCoilApi
|
||||
import com.google.accompanist.insets.navigationBarsHeight
|
||||
import com.google.accompanist.insets.statusBarsHeight
|
||||
import com.google.accompanist.pager.ExperimentalPagerApi
|
||||
import com.linx.common.baseData.Nav
|
||||
import com.linx.playAndroid.KeyNavigationRoute
|
||||
import com.linx.playAndroid.NavigationHost
|
||||
import com.linx.playAndroid.model.ProjectTreeData
|
||||
import com.linx.playAndroid.model.PublicNumChapterData
|
||||
import com.linx.playAndroid.public.AppBar
|
||||
import com.linx.playAndroid.public.BottomNavBar
|
||||
import com.linx.playAndroid.viewModel.ProjectViewModel
|
||||
import com.linx.playAndroid.viewModel.PublicNumViewModel
|
||||
|
||||
/**
|
||||
* 主界面
|
||||
* [onFinish] 点击两次返回关闭页面
|
||||
*/
|
||||
@ExperimentalCoilApi
|
||||
@ExperimentalPagerApi
|
||||
@Composable
|
||||
fun MainCompose(
|
||||
navHostController: NavHostController = rememberNavController(),
|
||||
onFinish: () -> Unit
|
||||
) {
|
||||
|
||||
//返回back堆栈的顶部条目
|
||||
val navBackStackEntry by navHostController.currentBackStackEntryAsState()
|
||||
//返回当前route
|
||||
val currentRoute = navBackStackEntry?.destination?.route ?: Nav.BottomNavScreen.HomeScreen.route
|
||||
|
||||
//是否为route == "main",主页面的内容
|
||||
if (isMainScreen(currentRoute)) {
|
||||
Scaffold(
|
||||
contentColor = MaterialTheme.colors.background,
|
||||
//标题栏
|
||||
topBar = {
|
||||
Column {
|
||||
//内容不挡住状态栏 如果不设置颜色这里会自己适配,但可能产生闪烁
|
||||
Spacer(
|
||||
modifier = Modifier
|
||||
.background(MaterialTheme.colors.primary)
|
||||
.statusBarsHeight()
|
||||
.fillMaxWidth()
|
||||
)
|
||||
|
||||
MainTopBar(Nav.bottomNavRoute.value, navHostController)
|
||||
}
|
||||
},
|
||||
//底部导航栏
|
||||
bottomBar = {
|
||||
Column {
|
||||
BottomNavBar(Nav.bottomNavRoute.value, navHostController)
|
||||
//内容不挡住导航栏 如果不设置颜色这里会自己适配,但可能产生闪烁
|
||||
Spacer(
|
||||
modifier = Modifier
|
||||
.background(MaterialTheme.colors.primary)
|
||||
.navigationBarsHeight()
|
||||
.fillMaxWidth()
|
||||
)
|
||||
}
|
||||
},
|
||||
//内容
|
||||
content = { paddingValues: PaddingValues ->
|
||||
//内容嵌套在Scaffold中
|
||||
NavigationHost(navHostController, paddingValues, onFinish)
|
||||
|
||||
OnTwoBackContent(navHostController)
|
||||
}
|
||||
)
|
||||
} else
|
||||
//独立页面
|
||||
NavigationHost(navHostController, onFinish = onFinish)
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 主页面的标题栏
|
||||
*/
|
||||
@Composable
|
||||
private fun MainTopBar(bottomNavScreen: Nav.BottomNavScreen, navHostController: NavHostController) {
|
||||
when (bottomNavScreen) {
|
||||
//首页
|
||||
Nav.BottomNavScreen.HomeScreen -> {
|
||||
AppBar(
|
||||
"首页",
|
||||
rightIcon = Icons.Default.Search,
|
||||
//跳转到搜索页面
|
||||
onRightClick = { navHostController.navigate(KeyNavigationRoute.SEARCH.route) })
|
||||
}
|
||||
//项目
|
||||
Nav.BottomNavScreen.ProjectScreen -> {
|
||||
|
||||
val projectViewModel: ProjectViewModel = viewModel()
|
||||
|
||||
//请求项目列表数据
|
||||
projectViewModel.getProjectTreeData()
|
||||
|
||||
val projectTreeData = projectViewModel.projectTreeData.observeAsState()
|
||||
|
||||
//顶部指示器
|
||||
ProjectTab(Nav.projectTopBarIndex, projectTreeData)
|
||||
}
|
||||
//广场
|
||||
Nav.BottomNavScreen.SquareScreen -> {
|
||||
//顶部指示器
|
||||
SquareTab(Nav.squareTopBarIndex)
|
||||
}
|
||||
//公众号
|
||||
Nav.BottomNavScreen.PublicNumScreen -> {
|
||||
|
||||
val publicNumViewModel: PublicNumViewModel = viewModel()
|
||||
|
||||
//请求公众号列表数据
|
||||
publicNumViewModel.getPublicNumChapterData()
|
||||
|
||||
val publicNumChapterData = publicNumViewModel.publicNumChapter.observeAsState()
|
||||
|
||||
//顶部指示器
|
||||
PublicNumTab(Nav.publicNumIndex, publicNumChapterData)
|
||||
|
||||
}
|
||||
//学习
|
||||
Nav.BottomNavScreen.LearnScreen -> {
|
||||
AppBar("学习")
|
||||
}
|
||||
//我的
|
||||
Nav.BottomNavScreen.MineScreen -> {
|
||||
AppBar(elevation = 0.dp)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 在主界面点击两次返回按钮,返回到手机桌面,再重新打开app,此时显示退出时的主界面
|
||||
*/
|
||||
@Composable
|
||||
private fun OnTwoBackContent(navHostController: NavHostController) {
|
||||
if (Nav.twoBackFinishActivity) {
|
||||
LaunchedEffect(Unit) {
|
||||
navHostController.navigate(Nav.bottomNavRoute.value.route) {
|
||||
popUpTo(navHostController.graph.findStartDestination().id) {
|
||||
saveState = true
|
||||
}
|
||||
//避免重建
|
||||
launchSingleTop = true
|
||||
//重新选择以前选择的项目时,恢复状态
|
||||
restoreState = true
|
||||
}
|
||||
Nav.twoBackFinishActivity = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 项目页面顶部的指示器
|
||||
*/
|
||||
@Composable
|
||||
private fun ProjectTab(
|
||||
projectTopBarIndex: MutableState<Int>,
|
||||
projectTreeData: State<List<ProjectTreeData>?>
|
||||
) {
|
||||
|
||||
if (projectTreeData.value == null) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.background(MaterialTheme.colors.primary)
|
||||
.fillMaxWidth()
|
||||
.height(54.dp)
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
ScrollableTabRow(
|
||||
selectedTabIndex = projectTopBarIndex.value,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(54.dp),
|
||||
//边缘padding
|
||||
edgePadding = 0.dp,
|
||||
backgroundColor = MaterialTheme.colors.primary
|
||||
) {
|
||||
projectTreeData.value!!.forEachIndexed { index, item ->
|
||||
Tab(
|
||||
text = { Text(item.name ?: "") },
|
||||
selected = projectTopBarIndex.value == index,
|
||||
onClick = {
|
||||
projectTopBarIndex.value = index
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val squareTopBarList = listOf<String>("广场", "每日一问", "体系", "导航")
|
||||
|
||||
/**
|
||||
* 广场页面的TopBar
|
||||
*/
|
||||
@Composable
|
||||
private fun SquareTab(squareTopBarIndex: MutableState<Int>) {
|
||||
|
||||
//顶部指示器
|
||||
ScrollableTabRow(
|
||||
selectedTabIndex = squareTopBarIndex.value,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(54.dp),
|
||||
backgroundColor = MaterialTheme.colors.primary
|
||||
) {
|
||||
squareTopBarList.forEachIndexed { index, item ->
|
||||
Tab(
|
||||
text = { Text(item) },
|
||||
selected = index == squareTopBarIndex.value,
|
||||
onClick = {
|
||||
squareTopBarIndex.value = index
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 公众号页面顶部的指示器
|
||||
*/
|
||||
@Composable
|
||||
private fun PublicNumTab(
|
||||
publicNumIndex: MutableState<Int>,
|
||||
publicNumChapterData: State<List<PublicNumChapterData>?>
|
||||
) {
|
||||
|
||||
if (publicNumChapterData.value == null) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.background(MaterialTheme.colors.primary)
|
||||
.fillMaxWidth()
|
||||
.height(54.dp)
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
ScrollableTabRow(
|
||||
selectedTabIndex = publicNumIndex.value,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(54.dp),
|
||||
//边缘padding
|
||||
edgePadding = 0.dp,
|
||||
backgroundColor = MaterialTheme.colors.primary
|
||||
) {
|
||||
publicNumChapterData.value!!.forEachIndexed { index, item ->
|
||||
Tab(
|
||||
text = { Text(item.name ?: "") },
|
||||
selected = publicNumIndex.value == index,
|
||||
onClick = {
|
||||
publicNumIndex.value = index
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否是首页的内容
|
||||
*/
|
||||
fun isMainScreen(route: String): Boolean = when (route) {
|
||||
Nav.BottomNavScreen.HomeScreen.route,
|
||||
Nav.BottomNavScreen.ProjectScreen.route,
|
||||
Nav.BottomNavScreen.SquareScreen.route,
|
||||
Nav.BottomNavScreen.PublicNumScreen.route,
|
||||
Nav.BottomNavScreen.LearnScreen.route,
|
||||
Nav.BottomNavScreen.MineScreen.route -> true
|
||||
else -> false
|
||||
}
|
||||
@@ -0,0 +1,256 @@
|
||||
package com.linx.playAndroid.composable
|
||||
|
||||
import androidx.compose.foundation.*
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Surface
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.ripple.rememberRipple
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.painter.Painter
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.navigation.NavHostController
|
||||
import com.linx.common.baseData.refreshUserMessageData
|
||||
import com.linx.common.ext.joinQQGroup
|
||||
import com.linx.common.ext.toast
|
||||
import com.linx.playAndroid.KeyNavigationRoute
|
||||
import com.linx.playAndroid.R
|
||||
import com.linx.playAndroid.viewModel.MineViewModel
|
||||
|
||||
/**
|
||||
* 我的页面
|
||||
*/
|
||||
@Composable
|
||||
fun MineCompose(navHostController: NavHostController) {
|
||||
|
||||
val mineViewModel: MineViewModel = viewModel()
|
||||
|
||||
mineViewModel.apply {
|
||||
//如果登录了就个人积分数据
|
||||
if (isLogin()) {
|
||||
//监听 刷新个人信息数据
|
||||
LaunchedEffect(refreshUserMessageData.value) {
|
||||
//获取个人积分数据
|
||||
getUserInfoIntegral()
|
||||
}
|
||||
} else if (userInfoIntegral.value != null)
|
||||
//如果退出登录的话会走这里
|
||||
userInfoIntegral.value = null
|
||||
}
|
||||
|
||||
//页面布局
|
||||
MineScreen(mineViewModel, navHostController)
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 页面布局
|
||||
*/
|
||||
@Composable
|
||||
private fun MineScreen(
|
||||
mineViewModel: MineViewModel,
|
||||
navHostController: NavHostController
|
||||
) {
|
||||
//个人积分数据
|
||||
val userInfoIntegralData = mineViewModel.userInfoIntegral.observeAsState()
|
||||
|
||||
Column(
|
||||
modifier = Modifier.background(MaterialTheme.colors.primary).fillMaxSize()
|
||||
) {
|
||||
|
||||
userInfoIntegralData.value.apply {
|
||||
//头像和名字
|
||||
HeadAndName(
|
||||
this?.username ?: "请先登录~",
|
||||
if (this?.userId == 0 || this?.userId == null) "" else this.userId.toString(),
|
||||
if (this?.rank == null) "" else rank.toString()
|
||||
) {
|
||||
//未登录才跳转到登录页面
|
||||
if (!mineViewModel.isLogin()) {
|
||||
//跳转到登录页面
|
||||
navHostController.navigate(KeyNavigationRoute.LOGIN.route)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val context = LocalContext.current
|
||||
|
||||
//下方列表
|
||||
Surface(
|
||||
shape = RoundedCornerShape(topStart = 20.dp, topEnd = 20.dp),
|
||||
color = MaterialTheme.colors.background,
|
||||
modifier = Modifier.fillMaxSize().padding(top = 50.dp)
|
||||
) {
|
||||
Column(modifier = Modifier.padding(top = 13.dp).fillMaxSize()) {
|
||||
MineListComposable(
|
||||
painterResource(R.mipmap.ic_jifen),
|
||||
"我的积分",
|
||||
userInfoIntegralData.value?.coinCount ?: 0
|
||||
) {
|
||||
if (mineViewModel.isLogin())
|
||||
//跳转到积分排行页面
|
||||
navHostController.navigate(
|
||||
KeyNavigationRoute.INTEGRAL_RANK.route
|
||||
)
|
||||
else {
|
||||
"请先登录".toast(context)
|
||||
}
|
||||
}
|
||||
MineListComposable(painterResource(R.mipmap.ic_collect), "我的收藏") {
|
||||
if (mineViewModel.isLogin())
|
||||
//跳转到我的收藏页面
|
||||
navHostController.navigate(
|
||||
KeyNavigationRoute.MY_COLLECT.route
|
||||
)
|
||||
else {
|
||||
"请先登录".toast(context)
|
||||
}
|
||||
}
|
||||
MineListComposable(painterResource(R.mipmap.ic_wenzhang), "我的文章") {
|
||||
if (mineViewModel.isLogin())
|
||||
//跳转到我的文章页面
|
||||
navHostController.navigate(
|
||||
KeyNavigationRoute.MY_SHARE_ARTICLES.route
|
||||
)
|
||||
else {
|
||||
"请先登录".toast(context)
|
||||
}
|
||||
}
|
||||
MineListComposable(painterResource(R.mipmap.ic_web), "开源网站") {
|
||||
navHostController.navigate("${KeyNavigationRoute.WEBVIEW.route}?url=https://www.wanandroid.com")
|
||||
}
|
||||
MineListComposable(painterResource(R.mipmap.ic_jairu), "加入我们") {
|
||||
joinQQGroup(context, "jtDR37meSA0N3ceEPYRk3IbaqkqtsQgm")
|
||||
}
|
||||
MineListComposable(painterResource(R.mipmap.ic_shezhi), "系统设置") {
|
||||
//跳转到设置页面
|
||||
navHostController.navigate(
|
||||
KeyNavigationRoute.SETTING.route
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 列表
|
||||
*/
|
||||
@Composable
|
||||
private fun MineListComposable(
|
||||
painter: Painter,
|
||||
leftStr: String,
|
||||
//积分
|
||||
integral: Int = -1,
|
||||
onClick: () -> Unit
|
||||
) {
|
||||
|
||||
Row(
|
||||
modifier = Modifier.background(MaterialTheme.colors.background)
|
||||
.clickable(
|
||||
onClick = onClick,
|
||||
interactionSource = MutableInteractionSource(),
|
||||
indication = null
|
||||
)
|
||||
.padding(start = 20.dp, end = 20.dp, top = 13.dp, bottom = 13.dp)
|
||||
.height(26.dp)
|
||||
.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
|
||||
Image(
|
||||
painter,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(26.dp)
|
||||
)
|
||||
|
||||
Text(
|
||||
leftStr,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = MaterialTheme.colors.secondaryVariant,
|
||||
fontSize = 15.sp,
|
||||
modifier = Modifier.padding(start = 14.dp)
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.weight(1f, true))
|
||||
|
||||
if (integral != -1) {
|
||||
Text(
|
||||
"当前积分:",
|
||||
color = Color.Gray,
|
||||
fontWeight = FontWeight.Light,
|
||||
fontSize = 13.sp
|
||||
)
|
||||
|
||||
Text(
|
||||
integral.toString(),
|
||||
color = MaterialTheme.colors.primary,
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = 18.sp
|
||||
)
|
||||
}
|
||||
|
||||
//箭头
|
||||
Icon(
|
||||
painterResource(R.mipmap.ic_right),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.padding(start = 8.dp).size(16.dp),
|
||||
tint = MaterialTheme.colors.secondaryVariant
|
||||
)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 头像和名字
|
||||
*/
|
||||
@Composable
|
||||
private fun HeadAndName(name: String, id: String, rank: String, goLogin: () -> Unit) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.clickable(
|
||||
onClick = goLogin,
|
||||
interactionSource = MutableInteractionSource(),
|
||||
indication = null
|
||||
).height(80.dp).fillMaxWidth()
|
||||
.padding(start = 20.dp)
|
||||
) {
|
||||
//头像
|
||||
Surface(
|
||||
//圆
|
||||
shape = CircleShape,
|
||||
modifier = Modifier.size(80.dp)
|
||||
) {
|
||||
Image(painterResource(R.mipmap.ic_account), contentDescription = null)
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier.padding(start = 20.dp).weight(1f).fillMaxHeight(),
|
||||
//平分
|
||||
verticalArrangement = Arrangement.SpaceEvenly
|
||||
) {
|
||||
Text(name, fontWeight = FontWeight.Bold, fontSize = 20.sp)
|
||||
|
||||
Row {
|
||||
Text("id: $id", fontSize = 14.sp)
|
||||
Text("排名: $rank", modifier = Modifier.padding(start = 20.dp), fontSize = 14.sp)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package com.linx.playAndroid.composable
|
||||
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.material.Scaffold
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowBack
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.paging.compose.collectAsLazyPagingItems
|
||||
import com.linx.playAndroid.model.MyCollectData
|
||||
import com.linx.playAndroid.public.*
|
||||
import com.linx.playAndroid.viewModel.MyCollectViewModel
|
||||
|
||||
/**
|
||||
* 我的收藏页面
|
||||
*/
|
||||
@Composable
|
||||
fun MyCollectCompose(navHostController: NavHostController) {
|
||||
|
||||
val myCollectViewModel: MyCollectViewModel = viewModel()
|
||||
|
||||
//布局
|
||||
MyCollectScreen(navHostController, myCollectViewModel)
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 我的收藏页面布局
|
||||
*/
|
||||
@Composable
|
||||
private fun MyCollectScreen(navHostController: NavHostController, myCollectViewModel: MyCollectViewModel) {
|
||||
|
||||
//获取我的收藏列表数据
|
||||
val myCollectListData = myCollectViewModel.myCollectListData.collectAsLazyPagingItems()
|
||||
|
||||
BaseScreen {
|
||||
Scaffold(
|
||||
topBar = {
|
||||
AppBar("我的收藏", leftIcon = Icons.Default.ArrowBack, onLeftClick = {
|
||||
navHostController.navigateUp()
|
||||
})
|
||||
},
|
||||
content = { paddingValues: PaddingValues ->
|
||||
SwipeRefreshContent(
|
||||
myCollectViewModel,
|
||||
myCollectListData
|
||||
) { index: Int, data: MyCollectData ->
|
||||
data.apply {
|
||||
HomeCardItemContent(
|
||||
getAuthor(author, null),
|
||||
false,
|
||||
false,
|
||||
niceDate ?: "刚刚",
|
||||
title ?: "",
|
||||
chapterName ?: "未知",
|
||||
true
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
package com.linx.playAndroid.composable
|
||||
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.material.Scaffold
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowBack
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.paging.compose.collectAsLazyPagingItems
|
||||
import com.linx.playAndroid.model.MyCollectData
|
||||
import com.linx.playAndroid.public.*
|
||||
import com.linx.playAndroid.viewModel.MyShareArticlesViewModel
|
||||
|
||||
/**
|
||||
* 我的文章页面
|
||||
*/
|
||||
@Composable
|
||||
fun MyShareArticlesCompose(navHostController: NavHostController) {
|
||||
|
||||
val myShareArticlesViewModel: MyShareArticlesViewModel = viewModel()
|
||||
|
||||
//布局
|
||||
MyShareArticlesScreen(navHostController, myShareArticlesViewModel)
|
||||
}
|
||||
|
||||
/**
|
||||
* 我的文章布局
|
||||
*/
|
||||
@Composable
|
||||
private fun MyShareArticlesScreen(
|
||||
navHostController: NavHostController,
|
||||
myShareArticlesViewModel: MyShareArticlesViewModel
|
||||
) {
|
||||
|
||||
val myShareArticlesListData = myShareArticlesViewModel.myShareArticlesListData.collectAsLazyPagingItems()
|
||||
|
||||
BaseScreen {
|
||||
Scaffold(
|
||||
topBar = {
|
||||
AppBar("我分享的文章", leftIcon = Icons.Default.ArrowBack, onLeftClick = {
|
||||
navHostController.navigateUp()
|
||||
})
|
||||
},
|
||||
content = { paddingValues: PaddingValues ->
|
||||
SwipeRefreshContent(
|
||||
myShareArticlesViewModel,
|
||||
myShareArticlesListData
|
||||
) { index: Int, data: MyCollectData ->
|
||||
data.apply {
|
||||
HomeCardItemContent(
|
||||
getAuthor(author, null),
|
||||
false,
|
||||
false,
|
||||
niceDate ?: "刚刚",
|
||||
title ?: "",
|
||||
chapterName ?: "未知",
|
||||
true
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package com.linx.playAndroid.composable
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.paging.compose.collectAsLazyPagingItems
|
||||
import com.linx.common.baseData.Nav
|
||||
import com.linx.common.ext.transitionDate
|
||||
import com.linx.playAndroid.KeyNavigationRoute
|
||||
import com.linx.playAndroid.model.ProjectListData
|
||||
import com.linx.playAndroid.public.SwipeRefreshContent
|
||||
import com.linx.playAndroid.public.BottomCard
|
||||
import com.linx.playAndroid.public.CenterCard
|
||||
import com.linx.playAndroid.public.TopCard
|
||||
import com.linx.playAndroid.viewModel.ProjectViewModel
|
||||
|
||||
/**
|
||||
* 项目页面
|
||||
*/
|
||||
@Composable
|
||||
fun ProjectCompose(navHostController: NavHostController) {
|
||||
|
||||
val projectViewModel: ProjectViewModel = viewModel()
|
||||
|
||||
//项目列表数据
|
||||
val projectListData = projectViewModel.projectListData.collectAsLazyPagingItems()
|
||||
|
||||
//TopBar的Index改变
|
||||
LaunchedEffect(Nav.projectTopBarIndex.value) {
|
||||
|
||||
if (Nav.projectTopBarIndex.value == projectViewModel.saveChangeProjectIndex) return@LaunchedEffect
|
||||
|
||||
projectViewModel.apply {
|
||||
//保存改变过index和offset的指示器Index
|
||||
saveChangeProjectIndex = Nav.projectTopBarIndex.value
|
||||
projectLazyListState.scrollToItem(0, 0)
|
||||
}
|
||||
|
||||
projectListData.refresh()
|
||||
}
|
||||
|
||||
//项目页面的内容
|
||||
SwipeRefreshContent(
|
||||
projectViewModel,
|
||||
projectListData,
|
||||
state = projectViewModel.projectLazyListState,
|
||||
cardHeight = 190.dp
|
||||
) { index, data ->
|
||||
ProjectItemContent(data) {
|
||||
navHostController.navigate("${KeyNavigationRoute.WEBVIEW.route}?url=${data.link}")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 项目页面列表子项
|
||||
*/
|
||||
@Composable
|
||||
private fun ProjectItemContent(project: ProjectListData, onClick: () -> Unit) {
|
||||
|
||||
Column(
|
||||
modifier = Modifier.clickable(onClick = onClick).padding(bottom = 6.dp, top = 6.dp)
|
||||
.padding(start = 8.dp, end = 8.dp)
|
||||
) {
|
||||
|
||||
TopCard(project.author ?: "", project.publishTime.transitionDate())
|
||||
|
||||
Row(
|
||||
modifier = Modifier.padding(3.dp).weight(1f)
|
||||
) {
|
||||
CenterCard(project.envelopePic ?: "", project.title ?: "", project.desc ?: "")
|
||||
}
|
||||
|
||||
BottomCard(
|
||||
"${project.superChapterName}·${project.chapterName}",
|
||||
project.collect
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
package com.linx.playAndroid.composable
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.paging.compose.collectAsLazyPagingItems
|
||||
import com.linx.common.baseData.Nav
|
||||
import com.linx.playAndroid.KeyNavigationRoute
|
||||
import com.linx.playAndroid.public.*
|
||||
import com.linx.playAndroid.viewModel.PublicNumViewModel
|
||||
|
||||
/**
|
||||
* 公众号页面
|
||||
*/
|
||||
@Composable
|
||||
fun PublicNumCompose(navHostController: NavHostController) {
|
||||
|
||||
val publicNumViewModel: PublicNumViewModel = viewModel()
|
||||
|
||||
//某个公众号历史文章列表数据
|
||||
val publicNumListData = publicNumViewModel.publicNumListData.collectAsLazyPagingItems()
|
||||
|
||||
//如果TopBar的Index改变的话需要刷新数据
|
||||
LaunchedEffect(Nav.publicNumIndex.value) {
|
||||
|
||||
if (Nav.publicNumIndex.value == publicNumViewModel.saveChangePublicNumIndex) return@LaunchedEffect
|
||||
|
||||
publicNumViewModel.apply {
|
||||
//保存改变过index和offset的指示器Index
|
||||
saveChangePublicNumIndex = Nav.publicNumIndex.value
|
||||
publicNumLazyListState.scrollToItem(0, 0)
|
||||
}
|
||||
|
||||
publicNumListData.refresh()
|
||||
}
|
||||
|
||||
//公众号页面的内容
|
||||
SwipeRefreshContent(
|
||||
publicNumViewModel,
|
||||
publicNumListData,
|
||||
state = publicNumViewModel.publicNumLazyListState
|
||||
) { index, data ->
|
||||
data.apply {
|
||||
HomeCardItemContent(
|
||||
getAuthor(author, shareUser),
|
||||
fresh,
|
||||
false,
|
||||
niceDate ?: "刚刚",
|
||||
title ?: "",
|
||||
if (superChapterName != null) "$superChapterName" else "未知",
|
||||
collect,
|
||||
isSpecific = false
|
||||
) {
|
||||
navHostController.navigate("${KeyNavigationRoute.WEBVIEW.route}?url=$link")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
package com.linx.playAndroid.composable
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.material.Button
|
||||
import androidx.compose.material.ButtonDefaults
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.navigation.NavHostController
|
||||
import com.linx.common.ext.toast
|
||||
import com.linx.playAndroid.R
|
||||
import com.linx.playAndroid.ui.theme.c_B3F
|
||||
import com.linx.playAndroid.viewModel.RegisterViewModel
|
||||
|
||||
/**
|
||||
* 注册界面
|
||||
*/
|
||||
@Composable
|
||||
fun RegisterCompose(navHostController: NavHostController) {
|
||||
|
||||
val registerViewModel: RegisterViewModel = viewModel()
|
||||
|
||||
val context = LocalContext.current
|
||||
|
||||
registerViewModel.apply {
|
||||
//toast
|
||||
mToast.observeAsState().value?.toast(context)
|
||||
|
||||
//注册成功后执行的代码块
|
||||
val userRegisterData = userRegisterData.observeAsState()
|
||||
LaunchedEffect(userRegisterData.value) {
|
||||
if (userRegisterData.value != null) {
|
||||
mToast.value = "注册成功"
|
||||
navHostController.navigateUp()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//页面布局
|
||||
RegisterScreen(navHostController, registerViewModel)
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册页面布局
|
||||
*/
|
||||
@Composable
|
||||
private fun RegisterScreen(navHostController: NavHostController, registerViewModel: RegisterViewModel) {
|
||||
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
//背景图
|
||||
Image(
|
||||
painter = painterResource(R.mipmap.login_bg),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentScale = ContentScale.FillBounds
|
||||
)
|
||||
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
item {
|
||||
|
||||
//返回上个页面和找回密码
|
||||
BackAndFinPassWordComposable() {
|
||||
//关闭页面
|
||||
navHostController.navigateUp()
|
||||
}
|
||||
|
||||
Image(
|
||||
modifier = Modifier.padding(top = 50.dp).size(100.dp)
|
||||
.align(Alignment.Center),
|
||||
painter = painterResource(R.mipmap.ic_account),
|
||||
contentDescription = null
|
||||
)
|
||||
|
||||
registerViewModel.apply {
|
||||
//用户名和密码
|
||||
UserNameAndPasswordComposable(userName, passWord, passwordVisible)
|
||||
}
|
||||
|
||||
//用户注册
|
||||
RegisterButtom() {
|
||||
registerViewModel.getUserRegisterData()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册按钮
|
||||
*/
|
||||
@Composable
|
||||
private fun RegisterButtom(registerClick: () -> Unit) {
|
||||
//注册
|
||||
Button(
|
||||
onClick = registerClick,
|
||||
modifier = Modifier
|
||||
.padding(start = 50.dp, end = 50.dp, top = 30.dp)
|
||||
.fillMaxWidth(),
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
//透明的背景
|
||||
backgroundColor = c_B3F,
|
||||
contentColor = Color.Black
|
||||
)
|
||||
) {
|
||||
Text("注册", fontSize = 14.sp)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
package com.linx.playAndroid.composable
|
||||
|
||||
import android.content.Context
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.Delete
|
||||
import androidx.compose.material.icons.filled.Search
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.navigation.NavHostController
|
||||
import com.linx.common.ext.toast
|
||||
import com.linx.playAndroid.public.*
|
||||
import com.linx.playAndroid.viewModel.SearchViewModel
|
||||
import com.linx.playAndroid.widget.roomUtil.SearchHistoryData
|
||||
import com.linx.playAndroid.widget.roomUtil.SearchHistoryHelper
|
||||
import com.linx.playAndroid.widget.roomUtil.SearchHistoryHelper.delete
|
||||
import com.linx.playAndroid.widget.roomUtil.SearchHistoryHelper.deleteAll
|
||||
import com.linx.playAndroid.widget.roomUtil.SearchHistoryHelper.insertSearchHistory
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/**
|
||||
* 搜索页面
|
||||
*/
|
||||
@Composable
|
||||
fun SearchCompose(navHostController: NavHostController) {
|
||||
|
||||
val context = LocalContext.current
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
val searchViewModel: SearchViewModel = viewModel()
|
||||
|
||||
//请求热门搜索
|
||||
searchViewModel.getHotKeyData()
|
||||
|
||||
var textFieldValue by remember { mutableStateOf(TextFieldValue("")) }
|
||||
|
||||
BaseScreen {
|
||||
Scaffold(topBar = {
|
||||
AppBar(textFieldValue = textFieldValue, onValueChange = { changeValue: TextFieldValue ->
|
||||
textFieldValue = changeValue
|
||||
}, leftIcon = Icons.Default.ArrowBack, onLeftClick = {
|
||||
navHostController.navigateUp()
|
||||
}, rightIcon = Icons.Default.Search, onRightClick = {
|
||||
|
||||
if (textFieldValue.text.isBlank()) {
|
||||
return@AppBar
|
||||
}
|
||||
|
||||
//保存到room数据库
|
||||
scope.launch {
|
||||
this.insertSearchHistory(context, SearchHistoryData(textFieldValue.text))
|
||||
}
|
||||
|
||||
//todo 跳转到搜索结果页面
|
||||
})
|
||||
}) {
|
||||
SearchScrren(context, scope, navHostController, searchViewModel)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 标题栏下方的内容
|
||||
*/
|
||||
@Composable
|
||||
private fun SearchScrren(
|
||||
context: Context,
|
||||
scope: CoroutineScope,
|
||||
navHostController: NavHostController,
|
||||
searchViewModel: SearchViewModel
|
||||
) {
|
||||
|
||||
//热搜标签
|
||||
val hotKeyList = searchViewModel.hotKeyListData.observeAsState().value
|
||||
|
||||
//搜索历史数据
|
||||
val searchHistoryListData =
|
||||
SearchHistoryHelper.getLiveDataAllSearchHistory(context).observeAsState()
|
||||
|
||||
Column(
|
||||
modifier = Modifier.padding(10.dp).fillMaxSize()
|
||||
) {
|
||||
|
||||
TopTextIconScreen("热门搜索")
|
||||
|
||||
LabelCustom(itemGap = FlowBoxGap(start = 0.dp, end = 4.dp, top = 4.dp, bottom = 4.dp)) {
|
||||
if (hotKeyList?.isEmpty() == true || hotKeyList == null) {
|
||||
Text(
|
||||
"暂无热词",
|
||||
fontWeight = FontWeight.Light,
|
||||
color = MaterialTheme.colors.secondaryVariant
|
||||
)
|
||||
return@LabelCustom
|
||||
}
|
||||
|
||||
hotKeyList.forEachIndexed { index, hotKeyData ->
|
||||
Button(onClick = {}) { Text(hotKeyData.name ?: "") }
|
||||
}
|
||||
}
|
||||
|
||||
TopTextIconScreen("搜索历史", true) {
|
||||
scope.launch {
|
||||
//删除数据库内的数据
|
||||
deleteAll(context)
|
||||
"搜索历史记录已清除".toast(context)
|
||||
}
|
||||
}
|
||||
|
||||
//搜索历史列表数据,这里传入倒叙后的数据list
|
||||
searchHistoryListData.value?.let { SearchHistoryScreen(context, it.reversed()) }
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 顶部的内容
|
||||
*/
|
||||
@Composable
|
||||
private fun TopTextIconScreen(
|
||||
text: String = "",
|
||||
//是否显示删除图标
|
||||
showDeleteIcon: Boolean = false,
|
||||
//删除图标点击事件
|
||||
deleteClick: () -> Unit = {}
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.height(30.dp).fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
Text(text, fontSize = 18.sp, color = MaterialTheme.colors.primary)
|
||||
|
||||
if (showDeleteIcon) {
|
||||
Icon(
|
||||
Icons.Default.Delete,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.clickable(
|
||||
onClick = deleteClick,
|
||||
indication = null,
|
||||
interactionSource = MutableInteractionSource()
|
||||
).size(16.dp),
|
||||
tint = MaterialTheme.colors.primary
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 搜索历史列表
|
||||
*/
|
||||
@Composable
|
||||
private fun SearchHistoryScreen(context: Context, list: List<SearchHistoryData>) {
|
||||
LazyColumn {
|
||||
itemsIndexed(list) { index: Int, item: SearchHistoryData ->
|
||||
RowTextIcon(item.text, paddingTop = 10.dp) {
|
||||
delete(context, item.id ?: 0)
|
||||
"已删除搜索历史:${item.text}".toast(context)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,247 @@
|
||||
package com.linx.playAndroid.composable
|
||||
|
||||
import androidx.compose.foundation.BorderStroke
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.GridCells
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.LazyVerticalGrid
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.navigation.NavHostController
|
||||
import com.airbnb.lottie.compose.LottieCompositionSpec
|
||||
import com.airbnb.lottie.compose.rememberLottieComposition
|
||||
import com.linx.common.baseData.CommonConstant
|
||||
import com.linx.common.baseData.themeColorList
|
||||
import com.linx.common.baseData.themeTypeState
|
||||
import com.linx.common.ext.toast
|
||||
import com.linx.common.model.ThemeType
|
||||
import com.linx.common.widget.SpUtilsMMKV
|
||||
import com.linx.net.widget.DataStoreUtils
|
||||
import com.linx.playAndroid.R
|
||||
import com.linx.playAndroid.public.*
|
||||
import com.linx.playAndroid.ui.theme.CustomThemeManager
|
||||
import com.linx.playAndroid.viewModel.SettingViewModel
|
||||
import com.linx.playAndroid.widget.CacheDataManager
|
||||
import com.linx.playAndroid.widget.SmallUtil
|
||||
|
||||
/**
|
||||
* 设置页面
|
||||
*/
|
||||
@Composable
|
||||
fun SettingCompose(navHostController: NavHostController) {
|
||||
|
||||
val settingViewModel: SettingViewModel = viewModel()
|
||||
|
||||
val content = LocalContext.current
|
||||
|
||||
BaseScreen {
|
||||
Scaffold(topBar = {
|
||||
val lottieComposition by rememberLottieComposition(
|
||||
spec = LottieCompositionSpec.RawRes(
|
||||
R.raw.loader
|
||||
)
|
||||
)
|
||||
AppBar("设置", leftIcon = Icons.Default.ArrowBack, onLeftClick = {
|
||||
navHostController.navigateUp()
|
||||
}, rightLottie = lottieComposition) {
|
||||
"哟,你发现了啥?Lottie动画!".toast(content)
|
||||
}
|
||||
},
|
||||
content = { paddingValues: PaddingValues ->
|
||||
SettingCenterScreen(navHostController, settingViewModel)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 中间的内容布局
|
||||
*/
|
||||
@Composable
|
||||
private fun SettingCenterScreen(
|
||||
navHostController: NavHostController,
|
||||
settingViewModel: SettingViewModel
|
||||
) {
|
||||
|
||||
val context = LocalContext.current
|
||||
|
||||
//是否显示首页置顶文章
|
||||
var showTopArticle by remember { mutableStateOf(SpUtilsMMKV.getBoolean(CommonConstant.GONE_ARTICLE_TOP) != true) }
|
||||
|
||||
//清除缓存弹窗
|
||||
var cacheDataState by remember { mutableStateOf(false) }
|
||||
if (cacheDataState) {
|
||||
//弹窗,关闭之后重新设置为false,下次可以继续监听
|
||||
SimpleAlertDialog("温馨提示", "确定清理缓存吗", confirmStr = "清理", confirmClick = {
|
||||
CacheDataManager.clearAllCache(context)
|
||||
}) { cacheDataState = false }
|
||||
}
|
||||
|
||||
//退出登录弹窗
|
||||
var exitLoginState by remember { mutableStateOf(false) }
|
||||
if (exitLoginState) {
|
||||
SimpleAlertDialog("温馨提示", "确定退出登录吗", confirmStr = "退出", confirmClick = {
|
||||
//同步清除cookie
|
||||
DataStoreUtils.clearSync()
|
||||
//设置为未登录
|
||||
SpUtilsMMKV.removeKey(CommonConstant.IS_LOGIN)
|
||||
navHostController.navigateUp()
|
||||
}) { exitLoginState = false }
|
||||
}
|
||||
|
||||
//主题颜色弹窗
|
||||
var themeColorState by remember { mutableStateOf(false) }
|
||||
if (themeColorState) {
|
||||
ContentCustomAlertDialog("主题颜色设置", textCompose = {
|
||||
ThemeSelectedScreen()
|
||||
}) { themeColorState = false }
|
||||
}
|
||||
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
item {
|
||||
TopText("基本设置")
|
||||
|
||||
ColumnTextTextScreen("显示置顶文章", "开启后首页会显示置顶文章", onClick = {
|
||||
showTopArticle = !showTopArticle
|
||||
when (showTopArticle) {
|
||||
//显示
|
||||
true -> SpUtilsMMKV.removeKey(CommonConstant.GONE_ARTICLE_TOP)
|
||||
//隐藏
|
||||
false -> SpUtilsMMKV.put(CommonConstant.GONE_ARTICLE_TOP, true)
|
||||
}
|
||||
}) {
|
||||
Checkbox(
|
||||
showTopArticle,
|
||||
onCheckedChange = null,
|
||||
modifier = Modifier.size(6.dp),
|
||||
colors = CheckboxDefaults.colors(checkedColor = MaterialTheme.colors.primary)
|
||||
)
|
||||
}
|
||||
|
||||
ColumnTextTextScreen("清除缓存", CacheDataManager.getTotalCacheSize(context), onClick = {
|
||||
cacheDataState = true
|
||||
})
|
||||
ColumnTextTextScreen("退出", "退出登录", onClick = {
|
||||
if (settingViewModel.isLogin())
|
||||
exitLoginState = true
|
||||
else "您还未登录".toast(context)
|
||||
})
|
||||
//分割线
|
||||
Spacer(modifier = Modifier.background(Color.Gray).fillMaxWidth().height(1.dp))
|
||||
|
||||
TopText("其他设置")
|
||||
ColumnTextTextScreen("主题颜色", "设置App主题颜色", context = {
|
||||
Surface(
|
||||
modifier = Modifier.size(30.dp),
|
||||
shape = CircleShape,
|
||||
color = MaterialTheme.colors.primary,
|
||||
content = {}
|
||||
)
|
||||
}, onClick = {
|
||||
themeColorState = true
|
||||
})
|
||||
//分割线
|
||||
Spacer(modifier = Modifier.background(Color.Gray).fillMaxWidth().height(1.dp))
|
||||
|
||||
TopText("关于")
|
||||
ColumnTextTextScreen("版本", "当前版本:${SmallUtil.packageCode(context)}")
|
||||
ColumnTextTextScreen("作者", "LinXiang")
|
||||
ColumnTextTextScreen("项目源码", "https://github.com/linxiangcheer/PlayAndroid")
|
||||
ColumnTextTextScreen("版权声明", "仅供个人及非商业用途使用")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 主题选择布局
|
||||
*/
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
private fun ThemeSelectedScreen() {
|
||||
//aspectRatio 宽高1:1
|
||||
val modifier = Modifier.aspectRatio(1f).padding(4.dp)
|
||||
//垂直GridList
|
||||
LazyVerticalGrid(
|
||||
//每行的数量
|
||||
cells = GridCells.Fixed(4)
|
||||
) {
|
||||
itemsIndexed(themeColorList) { index: Int, theme: ThemeType ->
|
||||
Surface(
|
||||
border = BorderStroke(1.dp, MaterialTheme.colors.secondaryVariant),
|
||||
modifier = modifier.clickable(onClick = {
|
||||
//保存主题颜色
|
||||
SpUtilsMMKV.put(CommonConstant.THEME_COLOR, index)
|
||||
themeTypeState.value = theme
|
||||
}),
|
||||
shape = CircleShape,
|
||||
color = CustomThemeManager.getThemeColor(theme).primary,
|
||||
) {
|
||||
//如果是当前选中的主题
|
||||
if (themeTypeState.value == theme) {
|
||||
Box(
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Icon(
|
||||
Icons.Default.Lock,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colors.background
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 列表第一个Text 主题颜色
|
||||
*/
|
||||
@Composable
|
||||
private fun TopText(text: String) {
|
||||
Text(
|
||||
text,
|
||||
fontSize = 14.sp,
|
||||
color = MaterialTheme.colors.primary,
|
||||
modifier = Modifier.padding(start = 20.dp, top = 13.dp, bottom = 13.dp)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 上下文字列表的布局
|
||||
*/
|
||||
@Composable
|
||||
private fun ColumnTextTextScreen(
|
||||
topStr: String,
|
||||
bottomStr: String,
|
||||
paddingTop: Dp = 13.dp,
|
||||
paddingBottom: Dp = 13.dp,
|
||||
onClick: () -> Unit = {},
|
||||
context: @Composable () -> Unit = {},
|
||||
) {
|
||||
ColumnTextText(
|
||||
topStr,
|
||||
bottomStr,
|
||||
modifier = Modifier.clickable(onClick = onClick)
|
||||
.padding(top = paddingTop, bottom = paddingBottom, start = 20.dp, end = 20.dp),
|
||||
context = context
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
package com.linx.playAndroid.composable
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyListState
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.material.Button
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.paging.compose.LazyPagingItems
|
||||
import androidx.paging.compose.collectAsLazyPagingItems
|
||||
import com.linx.common.baseData.Nav
|
||||
import com.linx.playAndroid.KeyNavigationRoute
|
||||
import com.linx.playAndroid.model.NaviData
|
||||
import com.linx.playAndroid.model.SystemData
|
||||
import com.linx.playAndroid.model.UserArticleListData
|
||||
import com.linx.playAndroid.public.*
|
||||
import com.linx.playAndroid.viewModel.SquareViewModel
|
||||
|
||||
/**
|
||||
* 广场页面
|
||||
*/
|
||||
@Composable
|
||||
fun SquareCompose(navHostController: NavHostController) {
|
||||
|
||||
val squareViewModel: SquareViewModel = viewModel()
|
||||
|
||||
val userArticleListData = squareViewModel.userArticleListData.collectAsLazyPagingItems()
|
||||
|
||||
val questionAnswerData = squareViewModel.questionAnswerData.collectAsLazyPagingItems()
|
||||
|
||||
val systemData = squareViewModel.systemData.observeAsState()
|
||||
|
||||
val naviData = squareViewModel.naviData.observeAsState()
|
||||
|
||||
when (Nav.squareTopBarIndex.value) {
|
||||
//广场页面
|
||||
0 -> {
|
||||
SquareAndQuestionComposable(
|
||||
navHostController,
|
||||
userArticleListData,
|
||||
squareViewModel,
|
||||
squareViewModel.squareIndexState
|
||||
)
|
||||
}
|
||||
//每日一问页面
|
||||
1 -> SquareAndQuestionComposable(
|
||||
navHostController,
|
||||
questionAnswerData,
|
||||
squareViewModel,
|
||||
squareViewModel.questionIndexState
|
||||
)
|
||||
//体系页面
|
||||
2 -> {
|
||||
if (systemData.value == null) squareViewModel.getSystemData()
|
||||
|
||||
SwipeRefreshContent(
|
||||
squareViewModel,
|
||||
systemData.value,
|
||||
state = squareViewModel.systemIndexState,
|
||||
noData = { squareViewModel.getSystemData() },
|
||||
) { data ->
|
||||
SystemCardItemContent(data.name ?: "", data.children)
|
||||
}
|
||||
}
|
||||
//导航页面
|
||||
else -> {
|
||||
if (naviData.value == null) squareViewModel.getNavi()
|
||||
|
||||
SwipeRefreshContent(
|
||||
squareViewModel,
|
||||
naviData.value,
|
||||
state = squareViewModel.naviIndexState,
|
||||
noData = { squareViewModel.getNavi() },
|
||||
) { data ->
|
||||
NaviCardItemContent(data.name ?: "", data.articles)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 广场和问答页面
|
||||
*/
|
||||
@Composable
|
||||
private fun SquareAndQuestionComposable(
|
||||
navHostController: NavHostController,
|
||||
listdata: LazyPagingItems<UserArticleListData>,
|
||||
squareViewModel: SquareViewModel,
|
||||
state: LazyListState = rememberLazyListState()
|
||||
) {
|
||||
//广场页面广场模块内容
|
||||
SwipeRefreshContent(
|
||||
squareViewModel,
|
||||
listdata,
|
||||
state = state
|
||||
) { index, data ->
|
||||
data.apply {
|
||||
HomeCardItemContent(
|
||||
getAuthor(author, shareUser),
|
||||
fresh,
|
||||
false,
|
||||
niceDate ?: "刚刚",
|
||||
title ?: "",
|
||||
superChapterName ?: "未知",
|
||||
collect
|
||||
) {
|
||||
navHostController.navigate("${KeyNavigationRoute.WEBVIEW.route}?url=$link")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 体系页面卡片的内容
|
||||
*/
|
||||
@Composable
|
||||
private fun SystemCardItemContent(title: String, list: List<SystemData.Children?>?) {
|
||||
|
||||
Column(
|
||||
//内边距
|
||||
modifier = Modifier.fillMaxSize()
|
||||
.padding(top = 6.dp, bottom = 6.dp, start = 10.dp, end = 10.dp)
|
||||
) {
|
||||
|
||||
Text(
|
||||
title,
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = 17.sp,
|
||||
color = MaterialTheme.colors.secondaryVariant
|
||||
)
|
||||
|
||||
//标签
|
||||
LabelCustom(itemGap = FlowBoxGap(6.dp)) {
|
||||
list?.forEach { data ->
|
||||
Button(onClick = {}) { Text(data?.name ?: "") }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 体系页面卡片的内容
|
||||
*/
|
||||
@Composable
|
||||
private fun NaviCardItemContent(title: String, list: List<NaviData.Article?>?) {
|
||||
|
||||
Column(
|
||||
//内边距
|
||||
modifier = Modifier.fillMaxSize()
|
||||
.padding(top = 6.dp, bottom = 6.dp, start = 10.dp, end = 10.dp)
|
||||
) {
|
||||
|
||||
Text(
|
||||
title,
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = 17.sp,
|
||||
color = MaterialTheme.colors.secondaryVariant
|
||||
)
|
||||
|
||||
//标签
|
||||
LabelCustom(itemGap = FlowBoxGap(start = 0.dp, end = 4.dp, top = 4.dp, bottom = 4.dp)) {
|
||||
|
||||
//没有标签的时候
|
||||
if (list == null || list.isEmpty()) {
|
||||
Text("暂无", fontWeight = FontWeight.Light)
|
||||
return@LabelCustom
|
||||
}
|
||||
|
||||
list.forEachIndexed { index, article ->
|
||||
Button(onClick = {}) { Text(article?.title ?: "") }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.linx.playAndroid.composable
|
||||
|
||||
import android.content.Context
|
||||
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
|
||||
import android.webkit.WebView
|
||||
import android.widget.LinearLayout
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ArrowBack
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import androidx.navigation.NavHostController
|
||||
import com.linx.playAndroid.public.AppBar
|
||||
import com.linx.playAndroid.public.BaseScreen
|
||||
|
||||
/**
|
||||
* H5页面
|
||||
*/
|
||||
@Composable
|
||||
fun WebViewCompose(navHostController: NavHostController, url: String) {
|
||||
|
||||
BaseScreen {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
|
||||
AppBar("玩Android", leftIcon = Icons.Default.ArrowBack, onLeftClick = {
|
||||
navHostController.navigateUp()
|
||||
})
|
||||
|
||||
AndroidView({ context: Context ->
|
||||
WebView(context).apply {
|
||||
layoutParams = LinearLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)
|
||||
}
|
||||
}, update = {
|
||||
//update方法是一个callback, inflate之后会执行, 读取的状态state值变化后也会被执行
|
||||
it.apply {
|
||||
loadUrl(url)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.linx.playAndroid.initializer
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import androidx.startup.Initializer
|
||||
import com.tencent.mmkv.MMKV
|
||||
|
||||
/**
|
||||
* 初始化MMKV
|
||||
*/
|
||||
class MmkvIniaializer: Initializer<String> {
|
||||
|
||||
override fun create(context: Context): String {
|
||||
Log.d("初始化", "初始化MMKV")
|
||||
val rootDir: String = MMKV.initialize(context)
|
||||
Log.d("初始化", "MMKV根目录:$rootDir")
|
||||
return rootDir
|
||||
}
|
||||
|
||||
//是否需要在?之后初始化
|
||||
override fun dependencies(): List<Class<out Initializer<*>>> {
|
||||
return emptyList()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.linx.playAndroid.initializer
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import androidx.startup.Initializer
|
||||
import com.linx.net.base.NetUrl
|
||||
import com.linx.net.base.ServiceCreator
|
||||
|
||||
class NetWorkInitializer: Initializer<Boolean> {
|
||||
|
||||
override fun create(context: Context): Boolean {
|
||||
Log.d("初始化", "初始化Retrofit2")
|
||||
ServiceCreator.initConfig(NetUrl.BASE_URL)
|
||||
return true
|
||||
}
|
||||
|
||||
//是否需要在?之后初始化
|
||||
override fun dependencies(): List<Class<out Initializer<*>>> {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.linx.playAndroid.initializer
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import androidx.startup.Initializer
|
||||
import com.linx.net.widget.DataStoreUtils
|
||||
|
||||
/**
|
||||
* 初始化工具类
|
||||
*/
|
||||
class WidgetInitializer: Initializer<Boolean> {
|
||||
|
||||
override fun create(context: Context): Boolean {
|
||||
Log.i("初始化", "初始化DataStore")
|
||||
DataStoreUtils.init(context)
|
||||
return true
|
||||
}
|
||||
|
||||
override fun dependencies(): List<Class<out Initializer<*>>> = emptyList()
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package com.linx.playAndroid.model
|
||||
|
||||
import androidx.annotation.Keep
|
||||
|
||||
/**
|
||||
* 首页轮播图
|
||||
*/
|
||||
@Keep
|
||||
data class HomeBannerData(
|
||||
val desc: String?,
|
||||
val id: Int,
|
||||
//图片链接
|
||||
val imagePath: String?,
|
||||
val isVisible: Int,
|
||||
val order: Int,
|
||||
val title: String?,
|
||||
val type: Int,
|
||||
//webView链接
|
||||
val url: String?
|
||||
)
|
||||
|
||||
/**
|
||||
* 首页列表数据
|
||||
*/
|
||||
@Keep
|
||||
data class ArticleListData(
|
||||
val apkLink: String?,
|
||||
val audit: Int,
|
||||
//作者
|
||||
val author: String?,
|
||||
val canEdit: Boolean,
|
||||
val chapterId: Int,
|
||||
val chapterName: String?,
|
||||
//是否收藏
|
||||
val collect: Boolean,
|
||||
val courseId: Int,
|
||||
val desc: String?,
|
||||
val descMd: String?,
|
||||
val envelopePic: String?,
|
||||
//是否是最新的
|
||||
val fresh: Boolean,
|
||||
val host: String?,
|
||||
val id: Int,
|
||||
//链接
|
||||
val link: String?,
|
||||
val niceDate: String?,
|
||||
val niceShareDate: String?,
|
||||
val origin: String?,
|
||||
val prefix: String?,
|
||||
val projectLink: String?,
|
||||
//发布时间
|
||||
val publishTime: Long,
|
||||
val realSuperChapterId: Int,
|
||||
val selfVisible: Int,
|
||||
val shareDate: Long,
|
||||
//分享用户
|
||||
val shareUser: String?,
|
||||
val superChapterId: Int,
|
||||
//来源渠道名
|
||||
val superChapterName: String?,
|
||||
val tags: List<Tag?>?,
|
||||
//标题
|
||||
val title: String?,
|
||||
val type: Int,
|
||||
val userId: Int,
|
||||
val visible: Int,
|
||||
val zan: Int
|
||||
) {
|
||||
@Keep
|
||||
data class Tag(
|
||||
val name: String?,
|
||||
val url: String?
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 轮播图数据
|
||||
*/
|
||||
@Keep
|
||||
data class BannerData(
|
||||
val imageUrl: String,
|
||||
val linkUrl: String
|
||||
)
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.linx.playAndroid.model
|
||||
|
||||
import androidx.annotation.Keep
|
||||
|
||||
/**
|
||||
* 积分排行
|
||||
*/
|
||||
@Keep
|
||||
data class CoinRankData(
|
||||
val coinCount: Int,
|
||||
val level: Int,
|
||||
val nickname: String?,
|
||||
val rank: String?,
|
||||
val userId: Int,
|
||||
val username: String?
|
||||
)
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.linx.playAndroid.model
|
||||
|
||||
import androidx.annotation.Keep
|
||||
|
||||
/**
|
||||
* 个人积分数据
|
||||
*/
|
||||
@Keep
|
||||
data class UserInfoIntegralData(
|
||||
val coinCount: Int,
|
||||
val level: Int,
|
||||
val nickname: String?,
|
||||
val rank: String?,
|
||||
val userId: Int,
|
||||
val username: String?
|
||||
)
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.linx.playAndroid.model
|
||||
|
||||
import androidx.annotation.Keep
|
||||
|
||||
/**
|
||||
* 我的收藏列表数据
|
||||
*/
|
||||
@Keep
|
||||
data class MyCollectData(
|
||||
val author: String?,
|
||||
val chapterId: Int,
|
||||
val chapterName: String?,
|
||||
val courseId: Int,
|
||||
val desc: String?,
|
||||
val envelopePic: String?,
|
||||
val id: Int,
|
||||
val link: String?,
|
||||
val niceDate: String?,
|
||||
val origin: String?,
|
||||
val originId: Int,
|
||||
val publishTime: Long,
|
||||
val title: String?,
|
||||
val userId: Int,
|
||||
val visible: Int,
|
||||
val zan: Int
|
||||
)
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.linx.playAndroid.model
|
||||
import androidx.annotation.Keep
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
/**
|
||||
* 我的文章页面的数据model
|
||||
*/
|
||||
@Keep
|
||||
data class MyShareArticlesListData<T>(
|
||||
val `data`: Data<T>,
|
||||
val errorCode: Int,
|
||||
val errorMsg: String?
|
||||
) {
|
||||
@Keep
|
||||
data class Data<T>(
|
||||
val coinInfo: CoinInfo?,
|
||||
val shareArticles: ShareArticles<T>
|
||||
) {
|
||||
@Keep
|
||||
data class CoinInfo(
|
||||
val coinCount: Int,
|
||||
val level: Int,
|
||||
val nickname: String?,
|
||||
val rank: String?,
|
||||
val userId: Int,
|
||||
val username: String?
|
||||
)
|
||||
|
||||
@Keep
|
||||
data class ShareArticles<T>(
|
||||
val curPage: Int,
|
||||
val datas: List<T>,
|
||||
val offset: Int,
|
||||
val over: Boolean,
|
||||
val pageCount: Int,
|
||||
val size: Int,
|
||||
val total: Int
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package com.linx.playAndroid.model
|
||||
|
||||
import androidx.annotation.Keep
|
||||
|
||||
/**
|
||||
* 项目页面的指示条
|
||||
*/
|
||||
@Keep
|
||||
data class ProjectTreeData(
|
||||
val children: List<Any?>?,
|
||||
val courseId: Int,
|
||||
//该id在获取该分类下项目时需要用到
|
||||
val id: Int,
|
||||
//分类名称
|
||||
val name: String?,
|
||||
val order: Int,
|
||||
val parentChapterId: Int,
|
||||
val userControlSetTop: Boolean,
|
||||
val visible: Int
|
||||
)
|
||||
|
||||
/**
|
||||
* 项目列表数据
|
||||
*/
|
||||
@Keep
|
||||
data class ProjectListData(
|
||||
val apkLink: String?,
|
||||
val audit: Int,
|
||||
val author: String?,
|
||||
val canEdit: Boolean,
|
||||
val chapterId: Int,
|
||||
val chapterName: String?,
|
||||
val collect: Boolean,
|
||||
val courseId: Int,
|
||||
val desc: String?,
|
||||
val descMd: String?,
|
||||
val envelopePic: String?,
|
||||
val fresh: Boolean,
|
||||
val host: String?,
|
||||
val id: Int,
|
||||
val link: String?,
|
||||
val niceDate: String?,
|
||||
val niceShareDate: String?,
|
||||
val origin: String?,
|
||||
val prefix: String?,
|
||||
val projectLink: String?,
|
||||
val publishTime: Long,
|
||||
val realSuperChapterId: Int,
|
||||
val selfVisible: Int,
|
||||
val shareDate: Long,
|
||||
val shareUser: String?,
|
||||
val superChapterId: Int,
|
||||
val superChapterName: String?,
|
||||
val tags: List<Tag?>?,
|
||||
val title: String?,
|
||||
val type: Int,
|
||||
val userId: Int,
|
||||
val visible: Int,
|
||||
val zan: Int
|
||||
) {
|
||||
@Keep
|
||||
data class Tag(
|
||||
val name: String?,
|
||||
val url: String?
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package com.linx.playAndroid.model
|
||||
|
||||
import androidx.annotation.Keep
|
||||
|
||||
/**
|
||||
* 公众号列表数据
|
||||
*/
|
||||
@Keep
|
||||
data class PublicNumChapterData(
|
||||
val children: List<Any?>?,
|
||||
val courseId: Int,
|
||||
val id: Int,
|
||||
val name: String?,
|
||||
val order: Int,
|
||||
val parentChapterId: Int,
|
||||
val userControlSetTop: Boolean,
|
||||
val visible: Int
|
||||
)
|
||||
|
||||
/**
|
||||
* 公众号历史文章列表
|
||||
*/
|
||||
@Keep
|
||||
data class PublicNumListData(
|
||||
val apkLink: String?,
|
||||
val audit: Int,
|
||||
val author: String?,
|
||||
val canEdit: Boolean,
|
||||
val chapterId: Int,
|
||||
val chapterName: String?,
|
||||
val collect: Boolean,
|
||||
val courseId: Int,
|
||||
val desc: String?,
|
||||
val descMd: String?,
|
||||
val envelopePic: String?,
|
||||
val fresh: Boolean,
|
||||
val host: String?,
|
||||
val id: Int,
|
||||
val link: String?,
|
||||
val niceDate: String?,
|
||||
val niceShareDate: String?,
|
||||
val origin: String?,
|
||||
val prefix: String?,
|
||||
val projectLink: String?,
|
||||
val publishTime: Long,
|
||||
val realSuperChapterId: Int,
|
||||
val selfVisible: Int,
|
||||
val shareDate: Long,
|
||||
val shareUser: String?,
|
||||
val superChapterId: Int,
|
||||
val superChapterName: String?,
|
||||
val tags: List<Tag?>?,
|
||||
val title: String?,
|
||||
val type: Int,
|
||||
val userId: Int,
|
||||
val visible: Int,
|
||||
val zan: Int
|
||||
) {
|
||||
@Keep
|
||||
data class Tag(
|
||||
val name: String?,
|
||||
val url: String?
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.linx.playAndroid.model
|
||||
|
||||
import androidx.annotation.Keep
|
||||
|
||||
/**
|
||||
* 注册
|
||||
*/
|
||||
@Keep
|
||||
data class RegisterDaat(
|
||||
val admin: Boolean,
|
||||
val chapterTops: List<Any?>?,
|
||||
val coinCount: Int,
|
||||
val collectIds: List<Any?>?,
|
||||
val email: String?,
|
||||
val icon: String?,
|
||||
val id: Int,
|
||||
val nickname: String?,
|
||||
val password: String?,
|
||||
val publicName: String?,
|
||||
val token: String?,
|
||||
val type: Int,
|
||||
val username: String?
|
||||
)
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.linx.playAndroid.model
|
||||
|
||||
import androidx.annotation.Keep
|
||||
|
||||
/**
|
||||
* 热词
|
||||
*/
|
||||
@Keep
|
||||
data class HotKeyData(
|
||||
val id: Int,
|
||||
//链接
|
||||
val link: String?,
|
||||
//关键字
|
||||
val name: String?,
|
||||
val order: Int,
|
||||
val visible: Int
|
||||
)
|
||||
@@ -0,0 +1,121 @@
|
||||
package com.linx.playAndroid.model
|
||||
|
||||
import androidx.annotation.Keep
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
|
||||
/**
|
||||
* 广场/ 问答数据
|
||||
*/
|
||||
@Keep
|
||||
data class UserArticleListData(
|
||||
val apkLink: String?,
|
||||
val audit: Int,
|
||||
val author: String?,
|
||||
val canEdit: Boolean,
|
||||
val chapterId: Int,
|
||||
val chapterName: String?,
|
||||
val collect: Boolean,
|
||||
val courseId: Int,
|
||||
val desc: String?,
|
||||
val descMd: String?,
|
||||
val envelopePic: String?,
|
||||
val fresh: Boolean,
|
||||
val host: String?,
|
||||
val id: Int,
|
||||
val link: String?,
|
||||
val niceDate: String?,
|
||||
val niceShareDate: String?,
|
||||
val origin: String?,
|
||||
val prefix: String?,
|
||||
val projectLink: String?,
|
||||
val publishTime: Long,
|
||||
val realSuperChapterId: Int,
|
||||
val selfVisible: Int,
|
||||
val shareDate: Long,
|
||||
val shareUser: String?,
|
||||
val superChapterId: Int,
|
||||
val superChapterName: String?,
|
||||
val tags: List<Any?>?,
|
||||
val title: String?,
|
||||
val type: Int,
|
||||
val userId: Int,
|
||||
val visible: Int,
|
||||
val zan: Int
|
||||
)
|
||||
|
||||
/**
|
||||
* 体系数据
|
||||
*/
|
||||
@Keep
|
||||
data class SystemData(
|
||||
val children: List<Children?>?,
|
||||
val courseId: Int,
|
||||
val id: Int,
|
||||
//体系大项名称
|
||||
val name: String?,
|
||||
val order: Int,
|
||||
val parentChapterId: Int,
|
||||
val userControlSetTop: Boolean,
|
||||
val visible: Int
|
||||
) {
|
||||
@Keep
|
||||
data class Children(
|
||||
val children: List<Any?>?,
|
||||
val courseId: Int,
|
||||
val id: Int,
|
||||
//体系子项名称
|
||||
val name: String?,
|
||||
val order: Int,
|
||||
val parentChapterId: Int,
|
||||
val userControlSetTop: Boolean,
|
||||
val visible: Int
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 导航数据
|
||||
*/
|
||||
@Keep
|
||||
data class NaviData(
|
||||
val articles: List<Article?>?,
|
||||
val cid: Int,
|
||||
val name: String?
|
||||
) {
|
||||
@Keep
|
||||
data class Article(
|
||||
val apkLink: String?,
|
||||
val audit: Int,
|
||||
val author: String?,
|
||||
val canEdit: Boolean,
|
||||
val chapterId: Int,
|
||||
val chapterName: String?,
|
||||
val collect: Boolean,
|
||||
val courseId: Int,
|
||||
val desc: String?,
|
||||
val descMd: String?,
|
||||
val envelopePic: String?,
|
||||
val fresh: Boolean,
|
||||
val host: String?,
|
||||
val id: Int,
|
||||
val link: String?,
|
||||
val niceDate: String?,
|
||||
val niceShareDate: String?,
|
||||
val origin: String?,
|
||||
val prefix: String?,
|
||||
val projectLink: String?,
|
||||
val publishTime: Long,
|
||||
val realSuperChapterId: Int,
|
||||
val selfVisible: Int,
|
||||
val shareDate: Any?,
|
||||
val shareUser: String?,
|
||||
val superChapterId: Int,
|
||||
val superChapterName: String?,
|
||||
val tags: List<Any?>?,
|
||||
val title: String?,
|
||||
val type: Int,
|
||||
val userId: Int,
|
||||
val visible: Int,
|
||||
val zan: Int
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
package com.linx.playAndroid.public
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.material.ripple.rememberRipple
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.text.input.ImeAction
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.airbnb.lottie.LottieComposition
|
||||
import com.airbnb.lottie.compose.LottieAnimation
|
||||
import com.airbnb.lottie.compose.LottieConstants
|
||||
import com.airbnb.lottie.compose.animateLottieCompositionAsState
|
||||
|
||||
/**
|
||||
* 标题栏
|
||||
*/
|
||||
@Composable
|
||||
fun AppBar(
|
||||
title: String = "",
|
||||
elevation: Dp = AppBarDefaults.TopAppBarElevation,
|
||||
leftIcon: ImageVector? = null,
|
||||
rightIcon: ImageVector? = null,
|
||||
onLeftClick: () -> Unit = {},
|
||||
onRightClick: () -> Unit = {}
|
||||
) {
|
||||
|
||||
TopAppBar(
|
||||
backgroundColor = MaterialTheme.colors.primary,
|
||||
modifier = Modifier.height(54.dp),
|
||||
elevation = elevation,
|
||||
) {
|
||||
|
||||
Box(
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
//左边图标
|
||||
if (leftIcon == null)
|
||||
Spacer(modifier = Modifier.size(10.dp))
|
||||
else
|
||||
Icon(leftIcon, contentDescription = null, modifier = Modifier.clickable {
|
||||
onLeftClick()
|
||||
}.padding(start = 10.dp), tint = MaterialTheme.colors.background)
|
||||
|
||||
//右边图标
|
||||
rightIcon?.let {
|
||||
Icon(rightIcon, contentDescription = null, Modifier.clickable {
|
||||
onRightClick()
|
||||
}.padding(end = 10.dp), tint = MaterialTheme.colors.background)
|
||||
}
|
||||
}
|
||||
|
||||
//标题文字
|
||||
Text(
|
||||
text = title,
|
||||
textAlign = TextAlign.Center,
|
||||
fontSize = 18.sp,
|
||||
color = MaterialTheme.colors.background
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 带动画的标题栏
|
||||
*/
|
||||
@Composable
|
||||
fun AppBar(
|
||||
title: String = "",
|
||||
elevation: Dp = AppBarDefaults.TopAppBarElevation,
|
||||
leftIcon: ImageVector? = null,
|
||||
rightLottie: LottieComposition? = null,
|
||||
onLeftClick: () -> Unit = {},
|
||||
onRightClick: () -> Unit,
|
||||
) {
|
||||
|
||||
TopAppBar(
|
||||
backgroundColor = MaterialTheme.colors.primary,
|
||||
modifier = Modifier.height(54.dp),
|
||||
elevation = elevation,
|
||||
) {
|
||||
|
||||
Box(
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
//左边图标
|
||||
if (leftIcon == null)
|
||||
Spacer(modifier = Modifier.size(10.dp))
|
||||
else
|
||||
Icon(leftIcon, contentDescription = null, modifier = Modifier.clickable {
|
||||
onLeftClick()
|
||||
}.padding(start = 10.dp), tint = MaterialTheme.colors.background)
|
||||
|
||||
//右边动画
|
||||
rightLottie?.let {
|
||||
val lottieAnimationState by animateLottieCompositionAsState(
|
||||
//动画资源句柄
|
||||
composition = rightLottie,
|
||||
//迭代次数
|
||||
iterations = LottieConstants.IterateForever,
|
||||
//动画播放状态
|
||||
isPlaying = true,
|
||||
//动画速度状态
|
||||
speed = 1f,
|
||||
//暂停后重新播放动画是否重新播放
|
||||
restartOnPlay = true
|
||||
)
|
||||
|
||||
LottieAnimation(
|
||||
rightLottie,
|
||||
lottieAnimationState,
|
||||
modifier = Modifier.clickable(
|
||||
onClick = onRightClick,
|
||||
interactionSource = MutableInteractionSource(),
|
||||
indication = null
|
||||
).size(60.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
//标题文字
|
||||
Text(
|
||||
text = title,
|
||||
textAlign = TextAlign.Center,
|
||||
fontSize = 18.sp,
|
||||
color = MaterialTheme.colors.background
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 带输入框的标题栏
|
||||
*/
|
||||
@Composable
|
||||
fun AppBar(
|
||||
textFieldValue: TextFieldValue,
|
||||
onValueChange: (changeValue: TextFieldValue) -> Unit,
|
||||
elevation: Dp = AppBarDefaults.TopAppBarElevation,
|
||||
leftIcon: ImageVector? = null,
|
||||
rightIcon: ImageVector? = null,
|
||||
onLeftClick: () -> Unit = {},
|
||||
onRightClick: () -> Unit = {}
|
||||
) {
|
||||
|
||||
TopAppBar(
|
||||
backgroundColor = MaterialTheme.colors.primary,
|
||||
modifier = Modifier.height(54.dp),
|
||||
elevation = elevation,
|
||||
) {
|
||||
|
||||
Box(
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
//左边图标
|
||||
if (leftIcon == null)
|
||||
Spacer(modifier = Modifier.size(10.dp))
|
||||
else
|
||||
Icon(leftIcon, contentDescription = null, modifier = Modifier.clickable {
|
||||
onLeftClick()
|
||||
}.padding(start = 10.dp), tint = MaterialTheme.colors.background)
|
||||
|
||||
//右边图标
|
||||
rightIcon?.let {
|
||||
Icon(rightIcon, contentDescription = null, Modifier.clickable {
|
||||
onRightClick()
|
||||
}.padding(end = 10.dp), tint = MaterialTheme.colors.background)
|
||||
}
|
||||
}
|
||||
|
||||
//标题文字
|
||||
TextField(
|
||||
value = textFieldValue,
|
||||
onValueChange = { changeValue ->
|
||||
onValueChange(changeValue)
|
||||
},
|
||||
//单行
|
||||
singleLine = true,
|
||||
colors = TextFieldDefaults.textFieldColors(
|
||||
//文字
|
||||
textColor = MaterialTheme.colors.background,
|
||||
//背景
|
||||
backgroundColor = MaterialTheme.colors.primary,
|
||||
//光标
|
||||
cursorColor = MaterialTheme.colors.background,
|
||||
//当输入框处于/不处于焦点时,底部指示器的颜色
|
||||
focusedIndicatorColor = MaterialTheme.colors.primary,
|
||||
unfocusedIndicatorColor = MaterialTheme.colors.primary
|
||||
),
|
||||
label = {
|
||||
Text("输入关键字搜索", color = Color.Gray)
|
||||
},
|
||||
keyboardOptions = KeyboardOptions(
|
||||
//图标
|
||||
imeAction = ImeAction.Search,
|
||||
),
|
||||
keyboardActions = KeyboardActions(
|
||||
onSearch = {
|
||||
onRightClick()
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
package com.linx.playAndroid.public
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.annotation.Keep
|
||||
import androidx.compose.animation.animateContentSize
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.gestures.*
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.input.pointer.*
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import coil.annotation.ExperimentalCoilApi
|
||||
import coil.compose.rememberImagePainter
|
||||
import com.google.accompanist.pager.ExperimentalPagerApi
|
||||
import com.google.accompanist.pager.HorizontalPager
|
||||
import com.google.accompanist.pager.rememberPagerState
|
||||
import com.linx.playAndroid.R
|
||||
import com.linx.playAndroid.model.BannerData
|
||||
import kotlinx.coroutines.delay
|
||||
|
||||
/**
|
||||
* 轮播图
|
||||
* [timeMillis] 停留时间
|
||||
* [loadImage] 加载中显示的布局
|
||||
* [indicatorAlignment] 指示点的的位置,默认是轮播图下方的中间,带一点padding
|
||||
* [onClick] 轮播图点击事件
|
||||
*/
|
||||
@ExperimentalCoilApi
|
||||
@ExperimentalPagerApi
|
||||
@Composable
|
||||
fun Banner(
|
||||
list: List<BannerData>?,
|
||||
timeMillis: Long = 3000,
|
||||
@DrawableRes loadImage: Int = R.mipmap.ic_web,
|
||||
indicatorAlignment: Alignment = Alignment.BottomCenter,
|
||||
onClick: (link: String) -> Unit = {}
|
||||
) {
|
||||
|
||||
Box(
|
||||
modifier = Modifier.background(MaterialTheme.colors.background).fillMaxWidth()
|
||||
.height(220.dp)
|
||||
) {
|
||||
|
||||
if (list == null) {
|
||||
//加载中的图片
|
||||
Image(
|
||||
painterResource(loadImage),
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentDescription = null,
|
||||
contentScale = ContentScale.Crop
|
||||
)
|
||||
} else {
|
||||
val pagerState = rememberPagerState(
|
||||
//总页数
|
||||
pageCount = list.size,
|
||||
//预加载的个数
|
||||
initialOffscreenLimit = 1,
|
||||
//是否无限循环
|
||||
infiniteLoop = true,
|
||||
//初始页面
|
||||
initialPage = 0
|
||||
)
|
||||
|
||||
//监听动画执行
|
||||
var executeChangePage by remember { mutableStateOf(false) }
|
||||
var currentPageIndex = 0
|
||||
|
||||
//自动滚动
|
||||
LaunchedEffect(pagerState.currentPage, executeChangePage) {
|
||||
if (pagerState.pageCount > 0) {
|
||||
delay(timeMillis)
|
||||
//这里直接+1就可以循环,前提是infiniteLoop == true
|
||||
pagerState.animateScrollToPage(pagerState.currentPage + 1)
|
||||
}
|
||||
}
|
||||
|
||||
HorizontalPager(
|
||||
state = pagerState,
|
||||
modifier = Modifier.pointerInput(pagerState.currentPage) {
|
||||
awaitPointerEventScope {
|
||||
while (true) {
|
||||
//PointerEventPass.Initial - 本控件优先处理手势,处理后再交给子组件
|
||||
val event = awaitPointerEvent(PointerEventPass.Initial)
|
||||
//获取到第一根按下的手指
|
||||
val dragEvent = event.changes.firstOrNull()
|
||||
when {
|
||||
//当前移动手势是否已被消费
|
||||
dragEvent!!.positionChangeConsumed() -> {
|
||||
return@awaitPointerEventScope
|
||||
}
|
||||
//是否已经按下(忽略按下手势已消费标记)
|
||||
dragEvent.changedToDownIgnoreConsumed() -> {
|
||||
//记录下当前的页面索引值
|
||||
currentPageIndex = pagerState.currentPage
|
||||
}
|
||||
//是否已经抬起(忽略按下手势已消费标记)
|
||||
dragEvent.changedToUpIgnoreConsumed() -> {
|
||||
//当页面没有任何滚动/动画的时候pagerState.targetPage为null,这个时候是单击事件
|
||||
if (pagerState.targetPage == null) return@awaitPointerEventScope
|
||||
//当pageCount大于1,且手指抬起时如果页面没有改变,就手动触发动画
|
||||
if (currentPageIndex == pagerState.currentPage && pagerState.pageCount > 1) {
|
||||
executeChangePage = !executeChangePage
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.clickable(onClick = { onClick(list[pagerState.currentPage].linkUrl) })
|
||||
.fillMaxSize(),
|
||||
) { page ->
|
||||
Image(
|
||||
painter = rememberImagePainter(list[page].imageUrl),
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentScale = ContentScale.Crop,
|
||||
contentDescription = null
|
||||
)
|
||||
}
|
||||
|
||||
Box(
|
||||
modifier = Modifier.align(indicatorAlignment)
|
||||
.padding(bottom = 6.dp, start = 6.dp, end = 6.dp)
|
||||
) {
|
||||
|
||||
//指示点
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
for (i in list.indices) {
|
||||
//大小
|
||||
var size by remember { mutableStateOf(5.dp) }
|
||||
size = if (pagerState.currentPage == i) 7.dp else 5.dp
|
||||
|
||||
//颜色
|
||||
val color =
|
||||
if (pagerState.currentPage == i) MaterialTheme.colors.primary else Color.Gray
|
||||
|
||||
Box(
|
||||
modifier = Modifier.clip(CircleShape).background(color)
|
||||
//当size改变的时候以动画的形式改变
|
||||
.animateContentSize().size(size)
|
||||
)
|
||||
//指示点间的间隔
|
||||
if (i != list.lastIndex) Spacer(
|
||||
modifier = Modifier.height(0.dp).width(4.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.linx.playAndroid.public
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.google.accompanist.insets.navigationBarsHeight
|
||||
import com.google.accompanist.insets.statusBarsHeight
|
||||
|
||||
/**
|
||||
* 在不想使用透明状态栏/导航栏的时候
|
||||
* 用这个作为基础的布局可以让内容不被状态栏/导航栏遮挡
|
||||
*/
|
||||
@Composable
|
||||
fun BaseScreen(content: @Composable () -> Unit) {
|
||||
|
||||
Column(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
|
||||
//内容不挡住状态栏 如果不设置颜色这里会自己适配,但可能产生闪烁
|
||||
Spacer(modifier = Modifier.background(MaterialTheme.colors.primary).statusBarsHeight().fillMaxWidth())
|
||||
|
||||
content()
|
||||
|
||||
//内容不挡住导航栏 如果不设置颜色这里会自己适配,但可能产生闪烁
|
||||
Spacer(modifier = Modifier.background(MaterialTheme.colors.primary).navigationBarsHeight().fillMaxWidth())
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package com.linx.playAndroid.public
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.offset
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.navigation.NavGraph.Companion.findStartDestination
|
||||
import androidx.navigation.NavHostController
|
||||
import com.linx.common.baseData.Nav
|
||||
|
||||
//底部导航栏列表
|
||||
val items = listOf(
|
||||
Nav.BottomNavScreen.HomeScreen,
|
||||
Nav.BottomNavScreen.ProjectScreen,
|
||||
Nav.BottomNavScreen.SquareScreen,
|
||||
Nav.BottomNavScreen.PublicNumScreen,
|
||||
Nav.BottomNavScreen.LearnScreen,
|
||||
Nav.BottomNavScreen.MineScreen
|
||||
)
|
||||
|
||||
/**
|
||||
* 底部导航栏
|
||||
*/
|
||||
@Composable
|
||||
fun BottomNavBar(
|
||||
bottomNavScreen: Nav.BottomNavScreen,
|
||||
navHostController: NavHostController
|
||||
) {
|
||||
BottomNavigation(
|
||||
backgroundColor = MaterialTheme.colors.primary
|
||||
) {
|
||||
items.forEach { bottomNavScreenItem: Nav.BottomNavScreen ->
|
||||
//记录动画
|
||||
val translationY = remember { androidx.compose.animation.core.Animatable(0F) }
|
||||
|
||||
//开启线程执行动画
|
||||
LaunchedEffect(bottomNavScreen) {
|
||||
if (bottomNavScreenItem == bottomNavScreen)
|
||||
translationY.animateTo(-4F)
|
||||
else translationY.animateTo(0F)
|
||||
}
|
||||
|
||||
BottomNavigationItem(
|
||||
icon = {
|
||||
Icon(
|
||||
painterResource(bottomNavScreenItem.id),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.size(24.dp).offset(
|
||||
0.dp,
|
||||
//上下偏移
|
||||
translationY.value.dp
|
||||
),
|
||||
)
|
||||
},
|
||||
//选中选项的颜色 (text\icon\波纹)
|
||||
selectedContentColor = MaterialTheme.colors.primary,
|
||||
//未选中选项的颜色 (text\icon\波纹)
|
||||
unselectedContentColor = MaterialTheme.colors.secondaryVariant,
|
||||
label = { Text(stringResource(bottomNavScreenItem.resourceId)) },
|
||||
selected = bottomNavScreen == bottomNavScreenItem,
|
||||
onClick = {
|
||||
//判断是否是当前的route,如果是就不做处理
|
||||
if (bottomNavScreenItem == bottomNavScreen) {
|
||||
return@BottomNavigationItem
|
||||
}
|
||||
//记录当前的Item
|
||||
Nav.bottomNavRoute.value = bottomNavScreenItem
|
||||
|
||||
navHostController.navigate(bottomNavScreenItem.route) {
|
||||
popUpTo(navHostController.graph.findStartDestination().id) {
|
||||
saveState = true
|
||||
}
|
||||
//避免重建
|
||||
launchSingleTop = true
|
||||
//重新选择以前选择的项目时,恢复状态
|
||||
restoreState = true
|
||||
}
|
||||
},
|
||||
modifier = Modifier.background(MaterialTheme.colors.background)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package com.linx.playAndroid.public
|
||||
|
||||
import androidx.compose.foundation.BorderStroke
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.Card
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
/**
|
||||
* 首页采用的Card
|
||||
* 卡片高度可以设置
|
||||
*/
|
||||
@Composable
|
||||
fun SimpleCard(
|
||||
cardHeight: Dp = 120.dp,
|
||||
elevation: Dp = 3.dp,
|
||||
shape: RoundedCornerShape = RoundedCornerShape(6.dp),
|
||||
content: @Composable () -> Unit
|
||||
) {
|
||||
|
||||
Card(
|
||||
modifier = Modifier
|
||||
//外边距
|
||||
.padding(bottom = 5.dp, start = 8.dp, end = 8.dp, top = 5.dp).fillMaxWidth()
|
||||
.height(cardHeight),
|
||||
elevation = elevation,
|
||||
backgroundColor = MaterialTheme.colors.background,
|
||||
shape = shape,
|
||||
border = if (isSystemInDarkTheme()) BorderStroke(1.dp, Color.White) else null
|
||||
) {
|
||||
content()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 卡片高度自适应
|
||||
*/
|
||||
@Composable
|
||||
fun SimpleCard(
|
||||
elevation: Dp = 3.dp,
|
||||
shape: RoundedCornerShape = RoundedCornerShape(6.dp),
|
||||
content: @Composable () -> Unit
|
||||
) {
|
||||
|
||||
Card(
|
||||
modifier = Modifier
|
||||
//外边距
|
||||
.padding(bottom = 5.dp, start = 8.dp, end = 8.dp, top = 5.dp).fillMaxWidth(),
|
||||
elevation = elevation,
|
||||
backgroundColor = MaterialTheme.colors.background,
|
||||
shape = shape,
|
||||
border = if (isSystemInDarkTheme()) BorderStroke(1.dp, Color.White) else null
|
||||
) {
|
||||
content()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
package com.linx.playAndroid.public
|
||||
|
||||
import androidx.compose.foundation.BorderStroke
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.gestures.Orientation
|
||||
import androidx.compose.foundation.gestures.draggable
|
||||
import androidx.compose.foundation.gestures.rememberDraggableState
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.materialIcon
|
||||
import androidx.compose.material.icons.materialPath
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.Color.Companion.Red
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.unit.IntOffset
|
||||
import androidx.compose.ui.unit.dp
|
||||
import kotlin.math.roundToInt
|
||||
import kotlin.random.Random
|
||||
|
||||
/**
|
||||
* 颜色选择器
|
||||
*/
|
||||
@Composable
|
||||
fun ColorPicker(
|
||||
onColorSelected: (Color) -> Unit
|
||||
) {
|
||||
//屏幕宽度 - dp
|
||||
val screenWidth = LocalConfiguration.current.screenWidthDp.dp
|
||||
//屏幕宽度 - px
|
||||
val screenWidthInPx = with(LocalDensity.current) { screenWidth.toPx() }
|
||||
//当前颜色
|
||||
var activeColor by remember {
|
||||
mutableStateOf(Red)
|
||||
}
|
||||
|
||||
val thisPadding = 8.dp
|
||||
|
||||
//减去左右两边间隙的宽度
|
||||
val max = screenWidth - (thisPadding * 2)
|
||||
val min = 0.dp
|
||||
val (minPx, maxPx) = with(LocalDensity.current) { min.toPx() to max.toPx() }
|
||||
|
||||
//偏移距离
|
||||
var dragOffset by remember {
|
||||
mutableStateOf(0f)
|
||||
}
|
||||
|
||||
Box(modifier = Modifier.padding(thisPadding)) {
|
||||
Spacer(
|
||||
modifier = Modifier
|
||||
.height(10.dp)
|
||||
.clip(RoundedCornerShape(5.dp))
|
||||
.fillMaxWidth()
|
||||
.background(brush = colorMapGradient(screenWidthInPx))
|
||||
.align(Alignment.Center)
|
||||
)
|
||||
Icon(
|
||||
imageVector = Icons.Filled.FiberManualRecord,
|
||||
contentDescription = null,
|
||||
tint = activeColor,
|
||||
modifier = Modifier
|
||||
.offset { IntOffset(dragOffset.roundToInt(), 0) }
|
||||
.border(
|
||||
border = BorderStroke(4.dp, MaterialTheme.colors.onSurface),
|
||||
shape = CircleShape
|
||||
)
|
||||
.draggable(
|
||||
orientation = Orientation.Horizontal,
|
||||
state = rememberDraggableState { delta ->
|
||||
val newValue = dragOffset + delta
|
||||
//确保该值位于minPx - maxPx内,小于等于minPx就是minPx,大于等于maxPx就是maxPx
|
||||
dragOffset = newValue.coerceIn(minPx, maxPx)
|
||||
activeColor = getActiveColor(dragOffset, screenWidthInPx)
|
||||
onColorSelected(activeColor)
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建颜色值List
|
||||
*/
|
||||
private fun createColorMap(): List<Color> {
|
||||
val colorList = mutableListOf<Color>()
|
||||
|
||||
for (i in 0..360 step 2) {
|
||||
val randomSaturation = 90 + Random.nextFloat() * 10
|
||||
val randomLightness = 50 + Random.nextFloat() * 10
|
||||
//https://blog.csdn.net/caobin_study/article/details/81627102
|
||||
val hsv = android.graphics.Color.HSVToColor(
|
||||
floatArrayOf(
|
||||
//色调范围 0 - 360
|
||||
i.toFloat(),
|
||||
//饱和度 0-1 超过更亮
|
||||
randomSaturation,
|
||||
//亮度 0-1
|
||||
randomLightness
|
||||
)
|
||||
)
|
||||
colorList += Color(hsv)
|
||||
}
|
||||
|
||||
return colorList
|
||||
}
|
||||
|
||||
/**
|
||||
* 颜色Border
|
||||
*/
|
||||
private fun colorMapGradient(screenWidthInPx: Float) = Brush.horizontalGradient(
|
||||
colors = createColorMap(),
|
||||
startX = 0f,
|
||||
endX = screenWidthInPx
|
||||
)
|
||||
|
||||
/**
|
||||
* 获取当前颜色条的偏移距离,以解码成当前颜色值
|
||||
*/
|
||||
private fun getActiveColor(dragPosition: Float, screenWidth: Float): Color {
|
||||
val hue = (dragPosition / screenWidth) * 360f
|
||||
val randomSaturation = 90 + Random.nextFloat() * 10
|
||||
val randomLightness = 50 + Random.nextFloat() * 10
|
||||
return Color(
|
||||
android.graphics.Color.HSVToColor(
|
||||
floatArrayOf(
|
||||
hue,
|
||||
randomSaturation,
|
||||
randomLightness
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
//圆
|
||||
val Icons.Filled.FiberManualRecord: ImageVector
|
||||
get() {
|
||||
if (_fiberManualRecord != null) {
|
||||
return _fiberManualRecord!!
|
||||
}
|
||||
_fiberManualRecord = materialIcon(name = "Filled.FiberManualRecord") {
|
||||
materialPath {
|
||||
moveTo(12.0f, 12.0f)
|
||||
moveToRelative(-8.0f, 0.0f)
|
||||
arcToRelative(
|
||||
8.0f, 8.0f, 0.0f,
|
||||
isMoreThanHalf = true,
|
||||
isPositiveArc = true,
|
||||
dx1 = 16.0f,
|
||||
dy1 = 0.0f
|
||||
)
|
||||
arcToRelative(
|
||||
8.0f, 8.0f, 0.0f,
|
||||
isMoreThanHalf = true,
|
||||
isPositiveArc = true,
|
||||
dx1 = -16.0f,
|
||||
dy1 = 0.0f
|
||||
)
|
||||
}
|
||||
}
|
||||
return _fiberManualRecord!!
|
||||
}
|
||||
|
||||
private var _fiberManualRecord: ImageVector? = null
|
||||
@@ -0,0 +1,110 @@
|
||||
package com.linx.playAndroid.public
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Favorite
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import coil.compose.rememberImagePainter
|
||||
|
||||
/**
|
||||
* Card顶部部分
|
||||
*/
|
||||
@Composable
|
||||
fun TopCard(author: String, desc: String) {
|
||||
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
Text(
|
||||
text = author,
|
||||
style = MaterialTheme.typography.body1,
|
||||
color = Color.Gray
|
||||
)
|
||||
Text(
|
||||
text = desc,
|
||||
style = MaterialTheme.typography.body1,
|
||||
color = Color.Gray,
|
||||
modifier = Modifier.padding(start = 10.dp)
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Card中间部分
|
||||
*/
|
||||
@Composable
|
||||
fun CenterCard(envelopePic: String, title: String, desc: String) {
|
||||
|
||||
val painter = rememberImagePainter(data = envelopePic)
|
||||
|
||||
Image(
|
||||
painter = painter,
|
||||
contentDescription = null,
|
||||
contentScale = ContentScale.Crop,
|
||||
modifier = Modifier.fillMaxHeight().width(130.dp)
|
||||
.padding(end = 4.dp)
|
||||
)
|
||||
|
||||
Column {
|
||||
Text(
|
||||
text = title,
|
||||
style = MaterialTheme.typography.h6,
|
||||
maxLines = 2,
|
||||
fontSize = 17.sp,
|
||||
//超长以...结尾
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
|
||||
Text(
|
||||
text = desc,
|
||||
style = MaterialTheme.typography.body1,
|
||||
color = Color.Gray,
|
||||
modifier = Modifier.padding(top = 3.dp),
|
||||
maxLines = 4,
|
||||
fontSize = 14.sp,
|
||||
//超长以...结尾
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Card底部部分
|
||||
*/
|
||||
@Composable
|
||||
fun BottomCard(
|
||||
//渠道名
|
||||
chapterName: String,
|
||||
//是否收藏
|
||||
collect: Boolean,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
|
||||
Row(
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.Bottom,
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
Text(chapterName, color = Color.Gray, fontSize = 12.sp)
|
||||
//收藏
|
||||
Icon(
|
||||
Icons.Default.Favorite,
|
||||
contentDescription = null,
|
||||
tint = if (collect) Color.Red else Color.LightGray
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package com.linx.playAndroid.public
|
||||
|
||||
import androidx.compose.material.AlertDialog
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
|
||||
/**
|
||||
* 简单的提示框
|
||||
* [confirmClick] 确定按钮的点击事件
|
||||
*/
|
||||
@Composable
|
||||
fun SimpleAlertDialog(
|
||||
title: String,
|
||||
contextStr: String,
|
||||
dismissStr: String = "取消",
|
||||
confirmStr: String = "确定",
|
||||
confirmClick: () -> Unit = {},
|
||||
onDismiss: () -> Unit
|
||||
) {
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
title = {
|
||||
Text(title)
|
||||
},
|
||||
text = {
|
||||
Text(contextStr)
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = onDismiss) {
|
||||
Text(dismissStr)
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(onClick = {
|
||||
confirmClick()
|
||||
onDismiss()
|
||||
}) {
|
||||
Text(confirmStr)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 内容自定义的弹窗
|
||||
*/
|
||||
@Composable
|
||||
fun ContentCustomAlertDialog(
|
||||
title: String,
|
||||
contextStr: String = "",
|
||||
textCompose: @Composable (() -> Unit)? = null,
|
||||
dismissStr: String? = null,
|
||||
confirmStr: String? = null,
|
||||
confirmClick: () -> Unit = {},
|
||||
onDismiss: () -> Unit
|
||||
) {
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
title = {
|
||||
Text(title)
|
||||
},
|
||||
text = {
|
||||
if (textCompose == null)
|
||||
Text(contextStr)
|
||||
else textCompose()
|
||||
},
|
||||
dismissButton = {
|
||||
if (dismissStr != null) {
|
||||
TextButton(onClick = onDismiss) {
|
||||
Text(dismissStr)
|
||||
}
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
if (confirmStr != null) {
|
||||
TextButton(onClick = {
|
||||
confirmClick()
|
||||
onDismiss()
|
||||
}) {
|
||||
Text(confirmStr)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.linx.playAndroid.public
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.Button
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.linx.common.R
|
||||
|
||||
/**
|
||||
* 无数据时候的布局
|
||||
*/
|
||||
@Composable
|
||||
fun ErrorComposable(title: String = "网络不佳,请点击重试", block: () -> Unit) {
|
||||
|
||||
Column(
|
||||
modifier = Modifier.background(MaterialTheme.colors.background).fillMaxSize(),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Image(
|
||||
modifier = Modifier.size(300.dp, 180.dp),
|
||||
painter = painterResource(id = R.mipmap.ic_net_empty),
|
||||
contentDescription = "网络问题",
|
||||
contentScale = ContentScale.Crop
|
||||
)
|
||||
|
||||
Button(modifier = Modifier.padding(8.dp), onClick = block) {
|
||||
Text(title)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
package com.linx.playAndroid.public
|
||||
|
||||
import androidx.compose.foundation.BorderStroke
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Surface
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Favorite
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.linx.common.ext.transitionDate
|
||||
import com.linx.playAndroid.ui.theme.c_b66731
|
||||
|
||||
/**
|
||||
* 列表卡片内的内容
|
||||
*/
|
||||
@Composable
|
||||
fun HomeCardItemContent(
|
||||
//作者
|
||||
author: String,
|
||||
//新的内容
|
||||
fresh: Boolean,
|
||||
//是否置顶,
|
||||
stick: Boolean = false,
|
||||
//发布时间
|
||||
niceDate: String,
|
||||
//标题
|
||||
title: String,
|
||||
//来源
|
||||
superChapterName: String,
|
||||
//是否收藏
|
||||
collect: Boolean,
|
||||
//是否显示具体时间
|
||||
isSpecific: Boolean = true,
|
||||
onClick: () -> Unit = {}
|
||||
) {
|
||||
Column(
|
||||
//内边距
|
||||
modifier = Modifier.clickable(onClick = onClick).fillMaxSize()
|
||||
.padding(top = 6.dp, bottom = 6.dp, start = 10.dp, end = 10.dp)
|
||||
) {
|
||||
|
||||
//上面的控件
|
||||
TopCard(author, fresh, stick, niceDate, isSpecific)
|
||||
|
||||
//中间的控件
|
||||
CenterCard(modifier = Modifier.weight(1f, true), title)
|
||||
|
||||
//下面的控件
|
||||
BottomCard(superChapterName, collect)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 底部部分
|
||||
*/
|
||||
@Composable
|
||||
private fun BottomCard(
|
||||
//渠道名
|
||||
chapterName: String,
|
||||
//是否收藏
|
||||
collect: Boolean
|
||||
) {
|
||||
|
||||
Row(
|
||||
modifier = Modifier.padding(top = 2.dp),
|
||||
verticalAlignment = Alignment.Bottom,
|
||||
) {
|
||||
redText(chapterName)
|
||||
Spacer(modifier = Modifier.weight(1f, true))
|
||||
//收藏
|
||||
Icon(
|
||||
Icons.Default.Favorite,
|
||||
contentDescription = null,
|
||||
tint = if (collect) Color.Red else Color.LightGray
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 中间部分
|
||||
*/
|
||||
@Composable
|
||||
private fun CenterCard(modifier: Modifier = Modifier, title: String) {
|
||||
|
||||
Surface(
|
||||
modifier = modifier.padding(top = 6.dp),
|
||||
color = MaterialTheme.colors.background
|
||||
) {
|
||||
Text(
|
||||
title,
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = 17.sp,
|
||||
color = MaterialTheme.colors.secondaryVariant,
|
||||
maxLines = 2
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 上面部分
|
||||
*/
|
||||
@Composable
|
||||
private fun TopCard(
|
||||
//作者
|
||||
author: String,
|
||||
//是否是最新的
|
||||
fresh: Boolean,
|
||||
//是否置顶
|
||||
stick: Boolean,
|
||||
//发布时间
|
||||
niceDate: String,
|
||||
//是否显示具体时间
|
||||
isSpecific: Boolean
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
grayText(author)
|
||||
if (fresh) {
|
||||
borderText("最新", modifier = Modifier.padding(start = 6.dp))
|
||||
}
|
||||
if (stick) {
|
||||
borderText("置顶", modifier = Modifier.padding(start = 6.dp))
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier.weight(1f, true),
|
||||
horizontalAlignment = Alignment.End
|
||||
) {
|
||||
grayText(niceDate)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 带边框的Text
|
||||
*/
|
||||
@Composable
|
||||
private fun borderText(str: String, color: Color = Color.Red, modifier: Modifier = Modifier) {
|
||||
Text(
|
||||
str,
|
||||
modifier = modifier.border(BorderStroke(1.dp, color), RoundedCornerShape(4.dp))
|
||||
.padding(start = 3.dp, end = 3.dp),
|
||||
color = color,
|
||||
fontSize = 10.sp
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 红色文字
|
||||
*/
|
||||
@Composable
|
||||
private fun redText(str: String = "", modifier: Modifier = Modifier) {
|
||||
Text(
|
||||
str,
|
||||
modifier = modifier,
|
||||
color = c_b66731,
|
||||
fontSize = 14.sp
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 灰色文字
|
||||
*/
|
||||
@Composable
|
||||
private fun grayText(str: String = "", modifier: Modifier = Modifier) {
|
||||
Text(
|
||||
str,
|
||||
modifier = modifier,
|
||||
color = Color.LightGray,
|
||||
fontSize = 12.sp
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取发布者昵称
|
||||
*/
|
||||
fun getAuthor(author: String?, shareUser: String?): String {
|
||||
return when {
|
||||
//isNotBlank 不为空且包含空字符以外的内容
|
||||
author?.isNotBlank() == true -> author.toString()
|
||||
shareUser?.isNotBlank() == true -> shareUser.toString()
|
||||
else -> "佚名"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
package com.linx.playAndroid.public
|
||||
|
||||
import android.graphics.Point
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.layout.Layout
|
||||
import androidx.compose.ui.layout.MeasurePolicy
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
/**
|
||||
* 标签布局
|
||||
*/
|
||||
@Composable
|
||||
fun LabelCustom(
|
||||
//基础样式配置
|
||||
modifier: Modifier = Modifier,
|
||||
//上下左右的间隔
|
||||
itemGap: FlowBoxGap = DefaultFlowBoxGap,
|
||||
content: @Composable () -> Unit
|
||||
) {
|
||||
//自定义组件,需要传入content和measurePolicy
|
||||
Layout(
|
||||
content = content,
|
||||
measurePolicy = flowBoxMeasurePolicy(itemGap),
|
||||
modifier = modifier
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回一个MeasurePolicy,用于告诉自定义组件怎么布局
|
||||
* [measurables] 子组件的内容
|
||||
* [constraints] 父布局的限制
|
||||
*/
|
||||
fun flowBoxMeasurePolicy(itemGap: FlowBoxGap) = MeasurePolicy { measurables, constraints ->
|
||||
|
||||
//储存子组件的位置
|
||||
val positions = arrayListOf<Point>()
|
||||
//默认当前组件的x点的位置
|
||||
var xPosition = 0
|
||||
//默认当前组件的y点的位置
|
||||
var yPosition = 0
|
||||
//当前行高度最大的组件的高度,用于换行时设置下一行的起始yPosition
|
||||
var currentLineMaxHeight = 0
|
||||
|
||||
//批量拿到子组件的信息
|
||||
val placeables = measurables.map { placeable ->
|
||||
placeable.measure(constraints = constraints)
|
||||
}
|
||||
|
||||
//对每个子组件进行操作
|
||||
placeables.forEach { placeable ->
|
||||
//计算间隔 横向
|
||||
val horizontalGap = itemGap.start.roundToPx() + itemGap.end.roundToPx()
|
||||
//计算间隔 纵向
|
||||
val verticalGap = itemGap.top.roundToPx() + itemGap.bottom.roundToPx()
|
||||
//如果 当前组件的宽度 加上左右间隔 加上起始x位置 大于父布局的最大宽度,则换行
|
||||
if (placeable.width + horizontalGap + xPosition > constraints.maxWidth) {
|
||||
//初始化x轴位置
|
||||
xPosition = 0
|
||||
//更新y轴位置
|
||||
yPosition += currentLineMaxHeight
|
||||
}
|
||||
//添加子组件布局位置(起始值)
|
||||
positions.add(
|
||||
Point(
|
||||
xPosition + itemGap.start.roundToPx(),
|
||||
yPosition + itemGap.top.roundToPx()
|
||||
)
|
||||
)
|
||||
//记录下一个组件的起始X位置 (x点位置 + 布局宽度 + 横向间隔)
|
||||
xPosition += placeable.width + horizontalGap
|
||||
/**
|
||||
* 记录当前行最大高度
|
||||
* [coerceAtLeast] 确保currentLineMaxHeight不小于指定的minimumValue
|
||||
* (组件高度 + 纵向间隔)
|
||||
*/
|
||||
/**
|
||||
* 记录当前行最大高度
|
||||
* [coerceAtLeast] 确保currentLineMaxHeight不小于指定的minimumValue
|
||||
* (组件高度 + 纵向间隔)
|
||||
*/
|
||||
currentLineMaxHeight = currentLineMaxHeight.coerceAtLeast(placeable.height + verticalGap)
|
||||
}
|
||||
//拿到所有子组件加起来的高度
|
||||
val height = yPosition + currentLineMaxHeight
|
||||
|
||||
//设置FlowBox的宽高
|
||||
layout(constraints.maxWidth, height) {
|
||||
/**
|
||||
* 遍历位置列表,设置子组件的位置
|
||||
* [zip]返回两个集合构建的对列表
|
||||
*/
|
||||
/**
|
||||
* 遍历位置列表,设置子组件的位置
|
||||
* [zip]返回两个集合构建的对列表
|
||||
*/
|
||||
positions.zip(placeables).forEach { (point, placeable) ->
|
||||
//在其父布局的坐标系中放置一个位于(x, y)处的可选变量
|
||||
placeable.placeRelative(point.x, point.y)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//默认Button之间间隔是0.dp
|
||||
val DefaultFlowBoxGap = FlowBoxGap(0.dp)
|
||||
|
||||
//定义一个Button Item间隔数据类, 存储上下左右的间隔
|
||||
data class FlowBoxGap(val start: Dp, val top: Dp, val end: Dp, val bottom: Dp) {
|
||||
constructor(gap: Dp): this(gap, gap, gap, gap)
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
package com.linx.playAndroid.public
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.*
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.paging.compose.LazyPagingItems
|
||||
import androidx.paging.compose.itemsIndexed
|
||||
import com.google.accompanist.swiperefresh.SwipeRefresh
|
||||
import com.google.accompanist.swiperefresh.rememberSwipeRefreshState
|
||||
import com.linx.common.widget.sleepTime
|
||||
import com.linx.playAndroid.public.paging.PagingStateUtil
|
||||
|
||||
/**
|
||||
* 带刷新头的Card布局
|
||||
* LazyPagingItems<T>
|
||||
*/
|
||||
@Composable
|
||||
fun <T : Any> SwipeRefreshContent(
|
||||
viewModel: ViewModel,
|
||||
lazyPagingListData: LazyPagingItems<T>,
|
||||
cardHeight: Dp = 120.dp,
|
||||
state: LazyListState = rememberLazyListState(),
|
||||
itemContent: LazyListScope.() -> Unit = {},
|
||||
content: @Composable (index: Int, data: T) -> Unit
|
||||
) {
|
||||
Column(modifier = Modifier.fillMaxSize()) {
|
||||
|
||||
val refreshState = rememberSwipeRefreshState(false)
|
||||
|
||||
SwipeRefresh(
|
||||
state = refreshState,
|
||||
onRefresh = {
|
||||
//显示刷新头
|
||||
refreshState.isRefreshing = true
|
||||
//刷新数据
|
||||
lazyPagingListData.refresh()
|
||||
viewModel.sleepTime(3000) {
|
||||
refreshState.isRefreshing = false
|
||||
}
|
||||
}
|
||||
) {
|
||||
//列表数据
|
||||
PagingStateUtil().pagingStateUtil(lazyPagingListData, refreshState, viewModel) {
|
||||
LazyColumn(modifier = Modifier.fillMaxSize(), state = state) {
|
||||
itemContent()
|
||||
itemsIndexed(lazyPagingListData) { index, data ->
|
||||
SimpleCard(cardHeight = cardHeight) {
|
||||
content(index, data!!)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 带刷新头的Card布局
|
||||
* LazyPagingItems<T>
|
||||
* Card高度自适应
|
||||
*/
|
||||
@Composable
|
||||
fun <T : Any> SwipeRefreshContent(
|
||||
viewModel: ViewModel,
|
||||
listData: List<T>?,
|
||||
state: LazyListState = rememberLazyListState(),
|
||||
noData: () -> Unit,
|
||||
content: @Composable (data: T) -> Unit
|
||||
) {
|
||||
|
||||
if (listData == null) return
|
||||
|
||||
if (listData.isEmpty()) {
|
||||
ErrorComposable("暂无数据,请点击重试") {
|
||||
noData()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
Column(modifier = Modifier.fillMaxSize()) {
|
||||
|
||||
val refreshState = rememberSwipeRefreshState(false)
|
||||
|
||||
SwipeRefresh(
|
||||
state = refreshState,
|
||||
onRefresh = {
|
||||
//显示刷新头
|
||||
refreshState.isRefreshing = true
|
||||
//刷新数据
|
||||
noData()
|
||||
viewModel.sleepTime(3000) {
|
||||
refreshState.isRefreshing = false
|
||||
}
|
||||
}
|
||||
) {
|
||||
LazyColumn(modifier = Modifier.fillMaxSize(), state = state) {
|
||||
itemsIndexed(listData) { index, data ->
|
||||
SimpleCard {
|
||||
content(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
package com.linx.playAndroid.public
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Close
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.linx.playAndroid.ui.theme.Typography
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/**
|
||||
* 标题
|
||||
*/
|
||||
@Composable
|
||||
fun TitleText(title: String, modifier: Modifier = Modifier) {
|
||||
Text(text = title, modifier = modifier.padding(8.dp), style = Typography.h3.copy(fontSize = 14.sp))
|
||||
}
|
||||
|
||||
/**
|
||||
* 副标题
|
||||
*/
|
||||
@Composable
|
||||
fun SubtitleText(subtitle: String, modifier: Modifier = Modifier) {
|
||||
Text(text = subtitle, style = Typography.subtitle2, modifier = modifier.padding(8.dp))
|
||||
}
|
||||
|
||||
/**
|
||||
* 两个Text上下分布的布局
|
||||
*/
|
||||
@Composable
|
||||
fun ColumnTextText(
|
||||
topStr: String,
|
||||
bottomStr: String,
|
||||
modifier: Modifier = Modifier,
|
||||
context: @Composable () -> Unit = {}
|
||||
) {
|
||||
|
||||
Row(
|
||||
modifier = Modifier.background(MaterialTheme.colors.background).then(modifier),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
|
||||
Column(
|
||||
modifier = Modifier.weight(1f, true)
|
||||
) {
|
||||
Text(topStr, color = MaterialTheme.colors.secondaryVariant, fontSize = 18.sp)
|
||||
Text(bottomStr, color = Color.Gray, fontSize = 14.sp)
|
||||
}
|
||||
|
||||
context()
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Text+Icon
|
||||
* 分别在最左边和最右边
|
||||
*/
|
||||
@Composable
|
||||
fun RowTextIcon(
|
||||
//text文本
|
||||
text: String,
|
||||
//Icon图标
|
||||
imageVector: ImageVector = Icons.Default.Close,
|
||||
paddingTop: Dp = 0.dp,
|
||||
//图标点击事件
|
||||
imageClick: (CoroutineScope.() -> Unit)? = null
|
||||
) {
|
||||
|
||||
var index by remember { mutableStateOf(0) }
|
||||
|
||||
LaunchedEffect(index) {
|
||||
|
||||
if (index == 0) return@LaunchedEffect
|
||||
|
||||
launch(Dispatchers.IO) {
|
||||
if (imageClick != null) imageClick()
|
||||
}
|
||||
}
|
||||
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().padding(top = paddingTop).height(30.dp),
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
|
||||
Text(text, fontSize = 16.sp, color = Color.Gray)
|
||||
|
||||
Icon(
|
||||
imageVector, contentDescription = null, modifier = Modifier.clickable(
|
||||
onClick = {
|
||||
imageClick?.let { index++ }
|
||||
}, indication = null,
|
||||
interactionSource = MutableInteractionSource()
|
||||
).padding(5.dp), tint = Color.Gray
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
package com.linx.playAndroid.public.paging
|
||||
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.paging.LoadState
|
||||
import androidx.paging.compose.LazyPagingItems
|
||||
import com.google.accompanist.swiperefresh.SwipeRefreshState
|
||||
import com.linx.common.widget.sleepTime
|
||||
import com.linx.playAndroid.public.ErrorComposable
|
||||
|
||||
class PagingStateUtil {
|
||||
|
||||
//是否显示无数据的布局
|
||||
var showNullScreen = false
|
||||
|
||||
/**
|
||||
* 统一管理Paging数据状态的方法
|
||||
* 错误处理、加载中的显示方式
|
||||
*/
|
||||
@Composable
|
||||
fun <T : Any> pagingStateUtil(
|
||||
//paging数据
|
||||
pagingData: LazyPagingItems<T>,
|
||||
//刷新状态
|
||||
refreshState: SwipeRefreshState,
|
||||
viewModel: ViewModel,
|
||||
content: @Composable () -> Unit
|
||||
) {
|
||||
|
||||
when (pagingData.loadState.refresh) {
|
||||
//未加载且未观察到错误
|
||||
is LoadState.NotLoading -> NotLoading(refreshState, viewModel) {
|
||||
//允许显示无数据布局 这里通常是第一次获取数据
|
||||
when (pagingData.itemCount) {
|
||||
0 -> {
|
||||
//第一次进入允许显示空布局
|
||||
if (!showNullScreen) showNullScreen = true
|
||||
//显示无数据布局
|
||||
else
|
||||
ErrorComposable("暂无数据,请点击重试") {
|
||||
pagingData.refresh()
|
||||
}
|
||||
}
|
||||
else -> content()
|
||||
}
|
||||
}
|
||||
//加载失败
|
||||
is LoadState.Error -> Error(pagingData, refreshState)
|
||||
//加载中
|
||||
LoadState.Loading -> Loading(refreshState)
|
||||
}
|
||||
|
||||
//如果在加载途中遇到错误的话,pagingData的状态为append
|
||||
when (pagingData.loadState.append) {
|
||||
//加载失败
|
||||
is LoadState.Error -> Error(pagingData, refreshState)
|
||||
//加载中
|
||||
LoadState.Loading -> Loading(refreshState)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 未加载且未观察到错误
|
||||
*/
|
||||
@Composable
|
||||
private fun NotLoading(
|
||||
refreshState: SwipeRefreshState,
|
||||
viewModel: ViewModel,
|
||||
content: @Composable () -> Unit
|
||||
) {
|
||||
|
||||
content()
|
||||
|
||||
//让刷新头停留一下子再收回去
|
||||
viewModel.sleepTime {
|
||||
refreshState.isRefreshing = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载失败
|
||||
*/
|
||||
@Composable
|
||||
private fun <T : Any> Error(
|
||||
pagingData: LazyPagingItems<T>,
|
||||
refreshState: SwipeRefreshState
|
||||
) {
|
||||
refreshState.isRefreshing = false
|
||||
ErrorComposable {
|
||||
pagingData.refresh()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载中
|
||||
*/
|
||||
@Composable
|
||||
private fun Loading(refreshState: SwipeRefreshState) {
|
||||
Row(modifier = Modifier.fillMaxSize()) { }
|
||||
//显示刷新头
|
||||
if (!refreshState.isRefreshing) refreshState.isRefreshing = true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.linx.playAndroid.repo
|
||||
|
||||
import com.linx.net.base.ServiceCreator
|
||||
import com.linx.playAndroid.service.HomeService
|
||||
|
||||
object HomeRepo {
|
||||
|
||||
//首页列表
|
||||
suspend fun getHomeList(page: Int) = ServiceCreator.getService<HomeService>().getHomeList(page)
|
||||
|
||||
//首页轮播图
|
||||
fun getBanner() = ServiceCreator.getService<HomeService>().getBanner()
|
||||
|
||||
//获取置顶文章列表
|
||||
fun getArticleTopList() = ServiceCreator.getService<HomeService>().getArticleTopList()
|
||||
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.linx.playAndroid.repo
|
||||
|
||||
import com.linx.net.base.ServiceCreator
|
||||
import com.linx.playAndroid.service.IntegralService
|
||||
|
||||
object IntegralRepo {
|
||||
|
||||
/**
|
||||
* 获取积分排行
|
||||
*/
|
||||
suspend fun getCoinRankList(page: Int) = ServiceCreator.getService<IntegralService>().getCoinRankList(page)
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.linx.playAndroid.repo
|
||||
|
||||
import com.linx.net.base.ServiceCreator
|
||||
import com.linx.playAndroid.service.LoginService
|
||||
import retrofit2.http.Body
|
||||
|
||||
object LoginRepo {
|
||||
|
||||
/**
|
||||
* 登录
|
||||
*/
|
||||
fun getUserLogin(username: String, password: String) =
|
||||
ServiceCreator.getService<LoginService>().getUserLogin(username, password)
|
||||
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.linx.playAndroid.repo
|
||||
|
||||
import com.linx.net.base.ServiceCreator
|
||||
import com.linx.playAndroid.service.MineService
|
||||
|
||||
object MineRepo {
|
||||
|
||||
/**
|
||||
* 获取个人积分
|
||||
*/
|
||||
fun getUserInfoIntegral() = ServiceCreator.getService<MineService>().getUserInfoIntegral()
|
||||
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.linx.playAndroid.repo
|
||||
|
||||
import com.linx.net.base.ServiceCreator
|
||||
import com.linx.playAndroid.service.MyCollectService
|
||||
|
||||
object MyCollectRepo {
|
||||
|
||||
/**
|
||||
* 我的收藏
|
||||
*/
|
||||
suspend fun getMyCollectList(page: Int) = ServiceCreator.getService<MyCollectService>().getMyCollectList(page)
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.linx.playAndroid.repo
|
||||
|
||||
import com.linx.net.base.ServiceCreator
|
||||
import com.linx.playAndroid.service.MyCollectService
|
||||
import com.linx.playAndroid.service.MyShareArticlesService
|
||||
|
||||
object MyShareArticlesRepo {
|
||||
|
||||
/**
|
||||
* 我的文章
|
||||
*/
|
||||
suspend fun getMyShareArticles(page: Int) = ServiceCreator.getService<MyShareArticlesService>().getMyShareArticles(page)
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.linx.playAndroid.repo
|
||||
|
||||
import android.util.Log
|
||||
import com.linx.net.base.ServiceCreator
|
||||
import com.linx.playAndroid.service.ProjectService
|
||||
|
||||
object ProjectRepo {
|
||||
|
||||
/**
|
||||
* 获取项目分类
|
||||
*/
|
||||
fun getProjectTree() = ServiceCreator.getService<ProjectService>().getProjectTree()
|
||||
|
||||
/**
|
||||
* 获取项目列表数据
|
||||
* [page] 页码
|
||||
* [cid] 分类的id
|
||||
*/
|
||||
suspend fun getProjectList(page: Int, cid: Int) = ServiceCreator.getService<ProjectService>().getProjectList(page, cid)
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.linx.playAndroid.repo
|
||||
|
||||
import com.linx.net.base.ServiceCreator
|
||||
import com.linx.playAndroid.service.PublicNumService
|
||||
|
||||
object PublicNumRepo {
|
||||
|
||||
/**
|
||||
* 获取公众号列表
|
||||
*/
|
||||
fun getPublicNumChapter() = ServiceCreator.getService<PublicNumService>().getPublicNumChapter()
|
||||
|
||||
/**
|
||||
* 获取某个公众号历史文章列表
|
||||
*/
|
||||
suspend fun getPublicNumList(id: Int, page: Int) = ServiceCreator.getService<PublicNumService>().getPublicNumList(id, page)
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.linx.playAndroid.repo
|
||||
|
||||
import com.linx.net.base.ServiceCreator
|
||||
import com.linx.playAndroid.service.RegisterService
|
||||
|
||||
object RegisterRepo {
|
||||
|
||||
/**
|
||||
* 注册
|
||||
*/
|
||||
fun getUserRegister(username: String, password: String, repassword: String) =
|
||||
ServiceCreator.getService<RegisterService>().getUserRegister(username, password, repassword)
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.linx.playAndroid.repo
|
||||
|
||||
import com.linx.net.base.ServiceCreator
|
||||
import com.linx.playAndroid.service.SearchService
|
||||
|
||||
object SearchRepo {
|
||||
|
||||
//获取搜索热词
|
||||
fun getHotKey() = ServiceCreator.getService<SearchService>().getHotKey()
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.linx.playAndroid.repo
|
||||
|
||||
import com.linx.net.base.ServiceCreator
|
||||
import com.linx.playAndroid.service.SquareService
|
||||
|
||||
/**
|
||||
* 广场
|
||||
*/
|
||||
object SquareRepo {
|
||||
|
||||
//获取广场列表数据
|
||||
suspend fun getUserArticleList(page: Int) = ServiceCreator.getService<SquareService>().getUserArticleList(page)
|
||||
|
||||
//获取问答列表数据
|
||||
suspend fun getQuestionAnswer(page: Int) = ServiceCreator.getService<SquareService>().getQuestionAnswers(page)
|
||||
|
||||
//获取体系数据
|
||||
fun getSystem() = ServiceCreator.getService<SquareService>().getSystem()
|
||||
|
||||
//获取导航数据
|
||||
fun getNavi() = ServiceCreator.getService<SquareService>().getNavi()
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.linx.playAndroid.service
|
||||
|
||||
import com.linx.net.paging.CommonalityPageModel
|
||||
import com.linx.net.base.NetUrl
|
||||
import com.linx.net.model.BaseResponse
|
||||
import com.linx.playAndroid.model.ArticleListData
|
||||
import retrofit2.Call
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Path
|
||||
|
||||
/**
|
||||
* 首页页面的网络请求接口
|
||||
*/
|
||||
interface HomeService {
|
||||
|
||||
@GET(NetUrl.ARTICLE_LIST)
|
||||
suspend fun getHomeList(@Path("page") page: Int): CommonalityPageModel<ArticleListData>
|
||||
|
||||
//首页轮播图
|
||||
@GET(NetUrl.HOME_BANNER)
|
||||
fun getBanner(): Call<BaseResponse>
|
||||
|
||||
//置顶文章
|
||||
@GET(NetUrl.ARTICLE_TOP)
|
||||
fun getArticleTopList(): Call<BaseResponse>
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.linx.playAndroid.service
|
||||
|
||||
import com.linx.net.base.NetUrl
|
||||
import com.linx.net.paging.CommonalityPageModel
|
||||
import com.linx.playAndroid.model.CoinRankData
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Path
|
||||
|
||||
interface IntegralService {
|
||||
|
||||
//积分排行
|
||||
@GET(NetUrl.COIN_RANK)
|
||||
suspend fun getCoinRankList(@Path("page") page: Int): CommonalityPageModel<CoinRankData>
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.linx.playAndroid.service
|
||||
|
||||
import com.linx.net.base.NetUrl
|
||||
import com.linx.net.model.BaseResponse
|
||||
import retrofit2.Call
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.Field
|
||||
import retrofit2.http.FormUrlEncoded
|
||||
import retrofit2.http.POST
|
||||
|
||||
interface LoginService {
|
||||
|
||||
//登录
|
||||
@FormUrlEncoded
|
||||
@POST(NetUrl.USER_LOGIN)
|
||||
fun getUserLogin(
|
||||
@Field("username") username: String,
|
||||
@Field("password") password: String
|
||||
): Call<BaseResponse>
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.linx.playAndroid.service
|
||||
|
||||
import com.linx.net.base.NetUrl
|
||||
import com.linx.net.model.BaseResponse
|
||||
import retrofit2.Call
|
||||
import retrofit2.http.GET
|
||||
|
||||
interface MineService {
|
||||
|
||||
//个人积分
|
||||
@GET(NetUrl.LG_COIN_USERINFO)
|
||||
fun getUserInfoIntegral(): Call<BaseResponse>
|
||||
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.linx.playAndroid.service
|
||||
|
||||
import com.linx.net.base.NetUrl
|
||||
import com.linx.net.model.BaseResponse
|
||||
import com.linx.net.paging.CommonalityPageModel
|
||||
import com.linx.playAndroid.model.MyCollectData
|
||||
import retrofit2.Call
|
||||
import retrofit2.http.FormUrlEncoded
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.POST
|
||||
import retrofit2.http.Path
|
||||
|
||||
interface MyCollectService {
|
||||
|
||||
//我的收藏
|
||||
@GET(NetUrl.MY_COLLECT)
|
||||
suspend fun getMyCollectList(@Path("page") page: Int): CommonalityPageModel<MyCollectData>
|
||||
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.linx.playAndroid.service
|
||||
|
||||
import com.linx.net.base.NetUrl
|
||||
import com.linx.net.model.BaseResponse
|
||||
import com.linx.net.paging.CommonalityPageModel
|
||||
import com.linx.playAndroid.model.MyCollectData
|
||||
import com.linx.playAndroid.model.MyShareArticlesListData
|
||||
import retrofit2.Call
|
||||
import retrofit2.http.FormUrlEncoded
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.POST
|
||||
import retrofit2.http.Path
|
||||
|
||||
interface MyShareArticlesService {
|
||||
|
||||
//我的文章
|
||||
@GET(NetUrl.MY_SHARE_ARTICLES)
|
||||
suspend fun getMyShareArticles(@Path("page") page: Int): MyShareArticlesListData<MyCollectData>
|
||||
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.linx.playAndroid.service
|
||||
|
||||
import com.linx.net.paging.CommonalityPageModel
|
||||
import com.linx.net.base.NetUrl
|
||||
import com.linx.net.model.BaseResponse
|
||||
import com.linx.playAndroid.model.ProjectListData
|
||||
import retrofit2.Call
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Path
|
||||
import retrofit2.http.Query
|
||||
|
||||
interface ProjectService {
|
||||
|
||||
//项目分类
|
||||
@GET(NetUrl.PROJECT_TREE)
|
||||
fun getProjectTree(): Call<BaseResponse>
|
||||
|
||||
//项目列表数据
|
||||
@GET(NetUrl.PROJECT_LIST)
|
||||
suspend fun getProjectList(@Path("page") page: Int, @Query("cid") cid: Int): CommonalityPageModel<ProjectListData>
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.linx.playAndroid.service
|
||||
|
||||
import com.linx.net.base.NetUrl
|
||||
import com.linx.net.model.BaseResponse
|
||||
import com.linx.net.paging.CommonalityPageModel
|
||||
import com.linx.playAndroid.model.PublicNumListData
|
||||
import retrofit2.Call
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Path
|
||||
|
||||
interface PublicNumService {
|
||||
|
||||
//获取公众号列表
|
||||
@GET(NetUrl.WXARTICLE_CHAPTERS)
|
||||
fun getPublicNumChapter(): Call<BaseResponse>
|
||||
|
||||
//获取某个公众号历史文章列表
|
||||
@GET(NetUrl.WXARTICLE_LIST)
|
||||
suspend fun getPublicNumList(@Path("id") id: Int, @Path("page") page: Int): CommonalityPageModel<PublicNumListData>
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.linx.playAndroid.service
|
||||
|
||||
import com.linx.net.base.NetUrl
|
||||
import com.linx.net.model.BaseResponse
|
||||
import retrofit2.Call
|
||||
import retrofit2.http.Field
|
||||
import retrofit2.http.FormUrlEncoded
|
||||
import retrofit2.http.POST
|
||||
|
||||
interface RegisterService {
|
||||
|
||||
//注册
|
||||
@FormUrlEncoded
|
||||
@POST(NetUrl.USER_REGISTER)
|
||||
fun getUserRegister(
|
||||
@Field("username") username: String,
|
||||
@Field("password") password: String,
|
||||
@Field("repassword") repassword: String,
|
||||
): Call<BaseResponse>
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.linx.playAndroid.service
|
||||
|
||||
import com.linx.net.base.NetUrl
|
||||
import com.linx.net.model.BaseResponse
|
||||
import retrofit2.Call
|
||||
import retrofit2.http.GET
|
||||
|
||||
/**
|
||||
* 搜索页面
|
||||
*/
|
||||
interface SearchService {
|
||||
|
||||
//搜索热词
|
||||
@GET(NetUrl.HOTKEY)
|
||||
fun getHotKey(): Call<BaseResponse>
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.linx.playAndroid.service
|
||||
|
||||
import com.linx.net.base.NetUrl
|
||||
import com.linx.net.model.BaseResponse
|
||||
import com.linx.net.paging.CommonalityPageModel
|
||||
import com.linx.playAndroid.model.UserArticleListData
|
||||
import retrofit2.Call
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Path
|
||||
|
||||
/**
|
||||
* 广场页面网络请求接口
|
||||
* questions and answers
|
||||
*/
|
||||
interface SquareService {
|
||||
|
||||
//广场数据
|
||||
@GET(NetUrl.USER_ARTICLE_LIST)
|
||||
suspend fun getUserArticleList(@Path("page") page: Int): CommonalityPageModel<UserArticleListData>
|
||||
|
||||
//问答数据
|
||||
@GET(NetUrl.WEN_DA)
|
||||
suspend fun getQuestionAnswers(@Path("page") page: Int): CommonalityPageModel<UserArticleListData>
|
||||
|
||||
//体系数据
|
||||
@GET(NetUrl.SYSTEM)
|
||||
fun getSystem(): Call<BaseResponse>
|
||||
|
||||
//导航数据
|
||||
@GET(NetUrl.NAVI)
|
||||
fun getNavi(): Call<BaseResponse>
|
||||
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package com.linx.playAndroid.ui.theme
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
||||
val Default200 = Color(0xFFBB86FC)
|
||||
val Default500 = Color(0xFF6200EE)
|
||||
val Default700 = Color(0xFF3700B3)
|
||||
val DefaultSecondary = Color(0xFF03DAC5)
|
||||
val DefaultOnPrimary = Color(0xFFFFFFFF)
|
||||
|
||||
val Theme1_200 = Color(0xFFff6659)
|
||||
val Theme1_500 = Color(0xFFd32f2f)
|
||||
val Theme1_700 = Color(0xFF9a0007)
|
||||
val Theme1Secondary = Color(0xFFff6659)
|
||||
val Theme1OnPrimary = Color(0xFFFFFFFF)
|
||||
|
||||
val Theme2_200 = Color(0xFF63a4ff)
|
||||
val Theme2_500 = Color(0xFF1976d2)
|
||||
val Theme2_700 = Color(0xFF004ba0)
|
||||
val Theme2Secondary = Color(0xFF63a4ff)
|
||||
val Theme2OnPrimary = Color(0xFFFFFFFF)
|
||||
|
||||
val Theme3_200 = Color(0xFFbe9c91)
|
||||
val Theme3_500 = Color(0xFF8d6e63)
|
||||
val Theme3_700 = Color(0xFF5f4339)
|
||||
val Theme3Secondary = Color(0xFFbe9c91)
|
||||
val Theme3OnPrimary = Color(0xFFFFFFFF)
|
||||
|
||||
val Theme4_200 = Color(0xFFffc947)
|
||||
val Theme4_500 = Color(0xFFff9800)
|
||||
val Theme4_700 = Color(0xFFc66900)
|
||||
val Theme4Secondary = Color(0xFFffc947)
|
||||
val Theme4OnPrimary = Color(0xFFFFFFFF)
|
||||
|
||||
val Theme5_200 = Color(0xFF60ad5e)
|
||||
val Theme5_500 = Color(0xFF2e7d32)
|
||||
val Theme5_700 = Color(0xFF005005)
|
||||
val Theme5Secondary = Color(0xFF60ad5e)
|
||||
val Theme5OnPrimary = Color(0xFFFFFFFF)
|
||||
|
||||
val c_b66731 = Color(0xFFb66731)
|
||||
|
||||
//带透明度
|
||||
val c_B3F = Color(0xB3FFFFFF)
|
||||
val c_80F = Color(0x80FFFFFF)
|
||||
|
||||
/**
|
||||
* 透明度
|
||||
* FF 100% 不透明
|
||||
* F2 95
|
||||
* E6 90
|
||||
* D9 85
|
||||
* CC 80
|
||||
* BF 75
|
||||
* B3 70
|
||||
* A6 65
|
||||
* 99 60
|
||||
* 8C 55
|
||||
* 80 50
|
||||
* 73 45
|
||||
* 66 40
|
||||
* 59 35
|
||||
* 4D 30
|
||||
* 40 25
|
||||
* 33 20
|
||||
* 26 15
|
||||
* 1A 10
|
||||
* 0D 5
|
||||
* 00 0 完全透明
|
||||
*/
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.linx.playAndroid.ui.theme
|
||||
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.Shapes
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
val Shapes = Shapes(
|
||||
small = RoundedCornerShape(4.dp),
|
||||
medium = RoundedCornerShape(4.dp),
|
||||
large = RoundedCornerShape(0.dp)
|
||||
)
|
||||
@@ -0,0 +1,215 @@
|
||||
package com.linx.playAndroid.ui.theme
|
||||
|
||||
import androidx.compose.foundation.isSystemInDarkTheme
|
||||
import androidx.compose.material.Colors
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.darkColors
|
||||
import androidx.compose.material.lightColors
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import com.linx.common.model.ThemeType
|
||||
|
||||
/* Other default colors to override
|
||||
background = Color.White,
|
||||
surface = Color.White,
|
||||
onPrimary = Color.White,
|
||||
onSecondary = Color.Black,
|
||||
onBackground = Color.Black,
|
||||
onSurface = Color.Black,
|
||||
*/
|
||||
|
||||
object CustomThemeManager {
|
||||
|
||||
object Default {
|
||||
val darkColors = darkColors(
|
||||
primary = Default200,
|
||||
primaryVariant = Default700,
|
||||
secondary = DefaultSecondary,
|
||||
secondaryVariant = Color.White,
|
||||
background = Color.Black,
|
||||
surface = Color.White,
|
||||
)
|
||||
|
||||
val lightColors = lightColors(
|
||||
primary = Default500,
|
||||
primaryVariant = Default700,
|
||||
secondary = DefaultSecondary,
|
||||
secondaryVariant = Color.Black,
|
||||
background = Color.White,
|
||||
surface = DefaultOnPrimary
|
||||
)
|
||||
}
|
||||
|
||||
object Theme1 {
|
||||
val darkColors = darkColors(
|
||||
primary = Theme1_200,
|
||||
primaryVariant = Theme1_700,
|
||||
secondary = Theme1Secondary,
|
||||
secondaryVariant = Color.White,
|
||||
background = Color.Black,
|
||||
surface = Color.White,
|
||||
)
|
||||
|
||||
val lightColors = lightColors(
|
||||
primary = Theme1_500,
|
||||
primaryVariant = Theme1_700,
|
||||
secondary = Theme1Secondary,
|
||||
secondaryVariant = Color.Black,
|
||||
background = Color.White,
|
||||
surface = Theme1OnPrimary
|
||||
)
|
||||
}
|
||||
|
||||
object Theme2 {
|
||||
val darkColors = darkColors(
|
||||
primary = Theme2_200,
|
||||
primaryVariant = Theme2_700,
|
||||
secondary = Theme2Secondary,
|
||||
secondaryVariant = Color.White,
|
||||
background = Color.Black,
|
||||
surface = Color.White,
|
||||
)
|
||||
|
||||
val lightColors = lightColors(
|
||||
primary = Theme2_500,
|
||||
primaryVariant = Theme2_700,
|
||||
secondary = Theme2Secondary,
|
||||
secondaryVariant = Color.Black,
|
||||
background = Color.White,
|
||||
surface = Theme2OnPrimary
|
||||
)
|
||||
}
|
||||
|
||||
object Theme3 {
|
||||
val darkColors = darkColors(
|
||||
primary = Theme3_200,
|
||||
primaryVariant = Theme3_700,
|
||||
secondary = Theme3Secondary,
|
||||
secondaryVariant = Color.White,
|
||||
background = Color.Black,
|
||||
surface = Color.White,
|
||||
)
|
||||
|
||||
val lightColors = lightColors(
|
||||
primary = Theme3_500,
|
||||
primaryVariant = Theme3_700,
|
||||
secondary = Theme3Secondary,
|
||||
secondaryVariant = Color.Black,
|
||||
background = Color.White,
|
||||
surface = Theme3OnPrimary
|
||||
)
|
||||
}
|
||||
|
||||
object Theme4 {
|
||||
val darkColors = darkColors(
|
||||
primary = Theme4_200,
|
||||
primaryVariant = Theme4_700,
|
||||
secondary = Theme4Secondary,
|
||||
secondaryVariant = Color.White,
|
||||
background = Color.Black,
|
||||
surface = Color.White,
|
||||
)
|
||||
|
||||
val lightColors = lightColors(
|
||||
primary = Theme4_500,
|
||||
primaryVariant = Theme4_700,
|
||||
secondary = Theme4Secondary,
|
||||
secondaryVariant = Color.Black,
|
||||
background = Color.White,
|
||||
surface = Theme4OnPrimary
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
object Theme5 {
|
||||
val darkColors = darkColors(
|
||||
primary = Theme5_200,
|
||||
primaryVariant = Theme5_700,
|
||||
secondary = Theme5Secondary,
|
||||
secondaryVariant = Color.White,
|
||||
background = Color.Black,
|
||||
surface = Color.White,
|
||||
)
|
||||
|
||||
val lightColors = lightColors(
|
||||
primary = Theme5_500,
|
||||
primaryVariant = Theme5_700,
|
||||
secondary = Theme5Secondary,
|
||||
secondaryVariant = Color.Black,
|
||||
background = Color.White,
|
||||
surface = Theme5OnPrimary
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun WanAndroidTheme(
|
||||
type: ThemeType,
|
||||
darkTheme: Boolean = isSystemInDarkTheme(),
|
||||
content: @Composable () -> Unit
|
||||
) {
|
||||
|
||||
val wrappedColor = getWrappedColor(type)
|
||||
val colors = if (darkTheme) wrappedColor.darkColors else wrappedColor.lightColors
|
||||
|
||||
MaterialTheme(
|
||||
colors = colors,
|
||||
typography = Typography,
|
||||
shapes = Shapes,
|
||||
content = content
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
fun getWrappedColor(type: ThemeType): WrappedColor {
|
||||
val darkColors: Colors
|
||||
val lightColors: Colors
|
||||
|
||||
when(type) {
|
||||
ThemeType.Default -> {
|
||||
darkColors = Default.darkColors
|
||||
lightColors = Default.lightColors
|
||||
}
|
||||
ThemeType.Theme1 -> {
|
||||
darkColors = Theme1.darkColors
|
||||
lightColors = Theme1.lightColors
|
||||
}
|
||||
ThemeType.Theme2 -> {
|
||||
darkColors = Theme2.darkColors
|
||||
lightColors = Theme2.lightColors
|
||||
}
|
||||
ThemeType.Theme3 -> {
|
||||
darkColors = Theme3.darkColors
|
||||
lightColors = Theme3.lightColors
|
||||
}
|
||||
ThemeType.Theme4 -> {
|
||||
darkColors = Theme4.darkColors
|
||||
lightColors = Theme4.lightColors
|
||||
}
|
||||
ThemeType.Theme5 -> {
|
||||
darkColors = Theme5.darkColors
|
||||
lightColors = Theme5.lightColors
|
||||
}
|
||||
}
|
||||
|
||||
return WrappedColor(lightColors, darkColors)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取主题样式颜色
|
||||
*/
|
||||
@Composable
|
||||
fun getThemeColor(themeType: ThemeType): Colors {
|
||||
return when(themeType) {
|
||||
ThemeType.Default -> if (isSystemInDarkTheme()) Default.darkColors else Default.lightColors
|
||||
ThemeType.Theme1 -> if (isSystemInDarkTheme()) Theme1.darkColors else Theme1.lightColors
|
||||
ThemeType.Theme2 -> if (isSystemInDarkTheme()) Theme2.darkColors else Theme2.lightColors
|
||||
ThemeType.Theme3 -> if (isSystemInDarkTheme()) Theme3.darkColors else Theme3.lightColors
|
||||
ThemeType.Theme4 -> if (isSystemInDarkTheme()) Theme4.darkColors else Theme4.lightColors
|
||||
ThemeType.Theme5 -> if (isSystemInDarkTheme()) Theme5.darkColors else Theme5.lightColors
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
data class WrappedColor(val lightColors: Colors, val darkColors: Colors)
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
package com.linx.playAndroid.ui.theme
|
||||
|
||||
import androidx.compose.material.Typography
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.sp
|
||||
|
||||
// Set of Material typography styles to start with
|
||||
val Typography = Typography(
|
||||
body1 = TextStyle(
|
||||
fontFamily = FontFamily.Default,
|
||||
fontWeight = FontWeight.Normal,
|
||||
fontSize = 16.sp
|
||||
)
|
||||
/* Other default text styles to override
|
||||
button = TextStyle(
|
||||
fontFamily = FontFamily.Default,
|
||||
fontWeight = FontWeight.W500,
|
||||
fontSize = 14.sp
|
||||
),
|
||||
caption = TextStyle(
|
||||
fontFamily = FontFamily.Default,
|
||||
fontWeight = FontWeight.Normal,
|
||||
fontSize = 12.sp
|
||||
)
|
||||
*/
|
||||
)
|
||||
@@ -0,0 +1,95 @@
|
||||
package com.linx.playAndroid.viewModel
|
||||
|
||||
import android.util.Log
|
||||
import androidx.compose.foundation.lazy.LazyListState
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.paging.Pager
|
||||
import androidx.paging.PagingConfig
|
||||
import androidx.paging.PagingData
|
||||
import androidx.paging.cachedIn
|
||||
import com.linx.common.base.BaseViewModel
|
||||
import com.linx.common.baseData.CommonConstant
|
||||
import com.linx.common.widget.SpUtilsMMKV
|
||||
import com.linx.net.ext.*
|
||||
import com.linx.net.paging.CommonPagingSource
|
||||
import com.linx.playAndroid.model.ArticleListData
|
||||
import com.linx.playAndroid.model.BannerData
|
||||
import com.linx.playAndroid.model.HomeBannerData
|
||||
import com.linx.playAndroid.repo.HomeRepo
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
/**
|
||||
* 首页
|
||||
*/
|
||||
class HomeViewModel : BaseViewModel() {
|
||||
|
||||
//首页列表状态
|
||||
val homeLazyListState: LazyListState = LazyListState()
|
||||
|
||||
//轮播图的数据
|
||||
private val _bannerListData = MutableLiveData<List<BannerData>>()
|
||||
val bannerListData: LiveData<List<BannerData>>
|
||||
get() = _bannerListData
|
||||
|
||||
/**
|
||||
* 获取首页轮播图
|
||||
*/
|
||||
fun getBannerData() = serverAwait {
|
||||
HomeRepo.getBanner().serverData().onSuccess {
|
||||
onBizError { code, message ->
|
||||
Log.e("xxx", "获取首页轮播图 接口异常 $code $message")
|
||||
}
|
||||
onBizOK<List<HomeBannerData>> { code, data, message ->
|
||||
//转换为轮播图数据
|
||||
val bannerList = mutableListOf<BannerData>()
|
||||
data?.forEach { data ->
|
||||
bannerList.add(BannerData(data.imagePath.toString(), data.url.toString()))
|
||||
}
|
||||
_bannerListData.postValue(bannerList)
|
||||
}
|
||||
}.onFailure {
|
||||
Log.e("xxx", "获取首页轮播图 接口异常 $it")
|
||||
}
|
||||
}
|
||||
|
||||
//首页列表
|
||||
val homeListData: Flow<PagingData<ArticleListData>>
|
||||
get() = _homeListData
|
||||
|
||||
private val _homeListData = Pager(PagingConfig(pageSize = 20)) {
|
||||
CommonPagingSource { nextPage: Int ->
|
||||
HomeRepo.getHomeList(nextPage)
|
||||
}
|
||||
}.flow.cachedIn(viewModelScope)
|
||||
|
||||
//置顶文章列表数据
|
||||
private val _articleTopList = MutableLiveData<List<ArticleListData>>()
|
||||
val articleTopList: LiveData<List<ArticleListData>>
|
||||
get() = _articleTopList
|
||||
|
||||
/**
|
||||
* 获取置顶文章列表
|
||||
*/
|
||||
fun getArticleTopListData() = serverAwait {
|
||||
|
||||
//是否隐藏置顶文章
|
||||
if (SpUtilsMMKV.getBoolean(CommonConstant.GONE_ARTICLE_TOP) == true) {
|
||||
_articleTopList.postValue(null)
|
||||
return@serverAwait
|
||||
}
|
||||
|
||||
HomeRepo.getArticleTopList().serverData().onSuccess {
|
||||
onBizError { code, message ->
|
||||
Log.e("xxx", "获取置顶文章列表 接口异常 $code $message")
|
||||
}
|
||||
onBizOK<List<ArticleListData>> { code, data, message ->
|
||||
_articleTopList.postValue(data)
|
||||
}
|
||||
}.onFailure {
|
||||
Log.e("xxx", "获取置顶文章列表 接口异常 $it")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package com.linx.playAndroid.viewModel
|
||||
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.paging.Pager
|
||||
import androidx.paging.PagingConfig
|
||||
import androidx.paging.PagingData
|
||||
import androidx.paging.cachedIn
|
||||
import com.linx.common.base.BaseViewModel
|
||||
import com.linx.net.ext.*
|
||||
import com.linx.net.paging.CommonPagingSource
|
||||
import com.linx.playAndroid.model.CoinRankData
|
||||
import com.linx.playAndroid.model.UserInfoIntegralData
|
||||
import com.linx.playAndroid.repo.IntegralRepo
|
||||
import com.linx.playAndroid.repo.MineRepo
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
/**
|
||||
* 积分排名
|
||||
*/
|
||||
class IntegralRankViewModel : BaseViewModel() {
|
||||
|
||||
//是否请求了个人积分数据
|
||||
var isRequestUserInfoData = false
|
||||
|
||||
//个人积分
|
||||
private val _userInfoIntegral = MutableLiveData<UserInfoIntegralData>()
|
||||
val userInfoIntegral: LiveData<UserInfoIntegralData>
|
||||
get() = _userInfoIntegral
|
||||
|
||||
/**
|
||||
* 获取个人积分数据
|
||||
*/
|
||||
fun getUserInfoIntegral() = serverAwait {
|
||||
|
||||
MineRepo.getUserInfoIntegral().serverData().onSuccess {
|
||||
onBizError { code, message ->
|
||||
Log.e("xxx", "获取个人积分数据 接口异常 $code $message")
|
||||
}
|
||||
onBizOK<UserInfoIntegralData> { code, data, message ->
|
||||
_userInfoIntegral.postValue(data)
|
||||
}
|
||||
}.onFailure {
|
||||
Log.e("xxx", "获取个人积分数据 接口异常 $it")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//积分排行
|
||||
val coinRankListData: Flow<PagingData<CoinRankData>>
|
||||
get() = _coinRankListData
|
||||
|
||||
//获取积分排行
|
||||
private val _coinRankListData = Pager(PagingConfig(pageSize = 20)) {
|
||||
CommonPagingSource { nextPage: Int ->
|
||||
IntegralRepo.getCoinRankList(nextPage)
|
||||
}
|
||||
}.flow.cachedIn(viewModelScope)
|
||||
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package com.linx.playAndroid.viewModel
|
||||
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import com.linx.common.base.BaseViewModel
|
||||
import com.linx.net.ext.*
|
||||
import com.linx.playAndroid.repo.LoginRepo
|
||||
|
||||
/**
|
||||
* 登录
|
||||
*/
|
||||
class LoginViewModel : BaseViewModel() {
|
||||
|
||||
//用于执行Toast的变量
|
||||
val mToast = MutableLiveData<String>()
|
||||
|
||||
//用户名
|
||||
val userName = mutableStateOf(TextFieldValue(""))
|
||||
|
||||
//密码
|
||||
val passWord = mutableStateOf(TextFieldValue(""))
|
||||
|
||||
//密码是否可见
|
||||
val passwordVisible = mutableStateOf(false)
|
||||
|
||||
//登录信息
|
||||
private val _userLoginData = MutableLiveData<String>()
|
||||
val userLoginData: LiveData<String>
|
||||
get() = _userLoginData
|
||||
|
||||
/**
|
||||
* 登录
|
||||
*/
|
||||
fun getUserLoginData() = serverAwait {
|
||||
|
||||
if (userName.value.text == "") {
|
||||
mToast.value = "用户名不能为空"
|
||||
return@serverAwait
|
||||
}
|
||||
|
||||
if (passWord.value.text == "") {
|
||||
mToast.value = "密码不能为空"
|
||||
return@serverAwait
|
||||
}
|
||||
|
||||
LoginRepo.getUserLogin(userName.value.text, passWord.value.text).serverData().onSuccess {
|
||||
onBizError { code, message ->
|
||||
mToast.postValue("登录失败 $message")
|
||||
Log.e("xxx", "登录 接口异常 $code $message")
|
||||
}
|
||||
onBizOK<String> { code, data, message ->
|
||||
_userLoginData.postValue("登录成功")
|
||||
}
|
||||
}.onFailure {
|
||||
mToast.postValue("登录失败 $it")
|
||||
Log.e("xxx", "登录 接口异常 $it")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.linx.playAndroid.viewModel
|
||||
|
||||
import com.linx.common.base.BaseViewModel
|
||||
|
||||
class MainViewModel: BaseViewModel() {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.linx.playAndroid.viewModel
|
||||
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import com.linx.common.base.BaseViewModel
|
||||
import com.linx.common.baseData.CommonConstant
|
||||
import com.linx.common.widget.SpUtilsMMKV
|
||||
import com.linx.net.ext.*
|
||||
import com.linx.playAndroid.model.UserInfoIntegralData
|
||||
import com.linx.playAndroid.repo.MineRepo
|
||||
|
||||
/**
|
||||
* 我的
|
||||
*/
|
||||
class MineViewModel : BaseViewModel() {
|
||||
|
||||
//个人积分
|
||||
private val _userInfoIntegral = MutableLiveData<UserInfoIntegralData>()
|
||||
val userInfoIntegral = _userInfoIntegral
|
||||
|
||||
/**
|
||||
* 获取个人积分数据
|
||||
*/
|
||||
fun getUserInfoIntegral() = serverAwait {
|
||||
MineRepo.getUserInfoIntegral().serverData().onSuccess {
|
||||
onBizError { code, message ->
|
||||
Log.e("xxx", "获取个人积分数据 接口异常 $code $message")
|
||||
}
|
||||
onBizOK<UserInfoIntegralData> { code, data, message ->
|
||||
_userInfoIntegral.postValue(data)
|
||||
}
|
||||
}.onFailure {
|
||||
Log.e("xxx", "获取个人积分数据 接口异常 $it")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.linx.playAndroid.viewModel
|
||||
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.paging.Pager
|
||||
import androidx.paging.PagingConfig
|
||||
import androidx.paging.PagingData
|
||||
import androidx.paging.cachedIn
|
||||
import com.linx.common.base.BaseViewModel
|
||||
import com.linx.net.ext.*
|
||||
import com.linx.net.paging.CommonPagingSource
|
||||
import com.linx.playAndroid.model.MyCollectData
|
||||
import com.linx.playAndroid.repo.MyCollectRepo
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
/**
|
||||
* 我的收藏
|
||||
*/
|
||||
class MyCollectViewModel : BaseViewModel() {
|
||||
|
||||
//我的收藏列表数据
|
||||
val myCollectListData: Flow<PagingData<MyCollectData>>
|
||||
get() = _myCollectListData
|
||||
|
||||
//获取我的收藏列表数据
|
||||
private val _myCollectListData = Pager(PagingConfig(pageSize = 20)) {
|
||||
CommonPagingSource { nextPage ->
|
||||
MyCollectRepo.getMyCollectList(nextPage)
|
||||
}
|
||||
}.flow.cachedIn(viewModelScope)
|
||||
|
||||
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
package com.linx.playAndroid.viewModel
|
||||
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.paging.*
|
||||
import com.linx.common.base.BaseViewModel
|
||||
import com.linx.net.paging.CommonPagingSource
|
||||
import com.linx.net.paging.CommonalityPageModel
|
||||
import com.linx.playAndroid.model.MyCollectData
|
||||
import com.linx.playAndroid.model.MyShareArticlesListData
|
||||
import com.linx.playAndroid.repo.MyShareArticlesRepo
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import java.lang.Exception
|
||||
|
||||
/**
|
||||
* 我的文章
|
||||
*/
|
||||
class MyShareArticlesViewModel : BaseViewModel() {
|
||||
|
||||
//我的文章列表数据
|
||||
val myShareArticlesListData: Flow<PagingData<MyCollectData>>
|
||||
get() = _myShareArticlesListData
|
||||
|
||||
//获取我的文章列表数据
|
||||
private val _myShareArticlesListData = Pager(PagingConfig(pageSize = 20)) {
|
||||
MyShareArticlesListDataSource { nextPage ->
|
||||
MyShareArticlesRepo.getMyShareArticles((nextPage + 1))
|
||||
}
|
||||
}.flow.cachedIn(viewModelScope)
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 我分享的文章列表的Paging数据源
|
||||
*/
|
||||
class MyShareArticlesListDataSource <T: Any> (private val block: suspend (nextPage: Int) -> MyShareArticlesListData<T>): PagingSource<Int, T>() {
|
||||
|
||||
override fun getRefreshKey(state: PagingState<Int, T>): Int? = null
|
||||
|
||||
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, T> {
|
||||
return try {
|
||||
//params.key为当前页码 页码从0开始
|
||||
val nextPage = params.key ?: 0
|
||||
//更新页码后请求数据
|
||||
val response = block.invoke(nextPage)
|
||||
LoadResult.Page(
|
||||
data = response.data.shareArticles.datas,
|
||||
//前一页页码
|
||||
prevKey = if (nextPage == 0) null else nextPage - 1,
|
||||
//后一页页码
|
||||
nextKey = if (nextPage < response.data.shareArticles.pageCount) nextPage + 1 else null
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
LoadResult.Error(e)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package com.linx.playAndroid.viewModel
|
||||
|
||||
import android.util.Log
|
||||
import androidx.compose.foundation.lazy.LazyListState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.paging.Pager
|
||||
import androidx.paging.PagingConfig
|
||||
import androidx.paging.PagingData
|
||||
import androidx.paging.cachedIn
|
||||
import com.linx.common.base.BaseViewModel
|
||||
import com.linx.common.baseData.Nav
|
||||
import com.linx.net.ext.*
|
||||
import com.linx.net.paging.CommonPagingSource
|
||||
import com.linx.playAndroid.model.ProjectListData
|
||||
import com.linx.playAndroid.model.ProjectTreeData
|
||||
import com.linx.playAndroid.repo.ProjectRepo
|
||||
import com.linx.playAndroid.widget.StoreData
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
/**
|
||||
* 项目
|
||||
*/
|
||||
class ProjectViewModel : BaseViewModel() {
|
||||
|
||||
//项目页面列表状态
|
||||
val projectLazyListState: LazyListState = LazyListState()
|
||||
|
||||
//保存改变过index和offset的指示器Index
|
||||
var saveChangeProjectIndex = 0
|
||||
|
||||
//选中分类的cid
|
||||
private val indexCid
|
||||
get() = StoreData.projectTopBarListData.value?.get(Nav.projectTopBarIndex.value)?.id ?: 0
|
||||
|
||||
//项目页面顶部指示器
|
||||
private val _projectTreeData = MutableLiveData<List<ProjectTreeData>>()
|
||||
val projectTreeData: LiveData<List<ProjectTreeData>>
|
||||
get() = _projectTreeData
|
||||
|
||||
/**
|
||||
* 获取项目页面顶部指示器数据
|
||||
*/
|
||||
fun getProjectTreeData() = serverAwait {
|
||||
ProjectRepo.getProjectTree().serverData().onSuccess {
|
||||
onBizError { code, message ->
|
||||
Log.e("xxx", "获取项目页面顶部指示器数据 接口异常 $code $message")
|
||||
}
|
||||
onBizOK<List<ProjectTreeData>> { code, data, message ->
|
||||
_projectTreeData.postValue(data)
|
||||
//临时存储指示器数据
|
||||
StoreData.projectTopBarListData.postValue(data)
|
||||
|
||||
//todo 获取了之后查看有多少个Index,就创建多少个LazyListState
|
||||
}
|
||||
}.onFailure {
|
||||
Log.e("xxx", "获取项目页面顶部指示器数据 接口异常 $it")
|
||||
}
|
||||
}
|
||||
|
||||
//项目列表数据
|
||||
val projectListData: Flow<PagingData<ProjectListData>>
|
||||
get() = _projectListData
|
||||
|
||||
private val _projectListData = Pager(PagingConfig(pageSize = 20)) {
|
||||
CommonPagingSource { nextPage: Int ->
|
||||
ProjectRepo.getProjectList(nextPage, indexCid)
|
||||
}
|
||||
}.flow.cachedIn(viewModelScope)
|
||||
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package com.linx.playAndroid.viewModel
|
||||
|
||||
import android.util.Log
|
||||
import androidx.compose.foundation.lazy.LazyListState
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.paging.Pager
|
||||
import androidx.paging.PagingConfig
|
||||
import androidx.paging.PagingData
|
||||
import androidx.paging.cachedIn
|
||||
import com.linx.common.base.BaseViewModel
|
||||
import com.linx.common.baseData.Nav
|
||||
import com.linx.net.ext.*
|
||||
import com.linx.net.paging.CommonPagingSource
|
||||
import com.linx.playAndroid.model.PublicNumChapterData
|
||||
import com.linx.playAndroid.model.PublicNumListData
|
||||
import com.linx.playAndroid.repo.PublicNumRepo
|
||||
import com.linx.playAndroid.widget.StoreData
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
/**
|
||||
* 公众号
|
||||
*/
|
||||
class PublicNumViewModel : BaseViewModel() {
|
||||
|
||||
//公众号页面列表状态
|
||||
val publicNumLazyListState: LazyListState = LazyListState()
|
||||
|
||||
//保存改变过index和offset的指示器Index
|
||||
var saveChangePublicNumIndex = 0
|
||||
|
||||
//公众号id
|
||||
private val indexId
|
||||
get() = StoreData.publicNumTopBarListData.value?.get(Nav.publicNumIndex.value)?.id ?: 408
|
||||
|
||||
//公众号列表
|
||||
private val _publicNumChapter = MutableLiveData<List<PublicNumChapterData>>()
|
||||
val publicNumChapter: LiveData<List<PublicNumChapterData>>
|
||||
get() = _publicNumChapter
|
||||
|
||||
/**
|
||||
* 获取公众号列表
|
||||
*/
|
||||
fun getPublicNumChapterData() = serverAwait {
|
||||
PublicNumRepo.getPublicNumChapter().serverData().onSuccess {
|
||||
onBizError { code, message ->
|
||||
Log.e("xxx", "获取公众号列表 顶部指示器数据 接口异常 $code $message")
|
||||
}
|
||||
onBizOK<List<PublicNumChapterData>> { code, data, message ->
|
||||
_publicNumChapter.postValue(data)
|
||||
//临时存储指示器数据
|
||||
StoreData.publicNumTopBarListData.postValue(data)
|
||||
}
|
||||
}.onFailure {
|
||||
Log.e("xxx", "获取公众号列表 顶部指示器数据 接口异常 $it")
|
||||
}
|
||||
}
|
||||
|
||||
//某个公众号历史文章列表数据
|
||||
val publicNumListData: Flow<PagingData<PublicNumListData>>
|
||||
get() = _publicNumListData
|
||||
|
||||
//获取某个公众号历史文章列表数据
|
||||
private val _publicNumListData = Pager(PagingConfig(pageSize = 20)) {
|
||||
CommonPagingSource { nextPage: Int ->
|
||||
PublicNumRepo.getPublicNumList(indexId, nextPage)
|
||||
}
|
||||
}.flow.cachedIn(viewModelScope)
|
||||
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package com.linx.playAndroid.viewModel
|
||||
|
||||
import android.util.Log
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import com.linx.common.base.BaseViewModel
|
||||
import com.linx.net.ext.*
|
||||
import com.linx.playAndroid.model.RegisterDaat
|
||||
import com.linx.playAndroid.repo.RegisterRepo
|
||||
|
||||
/**
|
||||
* 注册
|
||||
*/
|
||||
class RegisterViewModel : BaseViewModel() {
|
||||
|
||||
//用于执行Toast的变量
|
||||
val mToast = MutableLiveData<String>()
|
||||
|
||||
//用户名
|
||||
val userName = mutableStateOf(TextFieldValue(""))
|
||||
|
||||
//密码
|
||||
val passWord = mutableStateOf(TextFieldValue(""))
|
||||
|
||||
//密码是否可见
|
||||
val passwordVisible = mutableStateOf(false)
|
||||
|
||||
//注册信息
|
||||
private val _userRegisterData = MutableLiveData<RegisterDaat>()
|
||||
val userRegisterData: LiveData<RegisterDaat>
|
||||
get() = _userRegisterData
|
||||
|
||||
/**
|
||||
* 注册
|
||||
*/
|
||||
fun getUserRegisterData() = serverAwait {
|
||||
|
||||
if (userName.value.text == "") {
|
||||
mToast.value = "用户名不能为空"
|
||||
}
|
||||
|
||||
if (passWord.value.text == "") {
|
||||
mToast.value = "密码不能为空"
|
||||
return@serverAwait
|
||||
}
|
||||
|
||||
RegisterRepo.getUserRegister(userName.value.text, passWord.value.text, passWord.value.text)
|
||||
.serverData().onSuccess {
|
||||
onBizError { code, message ->
|
||||
mToast.value = "注册失败 $message"
|
||||
Log.e("xxx", "注册 接口异常 $code $message")
|
||||
}
|
||||
onBizOK<RegisterDaat> { code, data, message ->
|
||||
_userRegisterData.postValue(data)
|
||||
}
|
||||
}
|
||||
.onFailure {
|
||||
Log.e("xxx", "注册 接口异常 $it")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package com.linx.playAndroid.viewModel
|
||||
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import com.linx.common.base.BaseViewModel
|
||||
import com.linx.net.ext.*
|
||||
import com.linx.playAndroid.model.HotKeyData
|
||||
import com.linx.playAndroid.repo.SearchRepo
|
||||
|
||||
/**
|
||||
* 搜索
|
||||
*/
|
||||
class SearchViewModel : BaseViewModel() {
|
||||
|
||||
private val _hotKeyListData = MutableLiveData<List<HotKeyData>>()
|
||||
val hotKeyListData: LiveData<List<HotKeyData>>
|
||||
get() = _hotKeyListData
|
||||
|
||||
/**
|
||||
* 获取搜索热词
|
||||
*/
|
||||
fun getHotKeyData() = serverAwait {
|
||||
SearchRepo.getHotKey().serverData().onSuccess {
|
||||
onBizError { code, message ->
|
||||
Log.e("xxx", "获取搜索热词 接口异常 $code $message")
|
||||
}
|
||||
onBizOK<List<HotKeyData>> { code, data, message ->
|
||||
_hotKeyListData.postValue(data)
|
||||
}
|
||||
}.onFailure {
|
||||
Log.e("xxx", "获取搜索热词 接口异常 $it")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.linx.playAndroid.viewModel
|
||||
|
||||
import com.linx.common.base.BaseViewModel
|
||||
|
||||
/**
|
||||
* 设置
|
||||
*/
|
||||
class SettingViewModel: BaseViewModel() {
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
package com.linx.playAndroid.viewModel
|
||||
|
||||
import android.util.Log
|
||||
import androidx.compose.foundation.lazy.LazyListState
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.paging.Pager
|
||||
import androidx.paging.PagingConfig
|
||||
import androidx.paging.PagingData
|
||||
import androidx.paging.cachedIn
|
||||
import com.linx.common.base.BaseViewModel
|
||||
import com.linx.net.ext.*
|
||||
import com.linx.net.paging.CommonPagingSource
|
||||
import com.linx.playAndroid.model.NaviData
|
||||
import com.linx.playAndroid.model.SystemData
|
||||
import com.linx.playAndroid.model.UserArticleListData
|
||||
import com.linx.playAndroid.repo.SquareRepo
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
/**
|
||||
* 广场
|
||||
*/
|
||||
class SquareViewModel : BaseViewModel() {
|
||||
|
||||
//广场
|
||||
val squareIndexState: LazyListState = LazyListState()
|
||||
|
||||
//每日一问
|
||||
val questionIndexState: LazyListState = LazyListState()
|
||||
|
||||
//体系
|
||||
val systemIndexState: LazyListState = LazyListState()
|
||||
|
||||
//导航
|
||||
val naviIndexState: LazyListState = LazyListState()
|
||||
|
||||
//广场列表
|
||||
val userArticleListData: Flow<PagingData<UserArticleListData>>
|
||||
get() = _userArticleListData
|
||||
|
||||
private val _userArticleListData = Pager(PagingConfig(pageSize = 20)) {
|
||||
CommonPagingSource { nextPage: Int ->
|
||||
SquareRepo.getUserArticleList(nextPage)
|
||||
}
|
||||
}.flow.cachedIn(viewModelScope)
|
||||
|
||||
//问答列表
|
||||
val questionAnswerData: Flow<PagingData<UserArticleListData>>
|
||||
get() = _questionAnswerData
|
||||
|
||||
private val _questionAnswerData = Pager(PagingConfig(pageSize = 20)) {
|
||||
CommonPagingSource { nextPage: Int ->
|
||||
SquareRepo.getQuestionAnswer(nextPage)
|
||||
}
|
||||
}.flow.cachedIn(viewModelScope)
|
||||
|
||||
//体系数据
|
||||
private val _systemData = MutableLiveData<List<SystemData>>()
|
||||
val systemData: LiveData<List<SystemData>>
|
||||
get() = _systemData
|
||||
|
||||
//获取体系数据
|
||||
fun getSystemData() = serverAwait {
|
||||
SquareRepo.getSystem().serverData().onSuccess {
|
||||
onBizError { code, message ->
|
||||
Log.e("xxx", "获取体系数据 接口异常 $code $message")
|
||||
}
|
||||
onBizOK<List<SystemData>> { code, data, message ->
|
||||
_systemData.postValue(data)
|
||||
}
|
||||
}.onFailure {
|
||||
Log.e("xxx", "获取体系数据 接口异常$it")
|
||||
}
|
||||
}
|
||||
|
||||
//导航数据
|
||||
private val _naviData = MutableLiveData<List<NaviData>>()
|
||||
val naviData: LiveData<List<NaviData>>
|
||||
get() = _naviData
|
||||
|
||||
//获取导航数据
|
||||
fun getNavi() = serverAwait {
|
||||
SquareRepo.getNavi().serverData().onSuccess {
|
||||
onBizError { code, message ->
|
||||
Log.e("xxx", "获取导航数据 接口异常 $code $message")
|
||||
}
|
||||
onBizOK<List<NaviData>> { code, data, message ->
|
||||
_naviData.postValue(data)
|
||||
}
|
||||
}.onFailure {
|
||||
Log.e("xxx", "获取导航数据 接口异常$it")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
package com.linx.playAndroid.widget
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.os.Environment
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.linx.common.ext.toast
|
||||
import java.io.File
|
||||
import java.math.BigDecimal
|
||||
|
||||
object CacheDataManager {
|
||||
|
||||
/**
|
||||
* 获取App缓存大小
|
||||
*/
|
||||
fun getTotalCacheSize(context: Context): String {
|
||||
|
||||
var cacheSize = getFolderSize(context.cacheDir)
|
||||
if (Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED) {
|
||||
cacheSize += getFolderSize(context.externalCacheDir)
|
||||
}
|
||||
return getFormatSize(cacheSize.toDouble())
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理缓存
|
||||
*/
|
||||
fun clearAllCache(context: Context) {
|
||||
context.let {
|
||||
deleteDir(it.cacheDir)
|
||||
if (Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED) {
|
||||
if (it.externalCacheDir == null) {
|
||||
"清理缓存失败".toast(context)
|
||||
}
|
||||
return
|
||||
}
|
||||
it.externalCacheDir?.let { file ->
|
||||
if(deleteDir(file)){
|
||||
"清理缓存成功".toast(context)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun deleteDir(dir: File): Boolean {
|
||||
if (dir.isDirectory) {
|
||||
val children = dir.list()
|
||||
for (i in children.indices) {
|
||||
val success = deleteDir(File(dir, children[i]))
|
||||
if (!success) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return dir.delete()
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件
|
||||
* Context.getExternalFilesDir() --> SDCard/Android/data/你的应用的包名/files/
|
||||
* 目录,一般放一些长时间保存的数据
|
||||
* Context.getExternalCacheDir() -->
|
||||
* SDCard/Android/data/你的应用包名/cache/目录,一般存放临时缓存数据
|
||||
*/
|
||||
fun getFolderSize(file: File?): Long {
|
||||
var size: Long = 0
|
||||
file?.run {
|
||||
try {
|
||||
val fileList = listFiles()
|
||||
for (i in fileList.indices) {
|
||||
// 如果下面还有文件
|
||||
size += if (fileList[i].isDirectory) {
|
||||
getFolderSize(fileList[i])
|
||||
} else {
|
||||
fileList[i].length()
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
return size
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化单位
|
||||
*/
|
||||
fun getFormatSize(size: Double): String {
|
||||
|
||||
val kiloByte = size / 1024
|
||||
if (kiloByte < 1) {
|
||||
return size.toString() + "Byte"
|
||||
}
|
||||
|
||||
val megaByte = kiloByte / 1024
|
||||
|
||||
if (megaByte < 1) {
|
||||
val result1 = BigDecimal(kiloByte.toString())
|
||||
return result1.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString() + "KB"
|
||||
}
|
||||
|
||||
val gigaByte = megaByte / 1024
|
||||
|
||||
if (gigaByte < 1) {
|
||||
val result2 = BigDecimal(megaByte.toString())
|
||||
return result2.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString() + "MB"
|
||||
}
|
||||
|
||||
val teraBytes = gigaByte / 1024
|
||||
|
||||
if (teraBytes < 1) {
|
||||
val result3 = BigDecimal(gigaByte.toString())
|
||||
return result3.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString() + "GB"
|
||||
}
|
||||
|
||||
val result4 = BigDecimal(teraBytes)
|
||||
return result4.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString() + "TB"
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.linx.playAndroid.widget
|
||||
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
|
||||
object SmallUtil {
|
||||
|
||||
/**
|
||||
* 获取版本号
|
||||
* */
|
||||
fun packageCode(context: Context): String {
|
||||
val manager = context.packageManager
|
||||
var code = ""
|
||||
try {
|
||||
val info = manager.getPackageInfo(context.packageName, 0)
|
||||
code = info.versionName
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
return code
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.linx.playAndroid.widget
|
||||
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.luminance
|
||||
import com.google.accompanist.systemuicontroller.rememberSystemUiController
|
||||
|
||||
/**
|
||||
* 设置状态栏颜色
|
||||
*/
|
||||
class StatsBarUtil {
|
||||
|
||||
/**
|
||||
* [isTransparent] 是否透明
|
||||
* [statusBarColor] 状态栏颜色
|
||||
* [navigationBarColor] 导航栏颜色
|
||||
* [statusBarDarkIcons] 状态栏内容是否为深色
|
||||
* [navigationBarColorDarkIcons] 导航栏内容是否为深色
|
||||
*/
|
||||
@Composable
|
||||
fun StatsBarColor(
|
||||
isTransparent: Boolean = false,
|
||||
statusBarColor: Color = if (isTransparent) Color.Transparent else MaterialTheme.colors.primary,
|
||||
navigationBarColor: Color = if (isTransparent) Color.Transparent else MaterialTheme.colors.background,
|
||||
statusBarDarkIcons: Boolean = statusBarColor.luminance() > 0.5f,
|
||||
navigationBarColorDarkIcons: Boolean = navigationBarColor.luminance() > 0.5f
|
||||
//如果要求使用深色图标但没有,将调用一个lambda来转换颜色,默认情况下是应用一个黑色框框
|
||||
// transformColorForLightContent: (Color) -> Color
|
||||
) {
|
||||
rememberSystemUiController().apply {
|
||||
setStatusBarColor(statusBarColor, statusBarDarkIcons)
|
||||
setNavigationBarColor(navigationBarColor, navigationBarColorDarkIcons)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.linx.playAndroid.widget
|
||||
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import com.linx.playAndroid.model.ProjectTreeData
|
||||
import com.linx.playAndroid.model.PublicNumChapterData
|
||||
|
||||
/**
|
||||
* 存放临时数据
|
||||
*/
|
||||
object StoreData {
|
||||
|
||||
//项目页面顶部指示器数据
|
||||
val projectTopBarListData = MutableLiveData<List<ProjectTreeData>>()
|
||||
|
||||
//公众号页面顶部指示器数据
|
||||
val publicNumTopBarListData = MutableLiveData<List<PublicNumChapterData>>()
|
||||
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package com.linx.playAndroid.widget.roomUtil
|
||||
|
||||
import android.content.Context
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.room.*
|
||||
|
||||
//1、定义entity
|
||||
@Entity(tableName = "search_history_sp")
|
||||
data class SearchHistoryData(
|
||||
//搜索的文本内容
|
||||
@ColumnInfo
|
||||
val text: String
|
||||
) {
|
||||
//自增
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
//主键
|
||||
var id: Int? = null
|
||||
}
|
||||
|
||||
//2、定义Dao层
|
||||
@Dao
|
||||
interface SearchHistoryDao {
|
||||
|
||||
//增
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
fun insertSearchHistory(searchHistoryData: SearchHistoryData)
|
||||
|
||||
//删除某一项
|
||||
@Delete
|
||||
fun deleteSearchHistory(searchHistoryData: SearchHistoryData)
|
||||
|
||||
//删除全部
|
||||
@Query("delete from search_history_sp")
|
||||
fun deleteAll()
|
||||
|
||||
//删除某一项
|
||||
@Query("delete from search_history_sp where id = :id")
|
||||
fun delete(id: Int)
|
||||
|
||||
//查,这里可能为空
|
||||
@Query("select * from search_history_sp where id = :id")
|
||||
fun queryLiveDataSearchHistory(id: Int): LiveData<List<SearchHistoryData>>
|
||||
|
||||
//查找全部
|
||||
@Query("select * from search_history_sp")
|
||||
fun queryLiveDataAll(): LiveData<List<SearchHistoryData>>
|
||||
|
||||
//查
|
||||
@Query("select * from search_history_sp where id = :id")
|
||||
fun querySearchHistory(id: Int): List<SearchHistoryData>
|
||||
|
||||
//查,text是否存在,如果存在返回1,不存在返回0 limit 1只返回一条
|
||||
@Query("select count(*) from search_history_sp where text = :text limit 1")
|
||||
fun querySearchHistory(text: String): Int
|
||||
|
||||
//查找全部
|
||||
@Query("select * from search_history_sp")
|
||||
fun queryAll(): List<SearchHistoryData>?
|
||||
|
||||
}
|
||||
|
||||
//3、定义database数据库
|
||||
@Database(entities = [SearchHistoryData::class], version = 1, exportSchema = false)
|
||||
abstract class SearchHistoryDB : RoomDatabase() {
|
||||
|
||||
abstract fun searchHistoryDao(): SearchHistoryDao
|
||||
|
||||
companion object {
|
||||
|
||||
private const val DB_NAME = "search_history_db"
|
||||
|
||||
//保证不同线程对这个共享变量进行操作的可见性,并且禁止进行指令重排序.
|
||||
@Volatile
|
||||
private var instance: SearchHistoryDB? = null
|
||||
|
||||
//保证线程安全,锁住
|
||||
@Synchronized
|
||||
fun getInstance(context: Context): SearchHistoryDB =
|
||||
instance ?: Room.databaseBuilder(context, SearchHistoryDB::class.java, DB_NAME).build()
|
||||
.also { instance = it }
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
package com.linx.playAndroid.widget.roomUtil
|
||||
|
||||
import android.content.Context
|
||||
import androidx.lifecycle.LiveData
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/**
|
||||
* 搜索历史帮助类
|
||||
*/
|
||||
object SearchHistoryHelper {
|
||||
|
||||
/**
|
||||
* 获取room数据库中存储的搜索历史数据
|
||||
* 以普通数据对象形式返回
|
||||
*/
|
||||
fun getSearchHistoryAll(context: Context) = SearchHistoryDB.getInstance(context).searchHistoryDao().queryAll()
|
||||
|
||||
/**
|
||||
* 查询text是否存在,存在返回1,不存在返回0,只返回一条
|
||||
*/
|
||||
fun getSearchHistory(context: Context, text: String) = SearchHistoryDB.getInstance(context).searchHistoryDao().querySearchHistory(text)
|
||||
|
||||
/**
|
||||
* 获取room数据库中存储的搜索历史数据
|
||||
* 以LiveData形式返回
|
||||
*/
|
||||
fun getLiveDataAllSearchHistory(context: Context): LiveData<List<SearchHistoryData>> =
|
||||
SearchHistoryDB.getInstance(context).searchHistoryDao().queryLiveDataAll()
|
||||
|
||||
|
||||
/**
|
||||
* 删除room数据库中所有的搜索历史数据
|
||||
*/
|
||||
fun CoroutineScope.deleteAll(context: Context) {
|
||||
launch(Dispatchers.IO) {
|
||||
getSearchHistoryAll(context)?.let {
|
||||
SearchHistoryDB.getInstance(context).searchHistoryDao().deleteAll()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除某一项,根据id
|
||||
*/
|
||||
fun CoroutineScope.delete(context: Context, id: Int) {
|
||||
launch(Dispatchers.IO) {
|
||||
SearchHistoryDB.getInstance(context).searchHistoryDao().delete(id)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增搜索历史数据
|
||||
*/
|
||||
fun CoroutineScope.insertSearchHistory(context: Context, searchHistoryData: SearchHistoryData) {
|
||||
launch(Dispatchers.IO) {
|
||||
|
||||
//查询某个元素是否存在 如果存在的话不用保存
|
||||
val isExist = getSearchHistory(context, searchHistoryData.text)
|
||||
if (isExist == 1) return@launch
|
||||
|
||||
val check = getSearchHistoryAll(context) ?: return@launch
|
||||
|
||||
if (check.size >= 10) {
|
||||
//删除第一项
|
||||
check.first().id?.let { delete(context, it) }
|
||||
}
|
||||
|
||||
//新增数据
|
||||
SearchHistoryDB.getInstance(context).searchHistoryDao().insertSearchHistory(searchHistoryData)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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,170 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path
|
||||
android:fillColor="#3DDC84"
|
||||
android:pathData="M0,0h108v108h-108z" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M9,0L9,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,0L19,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,0L29,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,0L39,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,0L49,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,0L59,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,0L69,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,0L79,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M89,0L89,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M99,0L99,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,9L108,9"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,19L108,19"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,29L108,29"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,39L108,39"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,49L108,49"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,59L108,59"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,69L108,69"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,79L108,79"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,89L108,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,99L108,99"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,29L89,29"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,39L89,39"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,49L89,49"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,59L89,59"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,69L89,69"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,79L89,79"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,19L29,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,19L39,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,19L49,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,19L59,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,19L69,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,19L79,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
</vector>
|
||||
@@ -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="@drawable/ic_launcher_background" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||
</adaptive-icon>
|
||||
@@ -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="@drawable/ic_launcher_background" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||
</adaptive-icon>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.4 KiB |
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user