This commit is contained in:
coco
2026-07-03 16:23:31 +08:00
commit 7a4fb0e6ae
1979 changed files with 101570 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
/build
+141
View File
@@ -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"
}
+38
View File
@@ -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
)
}
}
}
)
}
}
@@ -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)
}
@@ -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 }
}
}
@@ -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