diff --git a/foreground/src/main/java/com/gyf/csams/Api.kt b/foreground/src/main/java/com/gyf/csams/Api.kt index a19c7ba..4eb8ff2 100644 --- a/foreground/src/main/java/com/gyf/csams/Api.kt +++ b/foreground/src/main/java/com/gyf/csams/Api.kt @@ -17,10 +17,19 @@ enum class TestApi(val path: String) : UrlPath { * @property path */ enum class AccountApi(val path: String) : UrlPath { + //注册 Register("/register"), + + //学号检测 CheckId("/register/checkId"), + + //登录 Login("/login"), + + //令牌校验 LoginToken("/login/token"), + + //登出 Logout("/logout"); @@ -29,13 +38,36 @@ enum class AccountApi(val path: String) : UrlPath { } } +/** + * 主页接口 + * + * @property path + */ enum class MainApi(val path: String) : UrlPath { + //热门活动 HotActivity("/hotActivity"), + + //留言区 LeaveMessage("/leaveMessage"), - GetMessage("getMessage"); + GetMessage("/getMessage"); + + override fun build(): String { + return "/api/main${this.path}" + } +} + +/** + * 社团接口 + * + * @property path + */ +enum class AssociationApi(val path: String) : UrlPath { + Logo("/uploadLogo"), + Register("/register"); + override fun build(): String { - return "/api/main/${this.path}" + return "/api/association${this.path}" } } diff --git a/foreground/src/main/java/com/gyf/csams/InitViewModel.kt b/foreground/src/main/java/com/gyf/csams/InitViewModel.kt index 04942b7..6db1afe 100644 --- a/foreground/src/main/java/com/gyf/csams/InitViewModel.kt +++ b/foreground/src/main/java/com/gyf/csams/InitViewModel.kt @@ -77,7 +77,7 @@ class InitViewModel : ViewModel() { ), jsonParam = TokenVo( token = currentToken.token, - studentId = currentToken.studentId + userId = currentToken.userId ) ) } else if (tokenList != null && tokenList.size > 1) { 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 623ed72..b5ac53e 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 @@ -17,6 +17,7 @@ import com.baidu.mapapi.search.poi.* import com.baidu.mapapi.search.sug.SuggestionResult import com.baidu.mapapi.search.sug.SuggestionSearch import com.baidu.mapapi.search.sug.SuggestionSearchOption +import com.gyf.csams.BuildConfig import com.gyf.csams.MyLocationListener import com.gyf.csams.NOT_IMPL_TIP import com.gyf.csams.R @@ -434,10 +435,14 @@ class ApplyActViewModel(application: Application) : AndroidViewModel(application fun createMapView(context: Context): MapView { val mapView = MapView(context).apply { - MyLocationListener.location.value?.let { _ -> - // 设置初始中心点为用户附近 -// mCenter = LatLng(it.latitude, it.longitude) - mCenter = LatLng(39.963175, 116.400244) + MyLocationListener.location.value?.let { it -> + if (BuildConfig.DEBUG) { + //如果是测试版本地图中心设置为北京 + mCenter = LatLng(39.963175, 116.400244) + } else { + //否则设置初始中心点为用户附近 + mCenter = LatLng(it.latitude, it.longitude) + } map.setMapStatus(mapStatus(mCenter = mCenter)) 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 e8ecb55..cc3d6b9 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 @@ -1,42 +1,135 @@ package com.gyf.csams.association.model +import android.app.Application import android.net.Uri +import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import com.gyf.csams.NOT_IMPL_TIP -import com.gyf.lib.uikit.StringForm +import androidx.lifecycle.viewModelScope +import com.google.gson.reflect.TypeToken +import com.gyf.csams.Api +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.orhanobut.logger.Logger +import kotlinx.coroutines.launch +import java.io.File +import java.io.InputStream +data class RegAssociationVo(val name: String, val desc: String, val fileId: Int, val token: Token) -data class Image(val uri: Uri, val createTime: Long, val size: Long) - -class RegAssociationViewModel : ViewModel() { +class RegAssociationViewModel(application: Application) : AndroidViewModel(application) { val frameDesc = "社团注册资料" - val name = StringForm(formDesc = "社团名称", textLength = 5) - val desc = StringForm(formDesc = "社团简介", textLength = 30) + val name = ValidStringForm(formDesc = "社团名称", textLength = 5) + val desc = ValidStringForm(formDesc = "社团简介", textLength = 30) - val _picture = MutableLiveData() - val picture: LiveData = _picture + private val _picture = MutableLiveData() + val picture: LiveData = _picture - val piciurePlaceHolder = "请上传图片" + private val _fileId = MutableLiveData() + val fileId: LiveData = _fileId - val errorPicture = "图片加载失败,请联系管理员" + val picturePlaceHolder = "请上传图片" + val errorPicture = "图片加载失败,请联系管理员" fun setPicture(uri: Uri) { _picture.value = uri } + fun getInputSteam(): InputStream? { + _picture.value?.let { + val resolver = getApplication().contentResolver + return resolver.openInputStream(it) + } + throw IllegalArgumentException(UNKNOW_ERROR) + } + + + fun uploadPhoto(callback: (value: String) -> Unit) { + getInputSteam()?.readBytes()?.apply { + val token = TokenManager.token + if (token != null) { + + viewModelScope.launch { + val context = getApplication() + + runCatching { + val cacheFile = File(context.cacheDir, "${System.currentTimeMillis()}") + cacheFile.writeBytes(this@apply) + + HttpClient.uploadFile( + Api.buildUrl(AssociationApi.Logo), + SimpleCallback>("上传图片", onSuccess = { + Logger.i(it.message) + callback(it.message) + it.body?.let { + _fileId.postValue(it.first()) + } + + }, onFail = { + Logger.e(it) + callback("图片上传失败") + }, type = object : TypeToken>>() {}.type), + id = token.userId, + token = token.token, + fileList = arrayOf(cacheFile) + ) + } + } + } else { + callback(UNKNOW_ERROR) + } + } + } + /** - * TODO 注册社团 + * * * @param callback */ fun register(callback: (value: String) -> Unit) { - callback(NOT_IMPL_TIP) + val nameValue = name.formValue.value + val descValue = desc.formValue.value + val token = TokenManager.token + val fileId = _fileId.value + if (token != null && nameValue != null && descValue != null && fileId != null && + name.statusForm.value == FormStatus.Valid && desc.statusForm.value == FormStatus.Valid + ) { + viewModelScope.launch { + HttpClient.post( + Api.buildUrl(AssociationApi.Register), + SimpleCallback("注册社团", onSuccess = { + Logger.i(it.message) + callback(it.message) + name.clean() + desc.clean() + _picture.postValue(null) + }, onFail = { + Logger.e(it) + }, object : TypeToken>() {}.type), + jsonParam = + RegAssociationVo( + name = nameValue, + desc = descValue, + token = token, + fileId = fileId + ) + ) + } + } else { + callback(UNKNOW_ERROR) + } } } \ No newline at end of file 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 6012071..5dcae48 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 @@ -33,10 +33,7 @@ import com.gyf.csams.R import com.gyf.csams.association.model.RegAssociationViewModel 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.MainColumnFrame -import com.gyf.lib.uikit.ScaffoldModel +import com.gyf.lib.uikit.* import com.gyf.lib.util.BottomButton import com.orhanobut.logger.Logger @@ -61,30 +58,38 @@ class RegAssociationActivity : ComponentActivity() { modifier = Modifier .weight(0.1F) ) - Title() - Name() - Desc( - modifier = Modifier - .weight(0.1F) - .fillMaxWidth() - ) - Spacer(modifier = Modifier.weight(0.05F)) - Logo( - modifier = Modifier - .weight(0.2F) - .fillMaxWidth() - ) - Spacer(modifier = Modifier.weight(0.05F)) - - val model: RegAssociationViewModel = viewModel() - val scaffoldModel: ScaffoldModel = viewModel() - BottomButton( - modifier = Modifier.fillMaxWidth(), - confirmDesc = R.string.reg_btn - ) { - model.register { scaffoldModel.update(message = it) } - } - Spacer(modifier = Modifier.weight(0.05F)) + Title() + Name() + Desc( + modifier = Modifier + .weight(0.1F) + .fillMaxWidth() + ) + Spacer(modifier = Modifier.weight(0.05F)) + Logo( + modifier = Modifier + .weight(0.2F) + .fillMaxWidth() + ) + Spacer(modifier = Modifier.weight(0.05F)) + + val model: RegAssociationViewModel = viewModel() + val scaffoldModel: ScaffoldModel = viewModel() + val name by model.name.statusForm.observeAsState() + val desc by model.name.statusForm.observeAsState() + val fileId by model.fileId.observeAsState() + BottomButton( + modifier = Modifier.fillMaxWidth(), + enabled = name == FormStatus.Valid && desc == FormStatus.Valid && fileId != null, + confirmDesc = R.string.reg_btn + ) { + model.register { + scaffoldModel.update(message = it, actionLabel = "返回") { + finish() + } + } + } + Spacer(modifier = Modifier.weight(0.05F)) } } @@ -103,16 +108,21 @@ class RegAssociationActivity : ComponentActivity() { scaffoldModel: ScaffoldModel = viewModel(), modifier: Modifier ) { - val photoIntent = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI) - photoIntent.type = "image/*" + val photoIntent = Intent( + Intent.ACTION_PICK, + MediaStore.Images.Media.EXTERNAL_CONTENT_URI + ) val uri: Uri? by model.picture.observeAsState() val resultLauncher = rememberLauncherForActivityResult(contract = ActivityResultContracts.StartActivityForResult()) { when (it.resultCode) { Activity.RESULT_OK -> { - Logger.i("uri=${it.data?.data}") - it.data?.data?.let { it1 -> model.setPicture(it1) } + + it.data?.data?.apply { + Logger.i("uri=$this") + model.setPicture(this) + } } } } @@ -161,7 +171,7 @@ class RegAssociationActivity : ComponentActivity() { } } }) { - Text(text = model.piciurePlaceHolder) + Text(text = model.picturePlaceHolder) } } else { uri.let { @@ -175,17 +185,33 @@ class RegAssociationActivity : ComponentActivity() { ) .asImageBitmap(), contentDescription = null ) - IconButton(onClick = { - loadPicture() - }) { - Image( - painter = painterResource(id = R.drawable.ic_exchange_rate), - contentDescription = null - ) + Column( + modifier = Modifier.fillMaxHeight(), + verticalArrangement = Arrangement.SpaceEvenly + ) { + IconButton(onClick = { + loadPicture() + }) { + Image( + painter = painterResource(id = R.drawable.ic_exchange_rate), + contentDescription = null + ) + } + IconButton(onClick = { + scaffoldModel.update("确认上传此图片?", actionLabel = "确定") { + Logger.i("开始上传") + model.uploadPhoto { + scaffoldModel.update(message = it) + } + } + }) { + Image( + painter = painterResource(id = R.drawable.ic_upload), + contentDescription = null + ) + } } } - - } else { Text(text = model.errorPicture) } 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 58ab4ac..5521fcc 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 @@ -8,6 +8,7 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.google.gson.reflect.TypeToken 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 @@ -29,7 +30,7 @@ import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.launch -data class LeaveMessageFormatVo(val message: String, val studentId: String) +data class LeaveMessageFormatVo(val message: String, val user: UserVo) /** * 跑马灯 @@ -70,17 +71,7 @@ class MarqueeViewModel : AbstractComment() { if (it.body == true) { callback("留言发送成功") newContent.clean() - _marqueeTexts.value?.apply { - val c = mutableListOf() - c.add( - LeaveMessageFormatVo( - message = "${TokenManager.token?.name}说:${newContent.formValue.value}", - studentId = "${TokenManager.token?.studentId}" - ) - ) - c.addAll(this) - _marqueeTexts.postValue(c) - } + loadMessage(callback) } else { callback("留言发送失败") } @@ -286,7 +277,7 @@ data class InfoVo( override val desc: String ) : PersonInfoVo() -data class UserLogoutVo(val studentId: String) +data class UserLogoutVo(val userId: Int) /** * 个人中心 @@ -319,35 +310,37 @@ class CenterViewModel : ViewModel() { } fun logout(context: Activity, callback: (message: String) -> Unit) { - val studentId = TokenManager.token?.studentId - if (studentId != null) { - Logger.i("帐号$studentId 将要退出登录") + val userId = TokenManager.token?.userId + if (userId != null) { + Logger.i("帐号$userId 将要退出登录") viewModelScope.launch { - HttpClient.post(Api.buildUrl(AccountApi.Logout), - SimpleCallback(action = "登出", onSuccess = { it -> - it.body?.let { - if (it) { - viewModelScope.launch { - val db = AppDatabase.getInstance(context = context) - db?.tokenDao()?.deleteAll() - Logger.i("退出登陆成功") - context.finish() - context.startActivity( - Intent( - context, - AccountActivity::class.java + HttpClient.post( + Api.buildUrl(AccountApi.Logout), + SimpleCallback( + action = "登出", onSuccess = { it -> + it.body?.let { + if (it) { + viewModelScope.launch { + val db = AppDatabase.getInstance(context = context) + db?.tokenDao()?.deleteAll() + Logger.i("退出登陆成功") + context.finish() + context.startActivity( + Intent( + context, + AccountActivity::class.java + ) ) - ) + } + TokenManager.token = null } - TokenManager.token = null - } } callback(it.message) }, onFail = { callback("退出登陆失败") }, type = object : TypeToken>() {}.type ), - jsonParam = UserLogoutVo(studentId = studentId) + jsonParam = UserLogoutVo(userId = userId) ) } } else { diff --git a/foreground/src/main/java/com/gyf/csams/util/TokenUtil.kt b/foreground/src/main/java/com/gyf/csams/util/TokenUtil.kt index 775dee9..513d9a1 100644 --- a/foreground/src/main/java/com/gyf/csams/util/TokenUtil.kt +++ b/foreground/src/main/java/com/gyf/csams/util/TokenUtil.kt @@ -11,21 +11,17 @@ import androidx.room.* */ @Entity data class Token( - @PrimaryKey val studentId: String, + @PrimaryKey val userId: Int, @ColumnInfo val token: String, - @ColumnInfo val createTime: Long, - @ColumnInfo val name: String + @ColumnInfo val createTime: Long ) - -//data class TokenVo(val token:String,val studentId:String) - /** * 令牌传输 * * @property token */ -data class TokenVo(val token: String, val studentId: String) +data class TokenVo(val token: String, val userId: Int) @Dao interface TokenDao { @@ -44,6 +40,7 @@ interface TokenDao { object TokenManager { var token: Token? = null + } diff --git a/lib/src/main/java/com/gyf/lib/util/BottomButton.kt b/lib/src/main/java/com/gyf/lib/util/BottomButton.kt index ae24ca2..6f32556 100644 --- a/lib/src/main/java/com/gyf/lib/util/BottomButton.kt +++ b/lib/src/main/java/com/gyf/lib/util/BottomButton.kt @@ -20,6 +20,7 @@ import com.gyf.lib.R @Composable fun BottomButton( modifier: Modifier = Modifier, + enabled: Boolean = true, @StringRes confirmDesc: Int = R.string.confirm_btn, @StringRes backDesc: Int = R.string.back_btn, onConfirm: () -> Unit @@ -28,6 +29,7 @@ fun BottomButton( Row(modifier = modifier, horizontalArrangement = Arrangement.Center) { OutlinedButton( onClick = onConfirm, + enabled = enabled, modifier = Modifier.background(color = MaterialTheme.colors.primary) ) { Text(text = stringResource(id = confirmDesc)) 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 3b7c848..d6ad413 100644 --- a/lib/src/main/java/com/gyf/lib/util/HttpUtil.kt +++ b/lib/src/main/java/com/gyf/lib/util/HttpUtil.kt @@ -4,11 +4,17 @@ import com.google.gson.Gson import com.orhanobut.logger.Logger import okhttp3.* import okhttp3.MediaType.Companion.toMediaType +import okhttp3.RequestBody.Companion.asRequestBody import okhttp3.RequestBody.Companion.toRequestBody +import java.io.File import java.io.IOException import java.lang.reflect.Type import java.net.SocketTimeoutException +interface TokenInterface { + fun token(): String +} + object HttpClient { private val httpClient: OkHttpClient = OkHttpClient() @@ -59,6 +65,7 @@ object HttpClient { * @param params */ fun get(url: String, callback: Callback, params: Map? = null) { + Logger.i("request url=$url") val request = Request.Builder() .url(url.plus(buildQueryParams(params = params))) .build() @@ -68,34 +75,36 @@ object HttpClient { /** * HTTP POST - * 发送表单 + * 发送JSON * * @param url * @param callback - * @param params + * @param jsonBody */ - fun post(url: String, callback: Callback, params: Map? = null) { + @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) { + Logger.json(jsonBody) val request = Request.Builder() .url(url) - .post(body = buildFormBody(params)) + .post(body = jsonBody.toRequestBody(contentType = JSON_CONTENT_TYPE)) .build() val call = httpClient.newCall(request) call.enqueue(callback) } /** - * 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) @@ -105,12 +114,27 @@ object HttpClient { call.enqueue(callback) } - fun post(url: String, callback: Callback, jsonParam: Any) { - val jsonBody = json.toJson(jsonParam) - Logger.json(jsonBody) + /** + * 上传文件 + * + * @param url + * @param callback + */ + fun uploadFile(url: String, callback: Callback, id: Int, token: String, vararg fileList: File) { + Logger.i("request url=$url") + val body = MultipartBody.Builder() + body.addFormDataPart("id", id.toString()) + body.addFormDataPart("token", token) + fileList.withIndex().forEach { + body.addFormDataPart( + "file${it.index}", + it.value.name, + it.value.asRequestBody() + ) + } val request = Request.Builder() .url(url) - .post(body = jsonBody.toRequestBody(contentType = JSON_CONTENT_TYPE)) + .post(body = body.build()) .build() val call = httpClient.newCall(request) call.enqueue(callback)