前台room依赖移动到lib模块

TokenUtil.kt迁移到lib模块
根build.gradle.kts迁移变量到子模块
增加拉取通知的后台服务 MessageService.kt
增加通知相关接口
master
pan 3 years ago
parent 94b057d1cf
commit e7f07a3415
  1. 6
      background/build.gradle.kts
  2. 4
      background/src/main/java/com/gyf/csams/account/ui/LoginActivity.kt
  3. 11
      background/src/main/java/com/gyf/csams/main/model/MainViewModel.kt
  4. 4
      background/src/main/java/com/gyf/csams/main/ui/AssociationManagementActivity.kt
  5. 4
      background/src/main/java/com/gyf/csams/main/ui/DepartmentActivity.kt
  6. 2
      background/src/main/java/com/gyf/csams/main/ui/MainActivity.kt
  7. 4
      background/src/main/java/com/gyf/csams/main/ui/ManagementOfficerActivity.kt
  8. 4
      background/src/main/java/com/gyf/csams/main/ui/MenuActivity.kt
  9. 4
      background/src/main/java/com/gyf/csams/uikit/Table.kt
  10. 8
      build.gradle.kts
  11. 8
      foreground/build.gradle.kts
  12. 2
      foreground/src/main/AndroidManifest.xml
  13. 9
      foreground/src/main/java/com/gyf/csams/Api.kt
  14. 43
      foreground/src/main/java/com/gyf/csams/InitActivity.kt
  15. 36
      foreground/src/main/java/com/gyf/csams/InitViewModel.kt
  16. 58
      foreground/src/main/java/com/gyf/csams/MainApplication.kt
  17. 6
      foreground/src/main/java/com/gyf/csams/account/model/AccountViewModel.kt
  18. 2
      foreground/src/main/java/com/gyf/csams/activity/model/ApplyActViewModel.kt
  19. 12
      foreground/src/main/java/com/gyf/csams/activity/ui/ActivityDetailActivity.kt
  20. 4
      foreground/src/main/java/com/gyf/csams/activity/ui/ApplyActActivity.kt
  21. 7
      foreground/src/main/java/com/gyf/csams/association/model/RegAssociationViewModel.kt
  22. 10
      foreground/src/main/java/com/gyf/csams/association/ui/AssociationActivity.kt
  23. 4
      foreground/src/main/java/com/gyf/csams/association/ui/ExamActivity.kt
  24. 4
      foreground/src/main/java/com/gyf/csams/association/ui/ReNameActivity.kt
  25. 30
      foreground/src/main/java/com/gyf/csams/association/ui/RegAssociationActivity.kt
  26. 104
      foreground/src/main/java/com/gyf/csams/main/model/MainViewModel.kt
  27. 45
      foreground/src/main/java/com/gyf/csams/main/ui/MainActivity.kt
  28. 82
      foreground/src/main/java/com/gyf/csams/message/model/SysMessageViewModel.kt
  29. 4
      foreground/src/main/java/com/gyf/csams/message/ui/MessageActivity.kt
  30. 22
      foreground/src/main/java/com/gyf/csams/message/ui/SysMessageActivity.kt
  31. 23
      foreground/src/main/java/com/gyf/csams/uikit/ViewModel.kt
  32. 30
      foreground/src/main/java/com/gyf/csams/util/ContextUtil.kt
  33. 26
      lib/build.gradle.kts
  34. 73
      lib/src/main/java/com/gyf/NotificationWorker.kt
  35. 61
      lib/src/main/java/com/gyf/lib/MessageService.kt
  36. 23
      lib/src/main/java/com/gyf/lib/uikit/MainFrame.kt
  37. 62
      lib/src/main/java/com/gyf/lib/util/ContextUtil.kt
  38. 43
      lib/src/main/java/com/gyf/lib/util/HttpUtil.kt
  39. 24
      lib/src/main/java/com/gyf/lib/util/ImageUtil.kt
  40. 66
      lib/src/main/java/com/gyf/lib/util/NotificationUtil.kt
  41. 18
      lib/src/main/java/com/gyf/lib/util/TokenUtil.kt
  42. 15
      lib/src/main/res/drawable/ic_notification.xml
  43. 2
      lib/src/test/java/com/gyf/lib/ExampleUnitTest.kt

@ -20,7 +20,8 @@ android {
}
buildTypes {
val appName = "${rootProject.extra["background_app_name"]}"
val appName = "社团管理"
debug {
manifestPlaceholders.apply {
this["background_app_name"] = appName
@ -56,9 +57,6 @@ android {
dependencies {
implementation(project(":lib"))
// optional - Test helpers
testImplementation("androidx.room:room-testing:${rootProject.extra["room_version"]}")
//测试
testImplementation("junit:junit:4.13.2")
/**

@ -19,7 +19,7 @@ import androidx.lifecycle.viewmodel.compose.viewModel
import com.gyf.csams.account.model.LoginViewModel
import com.gyf.csams.main.ui.MainActivity
import com.gyf.lib.uikit.BaseTextField
import com.gyf.lib.uikit.BodyS
import com.gyf.lib.uikit.Body
import com.gyf.lib.uikit.MainBoxFrame
/**
@ -31,7 +31,7 @@ class LoginActivity : ComponentActivity() {
super.onCreate(savedInstanceState)
setContent {
BodyS {
Body {
val context = LocalContext.current as LoginActivity
MainBoxFrame(
background = { /*TODO 背景图*/ },

@ -6,17 +6,6 @@ import androidx.lifecycle.ViewModel
import com.gyf.lib.uikit.PersonInfoVo
import com.gyf.lib.util.randomChinese
object LocalToken {
lateinit var token: String
lateinit var infoVo: PersonInfoVo
fun register(token: String, infoVo: PersonInfoVo) {
this.token = token
this.infoVo = infoVo
}
}
/**
* 部长
*

@ -18,7 +18,7 @@ import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.gyf.csams.main.model.AssociationLevel
import com.gyf.csams.main.model.AssociationManagementViewModel
import com.gyf.lib.uikit.BodyS
import com.gyf.lib.uikit.Body
import com.gyf.lib.uikit.MainBoxFrame
import com.gyf.lib.uikit.ScaffoldModel
@ -31,7 +31,7 @@ class AssociationManagementActivity : ComponentActivity() {
super.onCreate(savedInstanceState)
setContent {
BodyS {
Body {
MainBoxFrame(background = { /*TODO*/ }) {
val model: AssociationManagementViewModel = viewModel()
val data by model.data.observeAsState()

@ -16,7 +16,7 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.gyf.csams.R
import com.gyf.lib.uikit.BodyS
import com.gyf.lib.uikit.Body
import com.gyf.lib.uikit.MainColumnFrame
@ -29,7 +29,7 @@ class DepartmentActivity : ComponentActivity() {
super.onCreate(savedInstanceState)
setContent {
BodyS {
Body {
MainColumnFrame(background = { /*TODO*/ }) {
val weight = 0.1F
val departWeight = 0.2F

@ -25,7 +25,7 @@ class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
BodyS {
Body {
val model: MainViewModel = viewModel()
val person by model.person.observeAsState()
MainColumnFrame(background = { /*TODO*/ }) {

@ -25,7 +25,7 @@ import com.gyf.csams.main.model.Duty
import com.gyf.csams.main.model.ManagementOfficerModel
import com.gyf.csams.main.model.MinisterVo
import com.gyf.csams.main.model.OfficerVo
import com.gyf.lib.uikit.BodyS
import com.gyf.lib.uikit.Body
import com.gyf.lib.uikit.MainColumnFrame
import com.gyf.lib.uikit.Profile
import com.gyf.lib.uikit.ScaffoldModel
@ -40,7 +40,7 @@ class ManagementOfficerActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
BodyS {
Body {
MainColumnFrame(background = { /*TODO*/ }) {
val weight = 1 / 3F
val model: ManagementOfficerModel = viewModel()

@ -16,7 +16,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.gyf.csams.main.model.MenuType
import com.gyf.lib.uikit.BodyS
import com.gyf.lib.uikit.Body
import com.gyf.lib.uikit.MainBoxFrame
/**
@ -33,7 +33,7 @@ class MenuActivity : ComponentActivity() {
menuType = intent?.getSerializableExtra(MenuType::name.name) as MenuType
setContent {
BodyS {
Body {
MainBoxFrame(background = { /*TODO*/ }, contentAlignment = Alignment.Center) {
Column(
modifier = Modifier

@ -18,7 +18,7 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.gyf.lib.ScrollListW
import com.gyf.lib.uikit.BodyS
import com.gyf.lib.uikit.Body
import com.gyf.lib.uikit.MainColumnFrame
/**
@ -33,7 +33,7 @@ fun <A> TestTable(
@StringRes title: Int? = null,
callback: @Composable (vo: A) -> Unit
) {
BodyS {
Body {
MainColumnFrame(background = { /*TODO*/ }) {
val listState = rememberLazyListState()
val model = viewModel(modelClass = clazz)

@ -5,14 +5,10 @@ val key_password by extra("123456")
buildscript {
//Jetpack Compose版本
val compose_version by extra("1.0.0-beta07")
//生命周期组件版本
val lifecycle_version by extra("2.3.1")
//APP应用名字
val foreground_app_name by extra("学生社团")
val SERVER_ADDRESS by extra("http://192.168.50.107:8080")
val room_version by extra("2.3.0")
val kotlin_version by extra("1.4.32")
val background_app_name by extra("社团管理")
repositories {
maven("https://maven.aliyun.com/repository/google")
maven("https://maven.aliyun.com/repository/public")

@ -1,7 +1,6 @@
plugins {
id("com.android.application")
id("kotlin-android")
id("kotlin-kapt")
}
android {
@ -30,7 +29,8 @@ android {
}
buildTypes {
val appName = "${rootProject.extra["foreground_app_name"]}"
//APP应用名字
val appName = "学生社团"
val serverAddress = rootProject.extra["SERVER_ADDRESS"]
debug {
manifestPlaceholders.apply {
@ -74,10 +74,6 @@ android {
dependencies {
implementation(project(":lib"))
implementation(files("libs\\BaiduLBS_Android.jar"))
kapt("androidx.room:room-compiler:${rootProject.extra["room_version"]}")
// optional - Test helpers
testImplementation("androidx.room:room-testing:${rootProject.extra["room_version"]}")
//测试
testImplementation("junit:junit:4.13.2")
/**

@ -104,6 +104,8 @@
<activity
android:name=".message.ui.SysMessageActivity"
android:exported="true" />
<service android:name="com.gyf.lib.MessageService" />
</application>
</manifest>

@ -71,6 +71,15 @@ enum class AssociationApi(val path: String) : UrlPath {
}
}
enum class NotificationApi(val path: String) : UrlPath {
Count("/count"),
List("/list");
override fun build(): String {
return "/api/notification${this.path}"
}
}
/**
* 构建服务端请求接口地址
*

@ -4,11 +4,9 @@ import android.content.Intent
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Alignment
import androidx.compose.ui.platform.LocalContext
import androidx.lifecycle.viewmodel.compose.viewModel
import com.gyf.csams.account.ui.AccountActivity
import com.gyf.csams.main.ui.MainActivity
@ -16,6 +14,9 @@ import com.gyf.csams.uikit.AnimationText
import com.gyf.lib.uikit.Body
import com.gyf.lib.uikit.MainBoxFrame
import com.orhanobut.logger.Logger
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
class InitActivity : ComponentActivity() {
@ -23,44 +24,40 @@ class InitActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 检查网络
setContent {
Body {
MainBoxFrame(background = { /*TODO*/ }, contentAlignment = Alignment.Center) {
val initViewModel: InitViewModel = viewModel()
//检查网络
val isNetWorkWorking: Boolean? by initViewModel.isNetWorkWorking.observeAsState(
null
)
Logger.i("初始化")
when (isNetWorkWorking) {
null -> AnimationText(text = "测试服务端运行状态中。。。")
true -> {
Init()
finish()
}
false -> {
AnimationText(text = "无法连接到服务端,请检查服务端地址${BuildConfig.SERVER_ADDRESS}是否配置正确")
}
true -> init(initViewModel = initViewModel)
false -> AnimationText(text = "无法连接到服务端,请检查服务端地址${BuildConfig.SERVER_ADDRESS}是否配置正确")
}
}
}
}
}
@Composable
private fun Init(initViewModel: InitViewModel = viewModel()) {
Logger.i("初始化。。。。")
val context = LocalContext.current
private fun init(initViewModel: InitViewModel) {
//后台检查token
initViewModel.hasOnlyUserToken(context)
//监听token校验状态
val isValid: Boolean? by initViewModel.token.observeAsState(null)
when (isValid) {
false -> context.startActivity(Intent(context, AccountActivity::class.java))
true -> context.startActivity(Intent(context, MainActivity::class.java))
initViewModel.hasOnlyUserToken(onSuccess = {
startActivity(Intent(this, MainActivity::class.java))
}, onFail = {
startActivity(Intent(this, AccountActivity::class.java))
})
GlobalScope.launch {
delay(1000)
finish()
}
}
}

@ -1,30 +1,24 @@
package com.gyf.csams
import android.content.Context
import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.google.gson.reflect.TypeToken
import com.gyf.csams.util.*
import com.gyf.lib.util.ApiResponse
import com.gyf.lib.util.HttpClient
import com.gyf.csams.util.SimpleCallback
import com.gyf.lib.util.*
import com.orhanobut.logger.Logger
import kotlinx.coroutines.launch
class InitViewModel : ViewModel() {
class InitViewModel(application: Application) : AndroidViewModel(application) {
/**
* 服务器网络状态是否正常true=正常false=不正常
*/
private val _isNetWorkWorking = MutableLiveData<Boolean>()
val isNetWorkWorking: LiveData<Boolean> = _isNetWorkWorking
/**
* token
*/
private val _token = MutableLiveData<Boolean>()
val token: LiveData<Boolean> = _token
init {
checkServer()
@ -46,11 +40,13 @@ class InitViewModel : ViewModel() {
}
/**
* 查询本地是否有且只有一个用户token如果有则自动登录
*/
fun hasOnlyUserToken(context: Context) {
fun hasOnlyUserToken(onSuccess: () -> Unit, onFail: () -> Unit) {
viewModelScope.launch {
val context = getApplication<MainApplication>()
val db = AppDatabase.getInstance(context)
val tokenList = db?.tokenDao()?.queryAll()
if (tokenList != null && tokenList.size == 1) {
@ -64,21 +60,21 @@ class InitViewModel : ViewModel() {
action = action,
onSuccess = { it ->
it.body?.let {
_token.postValue(it)
Logger.i("token校验结果:${it}")
if (it) {
TokenManager.token = currentToken
}
onSuccess()
}
},
onFail = { TODO("token校验失败") },
onFail = {
Logger.e(it)
onFail()
},
type = object : TypeToken<ApiResponse<Boolean>>() {}.type
),
jsonParam = TokenVo(
token = currentToken.token,
userId = currentToken.userId
)
jsonParam = currentToken
)
} else if (tokenList != null && tokenList.size > 1) {
@ -86,10 +82,10 @@ class InitViewModel : ViewModel() {
Logger.i("本地存储了${tokenList.size}个令牌,需要手动登录")
db.tokenDao().deleteAll()
Logger.i("清空所有token")
_token.postValue(false)
onFail()
} else {
Logger.i("本地没有任何token,跳转到登录界面")
_token.postValue(false)
onFail()
}
}
}

@ -1,6 +1,10 @@
package com.gyf.csams
import android.app.Activity
import android.app.Application
import android.app.NotificationManager
import android.os.Build
import android.os.Bundle
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.asImageBitmap
import androidx.lifecycle.MutableLiveData
@ -16,17 +20,13 @@ import com.baidu.location.LocationClient
import com.baidu.mapapi.SDKInitializer
import com.gyf.csams.uikit.BackgroundImage
import com.gyf.lib.util.ImageUtil
import com.gyf.lib.util.NotificationUtil
import com.orhanobut.logger.AndroidLogAdapter
import com.orhanobut.logger.DiskLogAdapter
import com.orhanobut.logger.Logger
import okhttp3.OkHttpClient
class MainApplication : Application(), ImageLoaderFactory {
private val backgroundImage = mutableMapOf<BackgroundImage, MemoryCache.Key?>()
@ -82,7 +82,6 @@ class MainApplication : Application(), ImageLoaderFactory {
}
override fun onCreate() {
super.onCreate()
//初始化日志
@ -95,8 +94,22 @@ class MainApplication : Application(), ImageLoaderFactory {
Logger.i("${BuildConfig.foreground_app_name}启动")
SDKInitializer.initialize(this)
//预加载背景图
preloadImage()
/**
* https://developer.android.com/training/notify-user/build-notification?hl=zh-cn#java
* 必须先通过向 createNotificationChannel() 传递 NotificationChannel 的实例在系统中注册应用的通知渠道然后才能在 Android 8.0 及更高版本上提供通知
*/
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
NotificationUtil.createNotificationChannel(
applicationContext,
NotificationManager.IMPORTANCE_DEFAULT
)
}
//Activity生命周期进行集中管理
registerActivityLifecycleCallbacks(MyCall)
}
}
@ -142,3 +155,34 @@ object MyLocationListener : BDAbstractLocationListener() {
}
}
}
object MyCall : Application.ActivityLifecycleCallbacks {
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
Logger.i("${activity::class.java.name}[onActivityCreated]")
}
override fun onActivityStarted(activity: Activity) {
Logger.i("${activity::class.java.name}[onActivityStarted]")
}
override fun onActivityResumed(activity: Activity) {
Logger.i("${activity::class.java.name}[onActivityResumed]")
}
override fun onActivityPaused(activity: Activity) {
Logger.i("${activity::class.java.name}[onActivityPaused]")
}
override fun onActivityStopped(activity: Activity) {
Logger.i("${activity::class.java.name}[onActivityStopped]")
}
override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {
Logger.i("${activity::class.java.name}[onActivitySaveInstanceState]")
}
override fun onActivityDestroyed(activity: Activity) {
Logger.i("${activity::class.java.name}[onActivityDestroyed]")
}
}

@ -11,15 +11,11 @@ import com.gyf.csams.AccountApi
import com.gyf.csams.Api
import com.gyf.csams.R
import com.gyf.csams.account.ui.AccountRoute
import com.gyf.csams.util.AppDatabase
import com.gyf.csams.util.SimpleCallback
import com.gyf.csams.util.Token
import com.gyf.csams.util.TokenManager
import com.gyf.lib.uikit.FormStatus
import com.gyf.lib.uikit.StringForm
import com.gyf.lib.uikit.ValidStringForm
import com.gyf.lib.util.ApiResponse
import com.gyf.lib.util.HttpClient
import com.gyf.lib.util.*
import com.orhanobut.logger.Logger
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch

@ -21,9 +21,9 @@ import com.gyf.csams.BuildConfig
import com.gyf.csams.MyLocationListener
import com.gyf.csams.NOT_IMPL_TIP
import com.gyf.csams.R
import com.gyf.csams.util.ContextUtil
import com.gyf.lib.uikit.ScaffoldModel
import com.gyf.lib.uikit.StringForm
import com.gyf.lib.util.ContextUtil
import com.gyf.lib.util.DATETIME_FORMAT
import com.orhanobut.logger.Logger
import kotlinx.coroutines.GlobalScope

@ -23,11 +23,11 @@ import androidx.navigation.compose.composable
import com.gyf.csams.R
import com.gyf.csams.activity.model.*
import com.gyf.csams.uikit.*
import com.gyf.lib.uikit.Body
import com.gyf.lib.uikit.MainColumnFrame
import com.gyf.lib.uikit.ScaffoldModel
import com.gyf.lib.uikit.ShowSnackbar
import com.gyf.lib.uikit.TestBody
import com.gyf.lib.util.format
import com.orhanobut.logger.Logger
/**
* 活动详情
@ -41,7 +41,7 @@ class ActivityDetailActivity : ComponentActivity() {
setContent {
Body { nav ->
TestBody { nav, scaffoldState ->
val model: ActivityDetailViewModel = viewModel()
val currentMenuName by model.currentMenu.observeAsState(ActivityDetailMenu.startMenu)
Column {
@ -56,18 +56,22 @@ class ActivityDetailActivity : ComponentActivity() {
) {
composable(ActivityDetailMenu.Info.name) {
Info()
ShowSnackbar(scaffoldState = scaffoldState)
model.clickMenu(ActivityDetailMenu.Info)
}
composable(ActivityDetailMenu.Photo.name) {
Photo()
ShowSnackbar(scaffoldState = scaffoldState)
model.clickMenu(ActivityDetailMenu.Photo)
}
composable(ActivityDetailMenu.Member.name) {
Member()
ShowSnackbar(scaffoldState = scaffoldState)
model.clickMenu(ActivityDetailMenu.Member)
}
composable(ActivityDetailMenu.BBS.name) {
BBS()
ShowSnackbar(scaffoldState = scaffoldState)
model.clickMenu(ActivityDetailMenu.BBS)
}
}
@ -253,8 +257,6 @@ class ActivityDetailActivity : ComponentActivity() {
@Composable
private fun PhotoItem(modifier: Modifier = Modifier, vo: ActivityPhotoVo) {
// TODO 解决Build Warnning
Logger.i("$vo")
Box(
modifier = modifier.border(width = 1.dp, color = MaterialTheme.colors.onBackground),
contentAlignment = Alignment.Center

@ -42,7 +42,7 @@ import com.gyf.csams.uikit.Background
import com.gyf.csams.uikit.BackgroundImage
import com.gyf.csams.uikit.DescCard
import com.gyf.lib.uikit.BaseTextField
import com.gyf.lib.uikit.BodyS
import com.gyf.lib.uikit.Body
import com.gyf.lib.uikit.MainColumnFrame
import com.gyf.lib.uikit.ScaffoldModel
import com.gyf.lib.util.BottomButton
@ -81,7 +81,7 @@ class ApplyActActivity : AppCompatActivity() {
mLocationClient = (application as MainApplication).mLocationClient
setContent {
BodyS {
Body {
MainColumnFrame(background = { Background(image = BackgroundImage.ApplyActivity) }) {
val model: ApplyActViewModel = viewModel()
val scaffoldModel: ScaffoldModel = viewModel()

@ -12,12 +12,12 @@ import com.gyf.csams.AssociationApi
import com.gyf.csams.MainApplication
import com.gyf.csams.UNKNOW_ERROR
import com.gyf.csams.util.SimpleCallback
import com.gyf.csams.util.Token
import com.gyf.csams.util.TokenManager
import com.gyf.lib.uikit.FormStatus
import com.gyf.lib.uikit.ValidStringForm
import com.gyf.lib.util.ApiResponse
import com.gyf.lib.util.HttpClient
import com.gyf.lib.util.Token
import com.gyf.lib.util.TokenManager
import com.orhanobut.logger.Logger
import kotlinx.coroutines.launch
import java.io.File
@ -81,8 +81,7 @@ class RegAssociationViewModel(application: Application) : AndroidViewModel(appli
Logger.e(it)
callback("图片上传失败")
}, type = object : TypeToken<ApiResponse<List<Int>>>() {}.type),
id = token.userId,
token = token.token,
token = token,
fileList = arrayOf(cacheFile)
)
}

@ -30,10 +30,7 @@ import com.gyf.csams.activity.ui.ActivityDetailActivity
import com.gyf.csams.activity.ui.ApplyActActivity
import com.gyf.csams.association.model.*
import com.gyf.csams.uikit.*
import com.gyf.lib.uikit.BaseTextField
import com.gyf.lib.uikit.Body
import com.gyf.lib.uikit.MainColumnFrame
import com.gyf.lib.uikit.ScaffoldModel
import com.gyf.lib.uikit.*
import com.gyf.lib.util.randomChinese
import com.orhanobut.logger.Logger
@ -47,7 +44,7 @@ class AssociationActivity : ComponentActivity() {
super.onCreate(savedInstanceState)
setContent {
Body { nav ->
TestBody { nav, scaffoldState ->
val context = LocalContext.current as AssociationActivity
val model: AssociationViewModel = viewModel()
val currentMenuName: AssociationMenu by model.currentMenu.observeAsState(
@ -157,14 +154,17 @@ class AssociationActivity : ComponentActivity() {
composable(AssociationMenu.Member.name) {
model.clickMenu(AssociationMenu.Member)
Member()
ShowSnackbar(scaffoldState = scaffoldState)
}
composable(AssociationMenu.Main.name) {
model.clickMenu(AssociationMenu.Main)
Main()
ShowSnackbar(scaffoldState = scaffoldState)
}
composable(AssociationMenu.ActivityList.name) {
model.clickMenu(AssociationMenu.ActivityList)
AssociationList()
ShowSnackbar(scaffoldState = scaffoldState)
}
}
}

@ -23,7 +23,7 @@ import com.gyf.csams.association.model.*
import com.gyf.csams.uikit.Background
import com.gyf.csams.uikit.BackgroundImage
import com.gyf.lib.uikit.BaseTextField
import com.gyf.lib.uikit.BodyS
import com.gyf.lib.uikit.Body
import com.gyf.lib.uikit.MainColumnFrame
import com.gyf.lib.uikit.ScaffoldModel
import com.gyf.lib.util.BottomButton
@ -43,7 +43,7 @@ class ExamActivity : ComponentActivity() {
activityType = intent?.getSerializableExtra(ExamActivityType::name.name) as ExamActivityType
setContent {
BodyS {
Body {
MainColumnFrame(background = {
Background(
image = BackgroundImage.Exam,

@ -14,7 +14,7 @@ import com.gyf.csams.association.model.RenameViewModel
import com.gyf.csams.uikit.Background
import com.gyf.csams.uikit.BackgroundImage
import com.gyf.lib.uikit.BaseTextField
import com.gyf.lib.uikit.BodyS
import com.gyf.lib.uikit.Body
import com.gyf.lib.uikit.MainColumnFrame
import com.gyf.lib.uikit.ScaffoldModel
import com.gyf.lib.util.BottomButton
@ -28,7 +28,7 @@ class ReNameActivity : ComponentActivity() {
super.onCreate(savedInstanceState)
setContent {
BodyS {
Body {
MainColumnFrame(background = { Background(image = BackgroundImage.Rename) }) {
Spacer(
modifier = Modifier

@ -4,7 +4,6 @@ import android.Manifest
import android.app.Activity
import android.content.Intent
import android.content.pm.PackageManager
import android.graphics.BitmapFactory
import android.net.Uri
import android.os.Bundle
import android.provider.MediaStore
@ -19,12 +18,11 @@ import androidx.compose.material.IconButton
import androidx.compose.material.MaterialTheme
import androidx.compose.material.OutlinedButton
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.*
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import androidx.core.content.ContextCompat
@ -35,6 +33,7 @@ import com.gyf.csams.uikit.Background
import com.gyf.csams.uikit.BackgroundImage
import com.gyf.lib.uikit.*
import com.gyf.lib.util.BottomButton
import com.gyf.lib.util.ImageUtil
import com.orhanobut.logger.Logger
@ -46,8 +45,7 @@ class RegAssociationActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
BodyS {
Body {
MainColumnFrame(background = {
Background(
BackgroundImage.RegAssociation,
@ -174,17 +172,21 @@ class RegAssociationActivity : ComponentActivity() {
Text(text = model.picturePlaceHolder)
}
} else {
uri.let {
uri.let { it ->
if (it != null) {
Row {
Image(
bitmap = BitmapFactory.decodeStream(
contentResolver.openInputStream(
it
)
var logo: ImageBitmap? by remember {
mutableStateOf(null)
}
LaunchedEffect(it) {
logo = ImageUtil.getImage(this@RegAssociationActivity, uri)
}
logo?.let {
Image(
bitmap = it, contentDescription = null
)
.asImageBitmap(), contentDescription = null
)
}
Column(
modifier = Modifier.fillMaxHeight(),
verticalArrangement = Arrangement.SpaceEvenly

@ -1,30 +1,23 @@
package com.gyf.csams.main.model
import android.app.Activity
import android.app.Application
import android.content.Intent
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.lifecycle.*
import com.google.gson.reflect.TypeToken
import com.gyf.NotificationDto
import com.gyf.ReceiverType
import com.gyf.csams.*
import com.gyf.csams.account.model.UserVo
import com.gyf.csams.account.ui.AccountActivity
import com.gyf.csams.uikit.AbstractComment
import com.gyf.csams.uikit.OnlyToken
import com.gyf.csams.util.AppDatabase
import com.gyf.csams.util.SimpleCallback
import com.gyf.csams.util.Token
import com.gyf.csams.util.TokenManager
import com.gyf.lib.ScrollList
import com.gyf.lib.uikit.FormStatus
import com.gyf.lib.uikit.PersonInfoVo
import com.gyf.lib.uikit.StringForm
import com.gyf.lib.uikit.ValidStringForm
import com.gyf.lib.util.ApiResponse
import com.gyf.lib.util.HttpClient
import com.gyf.lib.util.randomChinese
import com.gyf.lib.util.randomNum
import com.gyf.lib.util.*
import com.orhanobut.logger.Logger
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
@ -32,6 +25,44 @@ import kotlinx.coroutines.launch
data class LeaveMessageFormatVo(val message: String, val user: UserVo)
class NotificationViewModel(application: Application) : AndroidViewModel(application) {
private val _count = MutableLiveData<Long>()
val count: LiveData<Long> = _count
init {
count()
}
/**
* 通知计数
*
*/
private fun count() {
viewModelScope.launch {
TokenManager.token?.let {
HttpClient.post(
url = Api.buildUrl(NotificationApi.Count),
SimpleCallback<Long>(action = "未读通知计数", onSuccess = {
it.body?.let {
_count.postValue(it)
}
Logger.i(it.message)
}, onFail = {
Logger.e(it)
}, type = object : TypeToken<ApiResponse<Long>>() {}.type),
jsonParam = NotificationDto(
receiverId = it.id,
receiverClient = ReceiverType.Foreground.name,
token = it
)
)
}
}
}
}
/**
* 跑马灯
*
@ -128,6 +159,7 @@ class MarqueeViewModel : AbstractComment() {
}
/**
* 社团
*
@ -137,52 +169,6 @@ data class AssociationListVo(val name: String)
data class LeaveMessageVo(val message: String, val token: Token)
/**
* 主页
*
*/
class MainViewModel : AbstractComment() {
override val newContent: ValidStringForm =
object : ValidStringForm(formDesc = "留言", textLength = 20) {
override fun check() {
_formValue.value?.let {
if (it.contains("\n")) _statusForm.value =
FormStatus.FormatError else _statusForm.value = FormStatus.Valid
}
}
}
/**
*
* @param callback
*/
override fun send(callback: (message: String) -> Unit) {
TokenManager.token.let {
if (it != null) {
viewModelScope.launch {
HttpClient.post(url = Api.buildUrl(MainApi.LeaveMessage),
SimpleCallback<Boolean>(
action = "发送留言", onSuccess = {
callback("留言发送${if (it.body == true) "成功" else "失败"}")
}, onFail = {
callback("留言发送失败")
}, type = object : TypeToken<ApiResponse<Boolean>>() {}.type
),
jsonParam = LeaveMessageVo(
message = newContent.formValue.value ?: "",
token = it
)
)
}
} else {
callback(UNKNOW_ERROR)
}
}
}
}
/**
* 社团列表
*
@ -310,7 +296,7 @@ class CenterViewModel : ViewModel() {
}
fun logout(context: Activity, callback: (message: String) -> Unit) {
val userId = TokenManager.token?.userId
val userId = TokenManager.token?.id
if (userId != null) {
Logger.i("帐号$userId 将要退出登录")
viewModelScope.launch {

@ -24,18 +24,14 @@ import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import com.gyf.csams.Api
import com.gyf.csams.MainApi
import com.gyf.csams.R
import com.gyf.csams.activity.ui.ActivityDetailActivity
import com.gyf.csams.association.ui.AssociationActivity
import com.gyf.csams.association.ui.RegAssociationActivity
import com.gyf.csams.main.model.AssociationListVo
import com.gyf.csams.main.model.CenterViewModel
import com.gyf.csams.main.model.ListViewModel
import com.gyf.csams.main.model.MarqueeViewModel
import com.gyf.csams.main.model.*
import com.gyf.csams.message.ui.MessageActivity
import com.gyf.csams.uikit.*
import com.gyf.lib.MessageService
import com.gyf.lib.uikit.*
import com.gyf.lib.util.randomChinese
@ -46,33 +42,35 @@ import com.gyf.lib.util.randomChinese
*/
class MainActivity : ComponentActivity() {
lateinit var imageModel: ImageModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
imageModel = ImageModel(application = application, Api.buildUrl(MainApi.HotActivity))
startService(Intent(this, MessageService::class.java))
setContent {
val imageViewModel: ImageViewModel = viewModel()
TestBody { nav, scaffoldState ->
NavHost(navController = nav, startDestination = MainMenu.Main.name) {
composable(MainMenu.Main.name) {
Main(navController = nav)
ShowSnackbar(scaffoldState = scaffoldState)
imageViewModel.start()
}
composable(MainMenu.List.name) {
AssociationList(navController = nav)
ShowSnackbar(scaffoldState = scaffoldState)
imageViewModel.cancel()
}
composable(MainMenu.Center.name) {
Center(navController = nav)
ShowSnackbar(scaffoldState = scaffoldState)
}
imageViewModel.cancel()
}
}
}
}
@ -80,15 +78,9 @@ class MainActivity : ComponentActivity() {
override fun onResume() {
super.onResume()
imageModel.start()
startService(Intent(this, MessageService::class.java))
}
override fun onPause() {
super.onPause()
imageModel.cancel()
}
/**
* 个人中心
*
@ -361,8 +353,9 @@ class MainActivity : ComponentActivity() {
*
*/
@Composable
private fun Notification() {
private fun Notification(notificationViewModel: NotificationViewModel = viewModel()) {
val context = LocalContext.current
val count by notificationViewModel.count.observeAsState(0)
Row(
horizontalArrangement = Arrangement.End,
modifier = Modifier
@ -373,7 +366,7 @@ class MainActivity : ComponentActivity() {
context.startActivity(Intent(context, MessageActivity::class.java))
}) {
Icon(
painter = painterResource(id = R.drawable.ic_notification),
painter = painterResource(id = if (count > 0) R.drawable.ic_notice else R.drawable.ic_notification),
contentDescription = null
)
}
@ -451,15 +444,19 @@ class MainActivity : ComponentActivity() {
*
*/
@Composable
private fun PosterWithDesc(scaffoldModel: ScaffoldModel = viewModel()) {
private fun PosterWithDesc(
scaffoldModel: ScaffoldModel = viewModel(),
viewModel: ImageViewModel = viewModel()
) {
val context = LocalContext.current as MainActivity
val error by context.imageModel.error.observeAsState()
val error by viewModel.error.observeAsState()
error?.let {
scaffoldModel.update(message = it)
context.imageModel.clearError()
viewModel.clearError()
}
Carousel(imageBitmap = context.imageModel.image) {
Carousel(imageBitmap = viewModel.image) {
Column(modifier = Modifier.clickable(onClick = {
context.startActivity(Intent(context, ActivityDetailActivity::class.java))
})) {

@ -1,21 +1,33 @@
package com.gyf.csams.message.model
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import com.google.gson.reflect.TypeToken
import com.gyf.NotificationDto
import com.gyf.PageDto
import com.gyf.ReceiverType
import com.gyf.csams.Api
import com.gyf.csams.NOT_IMPL_TIP
import com.gyf.csams.NotificationApi
import com.gyf.csams.util.SimpleCallback
import com.gyf.lib.ScrollList
import com.gyf.lib.util.randomChinese
import com.gyf.lib.util.randomDateTime
import com.gyf.lib.util.randomNum
import com.gyf.lib.util.ApiResponse
import com.gyf.lib.util.HttpClient
import com.gyf.lib.util.TokenManager
import com.orhanobut.logger.Logger
import kotlinx.coroutines.launch
import java.util.*
import kotlin.random.Random
enum class SystemType(val desc: String) {
Join("入团通知"),
ActCheck("活动审核通知"),
Rename("社团重命名审核通知")
}
data class NotificationVo(val title: String, val content: String, val id: Int, val createTime: Long)
/**
* 通知内容
*
@ -77,56 +89,56 @@ data class RenameContent(
* 系统通知数据状态管理
*
*/
class SysMessageViewModel : ScrollList<MessageContent>() {
class SysMessageViewModel : ScrollList<NotificationVo>() {
val title = "系统通知"
override val initSize: Int = 10
private val _currentPage = MutableLiveData<Long>()
val currentPage: LiveData<Long> = _currentPage
init {
load()
}
/**
* TODO 加载通知
*加载通知列表
*
*/
override fun load() {
viewModelScope.launch {
_data.value?.apply {
repeat(initSize) {
when ((0..2).random()) {
0 -> add(
JoinContent(
createTime = randomDateTime(),
readState = Random.nextBoolean(),
studentId = randomNum().toLong(),
studentName = randomChinese(5)
)
)
1 -> add(
ActCheckContent(
activityId = randomNum().toLong(),
activityName = randomChinese(5),
createTime = randomDateTime(),
readState = Random.nextBoolean()
)
)
2 -> add(
RenameContent(
oldAssociationName = randomChinese(3),
newAssociationName = randomChinese(3),
createTime = randomDateTime(),
readState = Random.nextBoolean()
)
TokenManager.token?.let { it ->
HttpClient.post(
Api.buildUrl(NotificationApi.List),
SimpleCallback<MutableList<NotificationVo>>(
action = "获取通知列表",
onSuccess = {
it.body?.let {
_data.postValue(it)
}
Logger.i(it.message)
},
onFail = {
Logger.e(it)
},
type = object :
TypeToken<ApiResponse<MutableList<NotificationVo>>>() {}.type
),
jsonParam = NotificationDto(
receiverId = it.id,
receiverClient = ReceiverType.Foreground.name,
token = it, page = PageDto(
currentPage = _currentPage.value ?: 1,
pageSize = initSize
)
}
}
)
)
}
}
}
/**
* TODO 加载更多通知
*TODO
*
* @param callback
*/

@ -22,7 +22,7 @@ import com.gyf.csams.message.model.MessageViewModel
import com.gyf.csams.uikit.Background
import com.gyf.csams.uikit.BackgroundImage
import com.gyf.csams.uikit.TextTopAppBar
import com.gyf.lib.uikit.BodyS
import com.gyf.lib.uikit.Body
import com.gyf.lib.uikit.MainColumnFrame
/**
@ -34,7 +34,7 @@ class MessageActivity : ComponentActivity() {
super.onCreate(savedInstanceState)
setContent {
BodyS {
Body {
val model: MessageViewModel = viewModel()
val context = LocalContext.current
MainColumnFrame(background = {

@ -22,9 +22,10 @@ import com.gyf.csams.message.model.*
import com.gyf.csams.uikit.Background
import com.gyf.csams.uikit.BackgroundImage
import com.gyf.csams.uikit.TextTopAppBar
import com.gyf.lib.uikit.BodyS
import com.gyf.lib.uikit.Body
import com.gyf.lib.uikit.MainColumnFrame
import com.gyf.lib.util.format
import java.util.*
/**
* 系统通知
@ -35,7 +36,7 @@ class SysMessageActivity : ComponentActivity() {
super.onCreate(savedInstanceState)
setContent {
BodyS {
Body {
MainColumnFrame(background = {
Background(
image = BackgroundImage.ActivityMessage,
@ -70,10 +71,10 @@ class SysMessageActivity : ComponentActivity() {
}
@Composable
private fun MessageItem(modifier: Modifier = Modifier, content: MessageContent) {
private fun MessageItem(modifier: Modifier = Modifier, content: NotificationVo) {
Card(modifier = modifier, backgroundColor = MaterialTheme.colors.background) {
Column(modifier = Modifier.padding(10.dp)) {
Text(text = content.type.desc, style = MaterialTheme.typography.h5)
Text(text = content.title, style = MaterialTheme.typography.h5)
Spacer(modifier = Modifier.height(5.dp))
Card(
@ -82,16 +83,17 @@ class SysMessageActivity : ComponentActivity() {
.fillMaxWidth()
.padding(10.dp)
) {
when (content) {
is JoinContent -> JoinMessage(content = content)
is ActCheckContent -> ActCheckMessage(content = content)
is RenameContent -> RenameMessage(content = content)
}
// when (content) {
// is JoinContent -> JoinMessage(content = content)
// is ActCheckContent -> ActCheckMessage(content = content)
// is RenameContent -> RenameMessage(content = content)
// }
Text(text = content.content)
}
Spacer(modifier = Modifier.height(10.dp))
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.End) {
Text(text = content.createTime.format())
Text(text = Date(content.createTime).format())
}
}
}

@ -7,10 +7,10 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import com.google.gson.reflect.TypeToken
import com.gyf.csams.Api
import com.gyf.csams.MainApi
import com.gyf.csams.R
import com.gyf.csams.util.SimpleCallback
import com.gyf.csams.util.Token
import com.gyf.csams.util.TokenManager
import com.gyf.lib.util.HttpClient
import com.gyf.lib.util.ImageUtil
import com.orhanobut.logger.Logger
@ -19,23 +19,14 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
sealed class BaseVo {
abstract val token: Token
}
data class OnlyToken(
override val token: Token = TokenManager.token ?: throw IllegalArgumentException("无法获取token")
) : BaseVo()
class ImageModel(application: Application, private val urlPath: String) :
AndroidViewModel(application) {
class ImageViewModel(application: Application) : AndroidViewModel(application) {
private val _image = MutableLiveData<ImageBitmap>()
private val _imageUrls = MutableLiveData<List<String>?>()
val image: LiveData<ImageBitmap> = _image
private var job: Job? = null
private val _error = MutableLiveData<String?>()
val error: LiveData<String?> = _error
private val urlPath = Api.buildUrl(MainApi.HotActivity)
fun clearError() {
_error.value = null
}
@ -63,6 +54,7 @@ class ImageModel(application: Application, private val urlPath: String) :
Logger.i("启动轮播")
if (job == null || job?.isCompleted == true || job?.isCancelled == true) {
job = viewModelScope.launch {
HttpClient.get(
url = urlPath,
SimpleCallback<List<String>>("获取轮播图", onSuccess = {
@ -101,6 +93,11 @@ class ImageModel(application: Application, private val urlPath: String) :
Logger.i("停止轮播")
job?.cancel()
}
override fun onCleared() {
super.onCleared()
cancel()
}
}

@ -1,30 +0,0 @@
package com.gyf.csams.util
import android.app.Activity
import android.view.View
import android.view.inputmethod.InputMethodManager
import com.orhanobut.logger.Logger
object ContextUtil {
fun hideKeyBoard(activity: Activity?) {
Logger.i("隐藏软键盘")
activity?.apply {
val imm = getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager
window.peekDecorView()?.let {
imm.hideSoftInputFromWindow(it.windowToken, 0)
}
}
}
fun testHideKeyBoard(activity: Activity) {
Logger.i("隐藏软键盘")
val imm = activity.getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager
//Find the currently focused view, so we can grab the correct window token from it.
var view: View? = activity.currentFocus
//If no view currently has focus, create a new one, just so we can grab a window token from it
if (view == null) {
view = View(activity)
}
imm.hideSoftInputFromWindow(view.windowToken, 0)
}
}

@ -2,6 +2,7 @@ plugins {
id("com.android.library")
id("kotlin-android")
// id("org.jetbrains.kotlin.plugin.serialization") version "1.4.32"
id("kotlin-kapt")
}
android {
@ -16,12 +17,19 @@ android {
}
buildTypes {
val serverAddress by extra("http://192.168.50.107:8080")
debug {
buildConfigField(type = "String", name = "SERVER_ADDRESS", value = "\"$serverAddress\"")
}
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
buildConfigField(type = "String", name = "SERVER_ADDRESS", value = "\"$serverAddress\"")
}
}
compileOptions {
@ -43,6 +51,15 @@ android {
}
dependencies {
//生命周期组件版本
val lifecycle_version = "2.3.1"
//https://developer.android.com/topic/libraries/architecture/workmanager/basics?hl=zh-cn
val work_version = "2.5.0"
val room_version = "2.3.0"
// Kotlin + coroutines
api("androidx.work:work-runtime-ktx:$work_version")
/**
* 针对最新的平台功能和 API 调整应用,同时还支持旧设备。
* https://developer.android.com/jetpack/androidx/releases/core
@ -75,7 +92,7 @@ dependencies {
* 生命周期感知型组件
* https://developer.android.com/jetpack/androidx/releases/lifecycle
*/
api("androidx.lifecycle:lifecycle-runtime-ktx:${rootProject.extra["lifecycle_version"]}")
api("androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version")
api("androidx.lifecycle:lifecycle-viewmodel-compose:1.0.0-alpha05")
/**
* 访问基于 Activity 构建的可组合 API。
@ -111,9 +128,12 @@ dependencies {
/**
* https://developer.android.com/jetpack/androidx/releases/room
*/
api("androidx.room:room-runtime:${rootProject.extra["room_version"]}")
api("androidx.room:room-runtime:$room_version")
// optional - Kotlin Extensions and Coroutines support for Room
api("androidx.room:room-ktx:${rootProject.extra["room_version"]}")
api("androidx.room:room-ktx:$room_version")
kapt("androidx.room:room-compiler:$room_version")
// optional - Test helpers
testImplementation("androidx.room:room-testing:$room_version")
// https://github.com/coil-kt/coil/blob/master/README-zh.md
api("io.coil-kt:coil:1.2.1")
}

@ -0,0 +1,73 @@
package com.gyf
import android.content.Context
import androidx.work.Data
import androidx.work.Worker
import androidx.work.WorkerParameters
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import com.gyf.lib.BuildConfig
import com.gyf.lib.util.*
import com.orhanobut.logger.Logger
import java.net.SocketTimeoutException
const val NOTIFICATION_API = "${BuildConfig.SERVER_ADDRESS}/api/notification/pull"
data class NotificationVo(val title: String, val content: String, val id: Int)
data class PageDto(val currentPage: Long, val pageSize: Int = 10)
data class NotificationDto(
val receiverId: Int,
val receiverClient: String,
override val token: Token,
val page: PageDto? = null
) : BaseToken()
/**
* 通知接收客户端
*
*/
enum class ReceiverType {
//前台
Foreground,
//后台
Background
}
class NotificationWorker(context: Context, workerParams: WorkerParameters) :
Worker(context, workerParams) {
override fun doWork(): Result {
TokenManager.token?.let {
return try {
Logger.i("开始拉取通知")
val data = HttpClient.postAsync<NotificationVo>(url = NOTIFICATION_API,
jsonParam = NotificationDto(
receiverId = it.id,
receiverClient = inputData.getString("receiverClient")
?: throw IllegalArgumentException("缺少receiverClient参数"),
token = it
),
type = object : TypeToken<ApiResponse<NotificationVo>>() {}.type
)
val result =
Data.Builder().putString(NotificationVo::class.java.name, Gson().toJson(data))
.build()
Logger.i("拉取通知成功:\n${result}")
Result.success(result)
} catch (e: SocketTimeoutException) {
Logger.e(e, "网络异常,拉取通知失败稍后再试")
Result.retry()
} catch (e: Exception) {
Logger.e(e, "发生未知异常,中止任务")
Result.failure()
}
}
Logger.w("找不到token信息,中止任务")
return Result.failure()
}
}

@ -0,0 +1,61 @@
package com.gyf.lib
import android.content.Intent
import androidx.core.app.JobIntentService
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import com.gyf.NOTIFICATION_API
import com.gyf.NotificationDto
import com.gyf.NotificationVo
import com.gyf.ReceiverType
import com.gyf.lib.util.ApiResponse
import com.gyf.lib.util.HttpClient
import com.gyf.lib.util.NotificationUtil
import com.gyf.lib.util.TokenManager
import com.orhanobut.logger.Logger
class MessageService : JobIntentService() {
val gson = Gson()
override fun onCreate() {
super.onCreate()
Logger.i("服务启动")
}
override fun onDestroy() {
super.onDestroy()
Logger.i("服务销毁")
}
override fun onHandleWork(intent: Intent) {
TokenManager.token?.let { it ->
HttpClient.postAsync<List<NotificationVo>>(url = NOTIFICATION_API,
jsonParam = NotificationDto(
receiverId = it.id,
receiverClient = ReceiverType.Foreground.name,
token = it
),
type = object : TypeToken<ApiResponse<List<NotificationVo>>>() {}.type
)?.let { it ->
Logger.i("拉取最新通知")
it.body?.forEach {
Logger.i("构造通知【${it.title}")
val builder =
NotificationCompat.Builder(applicationContext, NotificationUtil.CHANNEL_ID)
.setSmallIcon(R.drawable.ic_notification)
.setContentTitle(it.title)
.setContentText(it.content)
.setPriority(NotificationCompat.PRIORITY_HIGH)
builder.build()
with(NotificationManagerCompat.from(applicationContext)) {
// notificationId is a unique int for each notification that you must define
notify(it.id, builder.build())
}
}
}
}
}
}

@ -7,7 +7,6 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.navigation.NavHostController
import androidx.navigation.compose.rememberNavController
import com.gyf.lib.uikit.theme.CSAMSTheme
@ -146,11 +145,10 @@ fun <T : BottomBarMenu> MainBottomAppBar(
* @param content
*/
@Composable
fun BodyS(content: @Composable () -> Unit) {
fun Body(content: @Composable () -> Unit) {
CSAMSTheme {
Surface(color = MaterialTheme.colors.background) {
val scaffoldState = rememberScaffoldState()
Scaffold(scaffoldState = scaffoldState) {
content()
ShowSnackbar(scaffoldState = scaffoldState)
@ -159,25 +157,6 @@ fun BodyS(content: @Composable () -> Unit) {
}
}
/**
* 带底部导航的界面框架
*
* @param content
*/
@Composable
fun Body(content: @Composable (nav: NavHostController) -> Unit) {
CSAMSTheme {
Surface(color = MaterialTheme.colors.background) {
val navController = rememberNavController()
val scaffoldState = rememberScaffoldState()
Scaffold(scaffoldState = scaffoldState) {
content(nav = navController)
ShowSnackbar(scaffoldState = scaffoldState)
}
}
}
}
@Composable
fun TestBody(content: @Composable (nav: NavHostController, scaffoldState: ScaffoldState) -> Unit) {

@ -0,0 +1,62 @@
package com.gyf.lib.util
import android.app.Activity
import android.content.Context
import android.view.View
import android.view.inputmethod.InputMethodManager
import androidx.lifecycle.LiveData
import androidx.work.*
import com.gyf.NotificationWorker
import com.gyf.ReceiverType
import com.orhanobut.logger.Logger
import java.util.*
object ContextUtil {
private var name = "pullNotification"
private fun getNotification(context: Context): UUID {
val data = Data.Builder()
.putString("receiverClient", ReceiverType.Foreground.name)
.build()
val uploadWorkRequest: OneTimeWorkRequest =
OneTimeWorkRequestBuilder<NotificationWorker>()
.setInputData(data)
.build()
Logger.i("构建任务")
val workManager = WorkManager.getInstance(context)
workManager.enqueueUniqueWork(name, ExistingWorkPolicy.KEEP, uploadWorkRequest)
return uploadWorkRequest.id
}
fun getNotificationLiveData(context: Context): LiveData<WorkInfo> {
val id = getNotification(context = context)
return WorkManager.getInstance(context).getWorkInfoByIdLiveData(id)
}
fun getNotification(context: Context, callback: (wordId: UUID) -> Unit) {
callback(getNotification(context = context))
}
fun hideKeyBoard(activity: Activity?) {
Logger.i("隐藏软键盘")
activity?.apply {
val imm = getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager
window.peekDecorView()?.let {
imm.hideSoftInputFromWindow(it.windowToken, 0)
}
}
}
fun testHideKeyBoard(activity: Activity) {
Logger.i("隐藏软键盘")
val imm = activity.getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager
//Find the currently focused view, so we can grab the correct window token from it.
var view: View? = activity.currentFocus
//If no view currently has focus, create a new one, just so we can grab a window token from it
if (view == null) {
view = View(activity)
}
imm.hideSoftInputFromWindow(view.windowToken, 0)
}
}

@ -11,6 +11,7 @@ import java.io.IOException
import java.lang.reflect.Type
import java.net.SocketTimeoutException
interface TokenInterface {
fun token(): String
}
@ -74,18 +75,15 @@ object HttpClient {
}
/**
* HTTP POST
* 发送JSON
* 发送json表单
*
* @param url
* @param callback
* @param jsonBody
* @param jsonParam
*/
@Deprecated(
message = "",
replaceWith = ReplaceWith("com.gyf.lib.util.HttpClient.post(java.lang.String, okhttp3.Callback, java.lang.Object)")
)
fun post(url: String, callback: Callback, jsonBody: String) {
fun post(url: String, callback: Callback, jsonParam: Any) {
Logger.i("request url=$url")
val jsonBody = json.toJson(jsonParam)
Logger.json(jsonBody)
val request = Request.Builder()
.url(url)
@ -95,14 +93,7 @@ object HttpClient {
call.enqueue(callback)
}
/**
* 发送json表单
*
* @param url
* @param callback
* @param jsonParam
*/
fun post(url: String, callback: Callback, jsonParam: Any) {
fun <T> postAsync(url: String, jsonParam: Any, type: Type): ApiResponse<T>? {
Logger.i("request url=$url")
val jsonBody = json.toJson(jsonParam)
Logger.json(jsonBody)
@ -110,8 +101,13 @@ object HttpClient {
.url(url)
.post(body = jsonBody.toRequestBody(contentType = JSON_CONTENT_TYPE))
.build()
val call = httpClient.newCall(request)
call.enqueue(callback)
return httpClient.newCall(request).execute().use { response ->
return@use response.body?.string()?.run<String, ApiResponse<T>?> {
Logger.i("jsonStr:${this}")
Gson().fromJson(this, type)
}
}
}
/**
@ -120,11 +116,12 @@ object HttpClient {
* @param url
* @param callback
*/
fun uploadFile(url: String, callback: Callback, id: Int, token: String, vararg fileList: File) {
fun uploadFile(url: String, callback: Callback, token: Token, vararg fileList: File) {
Logger.i("request url=$url")
val body = MultipartBody.Builder()
body.addFormDataPart("id", id.toString())
body.addFormDataPart("token", token)
.addFormDataPart("id", token.id.toString())
.addFormDataPart("token", token.token)
.addFormDataPart("createTime", token.createTime.toString())
fileList.withIndex().forEach {
body.addFormDataPart(
"file${it.index}",
@ -156,12 +153,14 @@ interface GsonBuilderInterface {
val gson: Gson
}
abstract class HttpCallback<T>(
open class HttpCallback<T>(
private val action: String,
private val onSuccess: (res: ApiResponse<T>) -> Unit,
private val onFail: (error: String) -> Unit,
private val type: Type
) : Callback, GsonBuilderInterface {
override val gson: Gson = Gson()
override fun onFailure(call: Call, e: IOException) {
when (e) {
is SocketTimeoutException -> {

@ -10,17 +10,17 @@ import coil.request.ImageRequest
import coil.request.SuccessResult
import com.orhanobut.logger.Logger
class ImageUtil {
companion object {
/**
*
*
* @param data Set the data to load.
* The default supported data types are:
* String (mapped to a Uri)
* Uri ("android.resource", "content", "file", "http", and "https" schemes only)
* HttpUrl
* File
object ImageUtil {
/**
*
*
* @param data Set the data to load.
* The default supported data types are:
* String (mapped to a Uri)
* Uri ("android.resource", "content", "file", "http", and "https" schemes only)
* HttpUrl
* File
* DrawableRes
* Drawable
* Bitmap
@ -42,5 +42,5 @@ class ImageUtil {
}
}
}
}
}

@ -0,0 +1,66 @@
/*
* Copyright (C) 2017 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.gyf.lib.util
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import android.os.Build
import androidx.core.app.NotificationCompat
import com.gyf.lib.BuildConfig
/**
* Simplifies common [Notification] tasks.
*/
object NotificationUtil {
// The id of the channel.
const val CHANNEL_ID = BuildConfig.LIBRARY_PACKAGE_NAME
fun createNotificationChannel(
context: Context,
channelImportance: Int
): String? {
// NotificationChannels are required for Notifications on O (API 26) and above.
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// The user-visible name of the channel.
val channelName: CharSequence = ""
// The user-visible description of the channel.
val channelDescription = ""
val channelEnableVibrate = false
val channelLockscreenVisibility: Int = NotificationCompat.VISIBILITY_PUBLIC
// Initializes NotificationChannel.
val notificationChannel =
NotificationChannel(CHANNEL_ID, channelName, channelImportance)
notificationChannel.description = channelDescription
notificationChannel.enableVibration(channelEnableVibrate)
notificationChannel.lockscreenVisibility = channelLockscreenVisibility
// Adds NotificationChannel to system. Attempting to create an existing notification
// channel with its original values performs no operation, so it's safe to perform the
// below sequence.
val notificationManager: NotificationManager =
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.createNotificationChannel(notificationChannel)
CHANNEL_ID
} else {
// Returns null for pre-O (26) devices.
null
}
}
}

@ -1,4 +1,4 @@
package com.gyf.csams.util
package com.gyf.lib.util
import android.content.Context
import androidx.lifecycle.LiveData
@ -11,17 +11,19 @@ import androidx.room.*
*/
@Entity
data class Token(
@PrimaryKey val userId: Int,
@PrimaryKey val id: Int,
@ColumnInfo val token: String,
@ColumnInfo val createTime: Long
)
/**
* 令牌传输
*
* @property token
*/
data class TokenVo(val token: String, val userId: Int)
abstract class BaseToken {
abstract val token: Token
}
data class OnlyToken(
override val token: Token = TokenManager.token ?: throw IllegalArgumentException("无法获取token")
) : BaseToken()
@Dao
interface TokenDao {

@ -0,0 +1,15 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="64dp"
android:height="64dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:pathData="M752.77,955.39H257.92c-112.38,0 -203.39,-91.01 -203.39,-203.39V257.15C54.53,144.77 145.54,53.76 257.92,53.76h494.85C865.15,53.76 956.16,144.77 956.16,257.15v494.85c0,112.38 -91.01,203.39 -203.39,203.39z"
android:fillColor="#FF4D3C" />
<path
android:pathData="M616.58,765.95H370.56c-67.97,0 -123.01,-55.04 -123.01,-123.01V396.8c0,-67.97 55.04,-123.01 123.01,-123.01h166.27c9.98,0 18.18,8.06 18.18,18.18 0,9.98 -8.06,18.18 -18.18,18.18H370.56c-47.87,0 -86.66,38.78 -86.66,86.66v246.02c0,47.87 38.78,86.66 86.66,86.66h246.02c47.87,0 86.66,-38.78 86.66,-86.66V469.12c0,-9.98 8.06,-18.18 18.18,-18.18 9.98,0 18.18,8.06 18.18,18.18v173.82c0,67.97 -55.04,123.01 -123.01,123.01z"
android:fillColor="#FFFFFF" />
<path
android:pathData="M694.78,312.7m-113.41,0a113.41,113.41 0,1 0,226.82 0,113.41 113.41,0 1,0 -226.82,0Z"
android:fillColor="#FFFFFF" />
</vector>

@ -1,6 +1,6 @@
package com.gyf.lib
import org.junit.Assert.*
import org.junit.Assert.assertEquals
import org.junit.Test
/**

Loading…
Cancel
Save