a
@@ -0,0 +1 @@
|
||||
/build
|
||||
@@ -0,0 +1,75 @@
|
||||
plugins {
|
||||
id 'com.android.library'
|
||||
id 'kotlin-android'
|
||||
id 'kotlin-kapt'
|
||||
id 'kotlin-parcelize'
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdk compile_sdk_version
|
||||
|
||||
defaultConfig {
|
||||
minSdk min_sdk_version
|
||||
targetSdk target_sdk_version
|
||||
versionCode version_code
|
||||
versionName version_name
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility java_version
|
||||
targetCompatibility java_version
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api project(path: ':net')
|
||||
|
||||
api "androidx.core:core-ktx:$core_ktx_version"
|
||||
api "androidx.appcompat:appcompat:$appcompat_version"
|
||||
api "com.google.android.material:material:$material_version"
|
||||
api "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version"
|
||||
|
||||
//Initializer初始化
|
||||
api "androidx.startup:startup-runtime:$startup_version"
|
||||
|
||||
api "androidx.compose.ui:ui:$compose_version"
|
||||
api "androidx.compose.material:material:$compose_version"
|
||||
api "androidx.compose.ui:ui-tooling-preview:$compose_version"
|
||||
api "androidx.activity:activity-compose:$activity_compose_version"
|
||||
api "androidx.navigation:navigation-compose:$navigation_version"
|
||||
|
||||
//刷新头
|
||||
api "com.google.accompanist:accompanist-swiperefresh:$accompanist_pager"
|
||||
//UI ProvideWindowInsets正确获取状态栏高度
|
||||
api "com.google.accompanist:accompanist-insets-ui:$accompanist_pager"
|
||||
//控制UI栏
|
||||
api "com.google.accompanist:accompanist-systemuicontroller:$accompanist_pager"
|
||||
//提供了分页布局支持,类似viewPager
|
||||
api "com.google.accompanist:accompanist-pager:$accompanist_pager"
|
||||
|
||||
/**
|
||||
* compose提供有viewModel等其他方便的函数
|
||||
* 以下为Compose扩展库
|
||||
*/
|
||||
api "androidx.lifecycle:lifecycle-viewmodel-compose:$lifecycle_compose_version"
|
||||
//提供observeAsState等方法
|
||||
api "androidx.compose.runtime:runtime-livedata:$compose_version"
|
||||
|
||||
//Coil 核心库
|
||||
api "io.coil-kt:coil-compose:$coil_version"
|
||||
//Coil 选择添加
|
||||
// implementation("io.coil-kt:coil-gif:1.2.2")//支持GIF
|
||||
// implementation("io.coil-kt:coil-svg:1.2.2")//支持SVG
|
||||
// implementation("io.coil-kt:coil-video:1.2.2")//支持Video
|
||||
|
||||
//加载lottie动画
|
||||
api "com.airbnb.android:lottie-compose:$lottie_version"
|
||||
|
||||
// MMKV
|
||||
api "com.tencent:mmkv-static:$mmkv_version"
|
||||
|
||||
//room
|
||||
api "androidx.room:room-runtime:$room_version"
|
||||
kapt "androidx.room:room-compiler:$room_version"
|
||||
api "androidx.room:room-ktx:$room_version"
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.linx.common">
|
||||
|
||||
</manifest>
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.linx.common.base
|
||||
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.linx.common.baseData.CommonConstant
|
||||
import com.linx.common.ext.addTo
|
||||
import com.linx.common.model.ThemeType
|
||||
import com.linx.common.widget.SpUtilsMMKV
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/**
|
||||
* ViewModel的基类
|
||||
*/
|
||||
abstract class BaseViewModel: ViewModel() {
|
||||
|
||||
//job列表
|
||||
private val jobs: MutableList<Job> = mutableListOf<Job>()
|
||||
|
||||
//标记网络loading状态
|
||||
val isLoading = MutableLiveData<Boolean>()
|
||||
|
||||
protected fun serverAwait(block: suspend CoroutineScope.() -> Unit) = viewModelScope.launch {
|
||||
//协程启动之前
|
||||
isLoading.value = true
|
||||
block.invoke(this)
|
||||
//协程启动之后
|
||||
isLoading.value = false
|
||||
}.addTo(jobs)
|
||||
|
||||
//取消全部协程
|
||||
override fun onCleared() {
|
||||
jobs.forEach { it.cancel() }
|
||||
super.onCleared()
|
||||
}
|
||||
|
||||
//是否登录
|
||||
fun isLogin(): Boolean = SpUtilsMMKV.getBoolean(CommonConstant.IS_LOGIN) == true
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.linx.common.baseData
|
||||
|
||||
/**
|
||||
* 项目中的一切全局变量
|
||||
*/
|
||||
object CommonConstant {
|
||||
|
||||
//是否登录
|
||||
const val IS_LOGIN = "is_login"
|
||||
|
||||
//主题颜色
|
||||
const val THEME_COLOR = "theme_color"
|
||||
|
||||
//是否隐藏置顶文章
|
||||
const val GONE_ARTICLE_TOP = "article_top"
|
||||
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.linx.common.baseData
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.ui.graphics.painter.Painter
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import com.linx.common.R
|
||||
|
||||
/**
|
||||
* 导航相关
|
||||
*/
|
||||
object Nav {
|
||||
|
||||
//密封类关联目的地路线和字符串资源
|
||||
sealed class BottomNavScreen(val route: String, @StringRes val resourceId: Int, @DrawableRes val id: Int) {
|
||||
object HomeScreen: BottomNavScreen("home", R.string.bottom_home, R.mipmap.nav_home)
|
||||
object ProjectScreen: BottomNavScreen("project", R.string.bottom_project, R.mipmap.nav_project)
|
||||
object SquareScreen: BottomNavScreen("square", R.string.bottom_square, R.mipmap.nav_square)
|
||||
object PublicNumScreen: BottomNavScreen("publicNum", R.string.bottom_public_num, R.mipmap.nav_public_num)
|
||||
object LearnScreen: BottomNavScreen("learn", R.string.bottom_learn, R.mipmap.nav_learn)
|
||||
object MineScreen: BottomNavScreen("mine", R.string.bottom_mine, R.mipmap.nav_mine)
|
||||
}
|
||||
|
||||
//记录BottomNav当前的Item
|
||||
val bottomNavRoute = mutableStateOf<BottomNavScreen>(BottomNavScreen.HomeScreen)
|
||||
|
||||
//是否点击两次返回的activity
|
||||
var twoBackFinishActivity = false
|
||||
|
||||
//项目页面指示器index
|
||||
val projectTopBarIndex = mutableStateOf(0)
|
||||
|
||||
//广场页面指示器index
|
||||
val squareTopBarIndex = mutableStateOf(0)
|
||||
|
||||
//公众号页面指示器index
|
||||
val publicNumIndex = mutableStateOf(0)
|
||||
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.linx.common.baseData
|
||||
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import com.linx.common.model.ThemeType
|
||||
import com.linx.common.widget.SpUtilsMMKV
|
||||
|
||||
val themeColorList = listOf(
|
||||
ThemeType.Default,
|
||||
ThemeType.Theme1,
|
||||
ThemeType.Theme2,
|
||||
ThemeType.Theme3,
|
||||
ThemeType.Theme4,
|
||||
ThemeType.Theme5,
|
||||
)
|
||||
|
||||
/**
|
||||
* 标题栏和导航栏颜色
|
||||
*/
|
||||
val themeTypeState: MutableState<ThemeType> by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
|
||||
mutableStateOf(getThemeType())
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取保存下来的主题颜色
|
||||
*/
|
||||
private fun getThemeType(): ThemeType {
|
||||
return when(SpUtilsMMKV.getInt(CommonConstant.THEME_COLOR) ?: 0) {
|
||||
0 -> ThemeType.Default
|
||||
1 -> ThemeType.Theme1
|
||||
2 -> ThemeType.Theme2
|
||||
3 -> ThemeType.Theme3
|
||||
4 -> ThemeType.Theme4
|
||||
5 -> ThemeType.Theme5
|
||||
else -> ThemeType.Theme5
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新个人信息数据
|
||||
*/
|
||||
val refreshUserMessageData: MutableState<String> by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
|
||||
mutableStateOf("")
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.linx.common.ext
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
|
||||
/**
|
||||
* 加入qq聊天群
|
||||
*/
|
||||
fun joinQQGroup(context: Context, key: String): Boolean {
|
||||
val intent = Intent()
|
||||
intent.data =
|
||||
Uri.parse("mqqopensdkapi://bizAgent/qm/qr?url=http%3A%2F%2Fqm.qq.com%2Fcgi-bin%2Fqm%2Fqr%3Ffrom%3Dapp%26p%3Dandroid%26k%3D$key")
|
||||
// 此Flag可根据具体产品需要自定义,如设置,则在加群界面按返回,返回手Q主界面,不设置,按返回会返回到呼起产品界面 //intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
return try {
|
||||
context.startActivity(intent)
|
||||
true
|
||||
} catch (e: Exception) {
|
||||
// 未安装手Q或安装的版本不支持
|
||||
"未安装手机QQ或安装的版本不支持".toast(context)
|
||||
false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.linx.common.ext
|
||||
|
||||
import android.content.Context
|
||||
import android.widget.Toast
|
||||
import com.linx.common.widget.GsonUtils
|
||||
|
||||
/**
|
||||
* 一些常用的方法
|
||||
*/
|
||||
|
||||
fun Any?.toJsonStr(): String {
|
||||
return GsonUtils.toJson(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* toast
|
||||
*/
|
||||
fun Any.toast(context: Context) =
|
||||
Toast.makeText(context, this.toString(), Toast.LENGTH_SHORT).show()
|
||||
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.linx.common.ext
|
||||
|
||||
import kotlinx.coroutines.Job
|
||||
|
||||
/**
|
||||
* 添加Job到list中
|
||||
*/
|
||||
fun Job.addTo(list: MutableList<Job>) {
|
||||
list.add(this)
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.linx.common.ext
|
||||
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* Long类型的时间转Date
|
||||
* [type] 转换出来的日期格式
|
||||
*/
|
||||
fun Long.transitionDate(type: String = "yyyy-MM-dd HH:mm:ss"): String = SimpleDateFormat(type).format(
|
||||
Date(this)
|
||||
)
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.linx.common.model
|
||||
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
|
||||
/**
|
||||
* 标题栏的数据
|
||||
*/
|
||||
data class StatusBarTitleData(
|
||||
val title: String,
|
||||
val leftIcon: ImageVector? = null,
|
||||
val rightIcon: ImageVector? = null,
|
||||
val onLeftClick: () -> Unit = {},
|
||||
val onRightClick: () -> Unit = {}
|
||||
)
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.linx.common.model
|
||||
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
|
||||
/**
|
||||
* 主题类型
|
||||
*/
|
||||
enum class ThemeType {
|
||||
Default,
|
||||
Theme1,
|
||||
Theme2,
|
||||
Theme3,
|
||||
Theme4,
|
||||
Theme5
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.linx.common.widget
|
||||
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.GsonBuilder
|
||||
|
||||
/**
|
||||
* 数据解析帮助类
|
||||
*/
|
||||
object GsonUtils {
|
||||
|
||||
private val gsonBuilder: GsonBuilder by lazy { GsonBuilder() }
|
||||
|
||||
val gson: Gson by lazy { gsonBuilder.create() }
|
||||
|
||||
fun toJson(paramObject: Any?): String = gson.toJson(paramObject)
|
||||
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package com.linx.common.widget
|
||||
|
||||
import androidx.annotation.MainThread
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.Observer
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
|
||||
/**
|
||||
* 用一个原子 AtomicBoolean记录一次setValue
|
||||
* 在发送一次后在将AtomicBoolean设置为false,阻止后续前台重新触发时的数据发送。
|
||||
*/
|
||||
class SingleLiveEvent<T> : MutableLiveData<T>() {
|
||||
private val mPending = AtomicBoolean(false)
|
||||
override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
|
||||
super.observe(owner, { t ->
|
||||
if (mPending.compareAndSet(true, false)) {
|
||||
observer.onChanged(t)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@MainThread
|
||||
override fun setValue(t: T?) {
|
||||
mPending.set(true)
|
||||
super.setValue(t)
|
||||
}
|
||||
|
||||
/**
|
||||
* Used for cases where T is Void, to make calls cleaner.
|
||||
*/
|
||||
@MainThread
|
||||
fun call() {
|
||||
value = null
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.linx.common.widget
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/**
|
||||
* 开启一个IO线程,一段时间后执行代码
|
||||
*/
|
||||
@Composable
|
||||
fun sleepTime(millis: Long = 1500, block: () -> Unit) {
|
||||
LaunchedEffect(Unit) {
|
||||
launch(Dispatchers.IO) {
|
||||
Thread.sleep(millis)
|
||||
block()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 开启一个IO线程,一段时间后执行代码
|
||||
*/
|
||||
fun ViewModel.sleepTime(millis: Long = 1500, block: () -> Unit) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
Thread.sleep(millis)
|
||||
block()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
package com.linx.common.widget
|
||||
|
||||
import android.os.Parcelable
|
||||
import com.tencent.mmkv.MMKV
|
||||
import java.util.*
|
||||
/**
|
||||
* 腾讯mmkv
|
||||
* 自定义的key-value
|
||||
*/
|
||||
object SpUtilsMMKV {
|
||||
|
||||
var mmkv: MMKV? = null
|
||||
|
||||
init {
|
||||
mmkv = MMKV.defaultMMKV()
|
||||
}
|
||||
|
||||
fun put(key: String, value: Any?): Boolean {
|
||||
return when (value) {
|
||||
is String -> mmkv?.encode(key, value)!!
|
||||
is Float -> mmkv?.encode(key, value)!!
|
||||
is Boolean -> mmkv?.encode(key, value)!!
|
||||
is Int -> mmkv?.encode(key, value)!!
|
||||
is Long -> mmkv?.encode(key, value)!!
|
||||
is Double -> mmkv?.encode(key, value)!!
|
||||
is ByteArray -> mmkv?.encode(key, value)!!
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 这里使用安卓自带的Parcelable序列化,它比java支持的Serializer序列化性能好些
|
||||
*/
|
||||
fun <T : Parcelable> put(key: String, t: T?): Boolean {
|
||||
if (t == null) {
|
||||
return false
|
||||
}
|
||||
return mmkv?.encode(key, t)!!
|
||||
}
|
||||
|
||||
fun put(key: String, sets: Set<String>?): Boolean {
|
||||
if (sets == null) {
|
||||
return false
|
||||
}
|
||||
return mmkv?.encode(key, sets)!!
|
||||
}
|
||||
|
||||
fun getInt(key: String): Int? {
|
||||
return mmkv?.decodeInt(key, 0)
|
||||
}
|
||||
|
||||
fun getDouble(key: String): Double? {
|
||||
return mmkv?.decodeDouble(key, 0.00)
|
||||
}
|
||||
|
||||
fun getLong(key: String): Long? {
|
||||
return mmkv?.decodeLong(key, 0L)
|
||||
}
|
||||
|
||||
fun getBoolean(key: String): Boolean? {
|
||||
return mmkv?.decodeBool(key, false)
|
||||
}
|
||||
|
||||
fun getFloat(key: String): Float? {
|
||||
return mmkv?.decodeFloat(key, 0F)
|
||||
}
|
||||
|
||||
fun getByteArray(key: String): ByteArray? {
|
||||
return mmkv?.decodeBytes(key)
|
||||
}
|
||||
|
||||
fun getString(key: String): String? {
|
||||
return mmkv?.decodeString(key, "")
|
||||
}
|
||||
|
||||
/**
|
||||
* SpUtils.getParcelable<Class>("")
|
||||
*/
|
||||
inline fun <reified T : Parcelable> getParcelable(key: String): T? {
|
||||
return mmkv?.decodeParcelable(key, T::class.java)
|
||||
}
|
||||
|
||||
fun getStringSet(key: String): Set<String>? {
|
||||
return mmkv?.decodeStringSet(key, Collections.emptySet())
|
||||
}
|
||||
|
||||
fun removeKey(key: String) {
|
||||
mmkv?.removeValueForKey(key)
|
||||
}
|
||||
|
||||
fun clearAll() {
|
||||
mmkv?.clearAll()
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.linx.common.widget
|
||||
|
||||
import android.content.Context
|
||||
import android.view.KeyEvent
|
||||
import android.widget.Toast
|
||||
import androidx.activity.ComponentActivity
|
||||
import com.linx.common.baseData.Nav
|
||||
|
||||
/**
|
||||
* 点击两次返回按钮关闭app
|
||||
*/
|
||||
class TwoBackFinish {
|
||||
|
||||
companion object {
|
||||
var mExitTime: Long = 0
|
||||
}
|
||||
|
||||
fun execute(context: Context, finish:() -> Unit) {
|
||||
when {
|
||||
/**
|
||||
* 点击两次退出程序 有事件间隔,间隔内退出程序,否则提示
|
||||
*/
|
||||
(System.currentTimeMillis() - mExitTime) > 1500 -> {
|
||||
Toast.makeText(context, "再按一次退出程序", Toast.LENGTH_SHORT).show()
|
||||
mExitTime = System.currentTimeMillis()
|
||||
}
|
||||
else -> {
|
||||
Nav.twoBackFinishActivity = true
|
||||
finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 783 B |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="bottom_home">首页</string>
|
||||
<string name="bottom_project">项目</string>
|
||||
<string name="bottom_square">广场</string>
|
||||
<string name="bottom_public_num">公众号</string>
|
||||
<string name="bottom_learn">学习</string>
|
||||
<string name="bottom_mine">我的</string>
|
||||
</resources>
|
||||