前台room依赖移动到lib模块

TokenUtil.kt迁移到lib模块
根build.gradle.kts迁移变量到子模块
增加拉取通知的后台服务 MessageService.kt
增加通知相关接口
master
pan 4 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 { buildTypes {
val appName = "${rootProject.extra["background_app_name"]}"
val appName = "社团管理"
debug { debug {
manifestPlaceholders.apply { manifestPlaceholders.apply {
this["background_app_name"] = appName this["background_app_name"] = appName
@ -56,9 +57,6 @@ android {
dependencies { dependencies {
implementation(project(":lib")) implementation(project(":lib"))
// optional - Test helpers
testImplementation("androidx.room:room-testing:${rootProject.extra["room_version"]}")
//测试 //测试
testImplementation("junit:junit:4.13.2") 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.account.model.LoginViewModel
import com.gyf.csams.main.ui.MainActivity import com.gyf.csams.main.ui.MainActivity
import com.gyf.lib.uikit.BaseTextField import com.gyf.lib.uikit.BaseTextField
import com.gyf.lib.uikit.BodyS import com.gyf.lib.uikit.Body
import com.gyf.lib.uikit.MainBoxFrame import com.gyf.lib.uikit.MainBoxFrame
/** /**
@ -31,7 +31,7 @@ class LoginActivity : ComponentActivity() {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContent { setContent {
BodyS { Body {
val context = LocalContext.current as LoginActivity val context = LocalContext.current as LoginActivity
MainBoxFrame( MainBoxFrame(
background = { /*TODO 背景图*/ }, background = { /*TODO 背景图*/ },

@ -6,17 +6,6 @@ import androidx.lifecycle.ViewModel
import com.gyf.lib.uikit.PersonInfoVo import com.gyf.lib.uikit.PersonInfoVo
import com.gyf.lib.util.randomChinese 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 androidx.lifecycle.viewmodel.compose.viewModel
import com.gyf.csams.main.model.AssociationLevel import com.gyf.csams.main.model.AssociationLevel
import com.gyf.csams.main.model.AssociationManagementViewModel 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.MainBoxFrame
import com.gyf.lib.uikit.ScaffoldModel import com.gyf.lib.uikit.ScaffoldModel
@ -31,7 +31,7 @@ class AssociationManagementActivity : ComponentActivity() {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContent { setContent {
BodyS { Body {
MainBoxFrame(background = { /*TODO*/ }) { MainBoxFrame(background = { /*TODO*/ }) {
val model: AssociationManagementViewModel = viewModel() val model: AssociationManagementViewModel = viewModel()
val data by model.data.observeAsState() 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.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.gyf.csams.R import com.gyf.csams.R
import com.gyf.lib.uikit.BodyS import com.gyf.lib.uikit.Body
import com.gyf.lib.uikit.MainColumnFrame import com.gyf.lib.uikit.MainColumnFrame
@ -29,7 +29,7 @@ class DepartmentActivity : ComponentActivity() {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContent { setContent {
BodyS { Body {
MainColumnFrame(background = { /*TODO*/ }) { MainColumnFrame(background = { /*TODO*/ }) {
val weight = 0.1F val weight = 0.1F
val departWeight = 0.2F val departWeight = 0.2F

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

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

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

@ -5,14 +5,10 @@ val key_password by extra("123456")
buildscript { buildscript {
//Jetpack Compose版本 //Jetpack Compose版本
val compose_version by extra("1.0.0-beta07") 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 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 kotlin_version by extra("1.4.32")
val background_app_name by extra("社团管理")
repositories { repositories {
maven("https://maven.aliyun.com/repository/google") maven("https://maven.aliyun.com/repository/google")
maven("https://maven.aliyun.com/repository/public") maven("https://maven.aliyun.com/repository/public")

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

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

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

@ -1,6 +1,10 @@
package com.gyf.csams package com.gyf.csams
import android.app.Activity
import android.app.Application 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.ImageBitmap
import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.graphics.asImageBitmap
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
@ -16,17 +20,13 @@ import com.baidu.location.LocationClient
import com.baidu.mapapi.SDKInitializer import com.baidu.mapapi.SDKInitializer
import com.gyf.csams.uikit.BackgroundImage import com.gyf.csams.uikit.BackgroundImage
import com.gyf.lib.util.ImageUtil import com.gyf.lib.util.ImageUtil
import com.gyf.lib.util.NotificationUtil
import com.orhanobut.logger.AndroidLogAdapter import com.orhanobut.logger.AndroidLogAdapter
import com.orhanobut.logger.DiskLogAdapter import com.orhanobut.logger.DiskLogAdapter
import com.orhanobut.logger.Logger import com.orhanobut.logger.Logger
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
class MainApplication : Application(), ImageLoaderFactory { class MainApplication : Application(), ImageLoaderFactory {
private val backgroundImage = mutableMapOf<BackgroundImage, MemoryCache.Key?>() private val backgroundImage = mutableMapOf<BackgroundImage, MemoryCache.Key?>()
@ -82,7 +82,6 @@ class MainApplication : Application(), ImageLoaderFactory {
} }
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
//初始化日志 //初始化日志
@ -95,8 +94,22 @@ class MainApplication : Application(), ImageLoaderFactory {
Logger.i("${BuildConfig.foreground_app_name}启动") Logger.i("${BuildConfig.foreground_app_name}启动")
SDKInitializer.initialize(this) SDKInitializer.initialize(this)
//预加载背景图
preloadImage() 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.Api
import com.gyf.csams.R import com.gyf.csams.R
import com.gyf.csams.account.ui.AccountRoute import com.gyf.csams.account.ui.AccountRoute
import com.gyf.csams.util.AppDatabase
import com.gyf.csams.util.SimpleCallback 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.FormStatus
import com.gyf.lib.uikit.StringForm import com.gyf.lib.uikit.StringForm
import com.gyf.lib.uikit.ValidStringForm import com.gyf.lib.uikit.ValidStringForm
import com.gyf.lib.util.ApiResponse import com.gyf.lib.util.*
import com.gyf.lib.util.HttpClient
import com.orhanobut.logger.Logger import com.orhanobut.logger.Logger
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.launch import kotlinx.coroutines.launch

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

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

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

@ -12,12 +12,12 @@ import com.gyf.csams.AssociationApi
import com.gyf.csams.MainApplication import com.gyf.csams.MainApplication
import com.gyf.csams.UNKNOW_ERROR import com.gyf.csams.UNKNOW_ERROR
import com.gyf.csams.util.SimpleCallback 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.FormStatus
import com.gyf.lib.uikit.ValidStringForm import com.gyf.lib.uikit.ValidStringForm
import com.gyf.lib.util.ApiResponse import com.gyf.lib.util.ApiResponse
import com.gyf.lib.util.HttpClient import com.gyf.lib.util.HttpClient
import com.gyf.lib.util.Token
import com.gyf.lib.util.TokenManager
import com.orhanobut.logger.Logger import com.orhanobut.logger.Logger
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.io.File import java.io.File
@ -81,8 +81,7 @@ class RegAssociationViewModel(application: Application) : AndroidViewModel(appli
Logger.e(it) Logger.e(it)
callback("图片上传失败") callback("图片上传失败")
}, type = object : TypeToken<ApiResponse<List<Int>>>() {}.type), }, type = object : TypeToken<ApiResponse<List<Int>>>() {}.type),
id = token.userId, token = token,
token = token.token,
fileList = arrayOf(cacheFile) 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.activity.ui.ApplyActActivity
import com.gyf.csams.association.model.* import com.gyf.csams.association.model.*
import com.gyf.csams.uikit.* import com.gyf.csams.uikit.*
import com.gyf.lib.uikit.BaseTextField import com.gyf.lib.uikit.*
import com.gyf.lib.uikit.Body
import com.gyf.lib.uikit.MainColumnFrame
import com.gyf.lib.uikit.ScaffoldModel
import com.gyf.lib.util.randomChinese import com.gyf.lib.util.randomChinese
import com.orhanobut.logger.Logger import com.orhanobut.logger.Logger
@ -47,7 +44,7 @@ class AssociationActivity : ComponentActivity() {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContent { setContent {
Body { nav -> TestBody { nav, scaffoldState ->
val context = LocalContext.current as AssociationActivity val context = LocalContext.current as AssociationActivity
val model: AssociationViewModel = viewModel() val model: AssociationViewModel = viewModel()
val currentMenuName: AssociationMenu by model.currentMenu.observeAsState( val currentMenuName: AssociationMenu by model.currentMenu.observeAsState(
@ -157,14 +154,17 @@ class AssociationActivity : ComponentActivity() {
composable(AssociationMenu.Member.name) { composable(AssociationMenu.Member.name) {
model.clickMenu(AssociationMenu.Member) model.clickMenu(AssociationMenu.Member)
Member() Member()
ShowSnackbar(scaffoldState = scaffoldState)
} }
composable(AssociationMenu.Main.name) { composable(AssociationMenu.Main.name) {
model.clickMenu(AssociationMenu.Main) model.clickMenu(AssociationMenu.Main)
Main() Main()
ShowSnackbar(scaffoldState = scaffoldState)
} }
composable(AssociationMenu.ActivityList.name) { composable(AssociationMenu.ActivityList.name) {
model.clickMenu(AssociationMenu.ActivityList) model.clickMenu(AssociationMenu.ActivityList)
AssociationList() 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.Background
import com.gyf.csams.uikit.BackgroundImage import com.gyf.csams.uikit.BackgroundImage
import com.gyf.lib.uikit.BaseTextField 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.MainColumnFrame
import com.gyf.lib.uikit.ScaffoldModel import com.gyf.lib.uikit.ScaffoldModel
import com.gyf.lib.util.BottomButton import com.gyf.lib.util.BottomButton
@ -43,7 +43,7 @@ class ExamActivity : ComponentActivity() {
activityType = intent?.getSerializableExtra(ExamActivityType::name.name) as ExamActivityType activityType = intent?.getSerializableExtra(ExamActivityType::name.name) as ExamActivityType
setContent { setContent {
BodyS { Body {
MainColumnFrame(background = { MainColumnFrame(background = {
Background( Background(
image = BackgroundImage.Exam, 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.Background
import com.gyf.csams.uikit.BackgroundImage import com.gyf.csams.uikit.BackgroundImage
import com.gyf.lib.uikit.BaseTextField 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.MainColumnFrame
import com.gyf.lib.uikit.ScaffoldModel import com.gyf.lib.uikit.ScaffoldModel
import com.gyf.lib.util.BottomButton import com.gyf.lib.util.BottomButton
@ -28,7 +28,7 @@ class ReNameActivity : ComponentActivity() {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContent { setContent {
BodyS { Body {
MainColumnFrame(background = { Background(image = BackgroundImage.Rename) }) { MainColumnFrame(background = { Background(image = BackgroundImage.Rename) }) {
Spacer( Spacer(
modifier = Modifier modifier = Modifier

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

@ -1,30 +1,23 @@
package com.gyf.csams.main.model package com.gyf.csams.main.model
import android.app.Activity import android.app.Activity
import android.app.Application
import android.content.Intent import android.content.Intent
import androidx.lifecycle.LiveData import androidx.lifecycle.*
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.google.gson.reflect.TypeToken import com.google.gson.reflect.TypeToken
import com.gyf.NotificationDto
import com.gyf.ReceiverType
import com.gyf.csams.* import com.gyf.csams.*
import com.gyf.csams.account.model.UserVo import com.gyf.csams.account.model.UserVo
import com.gyf.csams.account.ui.AccountActivity import com.gyf.csams.account.ui.AccountActivity
import com.gyf.csams.uikit.AbstractComment 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.SimpleCallback
import com.gyf.csams.util.Token
import com.gyf.csams.util.TokenManager
import com.gyf.lib.ScrollList import com.gyf.lib.ScrollList
import com.gyf.lib.uikit.FormStatus import com.gyf.lib.uikit.FormStatus
import com.gyf.lib.uikit.PersonInfoVo import com.gyf.lib.uikit.PersonInfoVo
import com.gyf.lib.uikit.StringForm import com.gyf.lib.uikit.StringForm
import com.gyf.lib.uikit.ValidStringForm import com.gyf.lib.uikit.ValidStringForm
import com.gyf.lib.util.ApiResponse import com.gyf.lib.util.*
import com.gyf.lib.util.HttpClient
import com.gyf.lib.util.randomChinese
import com.gyf.lib.util.randomNum
import com.orhanobut.logger.Logger import com.orhanobut.logger.Logger
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
@ -32,6 +25,44 @@ import kotlinx.coroutines.launch
data class LeaveMessageFormatVo(val message: String, val user: UserVo) 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) 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) { fun logout(context: Activity, callback: (message: String) -> Unit) {
val userId = TokenManager.token?.userId val userId = TokenManager.token?.id
if (userId != null) { if (userId != null) {
Logger.i("帐号$userId 将要退出登录") Logger.i("帐号$userId 将要退出登录")
viewModelScope.launch { viewModelScope.launch {

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

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

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

@ -22,9 +22,10 @@ import com.gyf.csams.message.model.*
import com.gyf.csams.uikit.Background import com.gyf.csams.uikit.Background
import com.gyf.csams.uikit.BackgroundImage import com.gyf.csams.uikit.BackgroundImage
import com.gyf.csams.uikit.TextTopAppBar 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.uikit.MainColumnFrame
import com.gyf.lib.util.format import com.gyf.lib.util.format
import java.util.*
/** /**
* 系统通知 * 系统通知
@ -35,7 +36,7 @@ class SysMessageActivity : ComponentActivity() {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContent { setContent {
BodyS { Body {
MainColumnFrame(background = { MainColumnFrame(background = {
Background( Background(
image = BackgroundImage.ActivityMessage, image = BackgroundImage.ActivityMessage,
@ -70,10 +71,10 @@ class SysMessageActivity : ComponentActivity() {
} }
@Composable @Composable
private fun MessageItem(modifier: Modifier = Modifier, content: MessageContent) { private fun MessageItem(modifier: Modifier = Modifier, content: NotificationVo) {
Card(modifier = modifier, backgroundColor = MaterialTheme.colors.background) { Card(modifier = modifier, backgroundColor = MaterialTheme.colors.background) {
Column(modifier = Modifier.padding(10.dp)) { 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)) Spacer(modifier = Modifier.height(5.dp))
Card( Card(
@ -82,16 +83,17 @@ class SysMessageActivity : ComponentActivity() {
.fillMaxWidth() .fillMaxWidth()
.padding(10.dp) .padding(10.dp)
) { ) {
when (content) { // when (content) {
is JoinContent -> JoinMessage(content = content) // is JoinContent -> JoinMessage(content = content)
is ActCheckContent -> ActCheckMessage(content = content) // is ActCheckContent -> ActCheckMessage(content = content)
is RenameContent -> RenameMessage(content = content) // is RenameContent -> RenameMessage(content = content)
} // }
Text(text = content.content)
} }
Spacer(modifier = Modifier.height(10.dp)) Spacer(modifier = Modifier.height(10.dp))
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.End) { 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.MutableLiveData
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.google.gson.reflect.TypeToken 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.R
import com.gyf.csams.util.SimpleCallback 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.HttpClient
import com.gyf.lib.util.ImageUtil import com.gyf.lib.util.ImageUtil
import com.orhanobut.logger.Logger import com.orhanobut.logger.Logger
@ -19,23 +19,14 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
sealed class BaseVo { class ImageViewModel(application: Application) : AndroidViewModel(application) {
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) {
private val _image = MutableLiveData<ImageBitmap>() private val _image = MutableLiveData<ImageBitmap>()
private val _imageUrls = MutableLiveData<List<String>?>() private val _imageUrls = MutableLiveData<List<String>?>()
val image: LiveData<ImageBitmap> = _image val image: LiveData<ImageBitmap> = _image
private var job: Job? = null private var job: Job? = null
private val _error = MutableLiveData<String?>() private val _error = MutableLiveData<String?>()
val error: LiveData<String?> = _error val error: LiveData<String?> = _error
private val urlPath = Api.buildUrl(MainApi.HotActivity)
fun clearError() { fun clearError() {
_error.value = null _error.value = null
} }
@ -63,6 +54,7 @@ class ImageModel(application: Application, private val urlPath: String) :
Logger.i("启动轮播") Logger.i("启动轮播")
if (job == null || job?.isCompleted == true || job?.isCancelled == true) { if (job == null || job?.isCompleted == true || job?.isCancelled == true) {
job = viewModelScope.launch { job = viewModelScope.launch {
HttpClient.get( HttpClient.get(
url = urlPath, url = urlPath,
SimpleCallback<List<String>>("获取轮播图", onSuccess = { SimpleCallback<List<String>>("获取轮播图", onSuccess = {
@ -101,6 +93,11 @@ class ImageModel(application: Application, private val urlPath: String) :
Logger.i("停止轮播") Logger.i("停止轮播")
job?.cancel() 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("com.android.library")
id("kotlin-android") id("kotlin-android")
// id("org.jetbrains.kotlin.plugin.serialization") version "1.4.32" // id("org.jetbrains.kotlin.plugin.serialization") version "1.4.32"
id("kotlin-kapt")
} }
android { android {
@ -16,12 +17,19 @@ android {
} }
buildTypes { buildTypes {
val serverAddress by extra("http://192.168.50.107:8080")
debug {
buildConfigField(type = "String", name = "SERVER_ADDRESS", value = "\"$serverAddress\"")
}
release { release {
isMinifyEnabled = false isMinifyEnabled = false
proguardFiles( proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"), getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro" "proguard-rules.pro"
) )
buildConfigField(type = "String", name = "SERVER_ADDRESS", value = "\"$serverAddress\"")
} }
} }
compileOptions { compileOptions {
@ -43,6 +51,15 @@ android {
} }
dependencies { 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 调整应用,同时还支持旧设备。 * 针对最新的平台功能和 API 调整应用,同时还支持旧设备。
* https://developer.android.com/jetpack/androidx/releases/core * https://developer.android.com/jetpack/androidx/releases/core
@ -75,7 +92,7 @@ dependencies {
* 生命周期感知型组件 * 生命周期感知型组件
* https://developer.android.com/jetpack/androidx/releases/lifecycle * 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") api("androidx.lifecycle:lifecycle-viewmodel-compose:1.0.0-alpha05")
/** /**
* 访问基于 Activity 构建的可组合 API。 * 访问基于 Activity 构建的可组合 API。
@ -111,9 +128,12 @@ dependencies {
/** /**
* https://developer.android.com/jetpack/androidx/releases/room * 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 // 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 // https://github.com/coil-kt/coil/blob/master/README-zh.md
api("io.coil-kt:coil:1.2.1") 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.Modifier
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
import androidx.navigation.compose.rememberNavController import androidx.navigation.compose.rememberNavController
import com.gyf.lib.uikit.theme.CSAMSTheme import com.gyf.lib.uikit.theme.CSAMSTheme
@ -146,11 +145,10 @@ fun <T : BottomBarMenu> MainBottomAppBar(
* @param content * @param content
*/ */
@Composable @Composable
fun BodyS(content: @Composable () -> Unit) { fun Body(content: @Composable () -> Unit) {
CSAMSTheme { CSAMSTheme {
Surface(color = MaterialTheme.colors.background) { Surface(color = MaterialTheme.colors.background) {
val scaffoldState = rememberScaffoldState() val scaffoldState = rememberScaffoldState()
Scaffold(scaffoldState = scaffoldState) { Scaffold(scaffoldState = scaffoldState) {
content() content()
ShowSnackbar(scaffoldState = scaffoldState) 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 @Composable
fun TestBody(content: @Composable (nav: NavHostController, scaffoldState: ScaffoldState) -> Unit) { 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.lang.reflect.Type
import java.net.SocketTimeoutException import java.net.SocketTimeoutException
interface TokenInterface { interface TokenInterface {
fun token(): String fun token(): String
} }
@ -74,18 +75,15 @@ object HttpClient {
} }
/** /**
* HTTP POST * 发送json表单
* 发送JSON
* *
* @param url * @param url
* @param callback * @param callback
* @param jsonBody * @param jsonParam
*/ */
@Deprecated( fun post(url: String, callback: Callback, jsonParam: Any) {
message = "", Logger.i("request url=$url")
replaceWith = ReplaceWith("com.gyf.lib.util.HttpClient.post(java.lang.String, okhttp3.Callback, java.lang.Object)") val jsonBody = json.toJson(jsonParam)
)
fun post(url: String, callback: Callback, jsonBody: String) {
Logger.json(jsonBody) Logger.json(jsonBody)
val request = Request.Builder() val request = Request.Builder()
.url(url) .url(url)
@ -95,14 +93,7 @@ object HttpClient {
call.enqueue(callback) call.enqueue(callback)
} }
/** fun <T> postAsync(url: String, jsonParam: Any, type: Type): ApiResponse<T>? {
* 发送json表单
*
* @param url
* @param callback
* @param jsonParam
*/
fun post(url: String, callback: Callback, jsonParam: Any) {
Logger.i("request url=$url") Logger.i("request url=$url")
val jsonBody = json.toJson(jsonParam) val jsonBody = json.toJson(jsonParam)
Logger.json(jsonBody) Logger.json(jsonBody)
@ -110,8 +101,13 @@ object HttpClient {
.url(url) .url(url)
.post(body = jsonBody.toRequestBody(contentType = JSON_CONTENT_TYPE)) .post(body = jsonBody.toRequestBody(contentType = JSON_CONTENT_TYPE))
.build() .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 url
* @param callback * @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") Logger.i("request url=$url")
val body = MultipartBody.Builder() val body = MultipartBody.Builder()
body.addFormDataPart("id", id.toString()) .addFormDataPart("id", token.id.toString())
body.addFormDataPart("token", token) .addFormDataPart("token", token.token)
.addFormDataPart("createTime", token.createTime.toString())
fileList.withIndex().forEach { fileList.withIndex().forEach {
body.addFormDataPart( body.addFormDataPart(
"file${it.index}", "file${it.index}",
@ -156,12 +153,14 @@ interface GsonBuilderInterface {
val gson: Gson val gson: Gson
} }
abstract class HttpCallback<T>( open class HttpCallback<T>(
private val action: String, private val action: String,
private val onSuccess: (res: ApiResponse<T>) -> Unit, private val onSuccess: (res: ApiResponse<T>) -> Unit,
private val onFail: (error: String) -> Unit, private val onFail: (error: String) -> Unit,
private val type: Type private val type: Type
) : Callback, GsonBuilderInterface { ) : Callback, GsonBuilderInterface {
override val gson: Gson = Gson()
override fun onFailure(call: Call, e: IOException) { override fun onFailure(call: Call, e: IOException) {
when (e) { when (e) {
is SocketTimeoutException -> { is SocketTimeoutException -> {

@ -10,17 +10,17 @@ import coil.request.ImageRequest
import coil.request.SuccessResult import coil.request.SuccessResult
import com.orhanobut.logger.Logger import com.orhanobut.logger.Logger
class ImageUtil { object ImageUtil {
companion object {
/** /**
* *
* *
* @param data Set the data to load. * @param data Set the data to load.
* The default supported data types are: * The default supported data types are:
* String (mapped to a Uri) * String (mapped to a Uri)
* Uri ("android.resource", "content", "file", "http", and "https" schemes only) * Uri ("android.resource", "content", "file", "http", and "https" schemes only)
* HttpUrl * HttpUrl
* File * File
* DrawableRes * DrawableRes
* Drawable * Drawable
* Bitmap * 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 android.content.Context
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
@ -11,17 +11,19 @@ import androidx.room.*
*/ */
@Entity @Entity
data class Token( data class Token(
@PrimaryKey val userId: Int, @PrimaryKey val id: Int,
@ColumnInfo val token: String, @ColumnInfo val token: String,
@ColumnInfo val createTime: Long @ColumnInfo val createTime: Long
) )
/**
* 令牌传输 abstract class BaseToken {
* abstract val token: Token
* @property token }
*/
data class TokenVo(val token: String, val userId: Int) data class OnlyToken(
override val token: Token = TokenManager.token ?: throw IllegalArgumentException("无法获取token")
) : BaseToken()
@Dao @Dao
interface TokenDao { 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 package com.gyf.lib
import org.junit.Assert.* import org.junit.Assert.assertEquals
import org.junit.Test import org.junit.Test
/** /**

Loading…
Cancel
Save