diff --git a/background/build.gradle.kts b/background/build.gradle.kts index eb47e5b..bf16a4f 100644 --- a/background/build.gradle.kts +++ b/background/build.gradle.kts @@ -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") /** diff --git a/background/src/main/java/com/gyf/csams/account/ui/LoginActivity.kt b/background/src/main/java/com/gyf/csams/account/ui/LoginActivity.kt index 26da88a..f93bbe3 100644 --- a/background/src/main/java/com/gyf/csams/account/ui/LoginActivity.kt +++ b/background/src/main/java/com/gyf/csams/account/ui/LoginActivity.kt @@ -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 背景图*/ }, diff --git a/background/src/main/java/com/gyf/csams/main/model/MainViewModel.kt b/background/src/main/java/com/gyf/csams/main/model/MainViewModel.kt index 09f2ce6..26f2166 100644 --- a/background/src/main/java/com/gyf/csams/main/model/MainViewModel.kt +++ b/background/src/main/java/com/gyf/csams/main/model/MainViewModel.kt @@ -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 - } -} - /** * 部长 * diff --git a/background/src/main/java/com/gyf/csams/main/ui/AssociationManagementActivity.kt b/background/src/main/java/com/gyf/csams/main/ui/AssociationManagementActivity.kt index 18a1880..8414a5d 100644 --- a/background/src/main/java/com/gyf/csams/main/ui/AssociationManagementActivity.kt +++ b/background/src/main/java/com/gyf/csams/main/ui/AssociationManagementActivity.kt @@ -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() diff --git a/background/src/main/java/com/gyf/csams/main/ui/DepartmentActivity.kt b/background/src/main/java/com/gyf/csams/main/ui/DepartmentActivity.kt index 58ef387..d02daaf 100644 --- a/background/src/main/java/com/gyf/csams/main/ui/DepartmentActivity.kt +++ b/background/src/main/java/com/gyf/csams/main/ui/DepartmentActivity.kt @@ -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 diff --git a/background/src/main/java/com/gyf/csams/main/ui/MainActivity.kt b/background/src/main/java/com/gyf/csams/main/ui/MainActivity.kt index 11a9820..3f85014 100644 --- a/background/src/main/java/com/gyf/csams/main/ui/MainActivity.kt +++ b/background/src/main/java/com/gyf/csams/main/ui/MainActivity.kt @@ -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*/ }) { diff --git a/background/src/main/java/com/gyf/csams/main/ui/ManagementOfficerActivity.kt b/background/src/main/java/com/gyf/csams/main/ui/ManagementOfficerActivity.kt index 56222fc..131e6eb 100644 --- a/background/src/main/java/com/gyf/csams/main/ui/ManagementOfficerActivity.kt +++ b/background/src/main/java/com/gyf/csams/main/ui/ManagementOfficerActivity.kt @@ -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() diff --git a/background/src/main/java/com/gyf/csams/main/ui/MenuActivity.kt b/background/src/main/java/com/gyf/csams/main/ui/MenuActivity.kt index afed155..ff98a4f 100644 --- a/background/src/main/java/com/gyf/csams/main/ui/MenuActivity.kt +++ b/background/src/main/java/com/gyf/csams/main/ui/MenuActivity.kt @@ -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 diff --git a/background/src/main/java/com/gyf/csams/uikit/Table.kt b/background/src/main/java/com/gyf/csams/uikit/Table.kt index bd9b7f8..d0d3365 100644 --- a/background/src/main/java/com/gyf/csams/uikit/Table.kt +++ b/background/src/main/java/com/gyf/csams/uikit/Table.kt @@ -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 TestTable( @StringRes title: Int? = null, callback: @Composable (vo: A) -> Unit ) { - BodyS { + Body { MainColumnFrame(background = { /*TODO*/ }) { val listState = rememberLazyListState() val model = viewModel(modelClass = clazz) diff --git a/build.gradle.kts b/build.gradle.kts index df9e2cb..efa3f6e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -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") diff --git a/foreground/build.gradle.kts b/foreground/build.gradle.kts index 2b05dbb..eec3a37 100644 --- a/foreground/build.gradle.kts +++ b/foreground/build.gradle.kts @@ -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") /** diff --git a/foreground/src/main/AndroidManifest.xml b/foreground/src/main/AndroidManifest.xml index 36a66d9..1f44dd9 100644 --- a/foreground/src/main/AndroidManifest.xml +++ b/foreground/src/main/AndroidManifest.xml @@ -104,6 +104,8 @@ + + \ No newline at end of file diff --git a/foreground/src/main/java/com/gyf/csams/Api.kt b/foreground/src/main/java/com/gyf/csams/Api.kt index 4eb8ff2..1321671 100644 --- a/foreground/src/main/java/com/gyf/csams/Api.kt +++ b/foreground/src/main/java/com/gyf/csams/Api.kt @@ -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}" + } +} + /** * 构建服务端请求接口地址 * diff --git a/foreground/src/main/java/com/gyf/csams/InitActivity.kt b/foreground/src/main/java/com/gyf/csams/InitActivity.kt index 0b51267..53a48ef 100644 --- a/foreground/src/main/java/com/gyf/csams/InitActivity.kt +++ b/foreground/src/main/java/com/gyf/csams/InitActivity.kt @@ -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() } - } + } + diff --git a/foreground/src/main/java/com/gyf/csams/InitViewModel.kt b/foreground/src/main/java/com/gyf/csams/InitViewModel.kt index 6db1afe..7dd5dd7 100644 --- a/foreground/src/main/java/com/gyf/csams/InitViewModel.kt +++ b/foreground/src/main/java/com/gyf/csams/InitViewModel.kt @@ -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() val isNetWorkWorking: LiveData = _isNetWorkWorking - /** - * token - */ - private val _token = MutableLiveData() - val token: LiveData = _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() 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>() {}.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() } } } diff --git a/foreground/src/main/java/com/gyf/csams/MainApplication.kt b/foreground/src/main/java/com/gyf/csams/MainApplication.kt index 6877980..42556ef 100644 --- a/foreground/src/main/java/com/gyf/csams/MainApplication.kt +++ b/foreground/src/main/java/com/gyf/csams/MainApplication.kt @@ -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() @@ -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) + } } @@ -141,4 +154,35 @@ 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]") + } + } \ No newline at end of file diff --git a/foreground/src/main/java/com/gyf/csams/account/model/AccountViewModel.kt b/foreground/src/main/java/com/gyf/csams/account/model/AccountViewModel.kt index 24a1dbb..abf8c0b 100644 --- a/foreground/src/main/java/com/gyf/csams/account/model/AccountViewModel.kt +++ b/foreground/src/main/java/com/gyf/csams/account/model/AccountViewModel.kt @@ -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 diff --git a/foreground/src/main/java/com/gyf/csams/activity/model/ApplyActViewModel.kt b/foreground/src/main/java/com/gyf/csams/activity/model/ApplyActViewModel.kt index b5ac53e..cc766ad 100644 --- a/foreground/src/main/java/com/gyf/csams/activity/model/ApplyActViewModel.kt +++ b/foreground/src/main/java/com/gyf/csams/activity/model/ApplyActViewModel.kt @@ -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 diff --git a/foreground/src/main/java/com/gyf/csams/activity/ui/ActivityDetailActivity.kt b/foreground/src/main/java/com/gyf/csams/activity/ui/ActivityDetailActivity.kt index a6ea995..081e727 100644 --- a/foreground/src/main/java/com/gyf/csams/activity/ui/ActivityDetailActivity.kt +++ b/foreground/src/main/java/com/gyf/csams/activity/ui/ActivityDetailActivity.kt @@ -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 diff --git a/foreground/src/main/java/com/gyf/csams/activity/ui/ApplyActActivity.kt b/foreground/src/main/java/com/gyf/csams/activity/ui/ApplyActActivity.kt index cc929b4..1c464b8 100644 --- a/foreground/src/main/java/com/gyf/csams/activity/ui/ApplyActActivity.kt +++ b/foreground/src/main/java/com/gyf/csams/activity/ui/ApplyActActivity.kt @@ -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() diff --git a/foreground/src/main/java/com/gyf/csams/association/model/RegAssociationViewModel.kt b/foreground/src/main/java/com/gyf/csams/association/model/RegAssociationViewModel.kt index cc3d6b9..cfdc89b 100644 --- a/foreground/src/main/java/com/gyf/csams/association/model/RegAssociationViewModel.kt +++ b/foreground/src/main/java/com/gyf/csams/association/model/RegAssociationViewModel.kt @@ -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>>() {}.type), - id = token.userId, - token = token.token, + token = token, fileList = arrayOf(cacheFile) ) } diff --git a/foreground/src/main/java/com/gyf/csams/association/ui/AssociationActivity.kt b/foreground/src/main/java/com/gyf/csams/association/ui/AssociationActivity.kt index 3e3df2e..2309d6d 100644 --- a/foreground/src/main/java/com/gyf/csams/association/ui/AssociationActivity.kt +++ b/foreground/src/main/java/com/gyf/csams/association/ui/AssociationActivity.kt @@ -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) } } } diff --git a/foreground/src/main/java/com/gyf/csams/association/ui/ExamActivity.kt b/foreground/src/main/java/com/gyf/csams/association/ui/ExamActivity.kt index 5ade30d..03bec82 100644 --- a/foreground/src/main/java/com/gyf/csams/association/ui/ExamActivity.kt +++ b/foreground/src/main/java/com/gyf/csams/association/ui/ExamActivity.kt @@ -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, diff --git a/foreground/src/main/java/com/gyf/csams/association/ui/ReNameActivity.kt b/foreground/src/main/java/com/gyf/csams/association/ui/ReNameActivity.kt index 8ca52d1..b14f535 100644 --- a/foreground/src/main/java/com/gyf/csams/association/ui/ReNameActivity.kt +++ b/foreground/src/main/java/com/gyf/csams/association/ui/ReNameActivity.kt @@ -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 diff --git a/foreground/src/main/java/com/gyf/csams/association/ui/RegAssociationActivity.kt b/foreground/src/main/java/com/gyf/csams/association/ui/RegAssociationActivity.kt index 5dcae48..82cc1bd 100644 --- a/foreground/src/main/java/com/gyf/csams/association/ui/RegAssociationActivity.kt +++ b/foreground/src/main/java/com/gyf/csams/association/ui/RegAssociationActivity.kt @@ -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 diff --git a/foreground/src/main/java/com/gyf/csams/main/model/MainViewModel.kt b/foreground/src/main/java/com/gyf/csams/main/model/MainViewModel.kt index 5521fcc..727cc90 100644 --- a/foreground/src/main/java/com/gyf/csams/main/model/MainViewModel.kt +++ b/foreground/src/main/java/com/gyf/csams/main/model/MainViewModel.kt @@ -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() + val count: LiveData = _count + + init { + count() + } + + /** + * 通知计数 + * + */ + private fun count() { + viewModelScope.launch { + TokenManager.token?.let { + HttpClient.post( + url = Api.buildUrl(NotificationApi.Count), + SimpleCallback(action = "未读通知计数", onSuccess = { + it.body?.let { + _count.postValue(it) + } + Logger.i(it.message) + }, onFail = { + Logger.e(it) + }, type = object : TypeToken>() {}.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( - action = "发送留言", onSuccess = { - callback("留言发送${if (it.body == true) "成功" else "失败"}") - }, onFail = { - callback("留言发送失败") - }, type = object : TypeToken>() {}.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 { diff --git a/foreground/src/main/java/com/gyf/csams/main/ui/MainActivity.kt b/foreground/src/main/java/com/gyf/csams/main/ui/MainActivity.kt index 1acb91e..2013f3b 100644 --- a/foreground/src/main/java/com/gyf/csams/main/ui/MainActivity.kt +++ b/foreground/src/main/java/com/gyf/csams/main/ui/MainActivity.kt @@ -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)) })) { diff --git a/foreground/src/main/java/com/gyf/csams/message/model/SysMessageViewModel.kt b/foreground/src/main/java/com/gyf/csams/message/model/SysMessageViewModel.kt index c06bd88..ec9c389 100644 --- a/foreground/src/main/java/com/gyf/csams/message/model/SysMessageViewModel.kt +++ b/foreground/src/main/java/com/gyf/csams/message/model/SysMessageViewModel.kt @@ -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() { +class SysMessageViewModel : ScrollList() { val title = "系统通知" override val initSize: Int = 10 + private val _currentPage = MutableLiveData() + val currentPage: LiveData = _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>( + action = "获取通知列表", + onSuccess = { + it.body?.let { + _data.postValue(it) + } + Logger.i(it.message) + }, + onFail = { + Logger.e(it) + }, + type = object : + TypeToken>>() {}.type + ), + jsonParam = NotificationDto( + receiverId = it.id, + receiverClient = ReceiverType.Foreground.name, + token = it, page = PageDto( + currentPage = _currentPage.value ?: 1, + pageSize = initSize ) - } - } + ) + ) } } } /** - * TODO 加载更多通知 + *TODO * * @param callback */ diff --git a/foreground/src/main/java/com/gyf/csams/message/ui/MessageActivity.kt b/foreground/src/main/java/com/gyf/csams/message/ui/MessageActivity.kt index 3907c6a..c237553 100644 --- a/foreground/src/main/java/com/gyf/csams/message/ui/MessageActivity.kt +++ b/foreground/src/main/java/com/gyf/csams/message/ui/MessageActivity.kt @@ -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 = { diff --git a/foreground/src/main/java/com/gyf/csams/message/ui/SysMessageActivity.kt b/foreground/src/main/java/com/gyf/csams/message/ui/SysMessageActivity.kt index b8fcfdf..bad91f8 100644 --- a/foreground/src/main/java/com/gyf/csams/message/ui/SysMessageActivity.kt +++ b/foreground/src/main/java/com/gyf/csams/message/ui/SysMessageActivity.kt @@ -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()) } } } diff --git a/foreground/src/main/java/com/gyf/csams/uikit/ViewModel.kt b/foreground/src/main/java/com/gyf/csams/uikit/ViewModel.kt index 09f8b23..0b7fcb6 100644 --- a/foreground/src/main/java/com/gyf/csams/uikit/ViewModel.kt +++ b/foreground/src/main/java/com/gyf/csams/uikit/ViewModel.kt @@ -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() private val _imageUrls = MutableLiveData?>() val image: LiveData = _image private var job: Job? = null private val _error = MutableLiveData() val error: LiveData = _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>("获取轮播图", onSuccess = { @@ -101,6 +93,11 @@ class ImageModel(application: Application, private val urlPath: String) : Logger.i("停止轮播") job?.cancel() } + + override fun onCleared() { + super.onCleared() + cancel() + } } diff --git a/foreground/src/main/java/com/gyf/csams/util/ContextUtil.kt b/foreground/src/main/java/com/gyf/csams/util/ContextUtil.kt deleted file mode 100644 index 5a2c59e..0000000 --- a/foreground/src/main/java/com/gyf/csams/util/ContextUtil.kt +++ /dev/null @@ -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) - } -} \ No newline at end of file diff --git a/lib/build.gradle.kts b/lib/build.gradle.kts index ed72278..3da2b89 100644 --- a/lib/build.gradle.kts +++ b/lib/build.gradle.kts @@ -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") } \ No newline at end of file diff --git a/lib/src/main/java/com/gyf/NotificationWorker.kt b/lib/src/main/java/com/gyf/NotificationWorker.kt new file mode 100644 index 0000000..87079d5 --- /dev/null +++ b/lib/src/main/java/com/gyf/NotificationWorker.kt @@ -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(url = NOTIFICATION_API, + jsonParam = NotificationDto( + receiverId = it.id, + receiverClient = inputData.getString("receiverClient") + ?: throw IllegalArgumentException("缺少receiverClient参数"), + token = it + ), + type = object : TypeToken>() {}.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() + } + +} \ No newline at end of file diff --git a/lib/src/main/java/com/gyf/lib/MessageService.kt b/lib/src/main/java/com/gyf/lib/MessageService.kt new file mode 100644 index 0000000..1c383f8 --- /dev/null +++ b/lib/src/main/java/com/gyf/lib/MessageService.kt @@ -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>(url = NOTIFICATION_API, + jsonParam = NotificationDto( + receiverId = it.id, + receiverClient = ReceiverType.Foreground.name, + token = it + ), + type = object : TypeToken>>() {}.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()) + } + } + } + } + } +} \ No newline at end of file diff --git a/lib/src/main/java/com/gyf/lib/uikit/MainFrame.kt b/lib/src/main/java/com/gyf/lib/uikit/MainFrame.kt index 7d20e31..6120a9e 100644 --- a/lib/src/main/java/com/gyf/lib/uikit/MainFrame.kt +++ b/lib/src/main/java/com/gyf/lib/uikit/MainFrame.kt @@ -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 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) { diff --git a/lib/src/main/java/com/gyf/lib/util/ContextUtil.kt b/lib/src/main/java/com/gyf/lib/util/ContextUtil.kt new file mode 100644 index 0000000..9ef2d2a --- /dev/null +++ b/lib/src/main/java/com/gyf/lib/util/ContextUtil.kt @@ -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() + .setInputData(data) + .build() + Logger.i("构建任务") + val workManager = WorkManager.getInstance(context) + workManager.enqueueUniqueWork(name, ExistingWorkPolicy.KEEP, uploadWorkRequest) + return uploadWorkRequest.id + } + + fun getNotificationLiveData(context: Context): LiveData { + 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) + } +} \ No newline at end of file diff --git a/lib/src/main/java/com/gyf/lib/util/HttpUtil.kt b/lib/src/main/java/com/gyf/lib/util/HttpUtil.kt index d6ad413..a75e57f 100644 --- a/lib/src/main/java/com/gyf/lib/util/HttpUtil.kt +++ b/lib/src/main/java/com/gyf/lib/util/HttpUtil.kt @@ -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 postAsync(url: String, jsonParam: Any, type: Type): ApiResponse? { 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?> { + 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( +open class HttpCallback( private val action: String, private val onSuccess: (res: ApiResponse) -> 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 -> { diff --git a/lib/src/main/java/com/gyf/lib/util/ImageUtil.kt b/lib/src/main/java/com/gyf/lib/util/ImageUtil.kt index cb7ad09..97373d6 100644 --- a/lib/src/main/java/com/gyf/lib/util/ImageUtil.kt +++ b/lib/src/main/java/com/gyf/lib/util/ImageUtil.kt @@ -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 { } } } - } + } \ No newline at end of file diff --git a/lib/src/main/java/com/gyf/lib/util/NotificationUtil.kt b/lib/src/main/java/com/gyf/lib/util/NotificationUtil.kt new file mode 100644 index 0000000..b868fc2 --- /dev/null +++ b/lib/src/main/java/com/gyf/lib/util/NotificationUtil.kt @@ -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 + } + } +} \ No newline at end of file diff --git a/foreground/src/main/java/com/gyf/csams/util/TokenUtil.kt b/lib/src/main/java/com/gyf/lib/util/TokenUtil.kt similarity index 88% rename from foreground/src/main/java/com/gyf/csams/util/TokenUtil.kt rename to lib/src/main/java/com/gyf/lib/util/TokenUtil.kt index 513d9a1..7a69653 100644 --- a/foreground/src/main/java/com/gyf/csams/util/TokenUtil.kt +++ b/lib/src/main/java/com/gyf/lib/util/TokenUtil.kt @@ -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 { diff --git a/lib/src/main/res/drawable/ic_notification.xml b/lib/src/main/res/drawable/ic_notification.xml new file mode 100644 index 0000000..c5e25c2 --- /dev/null +++ b/lib/src/main/res/drawable/ic_notification.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/lib/src/test/java/com/gyf/lib/ExampleUnitTest.kt b/lib/src/test/java/com/gyf/lib/ExampleUnitTest.kt index ae27851..fd8acee 100644 --- a/lib/src/test/java/com/gyf/lib/ExampleUnitTest.kt +++ b/lib/src/test/java/com/gyf/lib/ExampleUnitTest.kt @@ -1,6 +1,6 @@ package com.gyf.lib -import org.junit.Assert.* +import org.junit.Assert.assertEquals import org.junit.Test /**