diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1df917d..8d4f33b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -6,6 +6,7 @@ + @@ -32,11 +33,14 @@ + + + + + + + diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1df917d..8d4f33b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -6,6 +6,7 @@ + @@ -32,11 +33,14 @@ + + + + + + + diff --git a/app/src/main/assets/svg/mine.svg b/app/src/main/assets/svg/mine.svg new file mode 100644 index 0000000..3fbb0a3 --- /dev/null +++ b/app/src/main/assets/svg/mine.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1df917d..8d4f33b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -6,6 +6,7 @@ + @@ -32,11 +33,14 @@ + + + + + + + diff --git a/app/src/main/assets/svg/mine.svg b/app/src/main/assets/svg/mine.svg new file mode 100644 index 0000000..3fbb0a3 --- /dev/null +++ b/app/src/main/assets/svg/mine.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt b/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt index d730417..b1972c2 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt @@ -1,8 +1,10 @@ package com.casic.smart.town.sanxi.extensions import com.casic.smart.town.sanxi.model.ErrorMessageModel +import com.casic.smart.town.sanxi.util.LocaleConstant import com.google.gson.Gson import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.utils.SaveKeyValues import org.json.JSONObject fun String.separateResponseCode(): Int { @@ -17,4 +19,15 @@ this, object : TypeToken() {}.type ) return errorModel.message.toString() +} + +/** + * 下载路径为 http://xx.com/static/ 拼接downloadUrl + * */ +fun String.appendDownloadUrl(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + LocaleConstant.DEFAULT_SERVER_CONFIG, LocaleConstant.DEFAULT_SERVER + ) as String + return "$defaultValue/static/${this}" } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1df917d..8d4f33b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -6,6 +6,7 @@ + @@ -32,11 +33,14 @@ + + + + + + + diff --git a/app/src/main/assets/svg/mine.svg b/app/src/main/assets/svg/mine.svg new file mode 100644 index 0000000..3fbb0a3 --- /dev/null +++ b/app/src/main/assets/svg/mine.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt b/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt index d730417..b1972c2 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt @@ -1,8 +1,10 @@ package com.casic.smart.town.sanxi.extensions import com.casic.smart.town.sanxi.model.ErrorMessageModel +import com.casic.smart.town.sanxi.util.LocaleConstant import com.google.gson.Gson import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.utils.SaveKeyValues import org.json.JSONObject fun String.separateResponseCode(): Int { @@ -17,4 +19,15 @@ this, object : TypeToken() {}.type ) return errorModel.message.toString() +} + +/** + * 下载路径为 http://xx.com/static/ 拼接downloadUrl + * */ +fun String.appendDownloadUrl(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + LocaleConstant.DEFAULT_SERVER_CONFIG, LocaleConstant.DEFAULT_SERVER + ) as String + return "$defaultValue/static/${this}" } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt b/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt new file mode 100644 index 0000000..1809dc5 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt @@ -0,0 +1,288 @@ +package com.casic.smart.town.sanxi.fragment + +import android.app.ProgressDialog +import android.content.Intent +import android.net.Uri +import android.os.Build +import android.os.CountDownTimer +import androidx.core.content.FileProvider +import androidx.lifecycle.ViewModelProvider +import com.casic.smart.town.sanxi.BuildConfig +import com.casic.smart.town.sanxi.R +import com.casic.smart.town.sanxi.extensions.appendDownloadUrl +import com.casic.smart.town.sanxi.model.UserDetailModel +import com.casic.smart.town.sanxi.util.AuthenticationHelper +import com.casic.smart.town.sanxi.util.LoadingDialogHub +import com.casic.smart.town.sanxi.util.LocaleConstant +import com.casic.smart.town.sanxi.util.RSAUtils +import com.casic.smart.town.sanxi.view.AboutUsActivity +import com.casic.smart.town.sanxi.view.LoginActivity +import com.casic.smart.town.sanxi.vm.LoginViewModel +import com.casic.smart.town.sanxi.vm.UserViewModel +import com.casic.smart.town.sanxi.vm.VersionViewModel +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.base.KotlinBaseFragment +import com.pengxh.kt.lite.callback.OnDownloadListener +import com.pengxh.kt.lite.extensions.* +import com.pengxh.kt.lite.utils.PageNavigationManager +import com.pengxh.kt.lite.utils.SaveKeyValues +import com.pengxh.kt.lite.vm.LoadState +import com.pengxh.kt.lite.widget.dialog.AlertControlDialog +import com.pengxh.kt.lite.widget.dialog.ChangePasswordDialog +import kotlinx.android.synthetic.main.fragment_mine.* +import java.io.File +import java.nio.charset.StandardCharsets + +class MinePageFragment : KotlinBaseFragment() { + + private lateinit var userData: UserDetailModel.Data + private lateinit var userViewModel: UserViewModel + private lateinit var loginViewModel: LoginViewModel + private lateinit var versionViewModel: VersionViewModel + private lateinit var progressDialog: ProgressDialog + + override fun initLayoutView(): Int = R.layout.fragment_mine + + override fun setupTopBarLayout() { + + } + + override fun initData() { + userViewModel = ViewModelProvider(this)[UserViewModel::class.java] + loginViewModel = ViewModelProvider(this)[LoginViewModel::class.java] + versionViewModel = ViewModelProvider(this)[VersionViewModel::class.java] + + //初始化下载对话框 + progressDialog = ProgressDialog(requireContext()) + progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL) + progressDialog.setProgressDrawable(resources.getDrawable(R.drawable.download_progress)) + progressDialog.setCanceledOnTouchOutside(false) + progressDialog.setCancelable(false) + + /** + * 数据监听 + * */ + dataObserve() + } + + override fun observeRequestState() { + userViewModel.loadState.observe(this, { + when (it) { + is LoadState.Loading -> LoadingDialogHub.show(requireActivity(), "修改中,请稍后") + is LoadState.Success -> { + "修改成功,请重新登录".show(requireContext()) + LoadingDialogHub.dismiss() + AuthenticationHelper.removeToken() + requireContext().navigatePageTo() + PageNavigationManager.finishAllActivity() + } + else -> LoadingDialogHub.dismiss() + } + }) + + versionViewModel.loadState.observe(this, { + when (it) { + is LoadState.Loading -> LoadingDialogHub.show(requireActivity(), "检查版本中,请稍后") + else -> LoadingDialogHub.dismiss() + } + }) + } + + override fun initEvent() { + userImageView.setOnClickListener { + "尽情期待~".show(requireContext()) + } + + updateUserButton.setOnClickListener { + LoadingDialogHub.show(requireActivity(), "同步中,请稍后...") + userViewModel.obtainUserDetail() + } + + changePwdLayout.setOnClickListener { + ChangePasswordDialog.Builder() + .setContext(requireContext()) + .setOnDialogButtonClickListener(object : + ChangePasswordDialog.OnDialogButtonClickListener { + override fun onConfirmClick(oldPwd: String, newPwd: String) { + val publicKey = + RSAUtils.keyStrToPublicKey(AuthenticationHelper.publicKey)!! + val oldPassKey = RSAUtils.encryptDataByPublicKey( + oldPwd.toByteArray(StandardCharsets.UTF_8), + publicKey + ) + val newPassKey = RSAUtils.encryptDataByPublicKey( + newPwd.toByteArray(StandardCharsets.UTF_8), + publicKey + ) + userViewModel.changePassword(oldPassKey, newPassKey) + } + }).build().show() + } + + aboutUsLayout.setOnClickListener { + requireContext().navigatePageTo() + } + + updateVersionLayout.setOnClickListener { + versionViewModel.updateVersion() + } + + clearCacheLayout.setOnClickListener { + //删除缓存之后在设置缓存大小 + LoadingDialogHub.show(requireActivity(), "清理中,请稍后") + File(requireContext().cacheDir.path).deleteFile() + object : CountDownTimer(1500, 500) { + override fun onTick(millisUntilFinished: Long) { + + } + + override fun onFinish() { + LoadingDialogHub.dismiss() + cacheSizeView.text = collectApplicationCache().formatFileSize() + } + }.start() + } + + loginOutButton.setOnClickListener { + AlertControlDialog.Builder() + .setContext(requireContext()) + .setTitle("退出登录") + .setMessage("确定要退出吗?") + .setNegativeButton("取消") + .setPositiveButton("确定") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + loginViewModel.out() + } + + override fun onCancelClick() {} + }).build().show() + } + } + + private fun dataObserve() { + userViewModel.userDetailModel.observe(this, { + if (it.code == 200) { + LoadingDialogHub.dismiss() + "同步完成".show(requireContext()) + userData = it.data + updateUserInfo() + } + }) + + versionViewModel.versionResultModel.observe(this, { + if (BuildConfig.VERSION_NAME == it.version) { + "已是最新版本,无需更新".show(requireContext()) + } else { + AlertControlDialog.Builder() + .setContext(requireContext()) + .setTitle("提示") + .setMessage("有新版本,是否更新?") + .setNegativeButton("稍后再说") + .setPositiveButton("立即下载") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + downloadApk(it.downloadUrl) + } + + override fun onCancelClick() { + + } + }).build().show() + } + }) + + loginViewModel.outResultModel.observe(this, { + if (it.code == 200) { + AuthenticationHelper.removeToken() + requireContext().navigatePageTo() + PageNavigationManager.finishAllActivity() + } + }) + } + + private fun collectApplicationCache(): Long { + return File(requireContext().cacheDir.path).calculateSize() + } + + override fun onResume() { + val userDetailJson = SaveKeyValues.getValue(LocaleConstant.USER_DETAIL_MODEL, "") as String + if (userDetailJson.isNotBlank()) { + userData = Gson().fromJson( + userDetailJson, object : TypeToken() {}.type + ) + updateUserInfo() + } + //缓存 + cacheSizeView.text = collectApplicationCache().formatFileSize() + super.onResume() + } + + private fun updateUserInfo() { + //设置头像,圆形,暂时是默认的 +// val roundDrawable = +// BitmapFactory.decodeResource(requireContext().resources, R.mipmap.login_casic) +// .createRoundDrawable( +// requireContext(), +// 3f.dp2px(requireContext()), +// R.color.mainThemeColor.convertColor(requireContext()) +// ) +// userImageView.setImageDrawable(roundDrawable) + userNameView.text = userData.name + userPhoneView.text = String.format("电话:${userData.phone}") + userDeptView.text = String.format("部门:${userData.deptName}") + } + + private fun downloadApk(url: String?) { + progressDialog.setMessage("下载新版本中...") + progressDialog.show() + if (url.toString().isBlank()) { + "抱歉,版本下载失败".show(requireContext()) + return + } + /** + * http://111.198.10.15:11304/static/apk/1.0.1.apk + * */ + val downloadPath = url!!.appendDownloadUrl() + //开始下载 + downloadPath.downloadFile(requireContext().createDownloadFileDir().toString(), + object : OnDownloadListener { + override fun onDownloadStart(totalBytes: Long) { + progressDialog.max = totalBytes.toInt() + } + + override fun onProgressChanged(currentBytes: Long) { + progressDialog.progress = currentBytes.toInt() + } + + override fun onDownloadEnd(file: File?) { + progressDialog.dismiss() + progressDialog.progress = 0 + //安装APK + installApk(file) + } + }) + } + + private fun installApk(apkPackage: File?) { + if (apkPackage == null) { + "文件异常,无法安装".show(requireContext()) + return + } + val intent = Intent(Intent.ACTION_VIEW) + val data: Uri + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { //判断版本大于等于7.0 + data = FileProvider.getUriForFile( + requireContext(), LocaleConstant.APP_AUTHORITY, apkPackage + ) + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) // 给目标应用一个临时授权 + } else { + data = Uri.fromFile(apkPackage) + } + intent.setDataAndType(data, "application/vnd.android.package-archive") + requireContext().startActivity(intent) + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1df917d..8d4f33b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -6,6 +6,7 @@ + @@ -32,11 +33,14 @@ + + + + + + + diff --git a/app/src/main/assets/svg/mine.svg b/app/src/main/assets/svg/mine.svg new file mode 100644 index 0000000..3fbb0a3 --- /dev/null +++ b/app/src/main/assets/svg/mine.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt b/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt index d730417..b1972c2 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt @@ -1,8 +1,10 @@ package com.casic.smart.town.sanxi.extensions import com.casic.smart.town.sanxi.model.ErrorMessageModel +import com.casic.smart.town.sanxi.util.LocaleConstant import com.google.gson.Gson import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.utils.SaveKeyValues import org.json.JSONObject fun String.separateResponseCode(): Int { @@ -17,4 +19,15 @@ this, object : TypeToken() {}.type ) return errorModel.message.toString() +} + +/** + * 下载路径为 http://xx.com/static/ 拼接downloadUrl + * */ +fun String.appendDownloadUrl(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + LocaleConstant.DEFAULT_SERVER_CONFIG, LocaleConstant.DEFAULT_SERVER + ) as String + return "$defaultValue/static/${this}" } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt b/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt new file mode 100644 index 0000000..1809dc5 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt @@ -0,0 +1,288 @@ +package com.casic.smart.town.sanxi.fragment + +import android.app.ProgressDialog +import android.content.Intent +import android.net.Uri +import android.os.Build +import android.os.CountDownTimer +import androidx.core.content.FileProvider +import androidx.lifecycle.ViewModelProvider +import com.casic.smart.town.sanxi.BuildConfig +import com.casic.smart.town.sanxi.R +import com.casic.smart.town.sanxi.extensions.appendDownloadUrl +import com.casic.smart.town.sanxi.model.UserDetailModel +import com.casic.smart.town.sanxi.util.AuthenticationHelper +import com.casic.smart.town.sanxi.util.LoadingDialogHub +import com.casic.smart.town.sanxi.util.LocaleConstant +import com.casic.smart.town.sanxi.util.RSAUtils +import com.casic.smart.town.sanxi.view.AboutUsActivity +import com.casic.smart.town.sanxi.view.LoginActivity +import com.casic.smart.town.sanxi.vm.LoginViewModel +import com.casic.smart.town.sanxi.vm.UserViewModel +import com.casic.smart.town.sanxi.vm.VersionViewModel +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.base.KotlinBaseFragment +import com.pengxh.kt.lite.callback.OnDownloadListener +import com.pengxh.kt.lite.extensions.* +import com.pengxh.kt.lite.utils.PageNavigationManager +import com.pengxh.kt.lite.utils.SaveKeyValues +import com.pengxh.kt.lite.vm.LoadState +import com.pengxh.kt.lite.widget.dialog.AlertControlDialog +import com.pengxh.kt.lite.widget.dialog.ChangePasswordDialog +import kotlinx.android.synthetic.main.fragment_mine.* +import java.io.File +import java.nio.charset.StandardCharsets + +class MinePageFragment : KotlinBaseFragment() { + + private lateinit var userData: UserDetailModel.Data + private lateinit var userViewModel: UserViewModel + private lateinit var loginViewModel: LoginViewModel + private lateinit var versionViewModel: VersionViewModel + private lateinit var progressDialog: ProgressDialog + + override fun initLayoutView(): Int = R.layout.fragment_mine + + override fun setupTopBarLayout() { + + } + + override fun initData() { + userViewModel = ViewModelProvider(this)[UserViewModel::class.java] + loginViewModel = ViewModelProvider(this)[LoginViewModel::class.java] + versionViewModel = ViewModelProvider(this)[VersionViewModel::class.java] + + //初始化下载对话框 + progressDialog = ProgressDialog(requireContext()) + progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL) + progressDialog.setProgressDrawable(resources.getDrawable(R.drawable.download_progress)) + progressDialog.setCanceledOnTouchOutside(false) + progressDialog.setCancelable(false) + + /** + * 数据监听 + * */ + dataObserve() + } + + override fun observeRequestState() { + userViewModel.loadState.observe(this, { + when (it) { + is LoadState.Loading -> LoadingDialogHub.show(requireActivity(), "修改中,请稍后") + is LoadState.Success -> { + "修改成功,请重新登录".show(requireContext()) + LoadingDialogHub.dismiss() + AuthenticationHelper.removeToken() + requireContext().navigatePageTo() + PageNavigationManager.finishAllActivity() + } + else -> LoadingDialogHub.dismiss() + } + }) + + versionViewModel.loadState.observe(this, { + when (it) { + is LoadState.Loading -> LoadingDialogHub.show(requireActivity(), "检查版本中,请稍后") + else -> LoadingDialogHub.dismiss() + } + }) + } + + override fun initEvent() { + userImageView.setOnClickListener { + "尽情期待~".show(requireContext()) + } + + updateUserButton.setOnClickListener { + LoadingDialogHub.show(requireActivity(), "同步中,请稍后...") + userViewModel.obtainUserDetail() + } + + changePwdLayout.setOnClickListener { + ChangePasswordDialog.Builder() + .setContext(requireContext()) + .setOnDialogButtonClickListener(object : + ChangePasswordDialog.OnDialogButtonClickListener { + override fun onConfirmClick(oldPwd: String, newPwd: String) { + val publicKey = + RSAUtils.keyStrToPublicKey(AuthenticationHelper.publicKey)!! + val oldPassKey = RSAUtils.encryptDataByPublicKey( + oldPwd.toByteArray(StandardCharsets.UTF_8), + publicKey + ) + val newPassKey = RSAUtils.encryptDataByPublicKey( + newPwd.toByteArray(StandardCharsets.UTF_8), + publicKey + ) + userViewModel.changePassword(oldPassKey, newPassKey) + } + }).build().show() + } + + aboutUsLayout.setOnClickListener { + requireContext().navigatePageTo() + } + + updateVersionLayout.setOnClickListener { + versionViewModel.updateVersion() + } + + clearCacheLayout.setOnClickListener { + //删除缓存之后在设置缓存大小 + LoadingDialogHub.show(requireActivity(), "清理中,请稍后") + File(requireContext().cacheDir.path).deleteFile() + object : CountDownTimer(1500, 500) { + override fun onTick(millisUntilFinished: Long) { + + } + + override fun onFinish() { + LoadingDialogHub.dismiss() + cacheSizeView.text = collectApplicationCache().formatFileSize() + } + }.start() + } + + loginOutButton.setOnClickListener { + AlertControlDialog.Builder() + .setContext(requireContext()) + .setTitle("退出登录") + .setMessage("确定要退出吗?") + .setNegativeButton("取消") + .setPositiveButton("确定") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + loginViewModel.out() + } + + override fun onCancelClick() {} + }).build().show() + } + } + + private fun dataObserve() { + userViewModel.userDetailModel.observe(this, { + if (it.code == 200) { + LoadingDialogHub.dismiss() + "同步完成".show(requireContext()) + userData = it.data + updateUserInfo() + } + }) + + versionViewModel.versionResultModel.observe(this, { + if (BuildConfig.VERSION_NAME == it.version) { + "已是最新版本,无需更新".show(requireContext()) + } else { + AlertControlDialog.Builder() + .setContext(requireContext()) + .setTitle("提示") + .setMessage("有新版本,是否更新?") + .setNegativeButton("稍后再说") + .setPositiveButton("立即下载") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + downloadApk(it.downloadUrl) + } + + override fun onCancelClick() { + + } + }).build().show() + } + }) + + loginViewModel.outResultModel.observe(this, { + if (it.code == 200) { + AuthenticationHelper.removeToken() + requireContext().navigatePageTo() + PageNavigationManager.finishAllActivity() + } + }) + } + + private fun collectApplicationCache(): Long { + return File(requireContext().cacheDir.path).calculateSize() + } + + override fun onResume() { + val userDetailJson = SaveKeyValues.getValue(LocaleConstant.USER_DETAIL_MODEL, "") as String + if (userDetailJson.isNotBlank()) { + userData = Gson().fromJson( + userDetailJson, object : TypeToken() {}.type + ) + updateUserInfo() + } + //缓存 + cacheSizeView.text = collectApplicationCache().formatFileSize() + super.onResume() + } + + private fun updateUserInfo() { + //设置头像,圆形,暂时是默认的 +// val roundDrawable = +// BitmapFactory.decodeResource(requireContext().resources, R.mipmap.login_casic) +// .createRoundDrawable( +// requireContext(), +// 3f.dp2px(requireContext()), +// R.color.mainThemeColor.convertColor(requireContext()) +// ) +// userImageView.setImageDrawable(roundDrawable) + userNameView.text = userData.name + userPhoneView.text = String.format("电话:${userData.phone}") + userDeptView.text = String.format("部门:${userData.deptName}") + } + + private fun downloadApk(url: String?) { + progressDialog.setMessage("下载新版本中...") + progressDialog.show() + if (url.toString().isBlank()) { + "抱歉,版本下载失败".show(requireContext()) + return + } + /** + * http://111.198.10.15:11304/static/apk/1.0.1.apk + * */ + val downloadPath = url!!.appendDownloadUrl() + //开始下载 + downloadPath.downloadFile(requireContext().createDownloadFileDir().toString(), + object : OnDownloadListener { + override fun onDownloadStart(totalBytes: Long) { + progressDialog.max = totalBytes.toInt() + } + + override fun onProgressChanged(currentBytes: Long) { + progressDialog.progress = currentBytes.toInt() + } + + override fun onDownloadEnd(file: File?) { + progressDialog.dismiss() + progressDialog.progress = 0 + //安装APK + installApk(file) + } + }) + } + + private fun installApk(apkPackage: File?) { + if (apkPackage == null) { + "文件异常,无法安装".show(requireContext()) + return + } + val intent = Intent(Intent.ACTION_VIEW) + val data: Uri + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { //判断版本大于等于7.0 + data = FileProvider.getUriForFile( + requireContext(), LocaleConstant.APP_AUTHORITY, apkPackage + ) + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) // 给目标应用一个临时授权 + } else { + data = Uri.fromFile(apkPackage) + } + intent.setDataAndType(data, "application/vnd.android.package-archive") + requireContext().startActivity(intent) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java new file mode 100644 index 0000000..467e33d --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java @@ -0,0 +1,43 @@ +package com.casic.smart.town.sanxi.model; + +/** + * 普通实体类,失败/成功数据结构一致 + */ +public class CommonResultModel { + private int code; + private String data; + private String message; + private boolean isSuccess; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return isSuccess; + } + + public void setSuccess(boolean success) { + isSuccess = success; + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1df917d..8d4f33b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -6,6 +6,7 @@ + @@ -32,11 +33,14 @@ + + + + + + + diff --git a/app/src/main/assets/svg/mine.svg b/app/src/main/assets/svg/mine.svg new file mode 100644 index 0000000..3fbb0a3 --- /dev/null +++ b/app/src/main/assets/svg/mine.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt b/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt index d730417..b1972c2 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt @@ -1,8 +1,10 @@ package com.casic.smart.town.sanxi.extensions import com.casic.smart.town.sanxi.model.ErrorMessageModel +import com.casic.smart.town.sanxi.util.LocaleConstant import com.google.gson.Gson import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.utils.SaveKeyValues import org.json.JSONObject fun String.separateResponseCode(): Int { @@ -17,4 +19,15 @@ this, object : TypeToken() {}.type ) return errorModel.message.toString() +} + +/** + * 下载路径为 http://xx.com/static/ 拼接downloadUrl + * */ +fun String.appendDownloadUrl(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + LocaleConstant.DEFAULT_SERVER_CONFIG, LocaleConstant.DEFAULT_SERVER + ) as String + return "$defaultValue/static/${this}" } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt b/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt new file mode 100644 index 0000000..1809dc5 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt @@ -0,0 +1,288 @@ +package com.casic.smart.town.sanxi.fragment + +import android.app.ProgressDialog +import android.content.Intent +import android.net.Uri +import android.os.Build +import android.os.CountDownTimer +import androidx.core.content.FileProvider +import androidx.lifecycle.ViewModelProvider +import com.casic.smart.town.sanxi.BuildConfig +import com.casic.smart.town.sanxi.R +import com.casic.smart.town.sanxi.extensions.appendDownloadUrl +import com.casic.smart.town.sanxi.model.UserDetailModel +import com.casic.smart.town.sanxi.util.AuthenticationHelper +import com.casic.smart.town.sanxi.util.LoadingDialogHub +import com.casic.smart.town.sanxi.util.LocaleConstant +import com.casic.smart.town.sanxi.util.RSAUtils +import com.casic.smart.town.sanxi.view.AboutUsActivity +import com.casic.smart.town.sanxi.view.LoginActivity +import com.casic.smart.town.sanxi.vm.LoginViewModel +import com.casic.smart.town.sanxi.vm.UserViewModel +import com.casic.smart.town.sanxi.vm.VersionViewModel +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.base.KotlinBaseFragment +import com.pengxh.kt.lite.callback.OnDownloadListener +import com.pengxh.kt.lite.extensions.* +import com.pengxh.kt.lite.utils.PageNavigationManager +import com.pengxh.kt.lite.utils.SaveKeyValues +import com.pengxh.kt.lite.vm.LoadState +import com.pengxh.kt.lite.widget.dialog.AlertControlDialog +import com.pengxh.kt.lite.widget.dialog.ChangePasswordDialog +import kotlinx.android.synthetic.main.fragment_mine.* +import java.io.File +import java.nio.charset.StandardCharsets + +class MinePageFragment : KotlinBaseFragment() { + + private lateinit var userData: UserDetailModel.Data + private lateinit var userViewModel: UserViewModel + private lateinit var loginViewModel: LoginViewModel + private lateinit var versionViewModel: VersionViewModel + private lateinit var progressDialog: ProgressDialog + + override fun initLayoutView(): Int = R.layout.fragment_mine + + override fun setupTopBarLayout() { + + } + + override fun initData() { + userViewModel = ViewModelProvider(this)[UserViewModel::class.java] + loginViewModel = ViewModelProvider(this)[LoginViewModel::class.java] + versionViewModel = ViewModelProvider(this)[VersionViewModel::class.java] + + //初始化下载对话框 + progressDialog = ProgressDialog(requireContext()) + progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL) + progressDialog.setProgressDrawable(resources.getDrawable(R.drawable.download_progress)) + progressDialog.setCanceledOnTouchOutside(false) + progressDialog.setCancelable(false) + + /** + * 数据监听 + * */ + dataObserve() + } + + override fun observeRequestState() { + userViewModel.loadState.observe(this, { + when (it) { + is LoadState.Loading -> LoadingDialogHub.show(requireActivity(), "修改中,请稍后") + is LoadState.Success -> { + "修改成功,请重新登录".show(requireContext()) + LoadingDialogHub.dismiss() + AuthenticationHelper.removeToken() + requireContext().navigatePageTo() + PageNavigationManager.finishAllActivity() + } + else -> LoadingDialogHub.dismiss() + } + }) + + versionViewModel.loadState.observe(this, { + when (it) { + is LoadState.Loading -> LoadingDialogHub.show(requireActivity(), "检查版本中,请稍后") + else -> LoadingDialogHub.dismiss() + } + }) + } + + override fun initEvent() { + userImageView.setOnClickListener { + "尽情期待~".show(requireContext()) + } + + updateUserButton.setOnClickListener { + LoadingDialogHub.show(requireActivity(), "同步中,请稍后...") + userViewModel.obtainUserDetail() + } + + changePwdLayout.setOnClickListener { + ChangePasswordDialog.Builder() + .setContext(requireContext()) + .setOnDialogButtonClickListener(object : + ChangePasswordDialog.OnDialogButtonClickListener { + override fun onConfirmClick(oldPwd: String, newPwd: String) { + val publicKey = + RSAUtils.keyStrToPublicKey(AuthenticationHelper.publicKey)!! + val oldPassKey = RSAUtils.encryptDataByPublicKey( + oldPwd.toByteArray(StandardCharsets.UTF_8), + publicKey + ) + val newPassKey = RSAUtils.encryptDataByPublicKey( + newPwd.toByteArray(StandardCharsets.UTF_8), + publicKey + ) + userViewModel.changePassword(oldPassKey, newPassKey) + } + }).build().show() + } + + aboutUsLayout.setOnClickListener { + requireContext().navigatePageTo() + } + + updateVersionLayout.setOnClickListener { + versionViewModel.updateVersion() + } + + clearCacheLayout.setOnClickListener { + //删除缓存之后在设置缓存大小 + LoadingDialogHub.show(requireActivity(), "清理中,请稍后") + File(requireContext().cacheDir.path).deleteFile() + object : CountDownTimer(1500, 500) { + override fun onTick(millisUntilFinished: Long) { + + } + + override fun onFinish() { + LoadingDialogHub.dismiss() + cacheSizeView.text = collectApplicationCache().formatFileSize() + } + }.start() + } + + loginOutButton.setOnClickListener { + AlertControlDialog.Builder() + .setContext(requireContext()) + .setTitle("退出登录") + .setMessage("确定要退出吗?") + .setNegativeButton("取消") + .setPositiveButton("确定") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + loginViewModel.out() + } + + override fun onCancelClick() {} + }).build().show() + } + } + + private fun dataObserve() { + userViewModel.userDetailModel.observe(this, { + if (it.code == 200) { + LoadingDialogHub.dismiss() + "同步完成".show(requireContext()) + userData = it.data + updateUserInfo() + } + }) + + versionViewModel.versionResultModel.observe(this, { + if (BuildConfig.VERSION_NAME == it.version) { + "已是最新版本,无需更新".show(requireContext()) + } else { + AlertControlDialog.Builder() + .setContext(requireContext()) + .setTitle("提示") + .setMessage("有新版本,是否更新?") + .setNegativeButton("稍后再说") + .setPositiveButton("立即下载") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + downloadApk(it.downloadUrl) + } + + override fun onCancelClick() { + + } + }).build().show() + } + }) + + loginViewModel.outResultModel.observe(this, { + if (it.code == 200) { + AuthenticationHelper.removeToken() + requireContext().navigatePageTo() + PageNavigationManager.finishAllActivity() + } + }) + } + + private fun collectApplicationCache(): Long { + return File(requireContext().cacheDir.path).calculateSize() + } + + override fun onResume() { + val userDetailJson = SaveKeyValues.getValue(LocaleConstant.USER_DETAIL_MODEL, "") as String + if (userDetailJson.isNotBlank()) { + userData = Gson().fromJson( + userDetailJson, object : TypeToken() {}.type + ) + updateUserInfo() + } + //缓存 + cacheSizeView.text = collectApplicationCache().formatFileSize() + super.onResume() + } + + private fun updateUserInfo() { + //设置头像,圆形,暂时是默认的 +// val roundDrawable = +// BitmapFactory.decodeResource(requireContext().resources, R.mipmap.login_casic) +// .createRoundDrawable( +// requireContext(), +// 3f.dp2px(requireContext()), +// R.color.mainThemeColor.convertColor(requireContext()) +// ) +// userImageView.setImageDrawable(roundDrawable) + userNameView.text = userData.name + userPhoneView.text = String.format("电话:${userData.phone}") + userDeptView.text = String.format("部门:${userData.deptName}") + } + + private fun downloadApk(url: String?) { + progressDialog.setMessage("下载新版本中...") + progressDialog.show() + if (url.toString().isBlank()) { + "抱歉,版本下载失败".show(requireContext()) + return + } + /** + * http://111.198.10.15:11304/static/apk/1.0.1.apk + * */ + val downloadPath = url!!.appendDownloadUrl() + //开始下载 + downloadPath.downloadFile(requireContext().createDownloadFileDir().toString(), + object : OnDownloadListener { + override fun onDownloadStart(totalBytes: Long) { + progressDialog.max = totalBytes.toInt() + } + + override fun onProgressChanged(currentBytes: Long) { + progressDialog.progress = currentBytes.toInt() + } + + override fun onDownloadEnd(file: File?) { + progressDialog.dismiss() + progressDialog.progress = 0 + //安装APK + installApk(file) + } + }) + } + + private fun installApk(apkPackage: File?) { + if (apkPackage == null) { + "文件异常,无法安装".show(requireContext()) + return + } + val intent = Intent(Intent.ACTION_VIEW) + val data: Uri + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { //判断版本大于等于7.0 + data = FileProvider.getUriForFile( + requireContext(), LocaleConstant.APP_AUTHORITY, apkPackage + ) + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) // 给目标应用一个临时授权 + } else { + data = Uri.fromFile(apkPackage) + } + intent.setDataAndType(data, "application/vnd.android.package-archive") + requireContext().startActivity(intent) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java new file mode 100644 index 0000000..467e33d --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java @@ -0,0 +1,43 @@ +package com.casic.smart.town.sanxi.model; + +/** + * 普通实体类,失败/成功数据结构一致 + */ +public class CommonResultModel { + private int code; + private String data; + private String message; + private boolean isSuccess; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return isSuccess; + } + + public void setSuccess(boolean success) { + isSuccess = success; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java new file mode 100644 index 0000000..9bdabef --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java @@ -0,0 +1,62 @@ +package com.casic.smart.town.sanxi.model; + +public class LoginResultModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private String kaptcha; + private String token; + + public String getKaptcha() { + return kaptcha; + } + + public void setKaptcha(String kaptcha) { + this.kaptcha = kaptcha; + } + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + } +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1df917d..8d4f33b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -6,6 +6,7 @@ + @@ -32,11 +33,14 @@ + + + + + + + diff --git a/app/src/main/assets/svg/mine.svg b/app/src/main/assets/svg/mine.svg new file mode 100644 index 0000000..3fbb0a3 --- /dev/null +++ b/app/src/main/assets/svg/mine.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt b/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt index d730417..b1972c2 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt @@ -1,8 +1,10 @@ package com.casic.smart.town.sanxi.extensions import com.casic.smart.town.sanxi.model.ErrorMessageModel +import com.casic.smart.town.sanxi.util.LocaleConstant import com.google.gson.Gson import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.utils.SaveKeyValues import org.json.JSONObject fun String.separateResponseCode(): Int { @@ -17,4 +19,15 @@ this, object : TypeToken() {}.type ) return errorModel.message.toString() +} + +/** + * 下载路径为 http://xx.com/static/ 拼接downloadUrl + * */ +fun String.appendDownloadUrl(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + LocaleConstant.DEFAULT_SERVER_CONFIG, LocaleConstant.DEFAULT_SERVER + ) as String + return "$defaultValue/static/${this}" } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt b/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt new file mode 100644 index 0000000..1809dc5 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt @@ -0,0 +1,288 @@ +package com.casic.smart.town.sanxi.fragment + +import android.app.ProgressDialog +import android.content.Intent +import android.net.Uri +import android.os.Build +import android.os.CountDownTimer +import androidx.core.content.FileProvider +import androidx.lifecycle.ViewModelProvider +import com.casic.smart.town.sanxi.BuildConfig +import com.casic.smart.town.sanxi.R +import com.casic.smart.town.sanxi.extensions.appendDownloadUrl +import com.casic.smart.town.sanxi.model.UserDetailModel +import com.casic.smart.town.sanxi.util.AuthenticationHelper +import com.casic.smart.town.sanxi.util.LoadingDialogHub +import com.casic.smart.town.sanxi.util.LocaleConstant +import com.casic.smart.town.sanxi.util.RSAUtils +import com.casic.smart.town.sanxi.view.AboutUsActivity +import com.casic.smart.town.sanxi.view.LoginActivity +import com.casic.smart.town.sanxi.vm.LoginViewModel +import com.casic.smart.town.sanxi.vm.UserViewModel +import com.casic.smart.town.sanxi.vm.VersionViewModel +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.base.KotlinBaseFragment +import com.pengxh.kt.lite.callback.OnDownloadListener +import com.pengxh.kt.lite.extensions.* +import com.pengxh.kt.lite.utils.PageNavigationManager +import com.pengxh.kt.lite.utils.SaveKeyValues +import com.pengxh.kt.lite.vm.LoadState +import com.pengxh.kt.lite.widget.dialog.AlertControlDialog +import com.pengxh.kt.lite.widget.dialog.ChangePasswordDialog +import kotlinx.android.synthetic.main.fragment_mine.* +import java.io.File +import java.nio.charset.StandardCharsets + +class MinePageFragment : KotlinBaseFragment() { + + private lateinit var userData: UserDetailModel.Data + private lateinit var userViewModel: UserViewModel + private lateinit var loginViewModel: LoginViewModel + private lateinit var versionViewModel: VersionViewModel + private lateinit var progressDialog: ProgressDialog + + override fun initLayoutView(): Int = R.layout.fragment_mine + + override fun setupTopBarLayout() { + + } + + override fun initData() { + userViewModel = ViewModelProvider(this)[UserViewModel::class.java] + loginViewModel = ViewModelProvider(this)[LoginViewModel::class.java] + versionViewModel = ViewModelProvider(this)[VersionViewModel::class.java] + + //初始化下载对话框 + progressDialog = ProgressDialog(requireContext()) + progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL) + progressDialog.setProgressDrawable(resources.getDrawable(R.drawable.download_progress)) + progressDialog.setCanceledOnTouchOutside(false) + progressDialog.setCancelable(false) + + /** + * 数据监听 + * */ + dataObserve() + } + + override fun observeRequestState() { + userViewModel.loadState.observe(this, { + when (it) { + is LoadState.Loading -> LoadingDialogHub.show(requireActivity(), "修改中,请稍后") + is LoadState.Success -> { + "修改成功,请重新登录".show(requireContext()) + LoadingDialogHub.dismiss() + AuthenticationHelper.removeToken() + requireContext().navigatePageTo() + PageNavigationManager.finishAllActivity() + } + else -> LoadingDialogHub.dismiss() + } + }) + + versionViewModel.loadState.observe(this, { + when (it) { + is LoadState.Loading -> LoadingDialogHub.show(requireActivity(), "检查版本中,请稍后") + else -> LoadingDialogHub.dismiss() + } + }) + } + + override fun initEvent() { + userImageView.setOnClickListener { + "尽情期待~".show(requireContext()) + } + + updateUserButton.setOnClickListener { + LoadingDialogHub.show(requireActivity(), "同步中,请稍后...") + userViewModel.obtainUserDetail() + } + + changePwdLayout.setOnClickListener { + ChangePasswordDialog.Builder() + .setContext(requireContext()) + .setOnDialogButtonClickListener(object : + ChangePasswordDialog.OnDialogButtonClickListener { + override fun onConfirmClick(oldPwd: String, newPwd: String) { + val publicKey = + RSAUtils.keyStrToPublicKey(AuthenticationHelper.publicKey)!! + val oldPassKey = RSAUtils.encryptDataByPublicKey( + oldPwd.toByteArray(StandardCharsets.UTF_8), + publicKey + ) + val newPassKey = RSAUtils.encryptDataByPublicKey( + newPwd.toByteArray(StandardCharsets.UTF_8), + publicKey + ) + userViewModel.changePassword(oldPassKey, newPassKey) + } + }).build().show() + } + + aboutUsLayout.setOnClickListener { + requireContext().navigatePageTo() + } + + updateVersionLayout.setOnClickListener { + versionViewModel.updateVersion() + } + + clearCacheLayout.setOnClickListener { + //删除缓存之后在设置缓存大小 + LoadingDialogHub.show(requireActivity(), "清理中,请稍后") + File(requireContext().cacheDir.path).deleteFile() + object : CountDownTimer(1500, 500) { + override fun onTick(millisUntilFinished: Long) { + + } + + override fun onFinish() { + LoadingDialogHub.dismiss() + cacheSizeView.text = collectApplicationCache().formatFileSize() + } + }.start() + } + + loginOutButton.setOnClickListener { + AlertControlDialog.Builder() + .setContext(requireContext()) + .setTitle("退出登录") + .setMessage("确定要退出吗?") + .setNegativeButton("取消") + .setPositiveButton("确定") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + loginViewModel.out() + } + + override fun onCancelClick() {} + }).build().show() + } + } + + private fun dataObserve() { + userViewModel.userDetailModel.observe(this, { + if (it.code == 200) { + LoadingDialogHub.dismiss() + "同步完成".show(requireContext()) + userData = it.data + updateUserInfo() + } + }) + + versionViewModel.versionResultModel.observe(this, { + if (BuildConfig.VERSION_NAME == it.version) { + "已是最新版本,无需更新".show(requireContext()) + } else { + AlertControlDialog.Builder() + .setContext(requireContext()) + .setTitle("提示") + .setMessage("有新版本,是否更新?") + .setNegativeButton("稍后再说") + .setPositiveButton("立即下载") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + downloadApk(it.downloadUrl) + } + + override fun onCancelClick() { + + } + }).build().show() + } + }) + + loginViewModel.outResultModel.observe(this, { + if (it.code == 200) { + AuthenticationHelper.removeToken() + requireContext().navigatePageTo() + PageNavigationManager.finishAllActivity() + } + }) + } + + private fun collectApplicationCache(): Long { + return File(requireContext().cacheDir.path).calculateSize() + } + + override fun onResume() { + val userDetailJson = SaveKeyValues.getValue(LocaleConstant.USER_DETAIL_MODEL, "") as String + if (userDetailJson.isNotBlank()) { + userData = Gson().fromJson( + userDetailJson, object : TypeToken() {}.type + ) + updateUserInfo() + } + //缓存 + cacheSizeView.text = collectApplicationCache().formatFileSize() + super.onResume() + } + + private fun updateUserInfo() { + //设置头像,圆形,暂时是默认的 +// val roundDrawable = +// BitmapFactory.decodeResource(requireContext().resources, R.mipmap.login_casic) +// .createRoundDrawable( +// requireContext(), +// 3f.dp2px(requireContext()), +// R.color.mainThemeColor.convertColor(requireContext()) +// ) +// userImageView.setImageDrawable(roundDrawable) + userNameView.text = userData.name + userPhoneView.text = String.format("电话:${userData.phone}") + userDeptView.text = String.format("部门:${userData.deptName}") + } + + private fun downloadApk(url: String?) { + progressDialog.setMessage("下载新版本中...") + progressDialog.show() + if (url.toString().isBlank()) { + "抱歉,版本下载失败".show(requireContext()) + return + } + /** + * http://111.198.10.15:11304/static/apk/1.0.1.apk + * */ + val downloadPath = url!!.appendDownloadUrl() + //开始下载 + downloadPath.downloadFile(requireContext().createDownloadFileDir().toString(), + object : OnDownloadListener { + override fun onDownloadStart(totalBytes: Long) { + progressDialog.max = totalBytes.toInt() + } + + override fun onProgressChanged(currentBytes: Long) { + progressDialog.progress = currentBytes.toInt() + } + + override fun onDownloadEnd(file: File?) { + progressDialog.dismiss() + progressDialog.progress = 0 + //安装APK + installApk(file) + } + }) + } + + private fun installApk(apkPackage: File?) { + if (apkPackage == null) { + "文件异常,无法安装".show(requireContext()) + return + } + val intent = Intent(Intent.ACTION_VIEW) + val data: Uri + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { //判断版本大于等于7.0 + data = FileProvider.getUriForFile( + requireContext(), LocaleConstant.APP_AUTHORITY, apkPackage + ) + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) // 给目标应用一个临时授权 + } else { + data = Uri.fromFile(apkPackage) + } + intent.setDataAndType(data, "application/vnd.android.package-archive") + requireContext().startActivity(intent) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java new file mode 100644 index 0000000..467e33d --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java @@ -0,0 +1,43 @@ +package com.casic.smart.town.sanxi.model; + +/** + * 普通实体类,失败/成功数据结构一致 + */ +public class CommonResultModel { + private int code; + private String data; + private String message; + private boolean isSuccess; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return isSuccess; + } + + public void setSuccess(boolean success) { + isSuccess = success; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java new file mode 100644 index 0000000..9bdabef --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java @@ -0,0 +1,62 @@ +package com.casic.smart.town.sanxi.model; + +public class LoginResultModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private String kaptcha; + private String token; + + public String getKaptcha() { + return kaptcha; + } + + public void setKaptcha(String kaptcha) { + this.kaptcha = kaptcha; + } + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java new file mode 100644 index 0000000..09f8d59 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java @@ -0,0 +1,80 @@ +package com.casic.smart.town.sanxi.model; + +public class PublicKeyModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private boolean appKaptcha; + private boolean kaptcha; + private String publicKey; + private String sid; + + public boolean isAppKaptcha() { + return appKaptcha; + } + + public void setAppKaptcha(boolean appKaptcha) { + this.appKaptcha = appKaptcha; + } + + public boolean isKaptcha() { + return kaptcha; + } + + public void setKaptcha(boolean kaptcha) { + this.kaptcha = kaptcha; + } + + public String getPublicKey() { + return publicKey; + } + + public void setPublicKey(String publicKey) { + this.publicKey = publicKey; + } + + public String getSid() { + return sid; + } + + public void setSid(String sid) { + this.sid = sid; + } + } +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1df917d..8d4f33b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -6,6 +6,7 @@ + @@ -32,11 +33,14 @@ + + + + + + + diff --git a/app/src/main/assets/svg/mine.svg b/app/src/main/assets/svg/mine.svg new file mode 100644 index 0000000..3fbb0a3 --- /dev/null +++ b/app/src/main/assets/svg/mine.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt b/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt index d730417..b1972c2 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt @@ -1,8 +1,10 @@ package com.casic.smart.town.sanxi.extensions import com.casic.smart.town.sanxi.model.ErrorMessageModel +import com.casic.smart.town.sanxi.util.LocaleConstant import com.google.gson.Gson import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.utils.SaveKeyValues import org.json.JSONObject fun String.separateResponseCode(): Int { @@ -17,4 +19,15 @@ this, object : TypeToken() {}.type ) return errorModel.message.toString() +} + +/** + * 下载路径为 http://xx.com/static/ 拼接downloadUrl + * */ +fun String.appendDownloadUrl(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + LocaleConstant.DEFAULT_SERVER_CONFIG, LocaleConstant.DEFAULT_SERVER + ) as String + return "$defaultValue/static/${this}" } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt b/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt new file mode 100644 index 0000000..1809dc5 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt @@ -0,0 +1,288 @@ +package com.casic.smart.town.sanxi.fragment + +import android.app.ProgressDialog +import android.content.Intent +import android.net.Uri +import android.os.Build +import android.os.CountDownTimer +import androidx.core.content.FileProvider +import androidx.lifecycle.ViewModelProvider +import com.casic.smart.town.sanxi.BuildConfig +import com.casic.smart.town.sanxi.R +import com.casic.smart.town.sanxi.extensions.appendDownloadUrl +import com.casic.smart.town.sanxi.model.UserDetailModel +import com.casic.smart.town.sanxi.util.AuthenticationHelper +import com.casic.smart.town.sanxi.util.LoadingDialogHub +import com.casic.smart.town.sanxi.util.LocaleConstant +import com.casic.smart.town.sanxi.util.RSAUtils +import com.casic.smart.town.sanxi.view.AboutUsActivity +import com.casic.smart.town.sanxi.view.LoginActivity +import com.casic.smart.town.sanxi.vm.LoginViewModel +import com.casic.smart.town.sanxi.vm.UserViewModel +import com.casic.smart.town.sanxi.vm.VersionViewModel +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.base.KotlinBaseFragment +import com.pengxh.kt.lite.callback.OnDownloadListener +import com.pengxh.kt.lite.extensions.* +import com.pengxh.kt.lite.utils.PageNavigationManager +import com.pengxh.kt.lite.utils.SaveKeyValues +import com.pengxh.kt.lite.vm.LoadState +import com.pengxh.kt.lite.widget.dialog.AlertControlDialog +import com.pengxh.kt.lite.widget.dialog.ChangePasswordDialog +import kotlinx.android.synthetic.main.fragment_mine.* +import java.io.File +import java.nio.charset.StandardCharsets + +class MinePageFragment : KotlinBaseFragment() { + + private lateinit var userData: UserDetailModel.Data + private lateinit var userViewModel: UserViewModel + private lateinit var loginViewModel: LoginViewModel + private lateinit var versionViewModel: VersionViewModel + private lateinit var progressDialog: ProgressDialog + + override fun initLayoutView(): Int = R.layout.fragment_mine + + override fun setupTopBarLayout() { + + } + + override fun initData() { + userViewModel = ViewModelProvider(this)[UserViewModel::class.java] + loginViewModel = ViewModelProvider(this)[LoginViewModel::class.java] + versionViewModel = ViewModelProvider(this)[VersionViewModel::class.java] + + //初始化下载对话框 + progressDialog = ProgressDialog(requireContext()) + progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL) + progressDialog.setProgressDrawable(resources.getDrawable(R.drawable.download_progress)) + progressDialog.setCanceledOnTouchOutside(false) + progressDialog.setCancelable(false) + + /** + * 数据监听 + * */ + dataObserve() + } + + override fun observeRequestState() { + userViewModel.loadState.observe(this, { + when (it) { + is LoadState.Loading -> LoadingDialogHub.show(requireActivity(), "修改中,请稍后") + is LoadState.Success -> { + "修改成功,请重新登录".show(requireContext()) + LoadingDialogHub.dismiss() + AuthenticationHelper.removeToken() + requireContext().navigatePageTo() + PageNavigationManager.finishAllActivity() + } + else -> LoadingDialogHub.dismiss() + } + }) + + versionViewModel.loadState.observe(this, { + when (it) { + is LoadState.Loading -> LoadingDialogHub.show(requireActivity(), "检查版本中,请稍后") + else -> LoadingDialogHub.dismiss() + } + }) + } + + override fun initEvent() { + userImageView.setOnClickListener { + "尽情期待~".show(requireContext()) + } + + updateUserButton.setOnClickListener { + LoadingDialogHub.show(requireActivity(), "同步中,请稍后...") + userViewModel.obtainUserDetail() + } + + changePwdLayout.setOnClickListener { + ChangePasswordDialog.Builder() + .setContext(requireContext()) + .setOnDialogButtonClickListener(object : + ChangePasswordDialog.OnDialogButtonClickListener { + override fun onConfirmClick(oldPwd: String, newPwd: String) { + val publicKey = + RSAUtils.keyStrToPublicKey(AuthenticationHelper.publicKey)!! + val oldPassKey = RSAUtils.encryptDataByPublicKey( + oldPwd.toByteArray(StandardCharsets.UTF_8), + publicKey + ) + val newPassKey = RSAUtils.encryptDataByPublicKey( + newPwd.toByteArray(StandardCharsets.UTF_8), + publicKey + ) + userViewModel.changePassword(oldPassKey, newPassKey) + } + }).build().show() + } + + aboutUsLayout.setOnClickListener { + requireContext().navigatePageTo() + } + + updateVersionLayout.setOnClickListener { + versionViewModel.updateVersion() + } + + clearCacheLayout.setOnClickListener { + //删除缓存之后在设置缓存大小 + LoadingDialogHub.show(requireActivity(), "清理中,请稍后") + File(requireContext().cacheDir.path).deleteFile() + object : CountDownTimer(1500, 500) { + override fun onTick(millisUntilFinished: Long) { + + } + + override fun onFinish() { + LoadingDialogHub.dismiss() + cacheSizeView.text = collectApplicationCache().formatFileSize() + } + }.start() + } + + loginOutButton.setOnClickListener { + AlertControlDialog.Builder() + .setContext(requireContext()) + .setTitle("退出登录") + .setMessage("确定要退出吗?") + .setNegativeButton("取消") + .setPositiveButton("确定") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + loginViewModel.out() + } + + override fun onCancelClick() {} + }).build().show() + } + } + + private fun dataObserve() { + userViewModel.userDetailModel.observe(this, { + if (it.code == 200) { + LoadingDialogHub.dismiss() + "同步完成".show(requireContext()) + userData = it.data + updateUserInfo() + } + }) + + versionViewModel.versionResultModel.observe(this, { + if (BuildConfig.VERSION_NAME == it.version) { + "已是最新版本,无需更新".show(requireContext()) + } else { + AlertControlDialog.Builder() + .setContext(requireContext()) + .setTitle("提示") + .setMessage("有新版本,是否更新?") + .setNegativeButton("稍后再说") + .setPositiveButton("立即下载") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + downloadApk(it.downloadUrl) + } + + override fun onCancelClick() { + + } + }).build().show() + } + }) + + loginViewModel.outResultModel.observe(this, { + if (it.code == 200) { + AuthenticationHelper.removeToken() + requireContext().navigatePageTo() + PageNavigationManager.finishAllActivity() + } + }) + } + + private fun collectApplicationCache(): Long { + return File(requireContext().cacheDir.path).calculateSize() + } + + override fun onResume() { + val userDetailJson = SaveKeyValues.getValue(LocaleConstant.USER_DETAIL_MODEL, "") as String + if (userDetailJson.isNotBlank()) { + userData = Gson().fromJson( + userDetailJson, object : TypeToken() {}.type + ) + updateUserInfo() + } + //缓存 + cacheSizeView.text = collectApplicationCache().formatFileSize() + super.onResume() + } + + private fun updateUserInfo() { + //设置头像,圆形,暂时是默认的 +// val roundDrawable = +// BitmapFactory.decodeResource(requireContext().resources, R.mipmap.login_casic) +// .createRoundDrawable( +// requireContext(), +// 3f.dp2px(requireContext()), +// R.color.mainThemeColor.convertColor(requireContext()) +// ) +// userImageView.setImageDrawable(roundDrawable) + userNameView.text = userData.name + userPhoneView.text = String.format("电话:${userData.phone}") + userDeptView.text = String.format("部门:${userData.deptName}") + } + + private fun downloadApk(url: String?) { + progressDialog.setMessage("下载新版本中...") + progressDialog.show() + if (url.toString().isBlank()) { + "抱歉,版本下载失败".show(requireContext()) + return + } + /** + * http://111.198.10.15:11304/static/apk/1.0.1.apk + * */ + val downloadPath = url!!.appendDownloadUrl() + //开始下载 + downloadPath.downloadFile(requireContext().createDownloadFileDir().toString(), + object : OnDownloadListener { + override fun onDownloadStart(totalBytes: Long) { + progressDialog.max = totalBytes.toInt() + } + + override fun onProgressChanged(currentBytes: Long) { + progressDialog.progress = currentBytes.toInt() + } + + override fun onDownloadEnd(file: File?) { + progressDialog.dismiss() + progressDialog.progress = 0 + //安装APK + installApk(file) + } + }) + } + + private fun installApk(apkPackage: File?) { + if (apkPackage == null) { + "文件异常,无法安装".show(requireContext()) + return + } + val intent = Intent(Intent.ACTION_VIEW) + val data: Uri + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { //判断版本大于等于7.0 + data = FileProvider.getUriForFile( + requireContext(), LocaleConstant.APP_AUTHORITY, apkPackage + ) + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) // 给目标应用一个临时授权 + } else { + data = Uri.fromFile(apkPackage) + } + intent.setDataAndType(data, "application/vnd.android.package-archive") + requireContext().startActivity(intent) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java new file mode 100644 index 0000000..467e33d --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java @@ -0,0 +1,43 @@ +package com.casic.smart.town.sanxi.model; + +/** + * 普通实体类,失败/成功数据结构一致 + */ +public class CommonResultModel { + private int code; + private String data; + private String message; + private boolean isSuccess; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return isSuccess; + } + + public void setSuccess(boolean success) { + isSuccess = success; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java new file mode 100644 index 0000000..9bdabef --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java @@ -0,0 +1,62 @@ +package com.casic.smart.town.sanxi.model; + +public class LoginResultModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private String kaptcha; + private String token; + + public String getKaptcha() { + return kaptcha; + } + + public void setKaptcha(String kaptcha) { + this.kaptcha = kaptcha; + } + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java new file mode 100644 index 0000000..09f8d59 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java @@ -0,0 +1,80 @@ +package com.casic.smart.town.sanxi.model; + +public class PublicKeyModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private boolean appKaptcha; + private boolean kaptcha; + private String publicKey; + private String sid; + + public boolean isAppKaptcha() { + return appKaptcha; + } + + public void setAppKaptcha(boolean appKaptcha) { + this.appKaptcha = appKaptcha; + } + + public boolean isKaptcha() { + return kaptcha; + } + + public void setKaptcha(boolean kaptcha) { + this.kaptcha = kaptcha; + } + + public String getPublicKey() { + return publicKey; + } + + public void setPublicKey(String publicKey) { + this.publicKey = publicKey; + } + + public String getSid() { + return sid; + } + + public void setSid(String sid) { + this.sid = sid; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java new file mode 100644 index 0000000..db577fb --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java @@ -0,0 +1,227 @@ +package com.casic.smart.town.sanxi.model; + + +import java.util.List; + +public class UserDetailModel { + private int code; + private Data data; + private String message; + private boolean success; + + public void setCode(int code) { + this.code = code; + } + + public int getCode() { + return code; + } + + public void setData(Data data) { + this.data = data; + } + + public Data getData() { + return data; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getMessage() { + return message; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public boolean getSuccess() { + return success; + } + + public static class Data { + + private String account; + private String attr1; + private String avatar; + private String bizData; + private List dataScope; + private String deptId; + private String deptName; + private List devices; + private String id; + private String ipAddr; + private String name; + private String phone; + private List roleList; + private List roleNames; + private List roleTips; + private String scopeType; + private String sysData; + private String targetId; + private String targetName; + private String tenantId; + + public void setAccount(String account) { + this.account = account; + } + + public String getAccount() { + return account; + } + + public void setAttr1(String attr1) { + this.attr1 = attr1; + } + + public String getAttr1() { + return attr1; + } + + public void setAvatar(String avatar) { + this.avatar = avatar; + } + + public String getAvatar() { + return avatar; + } + + public void setBizData(String bizData) { + this.bizData = bizData; + } + + public String getBizData() { + return bizData; + } + + public void setDataScope(List dataScope) { + this.dataScope = dataScope; + } + + public List getDataScope() { + return dataScope; + } + + public void setDeptId(String deptId) { + this.deptId = deptId; + } + + public String getDeptId() { + return deptId; + } + + public void setDeptName(String deptName) { + this.deptName = deptName; + } + + public String getDeptName() { + return deptName; + } + + public void setDevices(List devices) { + this.devices = devices; + } + + public List getDevices() { + return devices; + } + + public void setId(String id) { + this.id = id; + } + + public String getId() { + return id; + } + + public void setIpAddr(String ipAddr) { + this.ipAddr = ipAddr; + } + + public String getIpAddr() { + return ipAddr; + } + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setPhone(String phone) { + this.phone = phone; + } + + public String getPhone() { + return phone; + } + + public void setRoleList(List roleList) { + this.roleList = roleList; + } + + public List getRoleList() { + return roleList; + } + + public void setRoleNames(List roleNames) { + this.roleNames = roleNames; + } + + public List getRoleNames() { + return roleNames; + } + + public void setRoleTips(List roleTips) { + this.roleTips = roleTips; + } + + public List getRoleTips() { + return roleTips; + } + + public void setScopeType(String scopeType) { + this.scopeType = scopeType; + } + + public String getScopeType() { + return scopeType; + } + + public void setSysData(String sysData) { + this.sysData = sysData; + } + + public String getSysData() { + return sysData; + } + + public void setTargetId(String targetId) { + this.targetId = targetId; + } + + public String getTargetId() { + return targetId; + } + + public void setTargetName(String targetName) { + this.targetName = targetName; + } + + public String getTargetName() { + return targetName; + } + + public void setTenantId(String tenantId) { + this.tenantId = tenantId; + } + + public String getTenantId() { + return tenantId; + } + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1df917d..8d4f33b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -6,6 +6,7 @@ + @@ -32,11 +33,14 @@ + + + + + + + diff --git a/app/src/main/assets/svg/mine.svg b/app/src/main/assets/svg/mine.svg new file mode 100644 index 0000000..3fbb0a3 --- /dev/null +++ b/app/src/main/assets/svg/mine.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt b/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt index d730417..b1972c2 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt @@ -1,8 +1,10 @@ package com.casic.smart.town.sanxi.extensions import com.casic.smart.town.sanxi.model.ErrorMessageModel +import com.casic.smart.town.sanxi.util.LocaleConstant import com.google.gson.Gson import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.utils.SaveKeyValues import org.json.JSONObject fun String.separateResponseCode(): Int { @@ -17,4 +19,15 @@ this, object : TypeToken() {}.type ) return errorModel.message.toString() +} + +/** + * 下载路径为 http://xx.com/static/ 拼接downloadUrl + * */ +fun String.appendDownloadUrl(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + LocaleConstant.DEFAULT_SERVER_CONFIG, LocaleConstant.DEFAULT_SERVER + ) as String + return "$defaultValue/static/${this}" } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt b/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt new file mode 100644 index 0000000..1809dc5 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt @@ -0,0 +1,288 @@ +package com.casic.smart.town.sanxi.fragment + +import android.app.ProgressDialog +import android.content.Intent +import android.net.Uri +import android.os.Build +import android.os.CountDownTimer +import androidx.core.content.FileProvider +import androidx.lifecycle.ViewModelProvider +import com.casic.smart.town.sanxi.BuildConfig +import com.casic.smart.town.sanxi.R +import com.casic.smart.town.sanxi.extensions.appendDownloadUrl +import com.casic.smart.town.sanxi.model.UserDetailModel +import com.casic.smart.town.sanxi.util.AuthenticationHelper +import com.casic.smart.town.sanxi.util.LoadingDialogHub +import com.casic.smart.town.sanxi.util.LocaleConstant +import com.casic.smart.town.sanxi.util.RSAUtils +import com.casic.smart.town.sanxi.view.AboutUsActivity +import com.casic.smart.town.sanxi.view.LoginActivity +import com.casic.smart.town.sanxi.vm.LoginViewModel +import com.casic.smart.town.sanxi.vm.UserViewModel +import com.casic.smart.town.sanxi.vm.VersionViewModel +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.base.KotlinBaseFragment +import com.pengxh.kt.lite.callback.OnDownloadListener +import com.pengxh.kt.lite.extensions.* +import com.pengxh.kt.lite.utils.PageNavigationManager +import com.pengxh.kt.lite.utils.SaveKeyValues +import com.pengxh.kt.lite.vm.LoadState +import com.pengxh.kt.lite.widget.dialog.AlertControlDialog +import com.pengxh.kt.lite.widget.dialog.ChangePasswordDialog +import kotlinx.android.synthetic.main.fragment_mine.* +import java.io.File +import java.nio.charset.StandardCharsets + +class MinePageFragment : KotlinBaseFragment() { + + private lateinit var userData: UserDetailModel.Data + private lateinit var userViewModel: UserViewModel + private lateinit var loginViewModel: LoginViewModel + private lateinit var versionViewModel: VersionViewModel + private lateinit var progressDialog: ProgressDialog + + override fun initLayoutView(): Int = R.layout.fragment_mine + + override fun setupTopBarLayout() { + + } + + override fun initData() { + userViewModel = ViewModelProvider(this)[UserViewModel::class.java] + loginViewModel = ViewModelProvider(this)[LoginViewModel::class.java] + versionViewModel = ViewModelProvider(this)[VersionViewModel::class.java] + + //初始化下载对话框 + progressDialog = ProgressDialog(requireContext()) + progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL) + progressDialog.setProgressDrawable(resources.getDrawable(R.drawable.download_progress)) + progressDialog.setCanceledOnTouchOutside(false) + progressDialog.setCancelable(false) + + /** + * 数据监听 + * */ + dataObserve() + } + + override fun observeRequestState() { + userViewModel.loadState.observe(this, { + when (it) { + is LoadState.Loading -> LoadingDialogHub.show(requireActivity(), "修改中,请稍后") + is LoadState.Success -> { + "修改成功,请重新登录".show(requireContext()) + LoadingDialogHub.dismiss() + AuthenticationHelper.removeToken() + requireContext().navigatePageTo() + PageNavigationManager.finishAllActivity() + } + else -> LoadingDialogHub.dismiss() + } + }) + + versionViewModel.loadState.observe(this, { + when (it) { + is LoadState.Loading -> LoadingDialogHub.show(requireActivity(), "检查版本中,请稍后") + else -> LoadingDialogHub.dismiss() + } + }) + } + + override fun initEvent() { + userImageView.setOnClickListener { + "尽情期待~".show(requireContext()) + } + + updateUserButton.setOnClickListener { + LoadingDialogHub.show(requireActivity(), "同步中,请稍后...") + userViewModel.obtainUserDetail() + } + + changePwdLayout.setOnClickListener { + ChangePasswordDialog.Builder() + .setContext(requireContext()) + .setOnDialogButtonClickListener(object : + ChangePasswordDialog.OnDialogButtonClickListener { + override fun onConfirmClick(oldPwd: String, newPwd: String) { + val publicKey = + RSAUtils.keyStrToPublicKey(AuthenticationHelper.publicKey)!! + val oldPassKey = RSAUtils.encryptDataByPublicKey( + oldPwd.toByteArray(StandardCharsets.UTF_8), + publicKey + ) + val newPassKey = RSAUtils.encryptDataByPublicKey( + newPwd.toByteArray(StandardCharsets.UTF_8), + publicKey + ) + userViewModel.changePassword(oldPassKey, newPassKey) + } + }).build().show() + } + + aboutUsLayout.setOnClickListener { + requireContext().navigatePageTo() + } + + updateVersionLayout.setOnClickListener { + versionViewModel.updateVersion() + } + + clearCacheLayout.setOnClickListener { + //删除缓存之后在设置缓存大小 + LoadingDialogHub.show(requireActivity(), "清理中,请稍后") + File(requireContext().cacheDir.path).deleteFile() + object : CountDownTimer(1500, 500) { + override fun onTick(millisUntilFinished: Long) { + + } + + override fun onFinish() { + LoadingDialogHub.dismiss() + cacheSizeView.text = collectApplicationCache().formatFileSize() + } + }.start() + } + + loginOutButton.setOnClickListener { + AlertControlDialog.Builder() + .setContext(requireContext()) + .setTitle("退出登录") + .setMessage("确定要退出吗?") + .setNegativeButton("取消") + .setPositiveButton("确定") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + loginViewModel.out() + } + + override fun onCancelClick() {} + }).build().show() + } + } + + private fun dataObserve() { + userViewModel.userDetailModel.observe(this, { + if (it.code == 200) { + LoadingDialogHub.dismiss() + "同步完成".show(requireContext()) + userData = it.data + updateUserInfo() + } + }) + + versionViewModel.versionResultModel.observe(this, { + if (BuildConfig.VERSION_NAME == it.version) { + "已是最新版本,无需更新".show(requireContext()) + } else { + AlertControlDialog.Builder() + .setContext(requireContext()) + .setTitle("提示") + .setMessage("有新版本,是否更新?") + .setNegativeButton("稍后再说") + .setPositiveButton("立即下载") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + downloadApk(it.downloadUrl) + } + + override fun onCancelClick() { + + } + }).build().show() + } + }) + + loginViewModel.outResultModel.observe(this, { + if (it.code == 200) { + AuthenticationHelper.removeToken() + requireContext().navigatePageTo() + PageNavigationManager.finishAllActivity() + } + }) + } + + private fun collectApplicationCache(): Long { + return File(requireContext().cacheDir.path).calculateSize() + } + + override fun onResume() { + val userDetailJson = SaveKeyValues.getValue(LocaleConstant.USER_DETAIL_MODEL, "") as String + if (userDetailJson.isNotBlank()) { + userData = Gson().fromJson( + userDetailJson, object : TypeToken() {}.type + ) + updateUserInfo() + } + //缓存 + cacheSizeView.text = collectApplicationCache().formatFileSize() + super.onResume() + } + + private fun updateUserInfo() { + //设置头像,圆形,暂时是默认的 +// val roundDrawable = +// BitmapFactory.decodeResource(requireContext().resources, R.mipmap.login_casic) +// .createRoundDrawable( +// requireContext(), +// 3f.dp2px(requireContext()), +// R.color.mainThemeColor.convertColor(requireContext()) +// ) +// userImageView.setImageDrawable(roundDrawable) + userNameView.text = userData.name + userPhoneView.text = String.format("电话:${userData.phone}") + userDeptView.text = String.format("部门:${userData.deptName}") + } + + private fun downloadApk(url: String?) { + progressDialog.setMessage("下载新版本中...") + progressDialog.show() + if (url.toString().isBlank()) { + "抱歉,版本下载失败".show(requireContext()) + return + } + /** + * http://111.198.10.15:11304/static/apk/1.0.1.apk + * */ + val downloadPath = url!!.appendDownloadUrl() + //开始下载 + downloadPath.downloadFile(requireContext().createDownloadFileDir().toString(), + object : OnDownloadListener { + override fun onDownloadStart(totalBytes: Long) { + progressDialog.max = totalBytes.toInt() + } + + override fun onProgressChanged(currentBytes: Long) { + progressDialog.progress = currentBytes.toInt() + } + + override fun onDownloadEnd(file: File?) { + progressDialog.dismiss() + progressDialog.progress = 0 + //安装APK + installApk(file) + } + }) + } + + private fun installApk(apkPackage: File?) { + if (apkPackage == null) { + "文件异常,无法安装".show(requireContext()) + return + } + val intent = Intent(Intent.ACTION_VIEW) + val data: Uri + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { //判断版本大于等于7.0 + data = FileProvider.getUriForFile( + requireContext(), LocaleConstant.APP_AUTHORITY, apkPackage + ) + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) // 给目标应用一个临时授权 + } else { + data = Uri.fromFile(apkPackage) + } + intent.setDataAndType(data, "application/vnd.android.package-archive") + requireContext().startActivity(intent) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java new file mode 100644 index 0000000..467e33d --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java @@ -0,0 +1,43 @@ +package com.casic.smart.town.sanxi.model; + +/** + * 普通实体类,失败/成功数据结构一致 + */ +public class CommonResultModel { + private int code; + private String data; + private String message; + private boolean isSuccess; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return isSuccess; + } + + public void setSuccess(boolean success) { + isSuccess = success; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java new file mode 100644 index 0000000..9bdabef --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java @@ -0,0 +1,62 @@ +package com.casic.smart.town.sanxi.model; + +public class LoginResultModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private String kaptcha; + private String token; + + public String getKaptcha() { + return kaptcha; + } + + public void setKaptcha(String kaptcha) { + this.kaptcha = kaptcha; + } + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java new file mode 100644 index 0000000..09f8d59 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java @@ -0,0 +1,80 @@ +package com.casic.smart.town.sanxi.model; + +public class PublicKeyModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private boolean appKaptcha; + private boolean kaptcha; + private String publicKey; + private String sid; + + public boolean isAppKaptcha() { + return appKaptcha; + } + + public void setAppKaptcha(boolean appKaptcha) { + this.appKaptcha = appKaptcha; + } + + public boolean isKaptcha() { + return kaptcha; + } + + public void setKaptcha(boolean kaptcha) { + this.kaptcha = kaptcha; + } + + public String getPublicKey() { + return publicKey; + } + + public void setPublicKey(String publicKey) { + this.publicKey = publicKey; + } + + public String getSid() { + return sid; + } + + public void setSid(String sid) { + this.sid = sid; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java new file mode 100644 index 0000000..db577fb --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java @@ -0,0 +1,227 @@ +package com.casic.smart.town.sanxi.model; + + +import java.util.List; + +public class UserDetailModel { + private int code; + private Data data; + private String message; + private boolean success; + + public void setCode(int code) { + this.code = code; + } + + public int getCode() { + return code; + } + + public void setData(Data data) { + this.data = data; + } + + public Data getData() { + return data; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getMessage() { + return message; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public boolean getSuccess() { + return success; + } + + public static class Data { + + private String account; + private String attr1; + private String avatar; + private String bizData; + private List dataScope; + private String deptId; + private String deptName; + private List devices; + private String id; + private String ipAddr; + private String name; + private String phone; + private List roleList; + private List roleNames; + private List roleTips; + private String scopeType; + private String sysData; + private String targetId; + private String targetName; + private String tenantId; + + public void setAccount(String account) { + this.account = account; + } + + public String getAccount() { + return account; + } + + public void setAttr1(String attr1) { + this.attr1 = attr1; + } + + public String getAttr1() { + return attr1; + } + + public void setAvatar(String avatar) { + this.avatar = avatar; + } + + public String getAvatar() { + return avatar; + } + + public void setBizData(String bizData) { + this.bizData = bizData; + } + + public String getBizData() { + return bizData; + } + + public void setDataScope(List dataScope) { + this.dataScope = dataScope; + } + + public List getDataScope() { + return dataScope; + } + + public void setDeptId(String deptId) { + this.deptId = deptId; + } + + public String getDeptId() { + return deptId; + } + + public void setDeptName(String deptName) { + this.deptName = deptName; + } + + public String getDeptName() { + return deptName; + } + + public void setDevices(List devices) { + this.devices = devices; + } + + public List getDevices() { + return devices; + } + + public void setId(String id) { + this.id = id; + } + + public String getId() { + return id; + } + + public void setIpAddr(String ipAddr) { + this.ipAddr = ipAddr; + } + + public String getIpAddr() { + return ipAddr; + } + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setPhone(String phone) { + this.phone = phone; + } + + public String getPhone() { + return phone; + } + + public void setRoleList(List roleList) { + this.roleList = roleList; + } + + public List getRoleList() { + return roleList; + } + + public void setRoleNames(List roleNames) { + this.roleNames = roleNames; + } + + public List getRoleNames() { + return roleNames; + } + + public void setRoleTips(List roleTips) { + this.roleTips = roleTips; + } + + public List getRoleTips() { + return roleTips; + } + + public void setScopeType(String scopeType) { + this.scopeType = scopeType; + } + + public String getScopeType() { + return scopeType; + } + + public void setSysData(String sysData) { + this.sysData = sysData; + } + + public String getSysData() { + return sysData; + } + + public void setTargetId(String targetId) { + this.targetId = targetId; + } + + public String getTargetId() { + return targetId; + } + + public void setTargetName(String targetName) { + this.targetName = targetName; + } + + public String getTargetName() { + return targetName; + } + + public void setTenantId(String tenantId) { + this.tenantId = tenantId; + } + + public String getTenantId() { + return tenantId; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/VersionResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/VersionResultModel.java new file mode 100644 index 0000000..79374e0 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/VersionResultModel.java @@ -0,0 +1,61 @@ +package com.casic.smart.town.sanxi.model; + +public class VersionResultModel { + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private String downloadUrl; + private String version; + + public String getDownloadUrl() { + return downloadUrl; + } + + public void setDownloadUrl(String downloadUrl) { + this.downloadUrl = downloadUrl; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + } +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1df917d..8d4f33b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -6,6 +6,7 @@ + @@ -32,11 +33,14 @@ + + + + + + + diff --git a/app/src/main/assets/svg/mine.svg b/app/src/main/assets/svg/mine.svg new file mode 100644 index 0000000..3fbb0a3 --- /dev/null +++ b/app/src/main/assets/svg/mine.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt b/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt index d730417..b1972c2 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt @@ -1,8 +1,10 @@ package com.casic.smart.town.sanxi.extensions import com.casic.smart.town.sanxi.model.ErrorMessageModel +import com.casic.smart.town.sanxi.util.LocaleConstant import com.google.gson.Gson import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.utils.SaveKeyValues import org.json.JSONObject fun String.separateResponseCode(): Int { @@ -17,4 +19,15 @@ this, object : TypeToken() {}.type ) return errorModel.message.toString() +} + +/** + * 下载路径为 http://xx.com/static/ 拼接downloadUrl + * */ +fun String.appendDownloadUrl(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + LocaleConstant.DEFAULT_SERVER_CONFIG, LocaleConstant.DEFAULT_SERVER + ) as String + return "$defaultValue/static/${this}" } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt b/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt new file mode 100644 index 0000000..1809dc5 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt @@ -0,0 +1,288 @@ +package com.casic.smart.town.sanxi.fragment + +import android.app.ProgressDialog +import android.content.Intent +import android.net.Uri +import android.os.Build +import android.os.CountDownTimer +import androidx.core.content.FileProvider +import androidx.lifecycle.ViewModelProvider +import com.casic.smart.town.sanxi.BuildConfig +import com.casic.smart.town.sanxi.R +import com.casic.smart.town.sanxi.extensions.appendDownloadUrl +import com.casic.smart.town.sanxi.model.UserDetailModel +import com.casic.smart.town.sanxi.util.AuthenticationHelper +import com.casic.smart.town.sanxi.util.LoadingDialogHub +import com.casic.smart.town.sanxi.util.LocaleConstant +import com.casic.smart.town.sanxi.util.RSAUtils +import com.casic.smart.town.sanxi.view.AboutUsActivity +import com.casic.smart.town.sanxi.view.LoginActivity +import com.casic.smart.town.sanxi.vm.LoginViewModel +import com.casic.smart.town.sanxi.vm.UserViewModel +import com.casic.smart.town.sanxi.vm.VersionViewModel +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.base.KotlinBaseFragment +import com.pengxh.kt.lite.callback.OnDownloadListener +import com.pengxh.kt.lite.extensions.* +import com.pengxh.kt.lite.utils.PageNavigationManager +import com.pengxh.kt.lite.utils.SaveKeyValues +import com.pengxh.kt.lite.vm.LoadState +import com.pengxh.kt.lite.widget.dialog.AlertControlDialog +import com.pengxh.kt.lite.widget.dialog.ChangePasswordDialog +import kotlinx.android.synthetic.main.fragment_mine.* +import java.io.File +import java.nio.charset.StandardCharsets + +class MinePageFragment : KotlinBaseFragment() { + + private lateinit var userData: UserDetailModel.Data + private lateinit var userViewModel: UserViewModel + private lateinit var loginViewModel: LoginViewModel + private lateinit var versionViewModel: VersionViewModel + private lateinit var progressDialog: ProgressDialog + + override fun initLayoutView(): Int = R.layout.fragment_mine + + override fun setupTopBarLayout() { + + } + + override fun initData() { + userViewModel = ViewModelProvider(this)[UserViewModel::class.java] + loginViewModel = ViewModelProvider(this)[LoginViewModel::class.java] + versionViewModel = ViewModelProvider(this)[VersionViewModel::class.java] + + //初始化下载对话框 + progressDialog = ProgressDialog(requireContext()) + progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL) + progressDialog.setProgressDrawable(resources.getDrawable(R.drawable.download_progress)) + progressDialog.setCanceledOnTouchOutside(false) + progressDialog.setCancelable(false) + + /** + * 数据监听 + * */ + dataObserve() + } + + override fun observeRequestState() { + userViewModel.loadState.observe(this, { + when (it) { + is LoadState.Loading -> LoadingDialogHub.show(requireActivity(), "修改中,请稍后") + is LoadState.Success -> { + "修改成功,请重新登录".show(requireContext()) + LoadingDialogHub.dismiss() + AuthenticationHelper.removeToken() + requireContext().navigatePageTo() + PageNavigationManager.finishAllActivity() + } + else -> LoadingDialogHub.dismiss() + } + }) + + versionViewModel.loadState.observe(this, { + when (it) { + is LoadState.Loading -> LoadingDialogHub.show(requireActivity(), "检查版本中,请稍后") + else -> LoadingDialogHub.dismiss() + } + }) + } + + override fun initEvent() { + userImageView.setOnClickListener { + "尽情期待~".show(requireContext()) + } + + updateUserButton.setOnClickListener { + LoadingDialogHub.show(requireActivity(), "同步中,请稍后...") + userViewModel.obtainUserDetail() + } + + changePwdLayout.setOnClickListener { + ChangePasswordDialog.Builder() + .setContext(requireContext()) + .setOnDialogButtonClickListener(object : + ChangePasswordDialog.OnDialogButtonClickListener { + override fun onConfirmClick(oldPwd: String, newPwd: String) { + val publicKey = + RSAUtils.keyStrToPublicKey(AuthenticationHelper.publicKey)!! + val oldPassKey = RSAUtils.encryptDataByPublicKey( + oldPwd.toByteArray(StandardCharsets.UTF_8), + publicKey + ) + val newPassKey = RSAUtils.encryptDataByPublicKey( + newPwd.toByteArray(StandardCharsets.UTF_8), + publicKey + ) + userViewModel.changePassword(oldPassKey, newPassKey) + } + }).build().show() + } + + aboutUsLayout.setOnClickListener { + requireContext().navigatePageTo() + } + + updateVersionLayout.setOnClickListener { + versionViewModel.updateVersion() + } + + clearCacheLayout.setOnClickListener { + //删除缓存之后在设置缓存大小 + LoadingDialogHub.show(requireActivity(), "清理中,请稍后") + File(requireContext().cacheDir.path).deleteFile() + object : CountDownTimer(1500, 500) { + override fun onTick(millisUntilFinished: Long) { + + } + + override fun onFinish() { + LoadingDialogHub.dismiss() + cacheSizeView.text = collectApplicationCache().formatFileSize() + } + }.start() + } + + loginOutButton.setOnClickListener { + AlertControlDialog.Builder() + .setContext(requireContext()) + .setTitle("退出登录") + .setMessage("确定要退出吗?") + .setNegativeButton("取消") + .setPositiveButton("确定") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + loginViewModel.out() + } + + override fun onCancelClick() {} + }).build().show() + } + } + + private fun dataObserve() { + userViewModel.userDetailModel.observe(this, { + if (it.code == 200) { + LoadingDialogHub.dismiss() + "同步完成".show(requireContext()) + userData = it.data + updateUserInfo() + } + }) + + versionViewModel.versionResultModel.observe(this, { + if (BuildConfig.VERSION_NAME == it.version) { + "已是最新版本,无需更新".show(requireContext()) + } else { + AlertControlDialog.Builder() + .setContext(requireContext()) + .setTitle("提示") + .setMessage("有新版本,是否更新?") + .setNegativeButton("稍后再说") + .setPositiveButton("立即下载") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + downloadApk(it.downloadUrl) + } + + override fun onCancelClick() { + + } + }).build().show() + } + }) + + loginViewModel.outResultModel.observe(this, { + if (it.code == 200) { + AuthenticationHelper.removeToken() + requireContext().navigatePageTo() + PageNavigationManager.finishAllActivity() + } + }) + } + + private fun collectApplicationCache(): Long { + return File(requireContext().cacheDir.path).calculateSize() + } + + override fun onResume() { + val userDetailJson = SaveKeyValues.getValue(LocaleConstant.USER_DETAIL_MODEL, "") as String + if (userDetailJson.isNotBlank()) { + userData = Gson().fromJson( + userDetailJson, object : TypeToken() {}.type + ) + updateUserInfo() + } + //缓存 + cacheSizeView.text = collectApplicationCache().formatFileSize() + super.onResume() + } + + private fun updateUserInfo() { + //设置头像,圆形,暂时是默认的 +// val roundDrawable = +// BitmapFactory.decodeResource(requireContext().resources, R.mipmap.login_casic) +// .createRoundDrawable( +// requireContext(), +// 3f.dp2px(requireContext()), +// R.color.mainThemeColor.convertColor(requireContext()) +// ) +// userImageView.setImageDrawable(roundDrawable) + userNameView.text = userData.name + userPhoneView.text = String.format("电话:${userData.phone}") + userDeptView.text = String.format("部门:${userData.deptName}") + } + + private fun downloadApk(url: String?) { + progressDialog.setMessage("下载新版本中...") + progressDialog.show() + if (url.toString().isBlank()) { + "抱歉,版本下载失败".show(requireContext()) + return + } + /** + * http://111.198.10.15:11304/static/apk/1.0.1.apk + * */ + val downloadPath = url!!.appendDownloadUrl() + //开始下载 + downloadPath.downloadFile(requireContext().createDownloadFileDir().toString(), + object : OnDownloadListener { + override fun onDownloadStart(totalBytes: Long) { + progressDialog.max = totalBytes.toInt() + } + + override fun onProgressChanged(currentBytes: Long) { + progressDialog.progress = currentBytes.toInt() + } + + override fun onDownloadEnd(file: File?) { + progressDialog.dismiss() + progressDialog.progress = 0 + //安装APK + installApk(file) + } + }) + } + + private fun installApk(apkPackage: File?) { + if (apkPackage == null) { + "文件异常,无法安装".show(requireContext()) + return + } + val intent = Intent(Intent.ACTION_VIEW) + val data: Uri + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { //判断版本大于等于7.0 + data = FileProvider.getUriForFile( + requireContext(), LocaleConstant.APP_AUTHORITY, apkPackage + ) + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) // 给目标应用一个临时授权 + } else { + data = Uri.fromFile(apkPackage) + } + intent.setDataAndType(data, "application/vnd.android.package-archive") + requireContext().startActivity(intent) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java new file mode 100644 index 0000000..467e33d --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java @@ -0,0 +1,43 @@ +package com.casic.smart.town.sanxi.model; + +/** + * 普通实体类,失败/成功数据结构一致 + */ +public class CommonResultModel { + private int code; + private String data; + private String message; + private boolean isSuccess; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return isSuccess; + } + + public void setSuccess(boolean success) { + isSuccess = success; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java new file mode 100644 index 0000000..9bdabef --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java @@ -0,0 +1,62 @@ +package com.casic.smart.town.sanxi.model; + +public class LoginResultModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private String kaptcha; + private String token; + + public String getKaptcha() { + return kaptcha; + } + + public void setKaptcha(String kaptcha) { + this.kaptcha = kaptcha; + } + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java new file mode 100644 index 0000000..09f8d59 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java @@ -0,0 +1,80 @@ +package com.casic.smart.town.sanxi.model; + +public class PublicKeyModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private boolean appKaptcha; + private boolean kaptcha; + private String publicKey; + private String sid; + + public boolean isAppKaptcha() { + return appKaptcha; + } + + public void setAppKaptcha(boolean appKaptcha) { + this.appKaptcha = appKaptcha; + } + + public boolean isKaptcha() { + return kaptcha; + } + + public void setKaptcha(boolean kaptcha) { + this.kaptcha = kaptcha; + } + + public String getPublicKey() { + return publicKey; + } + + public void setPublicKey(String publicKey) { + this.publicKey = publicKey; + } + + public String getSid() { + return sid; + } + + public void setSid(String sid) { + this.sid = sid; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java new file mode 100644 index 0000000..db577fb --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java @@ -0,0 +1,227 @@ +package com.casic.smart.town.sanxi.model; + + +import java.util.List; + +public class UserDetailModel { + private int code; + private Data data; + private String message; + private boolean success; + + public void setCode(int code) { + this.code = code; + } + + public int getCode() { + return code; + } + + public void setData(Data data) { + this.data = data; + } + + public Data getData() { + return data; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getMessage() { + return message; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public boolean getSuccess() { + return success; + } + + public static class Data { + + private String account; + private String attr1; + private String avatar; + private String bizData; + private List dataScope; + private String deptId; + private String deptName; + private List devices; + private String id; + private String ipAddr; + private String name; + private String phone; + private List roleList; + private List roleNames; + private List roleTips; + private String scopeType; + private String sysData; + private String targetId; + private String targetName; + private String tenantId; + + public void setAccount(String account) { + this.account = account; + } + + public String getAccount() { + return account; + } + + public void setAttr1(String attr1) { + this.attr1 = attr1; + } + + public String getAttr1() { + return attr1; + } + + public void setAvatar(String avatar) { + this.avatar = avatar; + } + + public String getAvatar() { + return avatar; + } + + public void setBizData(String bizData) { + this.bizData = bizData; + } + + public String getBizData() { + return bizData; + } + + public void setDataScope(List dataScope) { + this.dataScope = dataScope; + } + + public List getDataScope() { + return dataScope; + } + + public void setDeptId(String deptId) { + this.deptId = deptId; + } + + public String getDeptId() { + return deptId; + } + + public void setDeptName(String deptName) { + this.deptName = deptName; + } + + public String getDeptName() { + return deptName; + } + + public void setDevices(List devices) { + this.devices = devices; + } + + public List getDevices() { + return devices; + } + + public void setId(String id) { + this.id = id; + } + + public String getId() { + return id; + } + + public void setIpAddr(String ipAddr) { + this.ipAddr = ipAddr; + } + + public String getIpAddr() { + return ipAddr; + } + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setPhone(String phone) { + this.phone = phone; + } + + public String getPhone() { + return phone; + } + + public void setRoleList(List roleList) { + this.roleList = roleList; + } + + public List getRoleList() { + return roleList; + } + + public void setRoleNames(List roleNames) { + this.roleNames = roleNames; + } + + public List getRoleNames() { + return roleNames; + } + + public void setRoleTips(List roleTips) { + this.roleTips = roleTips; + } + + public List getRoleTips() { + return roleTips; + } + + public void setScopeType(String scopeType) { + this.scopeType = scopeType; + } + + public String getScopeType() { + return scopeType; + } + + public void setSysData(String sysData) { + this.sysData = sysData; + } + + public String getSysData() { + return sysData; + } + + public void setTargetId(String targetId) { + this.targetId = targetId; + } + + public String getTargetId() { + return targetId; + } + + public void setTargetName(String targetName) { + this.targetName = targetName; + } + + public String getTargetName() { + return targetName; + } + + public void setTenantId(String tenantId) { + this.tenantId = tenantId; + } + + public String getTenantId() { + return tenantId; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/VersionResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/VersionResultModel.java new file mode 100644 index 0000000..79374e0 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/VersionResultModel.java @@ -0,0 +1,61 @@ +package com.casic.smart.town.sanxi.model; + +public class VersionResultModel { + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private String downloadUrl; + private String version; + + public String getDownloadUrl() { + return downloadUrl; + } + + public void setDownloadUrl(String downloadUrl) { + this.downloadUrl = downloadUrl; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/AuthenticationHelper.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/AuthenticationHelper.kt new file mode 100644 index 0000000..f498e4e --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/AuthenticationHelper.kt @@ -0,0 +1,24 @@ +package com.casic.smart.town.sanxi.util + +import com.pengxh.kt.lite.utils.SaveKeyValues + +object AuthenticationHelper { + + fun savePublicKey(key: String) { + SaveKeyValues.putValue("keyString", key) + } + + val publicKey: String? + get() = SaveKeyValues.getValue("keyString", "") as String? + + fun saveToken(token: String?) { + SaveKeyValues.putValue("token", token!!) + } + + val token: String? + get() = SaveKeyValues.getValue("token", "") as String? + + fun removeToken() { + SaveKeyValues.removeKey("token") + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1df917d..8d4f33b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -6,6 +6,7 @@ + @@ -32,11 +33,14 @@ + + + + + + + diff --git a/app/src/main/assets/svg/mine.svg b/app/src/main/assets/svg/mine.svg new file mode 100644 index 0000000..3fbb0a3 --- /dev/null +++ b/app/src/main/assets/svg/mine.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt b/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt index d730417..b1972c2 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt @@ -1,8 +1,10 @@ package com.casic.smart.town.sanxi.extensions import com.casic.smart.town.sanxi.model.ErrorMessageModel +import com.casic.smart.town.sanxi.util.LocaleConstant import com.google.gson.Gson import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.utils.SaveKeyValues import org.json.JSONObject fun String.separateResponseCode(): Int { @@ -17,4 +19,15 @@ this, object : TypeToken() {}.type ) return errorModel.message.toString() +} + +/** + * 下载路径为 http://xx.com/static/ 拼接downloadUrl + * */ +fun String.appendDownloadUrl(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + LocaleConstant.DEFAULT_SERVER_CONFIG, LocaleConstant.DEFAULT_SERVER + ) as String + return "$defaultValue/static/${this}" } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt b/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt new file mode 100644 index 0000000..1809dc5 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt @@ -0,0 +1,288 @@ +package com.casic.smart.town.sanxi.fragment + +import android.app.ProgressDialog +import android.content.Intent +import android.net.Uri +import android.os.Build +import android.os.CountDownTimer +import androidx.core.content.FileProvider +import androidx.lifecycle.ViewModelProvider +import com.casic.smart.town.sanxi.BuildConfig +import com.casic.smart.town.sanxi.R +import com.casic.smart.town.sanxi.extensions.appendDownloadUrl +import com.casic.smart.town.sanxi.model.UserDetailModel +import com.casic.smart.town.sanxi.util.AuthenticationHelper +import com.casic.smart.town.sanxi.util.LoadingDialogHub +import com.casic.smart.town.sanxi.util.LocaleConstant +import com.casic.smart.town.sanxi.util.RSAUtils +import com.casic.smart.town.sanxi.view.AboutUsActivity +import com.casic.smart.town.sanxi.view.LoginActivity +import com.casic.smart.town.sanxi.vm.LoginViewModel +import com.casic.smart.town.sanxi.vm.UserViewModel +import com.casic.smart.town.sanxi.vm.VersionViewModel +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.base.KotlinBaseFragment +import com.pengxh.kt.lite.callback.OnDownloadListener +import com.pengxh.kt.lite.extensions.* +import com.pengxh.kt.lite.utils.PageNavigationManager +import com.pengxh.kt.lite.utils.SaveKeyValues +import com.pengxh.kt.lite.vm.LoadState +import com.pengxh.kt.lite.widget.dialog.AlertControlDialog +import com.pengxh.kt.lite.widget.dialog.ChangePasswordDialog +import kotlinx.android.synthetic.main.fragment_mine.* +import java.io.File +import java.nio.charset.StandardCharsets + +class MinePageFragment : KotlinBaseFragment() { + + private lateinit var userData: UserDetailModel.Data + private lateinit var userViewModel: UserViewModel + private lateinit var loginViewModel: LoginViewModel + private lateinit var versionViewModel: VersionViewModel + private lateinit var progressDialog: ProgressDialog + + override fun initLayoutView(): Int = R.layout.fragment_mine + + override fun setupTopBarLayout() { + + } + + override fun initData() { + userViewModel = ViewModelProvider(this)[UserViewModel::class.java] + loginViewModel = ViewModelProvider(this)[LoginViewModel::class.java] + versionViewModel = ViewModelProvider(this)[VersionViewModel::class.java] + + //初始化下载对话框 + progressDialog = ProgressDialog(requireContext()) + progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL) + progressDialog.setProgressDrawable(resources.getDrawable(R.drawable.download_progress)) + progressDialog.setCanceledOnTouchOutside(false) + progressDialog.setCancelable(false) + + /** + * 数据监听 + * */ + dataObserve() + } + + override fun observeRequestState() { + userViewModel.loadState.observe(this, { + when (it) { + is LoadState.Loading -> LoadingDialogHub.show(requireActivity(), "修改中,请稍后") + is LoadState.Success -> { + "修改成功,请重新登录".show(requireContext()) + LoadingDialogHub.dismiss() + AuthenticationHelper.removeToken() + requireContext().navigatePageTo() + PageNavigationManager.finishAllActivity() + } + else -> LoadingDialogHub.dismiss() + } + }) + + versionViewModel.loadState.observe(this, { + when (it) { + is LoadState.Loading -> LoadingDialogHub.show(requireActivity(), "检查版本中,请稍后") + else -> LoadingDialogHub.dismiss() + } + }) + } + + override fun initEvent() { + userImageView.setOnClickListener { + "尽情期待~".show(requireContext()) + } + + updateUserButton.setOnClickListener { + LoadingDialogHub.show(requireActivity(), "同步中,请稍后...") + userViewModel.obtainUserDetail() + } + + changePwdLayout.setOnClickListener { + ChangePasswordDialog.Builder() + .setContext(requireContext()) + .setOnDialogButtonClickListener(object : + ChangePasswordDialog.OnDialogButtonClickListener { + override fun onConfirmClick(oldPwd: String, newPwd: String) { + val publicKey = + RSAUtils.keyStrToPublicKey(AuthenticationHelper.publicKey)!! + val oldPassKey = RSAUtils.encryptDataByPublicKey( + oldPwd.toByteArray(StandardCharsets.UTF_8), + publicKey + ) + val newPassKey = RSAUtils.encryptDataByPublicKey( + newPwd.toByteArray(StandardCharsets.UTF_8), + publicKey + ) + userViewModel.changePassword(oldPassKey, newPassKey) + } + }).build().show() + } + + aboutUsLayout.setOnClickListener { + requireContext().navigatePageTo() + } + + updateVersionLayout.setOnClickListener { + versionViewModel.updateVersion() + } + + clearCacheLayout.setOnClickListener { + //删除缓存之后在设置缓存大小 + LoadingDialogHub.show(requireActivity(), "清理中,请稍后") + File(requireContext().cacheDir.path).deleteFile() + object : CountDownTimer(1500, 500) { + override fun onTick(millisUntilFinished: Long) { + + } + + override fun onFinish() { + LoadingDialogHub.dismiss() + cacheSizeView.text = collectApplicationCache().formatFileSize() + } + }.start() + } + + loginOutButton.setOnClickListener { + AlertControlDialog.Builder() + .setContext(requireContext()) + .setTitle("退出登录") + .setMessage("确定要退出吗?") + .setNegativeButton("取消") + .setPositiveButton("确定") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + loginViewModel.out() + } + + override fun onCancelClick() {} + }).build().show() + } + } + + private fun dataObserve() { + userViewModel.userDetailModel.observe(this, { + if (it.code == 200) { + LoadingDialogHub.dismiss() + "同步完成".show(requireContext()) + userData = it.data + updateUserInfo() + } + }) + + versionViewModel.versionResultModel.observe(this, { + if (BuildConfig.VERSION_NAME == it.version) { + "已是最新版本,无需更新".show(requireContext()) + } else { + AlertControlDialog.Builder() + .setContext(requireContext()) + .setTitle("提示") + .setMessage("有新版本,是否更新?") + .setNegativeButton("稍后再说") + .setPositiveButton("立即下载") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + downloadApk(it.downloadUrl) + } + + override fun onCancelClick() { + + } + }).build().show() + } + }) + + loginViewModel.outResultModel.observe(this, { + if (it.code == 200) { + AuthenticationHelper.removeToken() + requireContext().navigatePageTo() + PageNavigationManager.finishAllActivity() + } + }) + } + + private fun collectApplicationCache(): Long { + return File(requireContext().cacheDir.path).calculateSize() + } + + override fun onResume() { + val userDetailJson = SaveKeyValues.getValue(LocaleConstant.USER_DETAIL_MODEL, "") as String + if (userDetailJson.isNotBlank()) { + userData = Gson().fromJson( + userDetailJson, object : TypeToken() {}.type + ) + updateUserInfo() + } + //缓存 + cacheSizeView.text = collectApplicationCache().formatFileSize() + super.onResume() + } + + private fun updateUserInfo() { + //设置头像,圆形,暂时是默认的 +// val roundDrawable = +// BitmapFactory.decodeResource(requireContext().resources, R.mipmap.login_casic) +// .createRoundDrawable( +// requireContext(), +// 3f.dp2px(requireContext()), +// R.color.mainThemeColor.convertColor(requireContext()) +// ) +// userImageView.setImageDrawable(roundDrawable) + userNameView.text = userData.name + userPhoneView.text = String.format("电话:${userData.phone}") + userDeptView.text = String.format("部门:${userData.deptName}") + } + + private fun downloadApk(url: String?) { + progressDialog.setMessage("下载新版本中...") + progressDialog.show() + if (url.toString().isBlank()) { + "抱歉,版本下载失败".show(requireContext()) + return + } + /** + * http://111.198.10.15:11304/static/apk/1.0.1.apk + * */ + val downloadPath = url!!.appendDownloadUrl() + //开始下载 + downloadPath.downloadFile(requireContext().createDownloadFileDir().toString(), + object : OnDownloadListener { + override fun onDownloadStart(totalBytes: Long) { + progressDialog.max = totalBytes.toInt() + } + + override fun onProgressChanged(currentBytes: Long) { + progressDialog.progress = currentBytes.toInt() + } + + override fun onDownloadEnd(file: File?) { + progressDialog.dismiss() + progressDialog.progress = 0 + //安装APK + installApk(file) + } + }) + } + + private fun installApk(apkPackage: File?) { + if (apkPackage == null) { + "文件异常,无法安装".show(requireContext()) + return + } + val intent = Intent(Intent.ACTION_VIEW) + val data: Uri + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { //判断版本大于等于7.0 + data = FileProvider.getUriForFile( + requireContext(), LocaleConstant.APP_AUTHORITY, apkPackage + ) + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) // 给目标应用一个临时授权 + } else { + data = Uri.fromFile(apkPackage) + } + intent.setDataAndType(data, "application/vnd.android.package-archive") + requireContext().startActivity(intent) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java new file mode 100644 index 0000000..467e33d --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java @@ -0,0 +1,43 @@ +package com.casic.smart.town.sanxi.model; + +/** + * 普通实体类,失败/成功数据结构一致 + */ +public class CommonResultModel { + private int code; + private String data; + private String message; + private boolean isSuccess; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return isSuccess; + } + + public void setSuccess(boolean success) { + isSuccess = success; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java new file mode 100644 index 0000000..9bdabef --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java @@ -0,0 +1,62 @@ +package com.casic.smart.town.sanxi.model; + +public class LoginResultModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private String kaptcha; + private String token; + + public String getKaptcha() { + return kaptcha; + } + + public void setKaptcha(String kaptcha) { + this.kaptcha = kaptcha; + } + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java new file mode 100644 index 0000000..09f8d59 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java @@ -0,0 +1,80 @@ +package com.casic.smart.town.sanxi.model; + +public class PublicKeyModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private boolean appKaptcha; + private boolean kaptcha; + private String publicKey; + private String sid; + + public boolean isAppKaptcha() { + return appKaptcha; + } + + public void setAppKaptcha(boolean appKaptcha) { + this.appKaptcha = appKaptcha; + } + + public boolean isKaptcha() { + return kaptcha; + } + + public void setKaptcha(boolean kaptcha) { + this.kaptcha = kaptcha; + } + + public String getPublicKey() { + return publicKey; + } + + public void setPublicKey(String publicKey) { + this.publicKey = publicKey; + } + + public String getSid() { + return sid; + } + + public void setSid(String sid) { + this.sid = sid; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java new file mode 100644 index 0000000..db577fb --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java @@ -0,0 +1,227 @@ +package com.casic.smart.town.sanxi.model; + + +import java.util.List; + +public class UserDetailModel { + private int code; + private Data data; + private String message; + private boolean success; + + public void setCode(int code) { + this.code = code; + } + + public int getCode() { + return code; + } + + public void setData(Data data) { + this.data = data; + } + + public Data getData() { + return data; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getMessage() { + return message; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public boolean getSuccess() { + return success; + } + + public static class Data { + + private String account; + private String attr1; + private String avatar; + private String bizData; + private List dataScope; + private String deptId; + private String deptName; + private List devices; + private String id; + private String ipAddr; + private String name; + private String phone; + private List roleList; + private List roleNames; + private List roleTips; + private String scopeType; + private String sysData; + private String targetId; + private String targetName; + private String tenantId; + + public void setAccount(String account) { + this.account = account; + } + + public String getAccount() { + return account; + } + + public void setAttr1(String attr1) { + this.attr1 = attr1; + } + + public String getAttr1() { + return attr1; + } + + public void setAvatar(String avatar) { + this.avatar = avatar; + } + + public String getAvatar() { + return avatar; + } + + public void setBizData(String bizData) { + this.bizData = bizData; + } + + public String getBizData() { + return bizData; + } + + public void setDataScope(List dataScope) { + this.dataScope = dataScope; + } + + public List getDataScope() { + return dataScope; + } + + public void setDeptId(String deptId) { + this.deptId = deptId; + } + + public String getDeptId() { + return deptId; + } + + public void setDeptName(String deptName) { + this.deptName = deptName; + } + + public String getDeptName() { + return deptName; + } + + public void setDevices(List devices) { + this.devices = devices; + } + + public List getDevices() { + return devices; + } + + public void setId(String id) { + this.id = id; + } + + public String getId() { + return id; + } + + public void setIpAddr(String ipAddr) { + this.ipAddr = ipAddr; + } + + public String getIpAddr() { + return ipAddr; + } + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setPhone(String phone) { + this.phone = phone; + } + + public String getPhone() { + return phone; + } + + public void setRoleList(List roleList) { + this.roleList = roleList; + } + + public List getRoleList() { + return roleList; + } + + public void setRoleNames(List roleNames) { + this.roleNames = roleNames; + } + + public List getRoleNames() { + return roleNames; + } + + public void setRoleTips(List roleTips) { + this.roleTips = roleTips; + } + + public List getRoleTips() { + return roleTips; + } + + public void setScopeType(String scopeType) { + this.scopeType = scopeType; + } + + public String getScopeType() { + return scopeType; + } + + public void setSysData(String sysData) { + this.sysData = sysData; + } + + public String getSysData() { + return sysData; + } + + public void setTargetId(String targetId) { + this.targetId = targetId; + } + + public String getTargetId() { + return targetId; + } + + public void setTargetName(String targetName) { + this.targetName = targetName; + } + + public String getTargetName() { + return targetName; + } + + public void setTenantId(String tenantId) { + this.tenantId = tenantId; + } + + public String getTenantId() { + return tenantId; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/VersionResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/VersionResultModel.java new file mode 100644 index 0000000..79374e0 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/VersionResultModel.java @@ -0,0 +1,61 @@ +package com.casic.smart.town.sanxi.model; + +public class VersionResultModel { + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private String downloadUrl; + private String version; + + public String getDownloadUrl() { + return downloadUrl; + } + + public void setDownloadUrl(String downloadUrl) { + this.downloadUrl = downloadUrl; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/AuthenticationHelper.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/AuthenticationHelper.kt new file mode 100644 index 0000000..f498e4e --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/AuthenticationHelper.kt @@ -0,0 +1,24 @@ +package com.casic.smart.town.sanxi.util + +import com.pengxh.kt.lite.utils.SaveKeyValues + +object AuthenticationHelper { + + fun savePublicKey(key: String) { + SaveKeyValues.putValue("keyString", key) + } + + val publicKey: String? + get() = SaveKeyValues.getValue("keyString", "") as String? + + fun saveToken(token: String?) { + SaveKeyValues.putValue("token", token!!) + } + + val token: String? + get() = SaveKeyValues.getValue("token", "") as String? + + fun removeToken() { + SaveKeyValues.removeKey("token") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt index ab1a36d..99f0d19 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt @@ -14,5 +14,9 @@ const val PAGE_LIMIT = 20 const val DEFAULT_SERVER_CONFIG = "defaultServerConfig" - const val DEFAULT_SERVER = "http://111.198.10.15:11308" + const val DEFAULT_SERVER = "http://36.133.189.112:8082" + const val APP_AUTHORITY = "com.casic.smart.town.sanxi.fileprovider" + const val ACCOUNT = "account" + const val PASSWORD = "password" + const val USER_DETAIL_MODEL = "userDetailModel" } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1df917d..8d4f33b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -6,6 +6,7 @@ + @@ -32,11 +33,14 @@ + + + + + + + diff --git a/app/src/main/assets/svg/mine.svg b/app/src/main/assets/svg/mine.svg new file mode 100644 index 0000000..3fbb0a3 --- /dev/null +++ b/app/src/main/assets/svg/mine.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt b/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt index d730417..b1972c2 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt @@ -1,8 +1,10 @@ package com.casic.smart.town.sanxi.extensions import com.casic.smart.town.sanxi.model.ErrorMessageModel +import com.casic.smart.town.sanxi.util.LocaleConstant import com.google.gson.Gson import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.utils.SaveKeyValues import org.json.JSONObject fun String.separateResponseCode(): Int { @@ -17,4 +19,15 @@ this, object : TypeToken() {}.type ) return errorModel.message.toString() +} + +/** + * 下载路径为 http://xx.com/static/ 拼接downloadUrl + * */ +fun String.appendDownloadUrl(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + LocaleConstant.DEFAULT_SERVER_CONFIG, LocaleConstant.DEFAULT_SERVER + ) as String + return "$defaultValue/static/${this}" } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt b/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt new file mode 100644 index 0000000..1809dc5 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt @@ -0,0 +1,288 @@ +package com.casic.smart.town.sanxi.fragment + +import android.app.ProgressDialog +import android.content.Intent +import android.net.Uri +import android.os.Build +import android.os.CountDownTimer +import androidx.core.content.FileProvider +import androidx.lifecycle.ViewModelProvider +import com.casic.smart.town.sanxi.BuildConfig +import com.casic.smart.town.sanxi.R +import com.casic.smart.town.sanxi.extensions.appendDownloadUrl +import com.casic.smart.town.sanxi.model.UserDetailModel +import com.casic.smart.town.sanxi.util.AuthenticationHelper +import com.casic.smart.town.sanxi.util.LoadingDialogHub +import com.casic.smart.town.sanxi.util.LocaleConstant +import com.casic.smart.town.sanxi.util.RSAUtils +import com.casic.smart.town.sanxi.view.AboutUsActivity +import com.casic.smart.town.sanxi.view.LoginActivity +import com.casic.smart.town.sanxi.vm.LoginViewModel +import com.casic.smart.town.sanxi.vm.UserViewModel +import com.casic.smart.town.sanxi.vm.VersionViewModel +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.base.KotlinBaseFragment +import com.pengxh.kt.lite.callback.OnDownloadListener +import com.pengxh.kt.lite.extensions.* +import com.pengxh.kt.lite.utils.PageNavigationManager +import com.pengxh.kt.lite.utils.SaveKeyValues +import com.pengxh.kt.lite.vm.LoadState +import com.pengxh.kt.lite.widget.dialog.AlertControlDialog +import com.pengxh.kt.lite.widget.dialog.ChangePasswordDialog +import kotlinx.android.synthetic.main.fragment_mine.* +import java.io.File +import java.nio.charset.StandardCharsets + +class MinePageFragment : KotlinBaseFragment() { + + private lateinit var userData: UserDetailModel.Data + private lateinit var userViewModel: UserViewModel + private lateinit var loginViewModel: LoginViewModel + private lateinit var versionViewModel: VersionViewModel + private lateinit var progressDialog: ProgressDialog + + override fun initLayoutView(): Int = R.layout.fragment_mine + + override fun setupTopBarLayout() { + + } + + override fun initData() { + userViewModel = ViewModelProvider(this)[UserViewModel::class.java] + loginViewModel = ViewModelProvider(this)[LoginViewModel::class.java] + versionViewModel = ViewModelProvider(this)[VersionViewModel::class.java] + + //初始化下载对话框 + progressDialog = ProgressDialog(requireContext()) + progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL) + progressDialog.setProgressDrawable(resources.getDrawable(R.drawable.download_progress)) + progressDialog.setCanceledOnTouchOutside(false) + progressDialog.setCancelable(false) + + /** + * 数据监听 + * */ + dataObserve() + } + + override fun observeRequestState() { + userViewModel.loadState.observe(this, { + when (it) { + is LoadState.Loading -> LoadingDialogHub.show(requireActivity(), "修改中,请稍后") + is LoadState.Success -> { + "修改成功,请重新登录".show(requireContext()) + LoadingDialogHub.dismiss() + AuthenticationHelper.removeToken() + requireContext().navigatePageTo() + PageNavigationManager.finishAllActivity() + } + else -> LoadingDialogHub.dismiss() + } + }) + + versionViewModel.loadState.observe(this, { + when (it) { + is LoadState.Loading -> LoadingDialogHub.show(requireActivity(), "检查版本中,请稍后") + else -> LoadingDialogHub.dismiss() + } + }) + } + + override fun initEvent() { + userImageView.setOnClickListener { + "尽情期待~".show(requireContext()) + } + + updateUserButton.setOnClickListener { + LoadingDialogHub.show(requireActivity(), "同步中,请稍后...") + userViewModel.obtainUserDetail() + } + + changePwdLayout.setOnClickListener { + ChangePasswordDialog.Builder() + .setContext(requireContext()) + .setOnDialogButtonClickListener(object : + ChangePasswordDialog.OnDialogButtonClickListener { + override fun onConfirmClick(oldPwd: String, newPwd: String) { + val publicKey = + RSAUtils.keyStrToPublicKey(AuthenticationHelper.publicKey)!! + val oldPassKey = RSAUtils.encryptDataByPublicKey( + oldPwd.toByteArray(StandardCharsets.UTF_8), + publicKey + ) + val newPassKey = RSAUtils.encryptDataByPublicKey( + newPwd.toByteArray(StandardCharsets.UTF_8), + publicKey + ) + userViewModel.changePassword(oldPassKey, newPassKey) + } + }).build().show() + } + + aboutUsLayout.setOnClickListener { + requireContext().navigatePageTo() + } + + updateVersionLayout.setOnClickListener { + versionViewModel.updateVersion() + } + + clearCacheLayout.setOnClickListener { + //删除缓存之后在设置缓存大小 + LoadingDialogHub.show(requireActivity(), "清理中,请稍后") + File(requireContext().cacheDir.path).deleteFile() + object : CountDownTimer(1500, 500) { + override fun onTick(millisUntilFinished: Long) { + + } + + override fun onFinish() { + LoadingDialogHub.dismiss() + cacheSizeView.text = collectApplicationCache().formatFileSize() + } + }.start() + } + + loginOutButton.setOnClickListener { + AlertControlDialog.Builder() + .setContext(requireContext()) + .setTitle("退出登录") + .setMessage("确定要退出吗?") + .setNegativeButton("取消") + .setPositiveButton("确定") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + loginViewModel.out() + } + + override fun onCancelClick() {} + }).build().show() + } + } + + private fun dataObserve() { + userViewModel.userDetailModel.observe(this, { + if (it.code == 200) { + LoadingDialogHub.dismiss() + "同步完成".show(requireContext()) + userData = it.data + updateUserInfo() + } + }) + + versionViewModel.versionResultModel.observe(this, { + if (BuildConfig.VERSION_NAME == it.version) { + "已是最新版本,无需更新".show(requireContext()) + } else { + AlertControlDialog.Builder() + .setContext(requireContext()) + .setTitle("提示") + .setMessage("有新版本,是否更新?") + .setNegativeButton("稍后再说") + .setPositiveButton("立即下载") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + downloadApk(it.downloadUrl) + } + + override fun onCancelClick() { + + } + }).build().show() + } + }) + + loginViewModel.outResultModel.observe(this, { + if (it.code == 200) { + AuthenticationHelper.removeToken() + requireContext().navigatePageTo() + PageNavigationManager.finishAllActivity() + } + }) + } + + private fun collectApplicationCache(): Long { + return File(requireContext().cacheDir.path).calculateSize() + } + + override fun onResume() { + val userDetailJson = SaveKeyValues.getValue(LocaleConstant.USER_DETAIL_MODEL, "") as String + if (userDetailJson.isNotBlank()) { + userData = Gson().fromJson( + userDetailJson, object : TypeToken() {}.type + ) + updateUserInfo() + } + //缓存 + cacheSizeView.text = collectApplicationCache().formatFileSize() + super.onResume() + } + + private fun updateUserInfo() { + //设置头像,圆形,暂时是默认的 +// val roundDrawable = +// BitmapFactory.decodeResource(requireContext().resources, R.mipmap.login_casic) +// .createRoundDrawable( +// requireContext(), +// 3f.dp2px(requireContext()), +// R.color.mainThemeColor.convertColor(requireContext()) +// ) +// userImageView.setImageDrawable(roundDrawable) + userNameView.text = userData.name + userPhoneView.text = String.format("电话:${userData.phone}") + userDeptView.text = String.format("部门:${userData.deptName}") + } + + private fun downloadApk(url: String?) { + progressDialog.setMessage("下载新版本中...") + progressDialog.show() + if (url.toString().isBlank()) { + "抱歉,版本下载失败".show(requireContext()) + return + } + /** + * http://111.198.10.15:11304/static/apk/1.0.1.apk + * */ + val downloadPath = url!!.appendDownloadUrl() + //开始下载 + downloadPath.downloadFile(requireContext().createDownloadFileDir().toString(), + object : OnDownloadListener { + override fun onDownloadStart(totalBytes: Long) { + progressDialog.max = totalBytes.toInt() + } + + override fun onProgressChanged(currentBytes: Long) { + progressDialog.progress = currentBytes.toInt() + } + + override fun onDownloadEnd(file: File?) { + progressDialog.dismiss() + progressDialog.progress = 0 + //安装APK + installApk(file) + } + }) + } + + private fun installApk(apkPackage: File?) { + if (apkPackage == null) { + "文件异常,无法安装".show(requireContext()) + return + } + val intent = Intent(Intent.ACTION_VIEW) + val data: Uri + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { //判断版本大于等于7.0 + data = FileProvider.getUriForFile( + requireContext(), LocaleConstant.APP_AUTHORITY, apkPackage + ) + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) // 给目标应用一个临时授权 + } else { + data = Uri.fromFile(apkPackage) + } + intent.setDataAndType(data, "application/vnd.android.package-archive") + requireContext().startActivity(intent) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java new file mode 100644 index 0000000..467e33d --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java @@ -0,0 +1,43 @@ +package com.casic.smart.town.sanxi.model; + +/** + * 普通实体类,失败/成功数据结构一致 + */ +public class CommonResultModel { + private int code; + private String data; + private String message; + private boolean isSuccess; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return isSuccess; + } + + public void setSuccess(boolean success) { + isSuccess = success; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java new file mode 100644 index 0000000..9bdabef --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java @@ -0,0 +1,62 @@ +package com.casic.smart.town.sanxi.model; + +public class LoginResultModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private String kaptcha; + private String token; + + public String getKaptcha() { + return kaptcha; + } + + public void setKaptcha(String kaptcha) { + this.kaptcha = kaptcha; + } + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java new file mode 100644 index 0000000..09f8d59 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java @@ -0,0 +1,80 @@ +package com.casic.smart.town.sanxi.model; + +public class PublicKeyModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private boolean appKaptcha; + private boolean kaptcha; + private String publicKey; + private String sid; + + public boolean isAppKaptcha() { + return appKaptcha; + } + + public void setAppKaptcha(boolean appKaptcha) { + this.appKaptcha = appKaptcha; + } + + public boolean isKaptcha() { + return kaptcha; + } + + public void setKaptcha(boolean kaptcha) { + this.kaptcha = kaptcha; + } + + public String getPublicKey() { + return publicKey; + } + + public void setPublicKey(String publicKey) { + this.publicKey = publicKey; + } + + public String getSid() { + return sid; + } + + public void setSid(String sid) { + this.sid = sid; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java new file mode 100644 index 0000000..db577fb --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java @@ -0,0 +1,227 @@ +package com.casic.smart.town.sanxi.model; + + +import java.util.List; + +public class UserDetailModel { + private int code; + private Data data; + private String message; + private boolean success; + + public void setCode(int code) { + this.code = code; + } + + public int getCode() { + return code; + } + + public void setData(Data data) { + this.data = data; + } + + public Data getData() { + return data; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getMessage() { + return message; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public boolean getSuccess() { + return success; + } + + public static class Data { + + private String account; + private String attr1; + private String avatar; + private String bizData; + private List dataScope; + private String deptId; + private String deptName; + private List devices; + private String id; + private String ipAddr; + private String name; + private String phone; + private List roleList; + private List roleNames; + private List roleTips; + private String scopeType; + private String sysData; + private String targetId; + private String targetName; + private String tenantId; + + public void setAccount(String account) { + this.account = account; + } + + public String getAccount() { + return account; + } + + public void setAttr1(String attr1) { + this.attr1 = attr1; + } + + public String getAttr1() { + return attr1; + } + + public void setAvatar(String avatar) { + this.avatar = avatar; + } + + public String getAvatar() { + return avatar; + } + + public void setBizData(String bizData) { + this.bizData = bizData; + } + + public String getBizData() { + return bizData; + } + + public void setDataScope(List dataScope) { + this.dataScope = dataScope; + } + + public List getDataScope() { + return dataScope; + } + + public void setDeptId(String deptId) { + this.deptId = deptId; + } + + public String getDeptId() { + return deptId; + } + + public void setDeptName(String deptName) { + this.deptName = deptName; + } + + public String getDeptName() { + return deptName; + } + + public void setDevices(List devices) { + this.devices = devices; + } + + public List getDevices() { + return devices; + } + + public void setId(String id) { + this.id = id; + } + + public String getId() { + return id; + } + + public void setIpAddr(String ipAddr) { + this.ipAddr = ipAddr; + } + + public String getIpAddr() { + return ipAddr; + } + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setPhone(String phone) { + this.phone = phone; + } + + public String getPhone() { + return phone; + } + + public void setRoleList(List roleList) { + this.roleList = roleList; + } + + public List getRoleList() { + return roleList; + } + + public void setRoleNames(List roleNames) { + this.roleNames = roleNames; + } + + public List getRoleNames() { + return roleNames; + } + + public void setRoleTips(List roleTips) { + this.roleTips = roleTips; + } + + public List getRoleTips() { + return roleTips; + } + + public void setScopeType(String scopeType) { + this.scopeType = scopeType; + } + + public String getScopeType() { + return scopeType; + } + + public void setSysData(String sysData) { + this.sysData = sysData; + } + + public String getSysData() { + return sysData; + } + + public void setTargetId(String targetId) { + this.targetId = targetId; + } + + public String getTargetId() { + return targetId; + } + + public void setTargetName(String targetName) { + this.targetName = targetName; + } + + public String getTargetName() { + return targetName; + } + + public void setTenantId(String tenantId) { + this.tenantId = tenantId; + } + + public String getTenantId() { + return tenantId; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/VersionResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/VersionResultModel.java new file mode 100644 index 0000000..79374e0 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/VersionResultModel.java @@ -0,0 +1,61 @@ +package com.casic.smart.town.sanxi.model; + +public class VersionResultModel { + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private String downloadUrl; + private String version; + + public String getDownloadUrl() { + return downloadUrl; + } + + public void setDownloadUrl(String downloadUrl) { + this.downloadUrl = downloadUrl; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/AuthenticationHelper.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/AuthenticationHelper.kt new file mode 100644 index 0000000..f498e4e --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/AuthenticationHelper.kt @@ -0,0 +1,24 @@ +package com.casic.smart.town.sanxi.util + +import com.pengxh.kt.lite.utils.SaveKeyValues + +object AuthenticationHelper { + + fun savePublicKey(key: String) { + SaveKeyValues.putValue("keyString", key) + } + + val publicKey: String? + get() = SaveKeyValues.getValue("keyString", "") as String? + + fun saveToken(token: String?) { + SaveKeyValues.putValue("token", token!!) + } + + val token: String? + get() = SaveKeyValues.getValue("token", "") as String? + + fun removeToken() { + SaveKeyValues.removeKey("token") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt index ab1a36d..99f0d19 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt @@ -14,5 +14,9 @@ const val PAGE_LIMIT = 20 const val DEFAULT_SERVER_CONFIG = "defaultServerConfig" - const val DEFAULT_SERVER = "http://111.198.10.15:11308" + const val DEFAULT_SERVER = "http://36.133.189.112:8082" + const val APP_AUTHORITY = "com.casic.smart.town.sanxi.fileprovider" + const val ACCOUNT = "account" + const val PASSWORD = "password" + const val USER_DETAIL_MODEL = "userDetailModel" } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/RSAUtils.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/RSAUtils.kt new file mode 100644 index 0000000..b9216ba --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/RSAUtils.kt @@ -0,0 +1,55 @@ +package com.casic.smart.town.sanxi.util + +import android.util.Base64 +import java.security.* +import java.security.spec.InvalidKeySpecException +import java.security.spec.X509EncodedKeySpec +import javax.crypto.BadPaddingException +import javax.crypto.Cipher +import javax.crypto.IllegalBlockSizeException +import javax.crypto.NoSuchPaddingException + +object RSAUtils { + //构建Cipher实例时所传入的的字符串,默认为"RSA/NONE/PKCS1Padding" + private fun processData(srcData: ByteArray, key: Key): ByteArray? { //用来保存处理结果 + var resultBytes: ByteArray? = null + try { //获取Cipher实例 + val cipher = Cipher.getInstance("RSA/None/PKCS1Padding") + //初始化Cipher,mode指定是加密还是解密,key为公钥或私钥 + cipher.init(Cipher.ENCRYPT_MODE, key) + //处理数据 + resultBytes = cipher.doFinal(srcData) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: NoSuchPaddingException) { + e.printStackTrace() + } catch (e: InvalidKeyException) { + e.printStackTrace() + } catch (e: BadPaddingException) { + e.printStackTrace() + } catch (e: IllegalBlockSizeException) { + e.printStackTrace() + } + return resultBytes + } + + fun encryptDataByPublicKey(srcData: ByteArray, publicKey: PublicKey): String { + val resultBytes = processData(srcData, publicKey) + return Base64.encodeToString(resultBytes, Base64.DEFAULT) + } + + fun keyStrToPublicKey(publicKeyStr: String?): PublicKey? { + var publicKey: PublicKey? = null + val keyBytes = Base64.decode(publicKeyStr, Base64.DEFAULT) + val keySpec = X509EncodedKeySpec(keyBytes) + try { + val keyFactory = KeyFactory.getInstance("RSA") + publicKey = keyFactory.generatePublic(keySpec) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: InvalidKeySpecException) { + e.printStackTrace() + } + return publicKey + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1df917d..8d4f33b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -6,6 +6,7 @@ + @@ -32,11 +33,14 @@ + + + + + + + diff --git a/app/src/main/assets/svg/mine.svg b/app/src/main/assets/svg/mine.svg new file mode 100644 index 0000000..3fbb0a3 --- /dev/null +++ b/app/src/main/assets/svg/mine.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt b/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt index d730417..b1972c2 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt @@ -1,8 +1,10 @@ package com.casic.smart.town.sanxi.extensions import com.casic.smart.town.sanxi.model.ErrorMessageModel +import com.casic.smart.town.sanxi.util.LocaleConstant import com.google.gson.Gson import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.utils.SaveKeyValues import org.json.JSONObject fun String.separateResponseCode(): Int { @@ -17,4 +19,15 @@ this, object : TypeToken() {}.type ) return errorModel.message.toString() +} + +/** + * 下载路径为 http://xx.com/static/ 拼接downloadUrl + * */ +fun String.appendDownloadUrl(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + LocaleConstant.DEFAULT_SERVER_CONFIG, LocaleConstant.DEFAULT_SERVER + ) as String + return "$defaultValue/static/${this}" } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt b/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt new file mode 100644 index 0000000..1809dc5 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt @@ -0,0 +1,288 @@ +package com.casic.smart.town.sanxi.fragment + +import android.app.ProgressDialog +import android.content.Intent +import android.net.Uri +import android.os.Build +import android.os.CountDownTimer +import androidx.core.content.FileProvider +import androidx.lifecycle.ViewModelProvider +import com.casic.smart.town.sanxi.BuildConfig +import com.casic.smart.town.sanxi.R +import com.casic.smart.town.sanxi.extensions.appendDownloadUrl +import com.casic.smart.town.sanxi.model.UserDetailModel +import com.casic.smart.town.sanxi.util.AuthenticationHelper +import com.casic.smart.town.sanxi.util.LoadingDialogHub +import com.casic.smart.town.sanxi.util.LocaleConstant +import com.casic.smart.town.sanxi.util.RSAUtils +import com.casic.smart.town.sanxi.view.AboutUsActivity +import com.casic.smart.town.sanxi.view.LoginActivity +import com.casic.smart.town.sanxi.vm.LoginViewModel +import com.casic.smart.town.sanxi.vm.UserViewModel +import com.casic.smart.town.sanxi.vm.VersionViewModel +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.base.KotlinBaseFragment +import com.pengxh.kt.lite.callback.OnDownloadListener +import com.pengxh.kt.lite.extensions.* +import com.pengxh.kt.lite.utils.PageNavigationManager +import com.pengxh.kt.lite.utils.SaveKeyValues +import com.pengxh.kt.lite.vm.LoadState +import com.pengxh.kt.lite.widget.dialog.AlertControlDialog +import com.pengxh.kt.lite.widget.dialog.ChangePasswordDialog +import kotlinx.android.synthetic.main.fragment_mine.* +import java.io.File +import java.nio.charset.StandardCharsets + +class MinePageFragment : KotlinBaseFragment() { + + private lateinit var userData: UserDetailModel.Data + private lateinit var userViewModel: UserViewModel + private lateinit var loginViewModel: LoginViewModel + private lateinit var versionViewModel: VersionViewModel + private lateinit var progressDialog: ProgressDialog + + override fun initLayoutView(): Int = R.layout.fragment_mine + + override fun setupTopBarLayout() { + + } + + override fun initData() { + userViewModel = ViewModelProvider(this)[UserViewModel::class.java] + loginViewModel = ViewModelProvider(this)[LoginViewModel::class.java] + versionViewModel = ViewModelProvider(this)[VersionViewModel::class.java] + + //初始化下载对话框 + progressDialog = ProgressDialog(requireContext()) + progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL) + progressDialog.setProgressDrawable(resources.getDrawable(R.drawable.download_progress)) + progressDialog.setCanceledOnTouchOutside(false) + progressDialog.setCancelable(false) + + /** + * 数据监听 + * */ + dataObserve() + } + + override fun observeRequestState() { + userViewModel.loadState.observe(this, { + when (it) { + is LoadState.Loading -> LoadingDialogHub.show(requireActivity(), "修改中,请稍后") + is LoadState.Success -> { + "修改成功,请重新登录".show(requireContext()) + LoadingDialogHub.dismiss() + AuthenticationHelper.removeToken() + requireContext().navigatePageTo() + PageNavigationManager.finishAllActivity() + } + else -> LoadingDialogHub.dismiss() + } + }) + + versionViewModel.loadState.observe(this, { + when (it) { + is LoadState.Loading -> LoadingDialogHub.show(requireActivity(), "检查版本中,请稍后") + else -> LoadingDialogHub.dismiss() + } + }) + } + + override fun initEvent() { + userImageView.setOnClickListener { + "尽情期待~".show(requireContext()) + } + + updateUserButton.setOnClickListener { + LoadingDialogHub.show(requireActivity(), "同步中,请稍后...") + userViewModel.obtainUserDetail() + } + + changePwdLayout.setOnClickListener { + ChangePasswordDialog.Builder() + .setContext(requireContext()) + .setOnDialogButtonClickListener(object : + ChangePasswordDialog.OnDialogButtonClickListener { + override fun onConfirmClick(oldPwd: String, newPwd: String) { + val publicKey = + RSAUtils.keyStrToPublicKey(AuthenticationHelper.publicKey)!! + val oldPassKey = RSAUtils.encryptDataByPublicKey( + oldPwd.toByteArray(StandardCharsets.UTF_8), + publicKey + ) + val newPassKey = RSAUtils.encryptDataByPublicKey( + newPwd.toByteArray(StandardCharsets.UTF_8), + publicKey + ) + userViewModel.changePassword(oldPassKey, newPassKey) + } + }).build().show() + } + + aboutUsLayout.setOnClickListener { + requireContext().navigatePageTo() + } + + updateVersionLayout.setOnClickListener { + versionViewModel.updateVersion() + } + + clearCacheLayout.setOnClickListener { + //删除缓存之后在设置缓存大小 + LoadingDialogHub.show(requireActivity(), "清理中,请稍后") + File(requireContext().cacheDir.path).deleteFile() + object : CountDownTimer(1500, 500) { + override fun onTick(millisUntilFinished: Long) { + + } + + override fun onFinish() { + LoadingDialogHub.dismiss() + cacheSizeView.text = collectApplicationCache().formatFileSize() + } + }.start() + } + + loginOutButton.setOnClickListener { + AlertControlDialog.Builder() + .setContext(requireContext()) + .setTitle("退出登录") + .setMessage("确定要退出吗?") + .setNegativeButton("取消") + .setPositiveButton("确定") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + loginViewModel.out() + } + + override fun onCancelClick() {} + }).build().show() + } + } + + private fun dataObserve() { + userViewModel.userDetailModel.observe(this, { + if (it.code == 200) { + LoadingDialogHub.dismiss() + "同步完成".show(requireContext()) + userData = it.data + updateUserInfo() + } + }) + + versionViewModel.versionResultModel.observe(this, { + if (BuildConfig.VERSION_NAME == it.version) { + "已是最新版本,无需更新".show(requireContext()) + } else { + AlertControlDialog.Builder() + .setContext(requireContext()) + .setTitle("提示") + .setMessage("有新版本,是否更新?") + .setNegativeButton("稍后再说") + .setPositiveButton("立即下载") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + downloadApk(it.downloadUrl) + } + + override fun onCancelClick() { + + } + }).build().show() + } + }) + + loginViewModel.outResultModel.observe(this, { + if (it.code == 200) { + AuthenticationHelper.removeToken() + requireContext().navigatePageTo() + PageNavigationManager.finishAllActivity() + } + }) + } + + private fun collectApplicationCache(): Long { + return File(requireContext().cacheDir.path).calculateSize() + } + + override fun onResume() { + val userDetailJson = SaveKeyValues.getValue(LocaleConstant.USER_DETAIL_MODEL, "") as String + if (userDetailJson.isNotBlank()) { + userData = Gson().fromJson( + userDetailJson, object : TypeToken() {}.type + ) + updateUserInfo() + } + //缓存 + cacheSizeView.text = collectApplicationCache().formatFileSize() + super.onResume() + } + + private fun updateUserInfo() { + //设置头像,圆形,暂时是默认的 +// val roundDrawable = +// BitmapFactory.decodeResource(requireContext().resources, R.mipmap.login_casic) +// .createRoundDrawable( +// requireContext(), +// 3f.dp2px(requireContext()), +// R.color.mainThemeColor.convertColor(requireContext()) +// ) +// userImageView.setImageDrawable(roundDrawable) + userNameView.text = userData.name + userPhoneView.text = String.format("电话:${userData.phone}") + userDeptView.text = String.format("部门:${userData.deptName}") + } + + private fun downloadApk(url: String?) { + progressDialog.setMessage("下载新版本中...") + progressDialog.show() + if (url.toString().isBlank()) { + "抱歉,版本下载失败".show(requireContext()) + return + } + /** + * http://111.198.10.15:11304/static/apk/1.0.1.apk + * */ + val downloadPath = url!!.appendDownloadUrl() + //开始下载 + downloadPath.downloadFile(requireContext().createDownloadFileDir().toString(), + object : OnDownloadListener { + override fun onDownloadStart(totalBytes: Long) { + progressDialog.max = totalBytes.toInt() + } + + override fun onProgressChanged(currentBytes: Long) { + progressDialog.progress = currentBytes.toInt() + } + + override fun onDownloadEnd(file: File?) { + progressDialog.dismiss() + progressDialog.progress = 0 + //安装APK + installApk(file) + } + }) + } + + private fun installApk(apkPackage: File?) { + if (apkPackage == null) { + "文件异常,无法安装".show(requireContext()) + return + } + val intent = Intent(Intent.ACTION_VIEW) + val data: Uri + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { //判断版本大于等于7.0 + data = FileProvider.getUriForFile( + requireContext(), LocaleConstant.APP_AUTHORITY, apkPackage + ) + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) // 给目标应用一个临时授权 + } else { + data = Uri.fromFile(apkPackage) + } + intent.setDataAndType(data, "application/vnd.android.package-archive") + requireContext().startActivity(intent) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java new file mode 100644 index 0000000..467e33d --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java @@ -0,0 +1,43 @@ +package com.casic.smart.town.sanxi.model; + +/** + * 普通实体类,失败/成功数据结构一致 + */ +public class CommonResultModel { + private int code; + private String data; + private String message; + private boolean isSuccess; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return isSuccess; + } + + public void setSuccess(boolean success) { + isSuccess = success; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java new file mode 100644 index 0000000..9bdabef --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java @@ -0,0 +1,62 @@ +package com.casic.smart.town.sanxi.model; + +public class LoginResultModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private String kaptcha; + private String token; + + public String getKaptcha() { + return kaptcha; + } + + public void setKaptcha(String kaptcha) { + this.kaptcha = kaptcha; + } + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java new file mode 100644 index 0000000..09f8d59 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java @@ -0,0 +1,80 @@ +package com.casic.smart.town.sanxi.model; + +public class PublicKeyModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private boolean appKaptcha; + private boolean kaptcha; + private String publicKey; + private String sid; + + public boolean isAppKaptcha() { + return appKaptcha; + } + + public void setAppKaptcha(boolean appKaptcha) { + this.appKaptcha = appKaptcha; + } + + public boolean isKaptcha() { + return kaptcha; + } + + public void setKaptcha(boolean kaptcha) { + this.kaptcha = kaptcha; + } + + public String getPublicKey() { + return publicKey; + } + + public void setPublicKey(String publicKey) { + this.publicKey = publicKey; + } + + public String getSid() { + return sid; + } + + public void setSid(String sid) { + this.sid = sid; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java new file mode 100644 index 0000000..db577fb --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java @@ -0,0 +1,227 @@ +package com.casic.smart.town.sanxi.model; + + +import java.util.List; + +public class UserDetailModel { + private int code; + private Data data; + private String message; + private boolean success; + + public void setCode(int code) { + this.code = code; + } + + public int getCode() { + return code; + } + + public void setData(Data data) { + this.data = data; + } + + public Data getData() { + return data; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getMessage() { + return message; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public boolean getSuccess() { + return success; + } + + public static class Data { + + private String account; + private String attr1; + private String avatar; + private String bizData; + private List dataScope; + private String deptId; + private String deptName; + private List devices; + private String id; + private String ipAddr; + private String name; + private String phone; + private List roleList; + private List roleNames; + private List roleTips; + private String scopeType; + private String sysData; + private String targetId; + private String targetName; + private String tenantId; + + public void setAccount(String account) { + this.account = account; + } + + public String getAccount() { + return account; + } + + public void setAttr1(String attr1) { + this.attr1 = attr1; + } + + public String getAttr1() { + return attr1; + } + + public void setAvatar(String avatar) { + this.avatar = avatar; + } + + public String getAvatar() { + return avatar; + } + + public void setBizData(String bizData) { + this.bizData = bizData; + } + + public String getBizData() { + return bizData; + } + + public void setDataScope(List dataScope) { + this.dataScope = dataScope; + } + + public List getDataScope() { + return dataScope; + } + + public void setDeptId(String deptId) { + this.deptId = deptId; + } + + public String getDeptId() { + return deptId; + } + + public void setDeptName(String deptName) { + this.deptName = deptName; + } + + public String getDeptName() { + return deptName; + } + + public void setDevices(List devices) { + this.devices = devices; + } + + public List getDevices() { + return devices; + } + + public void setId(String id) { + this.id = id; + } + + public String getId() { + return id; + } + + public void setIpAddr(String ipAddr) { + this.ipAddr = ipAddr; + } + + public String getIpAddr() { + return ipAddr; + } + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setPhone(String phone) { + this.phone = phone; + } + + public String getPhone() { + return phone; + } + + public void setRoleList(List roleList) { + this.roleList = roleList; + } + + public List getRoleList() { + return roleList; + } + + public void setRoleNames(List roleNames) { + this.roleNames = roleNames; + } + + public List getRoleNames() { + return roleNames; + } + + public void setRoleTips(List roleTips) { + this.roleTips = roleTips; + } + + public List getRoleTips() { + return roleTips; + } + + public void setScopeType(String scopeType) { + this.scopeType = scopeType; + } + + public String getScopeType() { + return scopeType; + } + + public void setSysData(String sysData) { + this.sysData = sysData; + } + + public String getSysData() { + return sysData; + } + + public void setTargetId(String targetId) { + this.targetId = targetId; + } + + public String getTargetId() { + return targetId; + } + + public void setTargetName(String targetName) { + this.targetName = targetName; + } + + public String getTargetName() { + return targetName; + } + + public void setTenantId(String tenantId) { + this.tenantId = tenantId; + } + + public String getTenantId() { + return tenantId; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/VersionResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/VersionResultModel.java new file mode 100644 index 0000000..79374e0 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/VersionResultModel.java @@ -0,0 +1,61 @@ +package com.casic.smart.town.sanxi.model; + +public class VersionResultModel { + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private String downloadUrl; + private String version; + + public String getDownloadUrl() { + return downloadUrl; + } + + public void setDownloadUrl(String downloadUrl) { + this.downloadUrl = downloadUrl; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/AuthenticationHelper.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/AuthenticationHelper.kt new file mode 100644 index 0000000..f498e4e --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/AuthenticationHelper.kt @@ -0,0 +1,24 @@ +package com.casic.smart.town.sanxi.util + +import com.pengxh.kt.lite.utils.SaveKeyValues + +object AuthenticationHelper { + + fun savePublicKey(key: String) { + SaveKeyValues.putValue("keyString", key) + } + + val publicKey: String? + get() = SaveKeyValues.getValue("keyString", "") as String? + + fun saveToken(token: String?) { + SaveKeyValues.putValue("token", token!!) + } + + val token: String? + get() = SaveKeyValues.getValue("token", "") as String? + + fun removeToken() { + SaveKeyValues.removeKey("token") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt index ab1a36d..99f0d19 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt @@ -14,5 +14,9 @@ const val PAGE_LIMIT = 20 const val DEFAULT_SERVER_CONFIG = "defaultServerConfig" - const val DEFAULT_SERVER = "http://111.198.10.15:11308" + const val DEFAULT_SERVER = "http://36.133.189.112:8082" + const val APP_AUTHORITY = "com.casic.smart.town.sanxi.fileprovider" + const val ACCOUNT = "account" + const val PASSWORD = "password" + const val USER_DETAIL_MODEL = "userDetailModel" } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/RSAUtils.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/RSAUtils.kt new file mode 100644 index 0000000..b9216ba --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/RSAUtils.kt @@ -0,0 +1,55 @@ +package com.casic.smart.town.sanxi.util + +import android.util.Base64 +import java.security.* +import java.security.spec.InvalidKeySpecException +import java.security.spec.X509EncodedKeySpec +import javax.crypto.BadPaddingException +import javax.crypto.Cipher +import javax.crypto.IllegalBlockSizeException +import javax.crypto.NoSuchPaddingException + +object RSAUtils { + //构建Cipher实例时所传入的的字符串,默认为"RSA/NONE/PKCS1Padding" + private fun processData(srcData: ByteArray, key: Key): ByteArray? { //用来保存处理结果 + var resultBytes: ByteArray? = null + try { //获取Cipher实例 + val cipher = Cipher.getInstance("RSA/None/PKCS1Padding") + //初始化Cipher,mode指定是加密还是解密,key为公钥或私钥 + cipher.init(Cipher.ENCRYPT_MODE, key) + //处理数据 + resultBytes = cipher.doFinal(srcData) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: NoSuchPaddingException) { + e.printStackTrace() + } catch (e: InvalidKeyException) { + e.printStackTrace() + } catch (e: BadPaddingException) { + e.printStackTrace() + } catch (e: IllegalBlockSizeException) { + e.printStackTrace() + } + return resultBytes + } + + fun encryptDataByPublicKey(srcData: ByteArray, publicKey: PublicKey): String { + val resultBytes = processData(srcData, publicKey) + return Base64.encodeToString(resultBytes, Base64.DEFAULT) + } + + fun keyStrToPublicKey(publicKeyStr: String?): PublicKey? { + var publicKey: PublicKey? = null + val keyBytes = Base64.decode(publicKeyStr, Base64.DEFAULT) + val keySpec = X509EncodedKeySpec(keyBytes) + try { + val keyFactory = KeyFactory.getInstance("RSA") + publicKey = keyFactory.generatePublic(keySpec) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: InvalidKeySpecException) { + e.printStackTrace() + } + return publicKey + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt index 63db0ec..50d1742 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt @@ -1,12 +1,64 @@ package com.casic.smart.town.sanxi.util.retrofit -import retrofit2.http.GET +import retrofit2.http.* interface RetrofitService { + /** + * PublicKey校验 + */ + @GET("/config/baseConfig") + suspend fun obtainPublicKey(): String + + /** + * 登录并获取Token + * + * @param sid + * @param account 用户名 + * @param secretKey 加密后的密码 + */ + @FormUrlEncoded + @POST("/user/login") + suspend fun login( + @Field("sid") sid: String, + @Field("username") account: String, + @Field("password") secretKey: String + ): String + + /** + * 退出登录 + */ + @GET("/user/logout") + suspend fun loginOut(@Header("token") token: String): String + + /** + * 获取用户信息 + */ + @GET("/user/info") + suspend fun obtainUserDetail(@Header("token") token: String): String + + /** + * 修改密码 + * + * @param oldPwd 旧密码 + * @param newPwd 新密码 + */ + @FormUrlEncoded + @POST("/mgr/changePwd") + suspend fun changePassword( + @Header("token") token: String, + @Field("oldPwd") oldPwd: String, + @Field("newPwd") newPwd: String + ): String /** * 根据布防状态统计闸井数量接口 */ @GET("/well/countByBfzt") - suspend fun countWellByState(): String + suspend fun countWellByState(@Header("token") token: String): String + + /** + * 更新APK版本 + */ + @POST("/app/checkVersion") + suspend fun obtainVersionResult(@Header("token") token: String): String } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1df917d..8d4f33b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -6,6 +6,7 @@ + @@ -32,11 +33,14 @@ + + + + + + + diff --git a/app/src/main/assets/svg/mine.svg b/app/src/main/assets/svg/mine.svg new file mode 100644 index 0000000..3fbb0a3 --- /dev/null +++ b/app/src/main/assets/svg/mine.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt b/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt index d730417..b1972c2 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt @@ -1,8 +1,10 @@ package com.casic.smart.town.sanxi.extensions import com.casic.smart.town.sanxi.model.ErrorMessageModel +import com.casic.smart.town.sanxi.util.LocaleConstant import com.google.gson.Gson import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.utils.SaveKeyValues import org.json.JSONObject fun String.separateResponseCode(): Int { @@ -17,4 +19,15 @@ this, object : TypeToken() {}.type ) return errorModel.message.toString() +} + +/** + * 下载路径为 http://xx.com/static/ 拼接downloadUrl + * */ +fun String.appendDownloadUrl(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + LocaleConstant.DEFAULT_SERVER_CONFIG, LocaleConstant.DEFAULT_SERVER + ) as String + return "$defaultValue/static/${this}" } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt b/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt new file mode 100644 index 0000000..1809dc5 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt @@ -0,0 +1,288 @@ +package com.casic.smart.town.sanxi.fragment + +import android.app.ProgressDialog +import android.content.Intent +import android.net.Uri +import android.os.Build +import android.os.CountDownTimer +import androidx.core.content.FileProvider +import androidx.lifecycle.ViewModelProvider +import com.casic.smart.town.sanxi.BuildConfig +import com.casic.smart.town.sanxi.R +import com.casic.smart.town.sanxi.extensions.appendDownloadUrl +import com.casic.smart.town.sanxi.model.UserDetailModel +import com.casic.smart.town.sanxi.util.AuthenticationHelper +import com.casic.smart.town.sanxi.util.LoadingDialogHub +import com.casic.smart.town.sanxi.util.LocaleConstant +import com.casic.smart.town.sanxi.util.RSAUtils +import com.casic.smart.town.sanxi.view.AboutUsActivity +import com.casic.smart.town.sanxi.view.LoginActivity +import com.casic.smart.town.sanxi.vm.LoginViewModel +import com.casic.smart.town.sanxi.vm.UserViewModel +import com.casic.smart.town.sanxi.vm.VersionViewModel +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.base.KotlinBaseFragment +import com.pengxh.kt.lite.callback.OnDownloadListener +import com.pengxh.kt.lite.extensions.* +import com.pengxh.kt.lite.utils.PageNavigationManager +import com.pengxh.kt.lite.utils.SaveKeyValues +import com.pengxh.kt.lite.vm.LoadState +import com.pengxh.kt.lite.widget.dialog.AlertControlDialog +import com.pengxh.kt.lite.widget.dialog.ChangePasswordDialog +import kotlinx.android.synthetic.main.fragment_mine.* +import java.io.File +import java.nio.charset.StandardCharsets + +class MinePageFragment : KotlinBaseFragment() { + + private lateinit var userData: UserDetailModel.Data + private lateinit var userViewModel: UserViewModel + private lateinit var loginViewModel: LoginViewModel + private lateinit var versionViewModel: VersionViewModel + private lateinit var progressDialog: ProgressDialog + + override fun initLayoutView(): Int = R.layout.fragment_mine + + override fun setupTopBarLayout() { + + } + + override fun initData() { + userViewModel = ViewModelProvider(this)[UserViewModel::class.java] + loginViewModel = ViewModelProvider(this)[LoginViewModel::class.java] + versionViewModel = ViewModelProvider(this)[VersionViewModel::class.java] + + //初始化下载对话框 + progressDialog = ProgressDialog(requireContext()) + progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL) + progressDialog.setProgressDrawable(resources.getDrawable(R.drawable.download_progress)) + progressDialog.setCanceledOnTouchOutside(false) + progressDialog.setCancelable(false) + + /** + * 数据监听 + * */ + dataObserve() + } + + override fun observeRequestState() { + userViewModel.loadState.observe(this, { + when (it) { + is LoadState.Loading -> LoadingDialogHub.show(requireActivity(), "修改中,请稍后") + is LoadState.Success -> { + "修改成功,请重新登录".show(requireContext()) + LoadingDialogHub.dismiss() + AuthenticationHelper.removeToken() + requireContext().navigatePageTo() + PageNavigationManager.finishAllActivity() + } + else -> LoadingDialogHub.dismiss() + } + }) + + versionViewModel.loadState.observe(this, { + when (it) { + is LoadState.Loading -> LoadingDialogHub.show(requireActivity(), "检查版本中,请稍后") + else -> LoadingDialogHub.dismiss() + } + }) + } + + override fun initEvent() { + userImageView.setOnClickListener { + "尽情期待~".show(requireContext()) + } + + updateUserButton.setOnClickListener { + LoadingDialogHub.show(requireActivity(), "同步中,请稍后...") + userViewModel.obtainUserDetail() + } + + changePwdLayout.setOnClickListener { + ChangePasswordDialog.Builder() + .setContext(requireContext()) + .setOnDialogButtonClickListener(object : + ChangePasswordDialog.OnDialogButtonClickListener { + override fun onConfirmClick(oldPwd: String, newPwd: String) { + val publicKey = + RSAUtils.keyStrToPublicKey(AuthenticationHelper.publicKey)!! + val oldPassKey = RSAUtils.encryptDataByPublicKey( + oldPwd.toByteArray(StandardCharsets.UTF_8), + publicKey + ) + val newPassKey = RSAUtils.encryptDataByPublicKey( + newPwd.toByteArray(StandardCharsets.UTF_8), + publicKey + ) + userViewModel.changePassword(oldPassKey, newPassKey) + } + }).build().show() + } + + aboutUsLayout.setOnClickListener { + requireContext().navigatePageTo() + } + + updateVersionLayout.setOnClickListener { + versionViewModel.updateVersion() + } + + clearCacheLayout.setOnClickListener { + //删除缓存之后在设置缓存大小 + LoadingDialogHub.show(requireActivity(), "清理中,请稍后") + File(requireContext().cacheDir.path).deleteFile() + object : CountDownTimer(1500, 500) { + override fun onTick(millisUntilFinished: Long) { + + } + + override fun onFinish() { + LoadingDialogHub.dismiss() + cacheSizeView.text = collectApplicationCache().formatFileSize() + } + }.start() + } + + loginOutButton.setOnClickListener { + AlertControlDialog.Builder() + .setContext(requireContext()) + .setTitle("退出登录") + .setMessage("确定要退出吗?") + .setNegativeButton("取消") + .setPositiveButton("确定") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + loginViewModel.out() + } + + override fun onCancelClick() {} + }).build().show() + } + } + + private fun dataObserve() { + userViewModel.userDetailModel.observe(this, { + if (it.code == 200) { + LoadingDialogHub.dismiss() + "同步完成".show(requireContext()) + userData = it.data + updateUserInfo() + } + }) + + versionViewModel.versionResultModel.observe(this, { + if (BuildConfig.VERSION_NAME == it.version) { + "已是最新版本,无需更新".show(requireContext()) + } else { + AlertControlDialog.Builder() + .setContext(requireContext()) + .setTitle("提示") + .setMessage("有新版本,是否更新?") + .setNegativeButton("稍后再说") + .setPositiveButton("立即下载") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + downloadApk(it.downloadUrl) + } + + override fun onCancelClick() { + + } + }).build().show() + } + }) + + loginViewModel.outResultModel.observe(this, { + if (it.code == 200) { + AuthenticationHelper.removeToken() + requireContext().navigatePageTo() + PageNavigationManager.finishAllActivity() + } + }) + } + + private fun collectApplicationCache(): Long { + return File(requireContext().cacheDir.path).calculateSize() + } + + override fun onResume() { + val userDetailJson = SaveKeyValues.getValue(LocaleConstant.USER_DETAIL_MODEL, "") as String + if (userDetailJson.isNotBlank()) { + userData = Gson().fromJson( + userDetailJson, object : TypeToken() {}.type + ) + updateUserInfo() + } + //缓存 + cacheSizeView.text = collectApplicationCache().formatFileSize() + super.onResume() + } + + private fun updateUserInfo() { + //设置头像,圆形,暂时是默认的 +// val roundDrawable = +// BitmapFactory.decodeResource(requireContext().resources, R.mipmap.login_casic) +// .createRoundDrawable( +// requireContext(), +// 3f.dp2px(requireContext()), +// R.color.mainThemeColor.convertColor(requireContext()) +// ) +// userImageView.setImageDrawable(roundDrawable) + userNameView.text = userData.name + userPhoneView.text = String.format("电话:${userData.phone}") + userDeptView.text = String.format("部门:${userData.deptName}") + } + + private fun downloadApk(url: String?) { + progressDialog.setMessage("下载新版本中...") + progressDialog.show() + if (url.toString().isBlank()) { + "抱歉,版本下载失败".show(requireContext()) + return + } + /** + * http://111.198.10.15:11304/static/apk/1.0.1.apk + * */ + val downloadPath = url!!.appendDownloadUrl() + //开始下载 + downloadPath.downloadFile(requireContext().createDownloadFileDir().toString(), + object : OnDownloadListener { + override fun onDownloadStart(totalBytes: Long) { + progressDialog.max = totalBytes.toInt() + } + + override fun onProgressChanged(currentBytes: Long) { + progressDialog.progress = currentBytes.toInt() + } + + override fun onDownloadEnd(file: File?) { + progressDialog.dismiss() + progressDialog.progress = 0 + //安装APK + installApk(file) + } + }) + } + + private fun installApk(apkPackage: File?) { + if (apkPackage == null) { + "文件异常,无法安装".show(requireContext()) + return + } + val intent = Intent(Intent.ACTION_VIEW) + val data: Uri + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { //判断版本大于等于7.0 + data = FileProvider.getUriForFile( + requireContext(), LocaleConstant.APP_AUTHORITY, apkPackage + ) + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) // 给目标应用一个临时授权 + } else { + data = Uri.fromFile(apkPackage) + } + intent.setDataAndType(data, "application/vnd.android.package-archive") + requireContext().startActivity(intent) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java new file mode 100644 index 0000000..467e33d --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java @@ -0,0 +1,43 @@ +package com.casic.smart.town.sanxi.model; + +/** + * 普通实体类,失败/成功数据结构一致 + */ +public class CommonResultModel { + private int code; + private String data; + private String message; + private boolean isSuccess; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return isSuccess; + } + + public void setSuccess(boolean success) { + isSuccess = success; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java new file mode 100644 index 0000000..9bdabef --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java @@ -0,0 +1,62 @@ +package com.casic.smart.town.sanxi.model; + +public class LoginResultModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private String kaptcha; + private String token; + + public String getKaptcha() { + return kaptcha; + } + + public void setKaptcha(String kaptcha) { + this.kaptcha = kaptcha; + } + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java new file mode 100644 index 0000000..09f8d59 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java @@ -0,0 +1,80 @@ +package com.casic.smart.town.sanxi.model; + +public class PublicKeyModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private boolean appKaptcha; + private boolean kaptcha; + private String publicKey; + private String sid; + + public boolean isAppKaptcha() { + return appKaptcha; + } + + public void setAppKaptcha(boolean appKaptcha) { + this.appKaptcha = appKaptcha; + } + + public boolean isKaptcha() { + return kaptcha; + } + + public void setKaptcha(boolean kaptcha) { + this.kaptcha = kaptcha; + } + + public String getPublicKey() { + return publicKey; + } + + public void setPublicKey(String publicKey) { + this.publicKey = publicKey; + } + + public String getSid() { + return sid; + } + + public void setSid(String sid) { + this.sid = sid; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java new file mode 100644 index 0000000..db577fb --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java @@ -0,0 +1,227 @@ +package com.casic.smart.town.sanxi.model; + + +import java.util.List; + +public class UserDetailModel { + private int code; + private Data data; + private String message; + private boolean success; + + public void setCode(int code) { + this.code = code; + } + + public int getCode() { + return code; + } + + public void setData(Data data) { + this.data = data; + } + + public Data getData() { + return data; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getMessage() { + return message; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public boolean getSuccess() { + return success; + } + + public static class Data { + + private String account; + private String attr1; + private String avatar; + private String bizData; + private List dataScope; + private String deptId; + private String deptName; + private List devices; + private String id; + private String ipAddr; + private String name; + private String phone; + private List roleList; + private List roleNames; + private List roleTips; + private String scopeType; + private String sysData; + private String targetId; + private String targetName; + private String tenantId; + + public void setAccount(String account) { + this.account = account; + } + + public String getAccount() { + return account; + } + + public void setAttr1(String attr1) { + this.attr1 = attr1; + } + + public String getAttr1() { + return attr1; + } + + public void setAvatar(String avatar) { + this.avatar = avatar; + } + + public String getAvatar() { + return avatar; + } + + public void setBizData(String bizData) { + this.bizData = bizData; + } + + public String getBizData() { + return bizData; + } + + public void setDataScope(List dataScope) { + this.dataScope = dataScope; + } + + public List getDataScope() { + return dataScope; + } + + public void setDeptId(String deptId) { + this.deptId = deptId; + } + + public String getDeptId() { + return deptId; + } + + public void setDeptName(String deptName) { + this.deptName = deptName; + } + + public String getDeptName() { + return deptName; + } + + public void setDevices(List devices) { + this.devices = devices; + } + + public List getDevices() { + return devices; + } + + public void setId(String id) { + this.id = id; + } + + public String getId() { + return id; + } + + public void setIpAddr(String ipAddr) { + this.ipAddr = ipAddr; + } + + public String getIpAddr() { + return ipAddr; + } + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setPhone(String phone) { + this.phone = phone; + } + + public String getPhone() { + return phone; + } + + public void setRoleList(List roleList) { + this.roleList = roleList; + } + + public List getRoleList() { + return roleList; + } + + public void setRoleNames(List roleNames) { + this.roleNames = roleNames; + } + + public List getRoleNames() { + return roleNames; + } + + public void setRoleTips(List roleTips) { + this.roleTips = roleTips; + } + + public List getRoleTips() { + return roleTips; + } + + public void setScopeType(String scopeType) { + this.scopeType = scopeType; + } + + public String getScopeType() { + return scopeType; + } + + public void setSysData(String sysData) { + this.sysData = sysData; + } + + public String getSysData() { + return sysData; + } + + public void setTargetId(String targetId) { + this.targetId = targetId; + } + + public String getTargetId() { + return targetId; + } + + public void setTargetName(String targetName) { + this.targetName = targetName; + } + + public String getTargetName() { + return targetName; + } + + public void setTenantId(String tenantId) { + this.tenantId = tenantId; + } + + public String getTenantId() { + return tenantId; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/VersionResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/VersionResultModel.java new file mode 100644 index 0000000..79374e0 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/VersionResultModel.java @@ -0,0 +1,61 @@ +package com.casic.smart.town.sanxi.model; + +public class VersionResultModel { + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private String downloadUrl; + private String version; + + public String getDownloadUrl() { + return downloadUrl; + } + + public void setDownloadUrl(String downloadUrl) { + this.downloadUrl = downloadUrl; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/AuthenticationHelper.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/AuthenticationHelper.kt new file mode 100644 index 0000000..f498e4e --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/AuthenticationHelper.kt @@ -0,0 +1,24 @@ +package com.casic.smart.town.sanxi.util + +import com.pengxh.kt.lite.utils.SaveKeyValues + +object AuthenticationHelper { + + fun savePublicKey(key: String) { + SaveKeyValues.putValue("keyString", key) + } + + val publicKey: String? + get() = SaveKeyValues.getValue("keyString", "") as String? + + fun saveToken(token: String?) { + SaveKeyValues.putValue("token", token!!) + } + + val token: String? + get() = SaveKeyValues.getValue("token", "") as String? + + fun removeToken() { + SaveKeyValues.removeKey("token") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt index ab1a36d..99f0d19 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt @@ -14,5 +14,9 @@ const val PAGE_LIMIT = 20 const val DEFAULT_SERVER_CONFIG = "defaultServerConfig" - const val DEFAULT_SERVER = "http://111.198.10.15:11308" + const val DEFAULT_SERVER = "http://36.133.189.112:8082" + const val APP_AUTHORITY = "com.casic.smart.town.sanxi.fileprovider" + const val ACCOUNT = "account" + const val PASSWORD = "password" + const val USER_DETAIL_MODEL = "userDetailModel" } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/RSAUtils.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/RSAUtils.kt new file mode 100644 index 0000000..b9216ba --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/RSAUtils.kt @@ -0,0 +1,55 @@ +package com.casic.smart.town.sanxi.util + +import android.util.Base64 +import java.security.* +import java.security.spec.InvalidKeySpecException +import java.security.spec.X509EncodedKeySpec +import javax.crypto.BadPaddingException +import javax.crypto.Cipher +import javax.crypto.IllegalBlockSizeException +import javax.crypto.NoSuchPaddingException + +object RSAUtils { + //构建Cipher实例时所传入的的字符串,默认为"RSA/NONE/PKCS1Padding" + private fun processData(srcData: ByteArray, key: Key): ByteArray? { //用来保存处理结果 + var resultBytes: ByteArray? = null + try { //获取Cipher实例 + val cipher = Cipher.getInstance("RSA/None/PKCS1Padding") + //初始化Cipher,mode指定是加密还是解密,key为公钥或私钥 + cipher.init(Cipher.ENCRYPT_MODE, key) + //处理数据 + resultBytes = cipher.doFinal(srcData) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: NoSuchPaddingException) { + e.printStackTrace() + } catch (e: InvalidKeyException) { + e.printStackTrace() + } catch (e: BadPaddingException) { + e.printStackTrace() + } catch (e: IllegalBlockSizeException) { + e.printStackTrace() + } + return resultBytes + } + + fun encryptDataByPublicKey(srcData: ByteArray, publicKey: PublicKey): String { + val resultBytes = processData(srcData, publicKey) + return Base64.encodeToString(resultBytes, Base64.DEFAULT) + } + + fun keyStrToPublicKey(publicKeyStr: String?): PublicKey? { + var publicKey: PublicKey? = null + val keyBytes = Base64.decode(publicKeyStr, Base64.DEFAULT) + val keySpec = X509EncodedKeySpec(keyBytes) + try { + val keyFactory = KeyFactory.getInstance("RSA") + publicKey = keyFactory.generatePublic(keySpec) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: InvalidKeySpecException) { + e.printStackTrace() + } + return publicKey + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt index 63db0ec..50d1742 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt @@ -1,12 +1,64 @@ package com.casic.smart.town.sanxi.util.retrofit -import retrofit2.http.GET +import retrofit2.http.* interface RetrofitService { + /** + * PublicKey校验 + */ + @GET("/config/baseConfig") + suspend fun obtainPublicKey(): String + + /** + * 登录并获取Token + * + * @param sid + * @param account 用户名 + * @param secretKey 加密后的密码 + */ + @FormUrlEncoded + @POST("/user/login") + suspend fun login( + @Field("sid") sid: String, + @Field("username") account: String, + @Field("password") secretKey: String + ): String + + /** + * 退出登录 + */ + @GET("/user/logout") + suspend fun loginOut(@Header("token") token: String): String + + /** + * 获取用户信息 + */ + @GET("/user/info") + suspend fun obtainUserDetail(@Header("token") token: String): String + + /** + * 修改密码 + * + * @param oldPwd 旧密码 + * @param newPwd 新密码 + */ + @FormUrlEncoded + @POST("/mgr/changePwd") + suspend fun changePassword( + @Header("token") token: String, + @Field("oldPwd") oldPwd: String, + @Field("newPwd") newPwd: String + ): String /** * 根据布防状态统计闸井数量接口 */ @GET("/well/countByBfzt") - suspend fun countWellByState(): String + suspend fun countWellByState(@Header("token") token: String): String + + /** + * 更新APK版本 + */ + @POST("/app/checkVersion") + suspend fun obtainVersionResult(@Header("token") token: String): String } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt index 46bbf4b..410444b 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt @@ -1,5 +1,6 @@ package com.casic.smart.town.sanxi.util.retrofit +import com.casic.smart.town.sanxi.util.AuthenticationHelper import com.casic.smart.town.sanxi.util.LocaleConstant import com.pengxh.kt.lite.utils.RetrofitFactory import com.pengxh.kt.lite.utils.SaveKeyValues @@ -15,9 +16,52 @@ } /** + * 验证PublicKey + */ + suspend fun authenticate(): String { + return api.obtainPublicKey() + } + + /** + * 登录并获取Token + */ + suspend fun login(sid: String, account: String, secretKey: String): String { + return api.login(sid, account, secretKey) + } + + /** + * 退出登录 + */ + suspend fun loginOut(): String { + return api.loginOut(AuthenticationHelper.token!!) + } + + /** + * 获取用户信息 + */ + suspend fun obtainUserDetail(): String { + return api.obtainUserDetail(AuthenticationHelper.token!!) + } + + /** + * 修改密码 + */ + suspend fun changePassword(oldPwd: String, newPwd: String): String { + return api.changePassword(AuthenticationHelper.token!!, oldPwd, newPwd) + } + + /** * 根据布防状态统计闸井数量接口 */ suspend fun countWellByState(): String { - return api.countWellByState() + return api.countWellByState(AuthenticationHelper.token!!) } + + /** + * 更新APK版本 + */ + suspend fun updateVersion(): String { + return api.obtainVersionResult(AuthenticationHelper.token!!) + } + } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1df917d..8d4f33b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -6,6 +6,7 @@ + @@ -32,11 +33,14 @@ + + + + + + + diff --git a/app/src/main/assets/svg/mine.svg b/app/src/main/assets/svg/mine.svg new file mode 100644 index 0000000..3fbb0a3 --- /dev/null +++ b/app/src/main/assets/svg/mine.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt b/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt index d730417..b1972c2 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt @@ -1,8 +1,10 @@ package com.casic.smart.town.sanxi.extensions import com.casic.smart.town.sanxi.model.ErrorMessageModel +import com.casic.smart.town.sanxi.util.LocaleConstant import com.google.gson.Gson import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.utils.SaveKeyValues import org.json.JSONObject fun String.separateResponseCode(): Int { @@ -17,4 +19,15 @@ this, object : TypeToken() {}.type ) return errorModel.message.toString() +} + +/** + * 下载路径为 http://xx.com/static/ 拼接downloadUrl + * */ +fun String.appendDownloadUrl(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + LocaleConstant.DEFAULT_SERVER_CONFIG, LocaleConstant.DEFAULT_SERVER + ) as String + return "$defaultValue/static/${this}" } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt b/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt new file mode 100644 index 0000000..1809dc5 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt @@ -0,0 +1,288 @@ +package com.casic.smart.town.sanxi.fragment + +import android.app.ProgressDialog +import android.content.Intent +import android.net.Uri +import android.os.Build +import android.os.CountDownTimer +import androidx.core.content.FileProvider +import androidx.lifecycle.ViewModelProvider +import com.casic.smart.town.sanxi.BuildConfig +import com.casic.smart.town.sanxi.R +import com.casic.smart.town.sanxi.extensions.appendDownloadUrl +import com.casic.smart.town.sanxi.model.UserDetailModel +import com.casic.smart.town.sanxi.util.AuthenticationHelper +import com.casic.smart.town.sanxi.util.LoadingDialogHub +import com.casic.smart.town.sanxi.util.LocaleConstant +import com.casic.smart.town.sanxi.util.RSAUtils +import com.casic.smart.town.sanxi.view.AboutUsActivity +import com.casic.smart.town.sanxi.view.LoginActivity +import com.casic.smart.town.sanxi.vm.LoginViewModel +import com.casic.smart.town.sanxi.vm.UserViewModel +import com.casic.smart.town.sanxi.vm.VersionViewModel +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.base.KotlinBaseFragment +import com.pengxh.kt.lite.callback.OnDownloadListener +import com.pengxh.kt.lite.extensions.* +import com.pengxh.kt.lite.utils.PageNavigationManager +import com.pengxh.kt.lite.utils.SaveKeyValues +import com.pengxh.kt.lite.vm.LoadState +import com.pengxh.kt.lite.widget.dialog.AlertControlDialog +import com.pengxh.kt.lite.widget.dialog.ChangePasswordDialog +import kotlinx.android.synthetic.main.fragment_mine.* +import java.io.File +import java.nio.charset.StandardCharsets + +class MinePageFragment : KotlinBaseFragment() { + + private lateinit var userData: UserDetailModel.Data + private lateinit var userViewModel: UserViewModel + private lateinit var loginViewModel: LoginViewModel + private lateinit var versionViewModel: VersionViewModel + private lateinit var progressDialog: ProgressDialog + + override fun initLayoutView(): Int = R.layout.fragment_mine + + override fun setupTopBarLayout() { + + } + + override fun initData() { + userViewModel = ViewModelProvider(this)[UserViewModel::class.java] + loginViewModel = ViewModelProvider(this)[LoginViewModel::class.java] + versionViewModel = ViewModelProvider(this)[VersionViewModel::class.java] + + //初始化下载对话框 + progressDialog = ProgressDialog(requireContext()) + progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL) + progressDialog.setProgressDrawable(resources.getDrawable(R.drawable.download_progress)) + progressDialog.setCanceledOnTouchOutside(false) + progressDialog.setCancelable(false) + + /** + * 数据监听 + * */ + dataObserve() + } + + override fun observeRequestState() { + userViewModel.loadState.observe(this, { + when (it) { + is LoadState.Loading -> LoadingDialogHub.show(requireActivity(), "修改中,请稍后") + is LoadState.Success -> { + "修改成功,请重新登录".show(requireContext()) + LoadingDialogHub.dismiss() + AuthenticationHelper.removeToken() + requireContext().navigatePageTo() + PageNavigationManager.finishAllActivity() + } + else -> LoadingDialogHub.dismiss() + } + }) + + versionViewModel.loadState.observe(this, { + when (it) { + is LoadState.Loading -> LoadingDialogHub.show(requireActivity(), "检查版本中,请稍后") + else -> LoadingDialogHub.dismiss() + } + }) + } + + override fun initEvent() { + userImageView.setOnClickListener { + "尽情期待~".show(requireContext()) + } + + updateUserButton.setOnClickListener { + LoadingDialogHub.show(requireActivity(), "同步中,请稍后...") + userViewModel.obtainUserDetail() + } + + changePwdLayout.setOnClickListener { + ChangePasswordDialog.Builder() + .setContext(requireContext()) + .setOnDialogButtonClickListener(object : + ChangePasswordDialog.OnDialogButtonClickListener { + override fun onConfirmClick(oldPwd: String, newPwd: String) { + val publicKey = + RSAUtils.keyStrToPublicKey(AuthenticationHelper.publicKey)!! + val oldPassKey = RSAUtils.encryptDataByPublicKey( + oldPwd.toByteArray(StandardCharsets.UTF_8), + publicKey + ) + val newPassKey = RSAUtils.encryptDataByPublicKey( + newPwd.toByteArray(StandardCharsets.UTF_8), + publicKey + ) + userViewModel.changePassword(oldPassKey, newPassKey) + } + }).build().show() + } + + aboutUsLayout.setOnClickListener { + requireContext().navigatePageTo() + } + + updateVersionLayout.setOnClickListener { + versionViewModel.updateVersion() + } + + clearCacheLayout.setOnClickListener { + //删除缓存之后在设置缓存大小 + LoadingDialogHub.show(requireActivity(), "清理中,请稍后") + File(requireContext().cacheDir.path).deleteFile() + object : CountDownTimer(1500, 500) { + override fun onTick(millisUntilFinished: Long) { + + } + + override fun onFinish() { + LoadingDialogHub.dismiss() + cacheSizeView.text = collectApplicationCache().formatFileSize() + } + }.start() + } + + loginOutButton.setOnClickListener { + AlertControlDialog.Builder() + .setContext(requireContext()) + .setTitle("退出登录") + .setMessage("确定要退出吗?") + .setNegativeButton("取消") + .setPositiveButton("确定") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + loginViewModel.out() + } + + override fun onCancelClick() {} + }).build().show() + } + } + + private fun dataObserve() { + userViewModel.userDetailModel.observe(this, { + if (it.code == 200) { + LoadingDialogHub.dismiss() + "同步完成".show(requireContext()) + userData = it.data + updateUserInfo() + } + }) + + versionViewModel.versionResultModel.observe(this, { + if (BuildConfig.VERSION_NAME == it.version) { + "已是最新版本,无需更新".show(requireContext()) + } else { + AlertControlDialog.Builder() + .setContext(requireContext()) + .setTitle("提示") + .setMessage("有新版本,是否更新?") + .setNegativeButton("稍后再说") + .setPositiveButton("立即下载") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + downloadApk(it.downloadUrl) + } + + override fun onCancelClick() { + + } + }).build().show() + } + }) + + loginViewModel.outResultModel.observe(this, { + if (it.code == 200) { + AuthenticationHelper.removeToken() + requireContext().navigatePageTo() + PageNavigationManager.finishAllActivity() + } + }) + } + + private fun collectApplicationCache(): Long { + return File(requireContext().cacheDir.path).calculateSize() + } + + override fun onResume() { + val userDetailJson = SaveKeyValues.getValue(LocaleConstant.USER_DETAIL_MODEL, "") as String + if (userDetailJson.isNotBlank()) { + userData = Gson().fromJson( + userDetailJson, object : TypeToken() {}.type + ) + updateUserInfo() + } + //缓存 + cacheSizeView.text = collectApplicationCache().formatFileSize() + super.onResume() + } + + private fun updateUserInfo() { + //设置头像,圆形,暂时是默认的 +// val roundDrawable = +// BitmapFactory.decodeResource(requireContext().resources, R.mipmap.login_casic) +// .createRoundDrawable( +// requireContext(), +// 3f.dp2px(requireContext()), +// R.color.mainThemeColor.convertColor(requireContext()) +// ) +// userImageView.setImageDrawable(roundDrawable) + userNameView.text = userData.name + userPhoneView.text = String.format("电话:${userData.phone}") + userDeptView.text = String.format("部门:${userData.deptName}") + } + + private fun downloadApk(url: String?) { + progressDialog.setMessage("下载新版本中...") + progressDialog.show() + if (url.toString().isBlank()) { + "抱歉,版本下载失败".show(requireContext()) + return + } + /** + * http://111.198.10.15:11304/static/apk/1.0.1.apk + * */ + val downloadPath = url!!.appendDownloadUrl() + //开始下载 + downloadPath.downloadFile(requireContext().createDownloadFileDir().toString(), + object : OnDownloadListener { + override fun onDownloadStart(totalBytes: Long) { + progressDialog.max = totalBytes.toInt() + } + + override fun onProgressChanged(currentBytes: Long) { + progressDialog.progress = currentBytes.toInt() + } + + override fun onDownloadEnd(file: File?) { + progressDialog.dismiss() + progressDialog.progress = 0 + //安装APK + installApk(file) + } + }) + } + + private fun installApk(apkPackage: File?) { + if (apkPackage == null) { + "文件异常,无法安装".show(requireContext()) + return + } + val intent = Intent(Intent.ACTION_VIEW) + val data: Uri + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { //判断版本大于等于7.0 + data = FileProvider.getUriForFile( + requireContext(), LocaleConstant.APP_AUTHORITY, apkPackage + ) + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) // 给目标应用一个临时授权 + } else { + data = Uri.fromFile(apkPackage) + } + intent.setDataAndType(data, "application/vnd.android.package-archive") + requireContext().startActivity(intent) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java new file mode 100644 index 0000000..467e33d --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java @@ -0,0 +1,43 @@ +package com.casic.smart.town.sanxi.model; + +/** + * 普通实体类,失败/成功数据结构一致 + */ +public class CommonResultModel { + private int code; + private String data; + private String message; + private boolean isSuccess; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return isSuccess; + } + + public void setSuccess(boolean success) { + isSuccess = success; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java new file mode 100644 index 0000000..9bdabef --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java @@ -0,0 +1,62 @@ +package com.casic.smart.town.sanxi.model; + +public class LoginResultModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private String kaptcha; + private String token; + + public String getKaptcha() { + return kaptcha; + } + + public void setKaptcha(String kaptcha) { + this.kaptcha = kaptcha; + } + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java new file mode 100644 index 0000000..09f8d59 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java @@ -0,0 +1,80 @@ +package com.casic.smart.town.sanxi.model; + +public class PublicKeyModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private boolean appKaptcha; + private boolean kaptcha; + private String publicKey; + private String sid; + + public boolean isAppKaptcha() { + return appKaptcha; + } + + public void setAppKaptcha(boolean appKaptcha) { + this.appKaptcha = appKaptcha; + } + + public boolean isKaptcha() { + return kaptcha; + } + + public void setKaptcha(boolean kaptcha) { + this.kaptcha = kaptcha; + } + + public String getPublicKey() { + return publicKey; + } + + public void setPublicKey(String publicKey) { + this.publicKey = publicKey; + } + + public String getSid() { + return sid; + } + + public void setSid(String sid) { + this.sid = sid; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java new file mode 100644 index 0000000..db577fb --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java @@ -0,0 +1,227 @@ +package com.casic.smart.town.sanxi.model; + + +import java.util.List; + +public class UserDetailModel { + private int code; + private Data data; + private String message; + private boolean success; + + public void setCode(int code) { + this.code = code; + } + + public int getCode() { + return code; + } + + public void setData(Data data) { + this.data = data; + } + + public Data getData() { + return data; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getMessage() { + return message; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public boolean getSuccess() { + return success; + } + + public static class Data { + + private String account; + private String attr1; + private String avatar; + private String bizData; + private List dataScope; + private String deptId; + private String deptName; + private List devices; + private String id; + private String ipAddr; + private String name; + private String phone; + private List roleList; + private List roleNames; + private List roleTips; + private String scopeType; + private String sysData; + private String targetId; + private String targetName; + private String tenantId; + + public void setAccount(String account) { + this.account = account; + } + + public String getAccount() { + return account; + } + + public void setAttr1(String attr1) { + this.attr1 = attr1; + } + + public String getAttr1() { + return attr1; + } + + public void setAvatar(String avatar) { + this.avatar = avatar; + } + + public String getAvatar() { + return avatar; + } + + public void setBizData(String bizData) { + this.bizData = bizData; + } + + public String getBizData() { + return bizData; + } + + public void setDataScope(List dataScope) { + this.dataScope = dataScope; + } + + public List getDataScope() { + return dataScope; + } + + public void setDeptId(String deptId) { + this.deptId = deptId; + } + + public String getDeptId() { + return deptId; + } + + public void setDeptName(String deptName) { + this.deptName = deptName; + } + + public String getDeptName() { + return deptName; + } + + public void setDevices(List devices) { + this.devices = devices; + } + + public List getDevices() { + return devices; + } + + public void setId(String id) { + this.id = id; + } + + public String getId() { + return id; + } + + public void setIpAddr(String ipAddr) { + this.ipAddr = ipAddr; + } + + public String getIpAddr() { + return ipAddr; + } + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setPhone(String phone) { + this.phone = phone; + } + + public String getPhone() { + return phone; + } + + public void setRoleList(List roleList) { + this.roleList = roleList; + } + + public List getRoleList() { + return roleList; + } + + public void setRoleNames(List roleNames) { + this.roleNames = roleNames; + } + + public List getRoleNames() { + return roleNames; + } + + public void setRoleTips(List roleTips) { + this.roleTips = roleTips; + } + + public List getRoleTips() { + return roleTips; + } + + public void setScopeType(String scopeType) { + this.scopeType = scopeType; + } + + public String getScopeType() { + return scopeType; + } + + public void setSysData(String sysData) { + this.sysData = sysData; + } + + public String getSysData() { + return sysData; + } + + public void setTargetId(String targetId) { + this.targetId = targetId; + } + + public String getTargetId() { + return targetId; + } + + public void setTargetName(String targetName) { + this.targetName = targetName; + } + + public String getTargetName() { + return targetName; + } + + public void setTenantId(String tenantId) { + this.tenantId = tenantId; + } + + public String getTenantId() { + return tenantId; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/VersionResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/VersionResultModel.java new file mode 100644 index 0000000..79374e0 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/VersionResultModel.java @@ -0,0 +1,61 @@ +package com.casic.smart.town.sanxi.model; + +public class VersionResultModel { + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private String downloadUrl; + private String version; + + public String getDownloadUrl() { + return downloadUrl; + } + + public void setDownloadUrl(String downloadUrl) { + this.downloadUrl = downloadUrl; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/AuthenticationHelper.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/AuthenticationHelper.kt new file mode 100644 index 0000000..f498e4e --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/AuthenticationHelper.kt @@ -0,0 +1,24 @@ +package com.casic.smart.town.sanxi.util + +import com.pengxh.kt.lite.utils.SaveKeyValues + +object AuthenticationHelper { + + fun savePublicKey(key: String) { + SaveKeyValues.putValue("keyString", key) + } + + val publicKey: String? + get() = SaveKeyValues.getValue("keyString", "") as String? + + fun saveToken(token: String?) { + SaveKeyValues.putValue("token", token!!) + } + + val token: String? + get() = SaveKeyValues.getValue("token", "") as String? + + fun removeToken() { + SaveKeyValues.removeKey("token") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt index ab1a36d..99f0d19 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt @@ -14,5 +14,9 @@ const val PAGE_LIMIT = 20 const val DEFAULT_SERVER_CONFIG = "defaultServerConfig" - const val DEFAULT_SERVER = "http://111.198.10.15:11308" + const val DEFAULT_SERVER = "http://36.133.189.112:8082" + const val APP_AUTHORITY = "com.casic.smart.town.sanxi.fileprovider" + const val ACCOUNT = "account" + const val PASSWORD = "password" + const val USER_DETAIL_MODEL = "userDetailModel" } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/RSAUtils.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/RSAUtils.kt new file mode 100644 index 0000000..b9216ba --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/RSAUtils.kt @@ -0,0 +1,55 @@ +package com.casic.smart.town.sanxi.util + +import android.util.Base64 +import java.security.* +import java.security.spec.InvalidKeySpecException +import java.security.spec.X509EncodedKeySpec +import javax.crypto.BadPaddingException +import javax.crypto.Cipher +import javax.crypto.IllegalBlockSizeException +import javax.crypto.NoSuchPaddingException + +object RSAUtils { + //构建Cipher实例时所传入的的字符串,默认为"RSA/NONE/PKCS1Padding" + private fun processData(srcData: ByteArray, key: Key): ByteArray? { //用来保存处理结果 + var resultBytes: ByteArray? = null + try { //获取Cipher实例 + val cipher = Cipher.getInstance("RSA/None/PKCS1Padding") + //初始化Cipher,mode指定是加密还是解密,key为公钥或私钥 + cipher.init(Cipher.ENCRYPT_MODE, key) + //处理数据 + resultBytes = cipher.doFinal(srcData) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: NoSuchPaddingException) { + e.printStackTrace() + } catch (e: InvalidKeyException) { + e.printStackTrace() + } catch (e: BadPaddingException) { + e.printStackTrace() + } catch (e: IllegalBlockSizeException) { + e.printStackTrace() + } + return resultBytes + } + + fun encryptDataByPublicKey(srcData: ByteArray, publicKey: PublicKey): String { + val resultBytes = processData(srcData, publicKey) + return Base64.encodeToString(resultBytes, Base64.DEFAULT) + } + + fun keyStrToPublicKey(publicKeyStr: String?): PublicKey? { + var publicKey: PublicKey? = null + val keyBytes = Base64.decode(publicKeyStr, Base64.DEFAULT) + val keySpec = X509EncodedKeySpec(keyBytes) + try { + val keyFactory = KeyFactory.getInstance("RSA") + publicKey = keyFactory.generatePublic(keySpec) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: InvalidKeySpecException) { + e.printStackTrace() + } + return publicKey + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt index 63db0ec..50d1742 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt @@ -1,12 +1,64 @@ package com.casic.smart.town.sanxi.util.retrofit -import retrofit2.http.GET +import retrofit2.http.* interface RetrofitService { + /** + * PublicKey校验 + */ + @GET("/config/baseConfig") + suspend fun obtainPublicKey(): String + + /** + * 登录并获取Token + * + * @param sid + * @param account 用户名 + * @param secretKey 加密后的密码 + */ + @FormUrlEncoded + @POST("/user/login") + suspend fun login( + @Field("sid") sid: String, + @Field("username") account: String, + @Field("password") secretKey: String + ): String + + /** + * 退出登录 + */ + @GET("/user/logout") + suspend fun loginOut(@Header("token") token: String): String + + /** + * 获取用户信息 + */ + @GET("/user/info") + suspend fun obtainUserDetail(@Header("token") token: String): String + + /** + * 修改密码 + * + * @param oldPwd 旧密码 + * @param newPwd 新密码 + */ + @FormUrlEncoded + @POST("/mgr/changePwd") + suspend fun changePassword( + @Header("token") token: String, + @Field("oldPwd") oldPwd: String, + @Field("newPwd") newPwd: String + ): String /** * 根据布防状态统计闸井数量接口 */ @GET("/well/countByBfzt") - suspend fun countWellByState(): String + suspend fun countWellByState(@Header("token") token: String): String + + /** + * 更新APK版本 + */ + @POST("/app/checkVersion") + suspend fun obtainVersionResult(@Header("token") token: String): String } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt index 46bbf4b..410444b 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt @@ -1,5 +1,6 @@ package com.casic.smart.town.sanxi.util.retrofit +import com.casic.smart.town.sanxi.util.AuthenticationHelper import com.casic.smart.town.sanxi.util.LocaleConstant import com.pengxh.kt.lite.utils.RetrofitFactory import com.pengxh.kt.lite.utils.SaveKeyValues @@ -15,9 +16,52 @@ } /** + * 验证PublicKey + */ + suspend fun authenticate(): String { + return api.obtainPublicKey() + } + + /** + * 登录并获取Token + */ + suspend fun login(sid: String, account: String, secretKey: String): String { + return api.login(sid, account, secretKey) + } + + /** + * 退出登录 + */ + suspend fun loginOut(): String { + return api.loginOut(AuthenticationHelper.token!!) + } + + /** + * 获取用户信息 + */ + suspend fun obtainUserDetail(): String { + return api.obtainUserDetail(AuthenticationHelper.token!!) + } + + /** + * 修改密码 + */ + suspend fun changePassword(oldPwd: String, newPwd: String): String { + return api.changePassword(AuthenticationHelper.token!!, oldPwd, newPwd) + } + + /** * 根据布防状态统计闸井数量接口 */ suspend fun countWellByState(): String { - return api.countWellByState() + return api.countWellByState(AuthenticationHelper.token!!) } + + /** + * 更新APK版本 + */ + suspend fun updateVersion(): String { + return api.obtainVersionResult(AuthenticationHelper.token!!) + } + } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/AboutUsActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/AboutUsActivity.kt new file mode 100644 index 0000000..7e43c47 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/AboutUsActivity.kt @@ -0,0 +1,34 @@ +package com.casic.smart.town.sanxi.view + +import com.casic.smart.town.sanxi.BuildConfig +import com.casic.smart.town.sanxi.R +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.kt.lite.base.KotlinBaseActivity +import com.pengxh.kt.lite.extensions.convertColor +import com.pengxh.kt.lite.utils.ImmerseStatusBarUtil +import kotlinx.android.synthetic.main.activity_about_us.* +import kotlinx.android.synthetic.main.include_base_title.* + +class AboutUsActivity : KotlinBaseActivity() { + + override fun initLayoutView(): Int = R.layout.activity_about_us + + override fun setupTopBarLayout() { + ImmersionBar.with(this).statusBarDarkFont(false).init() + ImmerseStatusBarUtil.setColor(this, R.color.mainThemeColor.convertColor(this)) + titleView.text = "关于我们" + leftBackView.setOnClickListener { finish() } + } + + override fun initData() { + versionCodeView.text = String.format("版本信息:V${BuildConfig.VERSION_NAME}") + } + + override fun observeRequestState() { + + } + + override fun initEvent() { + + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1df917d..8d4f33b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -6,6 +6,7 @@ + @@ -32,11 +33,14 @@ + + + + + + + diff --git a/app/src/main/assets/svg/mine.svg b/app/src/main/assets/svg/mine.svg new file mode 100644 index 0000000..3fbb0a3 --- /dev/null +++ b/app/src/main/assets/svg/mine.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt b/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt index d730417..b1972c2 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt @@ -1,8 +1,10 @@ package com.casic.smart.town.sanxi.extensions import com.casic.smart.town.sanxi.model.ErrorMessageModel +import com.casic.smart.town.sanxi.util.LocaleConstant import com.google.gson.Gson import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.utils.SaveKeyValues import org.json.JSONObject fun String.separateResponseCode(): Int { @@ -17,4 +19,15 @@ this, object : TypeToken() {}.type ) return errorModel.message.toString() +} + +/** + * 下载路径为 http://xx.com/static/ 拼接downloadUrl + * */ +fun String.appendDownloadUrl(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + LocaleConstant.DEFAULT_SERVER_CONFIG, LocaleConstant.DEFAULT_SERVER + ) as String + return "$defaultValue/static/${this}" } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt b/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt new file mode 100644 index 0000000..1809dc5 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt @@ -0,0 +1,288 @@ +package com.casic.smart.town.sanxi.fragment + +import android.app.ProgressDialog +import android.content.Intent +import android.net.Uri +import android.os.Build +import android.os.CountDownTimer +import androidx.core.content.FileProvider +import androidx.lifecycle.ViewModelProvider +import com.casic.smart.town.sanxi.BuildConfig +import com.casic.smart.town.sanxi.R +import com.casic.smart.town.sanxi.extensions.appendDownloadUrl +import com.casic.smart.town.sanxi.model.UserDetailModel +import com.casic.smart.town.sanxi.util.AuthenticationHelper +import com.casic.smart.town.sanxi.util.LoadingDialogHub +import com.casic.smart.town.sanxi.util.LocaleConstant +import com.casic.smart.town.sanxi.util.RSAUtils +import com.casic.smart.town.sanxi.view.AboutUsActivity +import com.casic.smart.town.sanxi.view.LoginActivity +import com.casic.smart.town.sanxi.vm.LoginViewModel +import com.casic.smart.town.sanxi.vm.UserViewModel +import com.casic.smart.town.sanxi.vm.VersionViewModel +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.base.KotlinBaseFragment +import com.pengxh.kt.lite.callback.OnDownloadListener +import com.pengxh.kt.lite.extensions.* +import com.pengxh.kt.lite.utils.PageNavigationManager +import com.pengxh.kt.lite.utils.SaveKeyValues +import com.pengxh.kt.lite.vm.LoadState +import com.pengxh.kt.lite.widget.dialog.AlertControlDialog +import com.pengxh.kt.lite.widget.dialog.ChangePasswordDialog +import kotlinx.android.synthetic.main.fragment_mine.* +import java.io.File +import java.nio.charset.StandardCharsets + +class MinePageFragment : KotlinBaseFragment() { + + private lateinit var userData: UserDetailModel.Data + private lateinit var userViewModel: UserViewModel + private lateinit var loginViewModel: LoginViewModel + private lateinit var versionViewModel: VersionViewModel + private lateinit var progressDialog: ProgressDialog + + override fun initLayoutView(): Int = R.layout.fragment_mine + + override fun setupTopBarLayout() { + + } + + override fun initData() { + userViewModel = ViewModelProvider(this)[UserViewModel::class.java] + loginViewModel = ViewModelProvider(this)[LoginViewModel::class.java] + versionViewModel = ViewModelProvider(this)[VersionViewModel::class.java] + + //初始化下载对话框 + progressDialog = ProgressDialog(requireContext()) + progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL) + progressDialog.setProgressDrawable(resources.getDrawable(R.drawable.download_progress)) + progressDialog.setCanceledOnTouchOutside(false) + progressDialog.setCancelable(false) + + /** + * 数据监听 + * */ + dataObserve() + } + + override fun observeRequestState() { + userViewModel.loadState.observe(this, { + when (it) { + is LoadState.Loading -> LoadingDialogHub.show(requireActivity(), "修改中,请稍后") + is LoadState.Success -> { + "修改成功,请重新登录".show(requireContext()) + LoadingDialogHub.dismiss() + AuthenticationHelper.removeToken() + requireContext().navigatePageTo() + PageNavigationManager.finishAllActivity() + } + else -> LoadingDialogHub.dismiss() + } + }) + + versionViewModel.loadState.observe(this, { + when (it) { + is LoadState.Loading -> LoadingDialogHub.show(requireActivity(), "检查版本中,请稍后") + else -> LoadingDialogHub.dismiss() + } + }) + } + + override fun initEvent() { + userImageView.setOnClickListener { + "尽情期待~".show(requireContext()) + } + + updateUserButton.setOnClickListener { + LoadingDialogHub.show(requireActivity(), "同步中,请稍后...") + userViewModel.obtainUserDetail() + } + + changePwdLayout.setOnClickListener { + ChangePasswordDialog.Builder() + .setContext(requireContext()) + .setOnDialogButtonClickListener(object : + ChangePasswordDialog.OnDialogButtonClickListener { + override fun onConfirmClick(oldPwd: String, newPwd: String) { + val publicKey = + RSAUtils.keyStrToPublicKey(AuthenticationHelper.publicKey)!! + val oldPassKey = RSAUtils.encryptDataByPublicKey( + oldPwd.toByteArray(StandardCharsets.UTF_8), + publicKey + ) + val newPassKey = RSAUtils.encryptDataByPublicKey( + newPwd.toByteArray(StandardCharsets.UTF_8), + publicKey + ) + userViewModel.changePassword(oldPassKey, newPassKey) + } + }).build().show() + } + + aboutUsLayout.setOnClickListener { + requireContext().navigatePageTo() + } + + updateVersionLayout.setOnClickListener { + versionViewModel.updateVersion() + } + + clearCacheLayout.setOnClickListener { + //删除缓存之后在设置缓存大小 + LoadingDialogHub.show(requireActivity(), "清理中,请稍后") + File(requireContext().cacheDir.path).deleteFile() + object : CountDownTimer(1500, 500) { + override fun onTick(millisUntilFinished: Long) { + + } + + override fun onFinish() { + LoadingDialogHub.dismiss() + cacheSizeView.text = collectApplicationCache().formatFileSize() + } + }.start() + } + + loginOutButton.setOnClickListener { + AlertControlDialog.Builder() + .setContext(requireContext()) + .setTitle("退出登录") + .setMessage("确定要退出吗?") + .setNegativeButton("取消") + .setPositiveButton("确定") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + loginViewModel.out() + } + + override fun onCancelClick() {} + }).build().show() + } + } + + private fun dataObserve() { + userViewModel.userDetailModel.observe(this, { + if (it.code == 200) { + LoadingDialogHub.dismiss() + "同步完成".show(requireContext()) + userData = it.data + updateUserInfo() + } + }) + + versionViewModel.versionResultModel.observe(this, { + if (BuildConfig.VERSION_NAME == it.version) { + "已是最新版本,无需更新".show(requireContext()) + } else { + AlertControlDialog.Builder() + .setContext(requireContext()) + .setTitle("提示") + .setMessage("有新版本,是否更新?") + .setNegativeButton("稍后再说") + .setPositiveButton("立即下载") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + downloadApk(it.downloadUrl) + } + + override fun onCancelClick() { + + } + }).build().show() + } + }) + + loginViewModel.outResultModel.observe(this, { + if (it.code == 200) { + AuthenticationHelper.removeToken() + requireContext().navigatePageTo() + PageNavigationManager.finishAllActivity() + } + }) + } + + private fun collectApplicationCache(): Long { + return File(requireContext().cacheDir.path).calculateSize() + } + + override fun onResume() { + val userDetailJson = SaveKeyValues.getValue(LocaleConstant.USER_DETAIL_MODEL, "") as String + if (userDetailJson.isNotBlank()) { + userData = Gson().fromJson( + userDetailJson, object : TypeToken() {}.type + ) + updateUserInfo() + } + //缓存 + cacheSizeView.text = collectApplicationCache().formatFileSize() + super.onResume() + } + + private fun updateUserInfo() { + //设置头像,圆形,暂时是默认的 +// val roundDrawable = +// BitmapFactory.decodeResource(requireContext().resources, R.mipmap.login_casic) +// .createRoundDrawable( +// requireContext(), +// 3f.dp2px(requireContext()), +// R.color.mainThemeColor.convertColor(requireContext()) +// ) +// userImageView.setImageDrawable(roundDrawable) + userNameView.text = userData.name + userPhoneView.text = String.format("电话:${userData.phone}") + userDeptView.text = String.format("部门:${userData.deptName}") + } + + private fun downloadApk(url: String?) { + progressDialog.setMessage("下载新版本中...") + progressDialog.show() + if (url.toString().isBlank()) { + "抱歉,版本下载失败".show(requireContext()) + return + } + /** + * http://111.198.10.15:11304/static/apk/1.0.1.apk + * */ + val downloadPath = url!!.appendDownloadUrl() + //开始下载 + downloadPath.downloadFile(requireContext().createDownloadFileDir().toString(), + object : OnDownloadListener { + override fun onDownloadStart(totalBytes: Long) { + progressDialog.max = totalBytes.toInt() + } + + override fun onProgressChanged(currentBytes: Long) { + progressDialog.progress = currentBytes.toInt() + } + + override fun onDownloadEnd(file: File?) { + progressDialog.dismiss() + progressDialog.progress = 0 + //安装APK + installApk(file) + } + }) + } + + private fun installApk(apkPackage: File?) { + if (apkPackage == null) { + "文件异常,无法安装".show(requireContext()) + return + } + val intent = Intent(Intent.ACTION_VIEW) + val data: Uri + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { //判断版本大于等于7.0 + data = FileProvider.getUriForFile( + requireContext(), LocaleConstant.APP_AUTHORITY, apkPackage + ) + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) // 给目标应用一个临时授权 + } else { + data = Uri.fromFile(apkPackage) + } + intent.setDataAndType(data, "application/vnd.android.package-archive") + requireContext().startActivity(intent) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java new file mode 100644 index 0000000..467e33d --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java @@ -0,0 +1,43 @@ +package com.casic.smart.town.sanxi.model; + +/** + * 普通实体类,失败/成功数据结构一致 + */ +public class CommonResultModel { + private int code; + private String data; + private String message; + private boolean isSuccess; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return isSuccess; + } + + public void setSuccess(boolean success) { + isSuccess = success; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java new file mode 100644 index 0000000..9bdabef --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java @@ -0,0 +1,62 @@ +package com.casic.smart.town.sanxi.model; + +public class LoginResultModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private String kaptcha; + private String token; + + public String getKaptcha() { + return kaptcha; + } + + public void setKaptcha(String kaptcha) { + this.kaptcha = kaptcha; + } + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java new file mode 100644 index 0000000..09f8d59 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java @@ -0,0 +1,80 @@ +package com.casic.smart.town.sanxi.model; + +public class PublicKeyModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private boolean appKaptcha; + private boolean kaptcha; + private String publicKey; + private String sid; + + public boolean isAppKaptcha() { + return appKaptcha; + } + + public void setAppKaptcha(boolean appKaptcha) { + this.appKaptcha = appKaptcha; + } + + public boolean isKaptcha() { + return kaptcha; + } + + public void setKaptcha(boolean kaptcha) { + this.kaptcha = kaptcha; + } + + public String getPublicKey() { + return publicKey; + } + + public void setPublicKey(String publicKey) { + this.publicKey = publicKey; + } + + public String getSid() { + return sid; + } + + public void setSid(String sid) { + this.sid = sid; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java new file mode 100644 index 0000000..db577fb --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java @@ -0,0 +1,227 @@ +package com.casic.smart.town.sanxi.model; + + +import java.util.List; + +public class UserDetailModel { + private int code; + private Data data; + private String message; + private boolean success; + + public void setCode(int code) { + this.code = code; + } + + public int getCode() { + return code; + } + + public void setData(Data data) { + this.data = data; + } + + public Data getData() { + return data; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getMessage() { + return message; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public boolean getSuccess() { + return success; + } + + public static class Data { + + private String account; + private String attr1; + private String avatar; + private String bizData; + private List dataScope; + private String deptId; + private String deptName; + private List devices; + private String id; + private String ipAddr; + private String name; + private String phone; + private List roleList; + private List roleNames; + private List roleTips; + private String scopeType; + private String sysData; + private String targetId; + private String targetName; + private String tenantId; + + public void setAccount(String account) { + this.account = account; + } + + public String getAccount() { + return account; + } + + public void setAttr1(String attr1) { + this.attr1 = attr1; + } + + public String getAttr1() { + return attr1; + } + + public void setAvatar(String avatar) { + this.avatar = avatar; + } + + public String getAvatar() { + return avatar; + } + + public void setBizData(String bizData) { + this.bizData = bizData; + } + + public String getBizData() { + return bizData; + } + + public void setDataScope(List dataScope) { + this.dataScope = dataScope; + } + + public List getDataScope() { + return dataScope; + } + + public void setDeptId(String deptId) { + this.deptId = deptId; + } + + public String getDeptId() { + return deptId; + } + + public void setDeptName(String deptName) { + this.deptName = deptName; + } + + public String getDeptName() { + return deptName; + } + + public void setDevices(List devices) { + this.devices = devices; + } + + public List getDevices() { + return devices; + } + + public void setId(String id) { + this.id = id; + } + + public String getId() { + return id; + } + + public void setIpAddr(String ipAddr) { + this.ipAddr = ipAddr; + } + + public String getIpAddr() { + return ipAddr; + } + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setPhone(String phone) { + this.phone = phone; + } + + public String getPhone() { + return phone; + } + + public void setRoleList(List roleList) { + this.roleList = roleList; + } + + public List getRoleList() { + return roleList; + } + + public void setRoleNames(List roleNames) { + this.roleNames = roleNames; + } + + public List getRoleNames() { + return roleNames; + } + + public void setRoleTips(List roleTips) { + this.roleTips = roleTips; + } + + public List getRoleTips() { + return roleTips; + } + + public void setScopeType(String scopeType) { + this.scopeType = scopeType; + } + + public String getScopeType() { + return scopeType; + } + + public void setSysData(String sysData) { + this.sysData = sysData; + } + + public String getSysData() { + return sysData; + } + + public void setTargetId(String targetId) { + this.targetId = targetId; + } + + public String getTargetId() { + return targetId; + } + + public void setTargetName(String targetName) { + this.targetName = targetName; + } + + public String getTargetName() { + return targetName; + } + + public void setTenantId(String tenantId) { + this.tenantId = tenantId; + } + + public String getTenantId() { + return tenantId; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/VersionResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/VersionResultModel.java new file mode 100644 index 0000000..79374e0 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/VersionResultModel.java @@ -0,0 +1,61 @@ +package com.casic.smart.town.sanxi.model; + +public class VersionResultModel { + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private String downloadUrl; + private String version; + + public String getDownloadUrl() { + return downloadUrl; + } + + public void setDownloadUrl(String downloadUrl) { + this.downloadUrl = downloadUrl; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/AuthenticationHelper.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/AuthenticationHelper.kt new file mode 100644 index 0000000..f498e4e --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/AuthenticationHelper.kt @@ -0,0 +1,24 @@ +package com.casic.smart.town.sanxi.util + +import com.pengxh.kt.lite.utils.SaveKeyValues + +object AuthenticationHelper { + + fun savePublicKey(key: String) { + SaveKeyValues.putValue("keyString", key) + } + + val publicKey: String? + get() = SaveKeyValues.getValue("keyString", "") as String? + + fun saveToken(token: String?) { + SaveKeyValues.putValue("token", token!!) + } + + val token: String? + get() = SaveKeyValues.getValue("token", "") as String? + + fun removeToken() { + SaveKeyValues.removeKey("token") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt index ab1a36d..99f0d19 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt @@ -14,5 +14,9 @@ const val PAGE_LIMIT = 20 const val DEFAULT_SERVER_CONFIG = "defaultServerConfig" - const val DEFAULT_SERVER = "http://111.198.10.15:11308" + const val DEFAULT_SERVER = "http://36.133.189.112:8082" + const val APP_AUTHORITY = "com.casic.smart.town.sanxi.fileprovider" + const val ACCOUNT = "account" + const val PASSWORD = "password" + const val USER_DETAIL_MODEL = "userDetailModel" } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/RSAUtils.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/RSAUtils.kt new file mode 100644 index 0000000..b9216ba --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/RSAUtils.kt @@ -0,0 +1,55 @@ +package com.casic.smart.town.sanxi.util + +import android.util.Base64 +import java.security.* +import java.security.spec.InvalidKeySpecException +import java.security.spec.X509EncodedKeySpec +import javax.crypto.BadPaddingException +import javax.crypto.Cipher +import javax.crypto.IllegalBlockSizeException +import javax.crypto.NoSuchPaddingException + +object RSAUtils { + //构建Cipher实例时所传入的的字符串,默认为"RSA/NONE/PKCS1Padding" + private fun processData(srcData: ByteArray, key: Key): ByteArray? { //用来保存处理结果 + var resultBytes: ByteArray? = null + try { //获取Cipher实例 + val cipher = Cipher.getInstance("RSA/None/PKCS1Padding") + //初始化Cipher,mode指定是加密还是解密,key为公钥或私钥 + cipher.init(Cipher.ENCRYPT_MODE, key) + //处理数据 + resultBytes = cipher.doFinal(srcData) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: NoSuchPaddingException) { + e.printStackTrace() + } catch (e: InvalidKeyException) { + e.printStackTrace() + } catch (e: BadPaddingException) { + e.printStackTrace() + } catch (e: IllegalBlockSizeException) { + e.printStackTrace() + } + return resultBytes + } + + fun encryptDataByPublicKey(srcData: ByteArray, publicKey: PublicKey): String { + val resultBytes = processData(srcData, publicKey) + return Base64.encodeToString(resultBytes, Base64.DEFAULT) + } + + fun keyStrToPublicKey(publicKeyStr: String?): PublicKey? { + var publicKey: PublicKey? = null + val keyBytes = Base64.decode(publicKeyStr, Base64.DEFAULT) + val keySpec = X509EncodedKeySpec(keyBytes) + try { + val keyFactory = KeyFactory.getInstance("RSA") + publicKey = keyFactory.generatePublic(keySpec) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: InvalidKeySpecException) { + e.printStackTrace() + } + return publicKey + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt index 63db0ec..50d1742 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt @@ -1,12 +1,64 @@ package com.casic.smart.town.sanxi.util.retrofit -import retrofit2.http.GET +import retrofit2.http.* interface RetrofitService { + /** + * PublicKey校验 + */ + @GET("/config/baseConfig") + suspend fun obtainPublicKey(): String + + /** + * 登录并获取Token + * + * @param sid + * @param account 用户名 + * @param secretKey 加密后的密码 + */ + @FormUrlEncoded + @POST("/user/login") + suspend fun login( + @Field("sid") sid: String, + @Field("username") account: String, + @Field("password") secretKey: String + ): String + + /** + * 退出登录 + */ + @GET("/user/logout") + suspend fun loginOut(@Header("token") token: String): String + + /** + * 获取用户信息 + */ + @GET("/user/info") + suspend fun obtainUserDetail(@Header("token") token: String): String + + /** + * 修改密码 + * + * @param oldPwd 旧密码 + * @param newPwd 新密码 + */ + @FormUrlEncoded + @POST("/mgr/changePwd") + suspend fun changePassword( + @Header("token") token: String, + @Field("oldPwd") oldPwd: String, + @Field("newPwd") newPwd: String + ): String /** * 根据布防状态统计闸井数量接口 */ @GET("/well/countByBfzt") - suspend fun countWellByState(): String + suspend fun countWellByState(@Header("token") token: String): String + + /** + * 更新APK版本 + */ + @POST("/app/checkVersion") + suspend fun obtainVersionResult(@Header("token") token: String): String } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt index 46bbf4b..410444b 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt @@ -1,5 +1,6 @@ package com.casic.smart.town.sanxi.util.retrofit +import com.casic.smart.town.sanxi.util.AuthenticationHelper import com.casic.smart.town.sanxi.util.LocaleConstant import com.pengxh.kt.lite.utils.RetrofitFactory import com.pengxh.kt.lite.utils.SaveKeyValues @@ -15,9 +16,52 @@ } /** + * 验证PublicKey + */ + suspend fun authenticate(): String { + return api.obtainPublicKey() + } + + /** + * 登录并获取Token + */ + suspend fun login(sid: String, account: String, secretKey: String): String { + return api.login(sid, account, secretKey) + } + + /** + * 退出登录 + */ + suspend fun loginOut(): String { + return api.loginOut(AuthenticationHelper.token!!) + } + + /** + * 获取用户信息 + */ + suspend fun obtainUserDetail(): String { + return api.obtainUserDetail(AuthenticationHelper.token!!) + } + + /** + * 修改密码 + */ + suspend fun changePassword(oldPwd: String, newPwd: String): String { + return api.changePassword(AuthenticationHelper.token!!, oldPwd, newPwd) + } + + /** * 根据布防状态统计闸井数量接口 */ suspend fun countWellByState(): String { - return api.countWellByState() + return api.countWellByState(AuthenticationHelper.token!!) } + + /** + * 更新APK版本 + */ + suspend fun updateVersion(): String { + return api.obtainVersionResult(AuthenticationHelper.token!!) + } + } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/AboutUsActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/AboutUsActivity.kt new file mode 100644 index 0000000..7e43c47 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/AboutUsActivity.kt @@ -0,0 +1,34 @@ +package com.casic.smart.town.sanxi.view + +import com.casic.smart.town.sanxi.BuildConfig +import com.casic.smart.town.sanxi.R +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.kt.lite.base.KotlinBaseActivity +import com.pengxh.kt.lite.extensions.convertColor +import com.pengxh.kt.lite.utils.ImmerseStatusBarUtil +import kotlinx.android.synthetic.main.activity_about_us.* +import kotlinx.android.synthetic.main.include_base_title.* + +class AboutUsActivity : KotlinBaseActivity() { + + override fun initLayoutView(): Int = R.layout.activity_about_us + + override fun setupTopBarLayout() { + ImmersionBar.with(this).statusBarDarkFont(false).init() + ImmerseStatusBarUtil.setColor(this, R.color.mainThemeColor.convertColor(this)) + titleView.text = "关于我们" + leftBackView.setOnClickListener { finish() } + } + + override fun initData() { + versionCodeView.text = String.format("版本信息:V${BuildConfig.VERSION_NAME}") + } + + override fun observeRequestState() { + + } + + override fun initEvent() { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/LoginActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/LoginActivity.kt new file mode 100644 index 0000000..b5d1d50 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/LoginActivity.kt @@ -0,0 +1,97 @@ +package com.casic.smart.town.sanxi.view + +import androidx.lifecycle.ViewModelProvider +import com.casic.smart.town.sanxi.R +import com.casic.smart.town.sanxi.util.AuthenticationHelper +import com.casic.smart.town.sanxi.util.LoadingDialogHub +import com.casic.smart.town.sanxi.util.LocaleConstant +import com.casic.smart.town.sanxi.util.RSAUtils +import com.casic.smart.town.sanxi.vm.AuthenticateViewModel +import com.casic.smart.town.sanxi.vm.LoginViewModel +import com.casic.smart.town.sanxi.vm.UserViewModel +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.kt.lite.base.KotlinBaseActivity +import com.pengxh.kt.lite.extensions.navigatePageTo +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.utils.SaveKeyValues +import kotlinx.android.synthetic.main.activity_login.* + +class LoginActivity : KotlinBaseActivity() { + + private lateinit var authenticateViewModel: AuthenticateViewModel + private lateinit var loginViewModel: LoginViewModel + private lateinit var userViewModel: UserViewModel + + override fun initLayoutView(): Int = R.layout.activity_login + + override fun setupTopBarLayout() { + ImmersionBar.with(this).statusBarDarkFont(true).init() + } + + override fun initData() { + // 设置默认账号密码 + userNameView.setText(SaveKeyValues.getValue(LocaleConstant.ACCOUNT, "") as String) + userPasswordView.setText(SaveKeyValues.getValue(LocaleConstant.PASSWORD, "") as String) + authenticateViewModel = ViewModelProvider(this)[AuthenticateViewModel::class.java] + loginViewModel = ViewModelProvider(this)[LoginViewModel::class.java] + userViewModel = ViewModelProvider(this)[UserViewModel::class.java] + + authenticateViewModel.keyModel.observe(this, { + if (it.code == 200) {//用code判断,别的判断可能有坑 + val keyString = it.data!!.publicKey!! + /** + * 修改密码需要用到key,先存着 + * */ + AuthenticationHelper.savePublicKey(keyString) + val publicKey = RSAUtils.keyStrToPublicKey(keyString) + + val account = userNameView.text.toString() + val userPassword = userPasswordView.text.toString() + val dataByPublicKey = + RSAUtils.encryptDataByPublicKey(userPassword.toByteArray(), publicKey!!) + //登录并获取Token,POST请求 + loginViewModel.enter(it.data!!.sid!!, account, dataByPublicKey) + loginViewModel.enterResultModel.observe(this, { loginResult -> + if (loginResult.code == 200) { + AuthenticationHelper.saveToken(loginResult.data!!.token!!) + /** + * 获取token之后保存用户信息 + * */ + userViewModel.obtainUserDetail() + //验证成功登录 + navigatePageTo() + finish() + } + }) + } + }) + } + + override fun observeRequestState() { + authenticateViewModel.loadState.observe(this, { + LoadingDialogHub.show(this, "登录中,请稍后") + }) + + loginViewModel.loadState.observe(this, { + LoadingDialogHub.dismiss() + }) + } + + override fun initEvent() { + loginButton.setOnClickListener { + val account = userNameView.text.toString().trim() + val userPassword = userPasswordView.text.toString().trim() + if (account.isBlank()) { + "用户名不能为空".show(this) + return@setOnClickListener + } + if (userPassword.isBlank()) { + "密码不能为空".show(this) + return@setOnClickListener + } + SaveKeyValues.putValue(LocaleConstant.ACCOUNT, account) + SaveKeyValues.putValue(LocaleConstant.PASSWORD, userPassword) + authenticateViewModel.obtainPublicKey() + } + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1df917d..8d4f33b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -6,6 +6,7 @@ + @@ -32,11 +33,14 @@ + + + + + + + diff --git a/app/src/main/assets/svg/mine.svg b/app/src/main/assets/svg/mine.svg new file mode 100644 index 0000000..3fbb0a3 --- /dev/null +++ b/app/src/main/assets/svg/mine.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt b/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt index d730417..b1972c2 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt @@ -1,8 +1,10 @@ package com.casic.smart.town.sanxi.extensions import com.casic.smart.town.sanxi.model.ErrorMessageModel +import com.casic.smart.town.sanxi.util.LocaleConstant import com.google.gson.Gson import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.utils.SaveKeyValues import org.json.JSONObject fun String.separateResponseCode(): Int { @@ -17,4 +19,15 @@ this, object : TypeToken() {}.type ) return errorModel.message.toString() +} + +/** + * 下载路径为 http://xx.com/static/ 拼接downloadUrl + * */ +fun String.appendDownloadUrl(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + LocaleConstant.DEFAULT_SERVER_CONFIG, LocaleConstant.DEFAULT_SERVER + ) as String + return "$defaultValue/static/${this}" } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt b/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt new file mode 100644 index 0000000..1809dc5 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt @@ -0,0 +1,288 @@ +package com.casic.smart.town.sanxi.fragment + +import android.app.ProgressDialog +import android.content.Intent +import android.net.Uri +import android.os.Build +import android.os.CountDownTimer +import androidx.core.content.FileProvider +import androidx.lifecycle.ViewModelProvider +import com.casic.smart.town.sanxi.BuildConfig +import com.casic.smart.town.sanxi.R +import com.casic.smart.town.sanxi.extensions.appendDownloadUrl +import com.casic.smart.town.sanxi.model.UserDetailModel +import com.casic.smart.town.sanxi.util.AuthenticationHelper +import com.casic.smart.town.sanxi.util.LoadingDialogHub +import com.casic.smart.town.sanxi.util.LocaleConstant +import com.casic.smart.town.sanxi.util.RSAUtils +import com.casic.smart.town.sanxi.view.AboutUsActivity +import com.casic.smart.town.sanxi.view.LoginActivity +import com.casic.smart.town.sanxi.vm.LoginViewModel +import com.casic.smart.town.sanxi.vm.UserViewModel +import com.casic.smart.town.sanxi.vm.VersionViewModel +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.base.KotlinBaseFragment +import com.pengxh.kt.lite.callback.OnDownloadListener +import com.pengxh.kt.lite.extensions.* +import com.pengxh.kt.lite.utils.PageNavigationManager +import com.pengxh.kt.lite.utils.SaveKeyValues +import com.pengxh.kt.lite.vm.LoadState +import com.pengxh.kt.lite.widget.dialog.AlertControlDialog +import com.pengxh.kt.lite.widget.dialog.ChangePasswordDialog +import kotlinx.android.synthetic.main.fragment_mine.* +import java.io.File +import java.nio.charset.StandardCharsets + +class MinePageFragment : KotlinBaseFragment() { + + private lateinit var userData: UserDetailModel.Data + private lateinit var userViewModel: UserViewModel + private lateinit var loginViewModel: LoginViewModel + private lateinit var versionViewModel: VersionViewModel + private lateinit var progressDialog: ProgressDialog + + override fun initLayoutView(): Int = R.layout.fragment_mine + + override fun setupTopBarLayout() { + + } + + override fun initData() { + userViewModel = ViewModelProvider(this)[UserViewModel::class.java] + loginViewModel = ViewModelProvider(this)[LoginViewModel::class.java] + versionViewModel = ViewModelProvider(this)[VersionViewModel::class.java] + + //初始化下载对话框 + progressDialog = ProgressDialog(requireContext()) + progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL) + progressDialog.setProgressDrawable(resources.getDrawable(R.drawable.download_progress)) + progressDialog.setCanceledOnTouchOutside(false) + progressDialog.setCancelable(false) + + /** + * 数据监听 + * */ + dataObserve() + } + + override fun observeRequestState() { + userViewModel.loadState.observe(this, { + when (it) { + is LoadState.Loading -> LoadingDialogHub.show(requireActivity(), "修改中,请稍后") + is LoadState.Success -> { + "修改成功,请重新登录".show(requireContext()) + LoadingDialogHub.dismiss() + AuthenticationHelper.removeToken() + requireContext().navigatePageTo() + PageNavigationManager.finishAllActivity() + } + else -> LoadingDialogHub.dismiss() + } + }) + + versionViewModel.loadState.observe(this, { + when (it) { + is LoadState.Loading -> LoadingDialogHub.show(requireActivity(), "检查版本中,请稍后") + else -> LoadingDialogHub.dismiss() + } + }) + } + + override fun initEvent() { + userImageView.setOnClickListener { + "尽情期待~".show(requireContext()) + } + + updateUserButton.setOnClickListener { + LoadingDialogHub.show(requireActivity(), "同步中,请稍后...") + userViewModel.obtainUserDetail() + } + + changePwdLayout.setOnClickListener { + ChangePasswordDialog.Builder() + .setContext(requireContext()) + .setOnDialogButtonClickListener(object : + ChangePasswordDialog.OnDialogButtonClickListener { + override fun onConfirmClick(oldPwd: String, newPwd: String) { + val publicKey = + RSAUtils.keyStrToPublicKey(AuthenticationHelper.publicKey)!! + val oldPassKey = RSAUtils.encryptDataByPublicKey( + oldPwd.toByteArray(StandardCharsets.UTF_8), + publicKey + ) + val newPassKey = RSAUtils.encryptDataByPublicKey( + newPwd.toByteArray(StandardCharsets.UTF_8), + publicKey + ) + userViewModel.changePassword(oldPassKey, newPassKey) + } + }).build().show() + } + + aboutUsLayout.setOnClickListener { + requireContext().navigatePageTo() + } + + updateVersionLayout.setOnClickListener { + versionViewModel.updateVersion() + } + + clearCacheLayout.setOnClickListener { + //删除缓存之后在设置缓存大小 + LoadingDialogHub.show(requireActivity(), "清理中,请稍后") + File(requireContext().cacheDir.path).deleteFile() + object : CountDownTimer(1500, 500) { + override fun onTick(millisUntilFinished: Long) { + + } + + override fun onFinish() { + LoadingDialogHub.dismiss() + cacheSizeView.text = collectApplicationCache().formatFileSize() + } + }.start() + } + + loginOutButton.setOnClickListener { + AlertControlDialog.Builder() + .setContext(requireContext()) + .setTitle("退出登录") + .setMessage("确定要退出吗?") + .setNegativeButton("取消") + .setPositiveButton("确定") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + loginViewModel.out() + } + + override fun onCancelClick() {} + }).build().show() + } + } + + private fun dataObserve() { + userViewModel.userDetailModel.observe(this, { + if (it.code == 200) { + LoadingDialogHub.dismiss() + "同步完成".show(requireContext()) + userData = it.data + updateUserInfo() + } + }) + + versionViewModel.versionResultModel.observe(this, { + if (BuildConfig.VERSION_NAME == it.version) { + "已是最新版本,无需更新".show(requireContext()) + } else { + AlertControlDialog.Builder() + .setContext(requireContext()) + .setTitle("提示") + .setMessage("有新版本,是否更新?") + .setNegativeButton("稍后再说") + .setPositiveButton("立即下载") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + downloadApk(it.downloadUrl) + } + + override fun onCancelClick() { + + } + }).build().show() + } + }) + + loginViewModel.outResultModel.observe(this, { + if (it.code == 200) { + AuthenticationHelper.removeToken() + requireContext().navigatePageTo() + PageNavigationManager.finishAllActivity() + } + }) + } + + private fun collectApplicationCache(): Long { + return File(requireContext().cacheDir.path).calculateSize() + } + + override fun onResume() { + val userDetailJson = SaveKeyValues.getValue(LocaleConstant.USER_DETAIL_MODEL, "") as String + if (userDetailJson.isNotBlank()) { + userData = Gson().fromJson( + userDetailJson, object : TypeToken() {}.type + ) + updateUserInfo() + } + //缓存 + cacheSizeView.text = collectApplicationCache().formatFileSize() + super.onResume() + } + + private fun updateUserInfo() { + //设置头像,圆形,暂时是默认的 +// val roundDrawable = +// BitmapFactory.decodeResource(requireContext().resources, R.mipmap.login_casic) +// .createRoundDrawable( +// requireContext(), +// 3f.dp2px(requireContext()), +// R.color.mainThemeColor.convertColor(requireContext()) +// ) +// userImageView.setImageDrawable(roundDrawable) + userNameView.text = userData.name + userPhoneView.text = String.format("电话:${userData.phone}") + userDeptView.text = String.format("部门:${userData.deptName}") + } + + private fun downloadApk(url: String?) { + progressDialog.setMessage("下载新版本中...") + progressDialog.show() + if (url.toString().isBlank()) { + "抱歉,版本下载失败".show(requireContext()) + return + } + /** + * http://111.198.10.15:11304/static/apk/1.0.1.apk + * */ + val downloadPath = url!!.appendDownloadUrl() + //开始下载 + downloadPath.downloadFile(requireContext().createDownloadFileDir().toString(), + object : OnDownloadListener { + override fun onDownloadStart(totalBytes: Long) { + progressDialog.max = totalBytes.toInt() + } + + override fun onProgressChanged(currentBytes: Long) { + progressDialog.progress = currentBytes.toInt() + } + + override fun onDownloadEnd(file: File?) { + progressDialog.dismiss() + progressDialog.progress = 0 + //安装APK + installApk(file) + } + }) + } + + private fun installApk(apkPackage: File?) { + if (apkPackage == null) { + "文件异常,无法安装".show(requireContext()) + return + } + val intent = Intent(Intent.ACTION_VIEW) + val data: Uri + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { //判断版本大于等于7.0 + data = FileProvider.getUriForFile( + requireContext(), LocaleConstant.APP_AUTHORITY, apkPackage + ) + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) // 给目标应用一个临时授权 + } else { + data = Uri.fromFile(apkPackage) + } + intent.setDataAndType(data, "application/vnd.android.package-archive") + requireContext().startActivity(intent) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java new file mode 100644 index 0000000..467e33d --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java @@ -0,0 +1,43 @@ +package com.casic.smart.town.sanxi.model; + +/** + * 普通实体类,失败/成功数据结构一致 + */ +public class CommonResultModel { + private int code; + private String data; + private String message; + private boolean isSuccess; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return isSuccess; + } + + public void setSuccess(boolean success) { + isSuccess = success; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java new file mode 100644 index 0000000..9bdabef --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java @@ -0,0 +1,62 @@ +package com.casic.smart.town.sanxi.model; + +public class LoginResultModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private String kaptcha; + private String token; + + public String getKaptcha() { + return kaptcha; + } + + public void setKaptcha(String kaptcha) { + this.kaptcha = kaptcha; + } + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java new file mode 100644 index 0000000..09f8d59 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java @@ -0,0 +1,80 @@ +package com.casic.smart.town.sanxi.model; + +public class PublicKeyModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private boolean appKaptcha; + private boolean kaptcha; + private String publicKey; + private String sid; + + public boolean isAppKaptcha() { + return appKaptcha; + } + + public void setAppKaptcha(boolean appKaptcha) { + this.appKaptcha = appKaptcha; + } + + public boolean isKaptcha() { + return kaptcha; + } + + public void setKaptcha(boolean kaptcha) { + this.kaptcha = kaptcha; + } + + public String getPublicKey() { + return publicKey; + } + + public void setPublicKey(String publicKey) { + this.publicKey = publicKey; + } + + public String getSid() { + return sid; + } + + public void setSid(String sid) { + this.sid = sid; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java new file mode 100644 index 0000000..db577fb --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java @@ -0,0 +1,227 @@ +package com.casic.smart.town.sanxi.model; + + +import java.util.List; + +public class UserDetailModel { + private int code; + private Data data; + private String message; + private boolean success; + + public void setCode(int code) { + this.code = code; + } + + public int getCode() { + return code; + } + + public void setData(Data data) { + this.data = data; + } + + public Data getData() { + return data; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getMessage() { + return message; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public boolean getSuccess() { + return success; + } + + public static class Data { + + private String account; + private String attr1; + private String avatar; + private String bizData; + private List dataScope; + private String deptId; + private String deptName; + private List devices; + private String id; + private String ipAddr; + private String name; + private String phone; + private List roleList; + private List roleNames; + private List roleTips; + private String scopeType; + private String sysData; + private String targetId; + private String targetName; + private String tenantId; + + public void setAccount(String account) { + this.account = account; + } + + public String getAccount() { + return account; + } + + public void setAttr1(String attr1) { + this.attr1 = attr1; + } + + public String getAttr1() { + return attr1; + } + + public void setAvatar(String avatar) { + this.avatar = avatar; + } + + public String getAvatar() { + return avatar; + } + + public void setBizData(String bizData) { + this.bizData = bizData; + } + + public String getBizData() { + return bizData; + } + + public void setDataScope(List dataScope) { + this.dataScope = dataScope; + } + + public List getDataScope() { + return dataScope; + } + + public void setDeptId(String deptId) { + this.deptId = deptId; + } + + public String getDeptId() { + return deptId; + } + + public void setDeptName(String deptName) { + this.deptName = deptName; + } + + public String getDeptName() { + return deptName; + } + + public void setDevices(List devices) { + this.devices = devices; + } + + public List getDevices() { + return devices; + } + + public void setId(String id) { + this.id = id; + } + + public String getId() { + return id; + } + + public void setIpAddr(String ipAddr) { + this.ipAddr = ipAddr; + } + + public String getIpAddr() { + return ipAddr; + } + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setPhone(String phone) { + this.phone = phone; + } + + public String getPhone() { + return phone; + } + + public void setRoleList(List roleList) { + this.roleList = roleList; + } + + public List getRoleList() { + return roleList; + } + + public void setRoleNames(List roleNames) { + this.roleNames = roleNames; + } + + public List getRoleNames() { + return roleNames; + } + + public void setRoleTips(List roleTips) { + this.roleTips = roleTips; + } + + public List getRoleTips() { + return roleTips; + } + + public void setScopeType(String scopeType) { + this.scopeType = scopeType; + } + + public String getScopeType() { + return scopeType; + } + + public void setSysData(String sysData) { + this.sysData = sysData; + } + + public String getSysData() { + return sysData; + } + + public void setTargetId(String targetId) { + this.targetId = targetId; + } + + public String getTargetId() { + return targetId; + } + + public void setTargetName(String targetName) { + this.targetName = targetName; + } + + public String getTargetName() { + return targetName; + } + + public void setTenantId(String tenantId) { + this.tenantId = tenantId; + } + + public String getTenantId() { + return tenantId; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/VersionResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/VersionResultModel.java new file mode 100644 index 0000000..79374e0 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/VersionResultModel.java @@ -0,0 +1,61 @@ +package com.casic.smart.town.sanxi.model; + +public class VersionResultModel { + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private String downloadUrl; + private String version; + + public String getDownloadUrl() { + return downloadUrl; + } + + public void setDownloadUrl(String downloadUrl) { + this.downloadUrl = downloadUrl; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/AuthenticationHelper.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/AuthenticationHelper.kt new file mode 100644 index 0000000..f498e4e --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/AuthenticationHelper.kt @@ -0,0 +1,24 @@ +package com.casic.smart.town.sanxi.util + +import com.pengxh.kt.lite.utils.SaveKeyValues + +object AuthenticationHelper { + + fun savePublicKey(key: String) { + SaveKeyValues.putValue("keyString", key) + } + + val publicKey: String? + get() = SaveKeyValues.getValue("keyString", "") as String? + + fun saveToken(token: String?) { + SaveKeyValues.putValue("token", token!!) + } + + val token: String? + get() = SaveKeyValues.getValue("token", "") as String? + + fun removeToken() { + SaveKeyValues.removeKey("token") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt index ab1a36d..99f0d19 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt @@ -14,5 +14,9 @@ const val PAGE_LIMIT = 20 const val DEFAULT_SERVER_CONFIG = "defaultServerConfig" - const val DEFAULT_SERVER = "http://111.198.10.15:11308" + const val DEFAULT_SERVER = "http://36.133.189.112:8082" + const val APP_AUTHORITY = "com.casic.smart.town.sanxi.fileprovider" + const val ACCOUNT = "account" + const val PASSWORD = "password" + const val USER_DETAIL_MODEL = "userDetailModel" } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/RSAUtils.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/RSAUtils.kt new file mode 100644 index 0000000..b9216ba --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/RSAUtils.kt @@ -0,0 +1,55 @@ +package com.casic.smart.town.sanxi.util + +import android.util.Base64 +import java.security.* +import java.security.spec.InvalidKeySpecException +import java.security.spec.X509EncodedKeySpec +import javax.crypto.BadPaddingException +import javax.crypto.Cipher +import javax.crypto.IllegalBlockSizeException +import javax.crypto.NoSuchPaddingException + +object RSAUtils { + //构建Cipher实例时所传入的的字符串,默认为"RSA/NONE/PKCS1Padding" + private fun processData(srcData: ByteArray, key: Key): ByteArray? { //用来保存处理结果 + var resultBytes: ByteArray? = null + try { //获取Cipher实例 + val cipher = Cipher.getInstance("RSA/None/PKCS1Padding") + //初始化Cipher,mode指定是加密还是解密,key为公钥或私钥 + cipher.init(Cipher.ENCRYPT_MODE, key) + //处理数据 + resultBytes = cipher.doFinal(srcData) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: NoSuchPaddingException) { + e.printStackTrace() + } catch (e: InvalidKeyException) { + e.printStackTrace() + } catch (e: BadPaddingException) { + e.printStackTrace() + } catch (e: IllegalBlockSizeException) { + e.printStackTrace() + } + return resultBytes + } + + fun encryptDataByPublicKey(srcData: ByteArray, publicKey: PublicKey): String { + val resultBytes = processData(srcData, publicKey) + return Base64.encodeToString(resultBytes, Base64.DEFAULT) + } + + fun keyStrToPublicKey(publicKeyStr: String?): PublicKey? { + var publicKey: PublicKey? = null + val keyBytes = Base64.decode(publicKeyStr, Base64.DEFAULT) + val keySpec = X509EncodedKeySpec(keyBytes) + try { + val keyFactory = KeyFactory.getInstance("RSA") + publicKey = keyFactory.generatePublic(keySpec) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: InvalidKeySpecException) { + e.printStackTrace() + } + return publicKey + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt index 63db0ec..50d1742 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt @@ -1,12 +1,64 @@ package com.casic.smart.town.sanxi.util.retrofit -import retrofit2.http.GET +import retrofit2.http.* interface RetrofitService { + /** + * PublicKey校验 + */ + @GET("/config/baseConfig") + suspend fun obtainPublicKey(): String + + /** + * 登录并获取Token + * + * @param sid + * @param account 用户名 + * @param secretKey 加密后的密码 + */ + @FormUrlEncoded + @POST("/user/login") + suspend fun login( + @Field("sid") sid: String, + @Field("username") account: String, + @Field("password") secretKey: String + ): String + + /** + * 退出登录 + */ + @GET("/user/logout") + suspend fun loginOut(@Header("token") token: String): String + + /** + * 获取用户信息 + */ + @GET("/user/info") + suspend fun obtainUserDetail(@Header("token") token: String): String + + /** + * 修改密码 + * + * @param oldPwd 旧密码 + * @param newPwd 新密码 + */ + @FormUrlEncoded + @POST("/mgr/changePwd") + suspend fun changePassword( + @Header("token") token: String, + @Field("oldPwd") oldPwd: String, + @Field("newPwd") newPwd: String + ): String /** * 根据布防状态统计闸井数量接口 */ @GET("/well/countByBfzt") - suspend fun countWellByState(): String + suspend fun countWellByState(@Header("token") token: String): String + + /** + * 更新APK版本 + */ + @POST("/app/checkVersion") + suspend fun obtainVersionResult(@Header("token") token: String): String } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt index 46bbf4b..410444b 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt @@ -1,5 +1,6 @@ package com.casic.smart.town.sanxi.util.retrofit +import com.casic.smart.town.sanxi.util.AuthenticationHelper import com.casic.smart.town.sanxi.util.LocaleConstant import com.pengxh.kt.lite.utils.RetrofitFactory import com.pengxh.kt.lite.utils.SaveKeyValues @@ -15,9 +16,52 @@ } /** + * 验证PublicKey + */ + suspend fun authenticate(): String { + return api.obtainPublicKey() + } + + /** + * 登录并获取Token + */ + suspend fun login(sid: String, account: String, secretKey: String): String { + return api.login(sid, account, secretKey) + } + + /** + * 退出登录 + */ + suspend fun loginOut(): String { + return api.loginOut(AuthenticationHelper.token!!) + } + + /** + * 获取用户信息 + */ + suspend fun obtainUserDetail(): String { + return api.obtainUserDetail(AuthenticationHelper.token!!) + } + + /** + * 修改密码 + */ + suspend fun changePassword(oldPwd: String, newPwd: String): String { + return api.changePassword(AuthenticationHelper.token!!, oldPwd, newPwd) + } + + /** * 根据布防状态统计闸井数量接口 */ suspend fun countWellByState(): String { - return api.countWellByState() + return api.countWellByState(AuthenticationHelper.token!!) } + + /** + * 更新APK版本 + */ + suspend fun updateVersion(): String { + return api.obtainVersionResult(AuthenticationHelper.token!!) + } + } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/AboutUsActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/AboutUsActivity.kt new file mode 100644 index 0000000..7e43c47 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/AboutUsActivity.kt @@ -0,0 +1,34 @@ +package com.casic.smart.town.sanxi.view + +import com.casic.smart.town.sanxi.BuildConfig +import com.casic.smart.town.sanxi.R +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.kt.lite.base.KotlinBaseActivity +import com.pengxh.kt.lite.extensions.convertColor +import com.pengxh.kt.lite.utils.ImmerseStatusBarUtil +import kotlinx.android.synthetic.main.activity_about_us.* +import kotlinx.android.synthetic.main.include_base_title.* + +class AboutUsActivity : KotlinBaseActivity() { + + override fun initLayoutView(): Int = R.layout.activity_about_us + + override fun setupTopBarLayout() { + ImmersionBar.with(this).statusBarDarkFont(false).init() + ImmerseStatusBarUtil.setColor(this, R.color.mainThemeColor.convertColor(this)) + titleView.text = "关于我们" + leftBackView.setOnClickListener { finish() } + } + + override fun initData() { + versionCodeView.text = String.format("版本信息:V${BuildConfig.VERSION_NAME}") + } + + override fun observeRequestState() { + + } + + override fun initEvent() { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/LoginActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/LoginActivity.kt new file mode 100644 index 0000000..b5d1d50 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/LoginActivity.kt @@ -0,0 +1,97 @@ +package com.casic.smart.town.sanxi.view + +import androidx.lifecycle.ViewModelProvider +import com.casic.smart.town.sanxi.R +import com.casic.smart.town.sanxi.util.AuthenticationHelper +import com.casic.smart.town.sanxi.util.LoadingDialogHub +import com.casic.smart.town.sanxi.util.LocaleConstant +import com.casic.smart.town.sanxi.util.RSAUtils +import com.casic.smart.town.sanxi.vm.AuthenticateViewModel +import com.casic.smart.town.sanxi.vm.LoginViewModel +import com.casic.smart.town.sanxi.vm.UserViewModel +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.kt.lite.base.KotlinBaseActivity +import com.pengxh.kt.lite.extensions.navigatePageTo +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.utils.SaveKeyValues +import kotlinx.android.synthetic.main.activity_login.* + +class LoginActivity : KotlinBaseActivity() { + + private lateinit var authenticateViewModel: AuthenticateViewModel + private lateinit var loginViewModel: LoginViewModel + private lateinit var userViewModel: UserViewModel + + override fun initLayoutView(): Int = R.layout.activity_login + + override fun setupTopBarLayout() { + ImmersionBar.with(this).statusBarDarkFont(true).init() + } + + override fun initData() { + // 设置默认账号密码 + userNameView.setText(SaveKeyValues.getValue(LocaleConstant.ACCOUNT, "") as String) + userPasswordView.setText(SaveKeyValues.getValue(LocaleConstant.PASSWORD, "") as String) + authenticateViewModel = ViewModelProvider(this)[AuthenticateViewModel::class.java] + loginViewModel = ViewModelProvider(this)[LoginViewModel::class.java] + userViewModel = ViewModelProvider(this)[UserViewModel::class.java] + + authenticateViewModel.keyModel.observe(this, { + if (it.code == 200) {//用code判断,别的判断可能有坑 + val keyString = it.data!!.publicKey!! + /** + * 修改密码需要用到key,先存着 + * */ + AuthenticationHelper.savePublicKey(keyString) + val publicKey = RSAUtils.keyStrToPublicKey(keyString) + + val account = userNameView.text.toString() + val userPassword = userPasswordView.text.toString() + val dataByPublicKey = + RSAUtils.encryptDataByPublicKey(userPassword.toByteArray(), publicKey!!) + //登录并获取Token,POST请求 + loginViewModel.enter(it.data!!.sid!!, account, dataByPublicKey) + loginViewModel.enterResultModel.observe(this, { loginResult -> + if (loginResult.code == 200) { + AuthenticationHelper.saveToken(loginResult.data!!.token!!) + /** + * 获取token之后保存用户信息 + * */ + userViewModel.obtainUserDetail() + //验证成功登录 + navigatePageTo() + finish() + } + }) + } + }) + } + + override fun observeRequestState() { + authenticateViewModel.loadState.observe(this, { + LoadingDialogHub.show(this, "登录中,请稍后") + }) + + loginViewModel.loadState.observe(this, { + LoadingDialogHub.dismiss() + }) + } + + override fun initEvent() { + loginButton.setOnClickListener { + val account = userNameView.text.toString().trim() + val userPassword = userPasswordView.text.toString().trim() + if (account.isBlank()) { + "用户名不能为空".show(this) + return@setOnClickListener + } + if (userPassword.isBlank()) { + "密码不能为空".show(this) + return@setOnClickListener + } + SaveKeyValues.putValue(LocaleConstant.ACCOUNT, account) + SaveKeyValues.putValue(LocaleConstant.PASSWORD, userPassword) + authenticateViewModel.obtainPublicKey() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt index 3bea1c9..a4fa951 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt @@ -7,6 +7,7 @@ import com.casic.smart.town.sanxi.R import com.casic.smart.town.sanxi.adapter.ViewPagerAdapter import com.casic.smart.town.sanxi.fragment.AlarmPageFragment +import com.casic.smart.town.sanxi.fragment.MinePageFragment import com.casic.smart.town.sanxi.fragment.MonitorPageFragment import com.casic.smart.town.sanxi.fragment.StatisticsPageFragment import com.gyf.immersionbar.ImmersionBar @@ -33,6 +34,7 @@ fragmentPages.add(StatisticsPageFragment()) fragmentPages.add(MonitorPageFragment()) fragmentPages.add(AlarmPageFragment()) + fragmentPages.add(MinePageFragment()) } override fun initLayoutView(): Int = R.layout.activity_main @@ -68,6 +70,9 @@ R.id.nav_alarm -> { mainViewPager.currentItem = 2 } + R.id.nav_more -> { + mainViewPager.currentItem = 3 + } } false } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1df917d..8d4f33b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -6,6 +6,7 @@ + @@ -32,11 +33,14 @@ + + + + + + + diff --git a/app/src/main/assets/svg/mine.svg b/app/src/main/assets/svg/mine.svg new file mode 100644 index 0000000..3fbb0a3 --- /dev/null +++ b/app/src/main/assets/svg/mine.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt b/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt index d730417..b1972c2 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt @@ -1,8 +1,10 @@ package com.casic.smart.town.sanxi.extensions import com.casic.smart.town.sanxi.model.ErrorMessageModel +import com.casic.smart.town.sanxi.util.LocaleConstant import com.google.gson.Gson import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.utils.SaveKeyValues import org.json.JSONObject fun String.separateResponseCode(): Int { @@ -17,4 +19,15 @@ this, object : TypeToken() {}.type ) return errorModel.message.toString() +} + +/** + * 下载路径为 http://xx.com/static/ 拼接downloadUrl + * */ +fun String.appendDownloadUrl(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + LocaleConstant.DEFAULT_SERVER_CONFIG, LocaleConstant.DEFAULT_SERVER + ) as String + return "$defaultValue/static/${this}" } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt b/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt new file mode 100644 index 0000000..1809dc5 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt @@ -0,0 +1,288 @@ +package com.casic.smart.town.sanxi.fragment + +import android.app.ProgressDialog +import android.content.Intent +import android.net.Uri +import android.os.Build +import android.os.CountDownTimer +import androidx.core.content.FileProvider +import androidx.lifecycle.ViewModelProvider +import com.casic.smart.town.sanxi.BuildConfig +import com.casic.smart.town.sanxi.R +import com.casic.smart.town.sanxi.extensions.appendDownloadUrl +import com.casic.smart.town.sanxi.model.UserDetailModel +import com.casic.smart.town.sanxi.util.AuthenticationHelper +import com.casic.smart.town.sanxi.util.LoadingDialogHub +import com.casic.smart.town.sanxi.util.LocaleConstant +import com.casic.smart.town.sanxi.util.RSAUtils +import com.casic.smart.town.sanxi.view.AboutUsActivity +import com.casic.smart.town.sanxi.view.LoginActivity +import com.casic.smart.town.sanxi.vm.LoginViewModel +import com.casic.smart.town.sanxi.vm.UserViewModel +import com.casic.smart.town.sanxi.vm.VersionViewModel +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.base.KotlinBaseFragment +import com.pengxh.kt.lite.callback.OnDownloadListener +import com.pengxh.kt.lite.extensions.* +import com.pengxh.kt.lite.utils.PageNavigationManager +import com.pengxh.kt.lite.utils.SaveKeyValues +import com.pengxh.kt.lite.vm.LoadState +import com.pengxh.kt.lite.widget.dialog.AlertControlDialog +import com.pengxh.kt.lite.widget.dialog.ChangePasswordDialog +import kotlinx.android.synthetic.main.fragment_mine.* +import java.io.File +import java.nio.charset.StandardCharsets + +class MinePageFragment : KotlinBaseFragment() { + + private lateinit var userData: UserDetailModel.Data + private lateinit var userViewModel: UserViewModel + private lateinit var loginViewModel: LoginViewModel + private lateinit var versionViewModel: VersionViewModel + private lateinit var progressDialog: ProgressDialog + + override fun initLayoutView(): Int = R.layout.fragment_mine + + override fun setupTopBarLayout() { + + } + + override fun initData() { + userViewModel = ViewModelProvider(this)[UserViewModel::class.java] + loginViewModel = ViewModelProvider(this)[LoginViewModel::class.java] + versionViewModel = ViewModelProvider(this)[VersionViewModel::class.java] + + //初始化下载对话框 + progressDialog = ProgressDialog(requireContext()) + progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL) + progressDialog.setProgressDrawable(resources.getDrawable(R.drawable.download_progress)) + progressDialog.setCanceledOnTouchOutside(false) + progressDialog.setCancelable(false) + + /** + * 数据监听 + * */ + dataObserve() + } + + override fun observeRequestState() { + userViewModel.loadState.observe(this, { + when (it) { + is LoadState.Loading -> LoadingDialogHub.show(requireActivity(), "修改中,请稍后") + is LoadState.Success -> { + "修改成功,请重新登录".show(requireContext()) + LoadingDialogHub.dismiss() + AuthenticationHelper.removeToken() + requireContext().navigatePageTo() + PageNavigationManager.finishAllActivity() + } + else -> LoadingDialogHub.dismiss() + } + }) + + versionViewModel.loadState.observe(this, { + when (it) { + is LoadState.Loading -> LoadingDialogHub.show(requireActivity(), "检查版本中,请稍后") + else -> LoadingDialogHub.dismiss() + } + }) + } + + override fun initEvent() { + userImageView.setOnClickListener { + "尽情期待~".show(requireContext()) + } + + updateUserButton.setOnClickListener { + LoadingDialogHub.show(requireActivity(), "同步中,请稍后...") + userViewModel.obtainUserDetail() + } + + changePwdLayout.setOnClickListener { + ChangePasswordDialog.Builder() + .setContext(requireContext()) + .setOnDialogButtonClickListener(object : + ChangePasswordDialog.OnDialogButtonClickListener { + override fun onConfirmClick(oldPwd: String, newPwd: String) { + val publicKey = + RSAUtils.keyStrToPublicKey(AuthenticationHelper.publicKey)!! + val oldPassKey = RSAUtils.encryptDataByPublicKey( + oldPwd.toByteArray(StandardCharsets.UTF_8), + publicKey + ) + val newPassKey = RSAUtils.encryptDataByPublicKey( + newPwd.toByteArray(StandardCharsets.UTF_8), + publicKey + ) + userViewModel.changePassword(oldPassKey, newPassKey) + } + }).build().show() + } + + aboutUsLayout.setOnClickListener { + requireContext().navigatePageTo() + } + + updateVersionLayout.setOnClickListener { + versionViewModel.updateVersion() + } + + clearCacheLayout.setOnClickListener { + //删除缓存之后在设置缓存大小 + LoadingDialogHub.show(requireActivity(), "清理中,请稍后") + File(requireContext().cacheDir.path).deleteFile() + object : CountDownTimer(1500, 500) { + override fun onTick(millisUntilFinished: Long) { + + } + + override fun onFinish() { + LoadingDialogHub.dismiss() + cacheSizeView.text = collectApplicationCache().formatFileSize() + } + }.start() + } + + loginOutButton.setOnClickListener { + AlertControlDialog.Builder() + .setContext(requireContext()) + .setTitle("退出登录") + .setMessage("确定要退出吗?") + .setNegativeButton("取消") + .setPositiveButton("确定") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + loginViewModel.out() + } + + override fun onCancelClick() {} + }).build().show() + } + } + + private fun dataObserve() { + userViewModel.userDetailModel.observe(this, { + if (it.code == 200) { + LoadingDialogHub.dismiss() + "同步完成".show(requireContext()) + userData = it.data + updateUserInfo() + } + }) + + versionViewModel.versionResultModel.observe(this, { + if (BuildConfig.VERSION_NAME == it.version) { + "已是最新版本,无需更新".show(requireContext()) + } else { + AlertControlDialog.Builder() + .setContext(requireContext()) + .setTitle("提示") + .setMessage("有新版本,是否更新?") + .setNegativeButton("稍后再说") + .setPositiveButton("立即下载") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + downloadApk(it.downloadUrl) + } + + override fun onCancelClick() { + + } + }).build().show() + } + }) + + loginViewModel.outResultModel.observe(this, { + if (it.code == 200) { + AuthenticationHelper.removeToken() + requireContext().navigatePageTo() + PageNavigationManager.finishAllActivity() + } + }) + } + + private fun collectApplicationCache(): Long { + return File(requireContext().cacheDir.path).calculateSize() + } + + override fun onResume() { + val userDetailJson = SaveKeyValues.getValue(LocaleConstant.USER_DETAIL_MODEL, "") as String + if (userDetailJson.isNotBlank()) { + userData = Gson().fromJson( + userDetailJson, object : TypeToken() {}.type + ) + updateUserInfo() + } + //缓存 + cacheSizeView.text = collectApplicationCache().formatFileSize() + super.onResume() + } + + private fun updateUserInfo() { + //设置头像,圆形,暂时是默认的 +// val roundDrawable = +// BitmapFactory.decodeResource(requireContext().resources, R.mipmap.login_casic) +// .createRoundDrawable( +// requireContext(), +// 3f.dp2px(requireContext()), +// R.color.mainThemeColor.convertColor(requireContext()) +// ) +// userImageView.setImageDrawable(roundDrawable) + userNameView.text = userData.name + userPhoneView.text = String.format("电话:${userData.phone}") + userDeptView.text = String.format("部门:${userData.deptName}") + } + + private fun downloadApk(url: String?) { + progressDialog.setMessage("下载新版本中...") + progressDialog.show() + if (url.toString().isBlank()) { + "抱歉,版本下载失败".show(requireContext()) + return + } + /** + * http://111.198.10.15:11304/static/apk/1.0.1.apk + * */ + val downloadPath = url!!.appendDownloadUrl() + //开始下载 + downloadPath.downloadFile(requireContext().createDownloadFileDir().toString(), + object : OnDownloadListener { + override fun onDownloadStart(totalBytes: Long) { + progressDialog.max = totalBytes.toInt() + } + + override fun onProgressChanged(currentBytes: Long) { + progressDialog.progress = currentBytes.toInt() + } + + override fun onDownloadEnd(file: File?) { + progressDialog.dismiss() + progressDialog.progress = 0 + //安装APK + installApk(file) + } + }) + } + + private fun installApk(apkPackage: File?) { + if (apkPackage == null) { + "文件异常,无法安装".show(requireContext()) + return + } + val intent = Intent(Intent.ACTION_VIEW) + val data: Uri + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { //判断版本大于等于7.0 + data = FileProvider.getUriForFile( + requireContext(), LocaleConstant.APP_AUTHORITY, apkPackage + ) + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) // 给目标应用一个临时授权 + } else { + data = Uri.fromFile(apkPackage) + } + intent.setDataAndType(data, "application/vnd.android.package-archive") + requireContext().startActivity(intent) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java new file mode 100644 index 0000000..467e33d --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java @@ -0,0 +1,43 @@ +package com.casic.smart.town.sanxi.model; + +/** + * 普通实体类,失败/成功数据结构一致 + */ +public class CommonResultModel { + private int code; + private String data; + private String message; + private boolean isSuccess; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return isSuccess; + } + + public void setSuccess(boolean success) { + isSuccess = success; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java new file mode 100644 index 0000000..9bdabef --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java @@ -0,0 +1,62 @@ +package com.casic.smart.town.sanxi.model; + +public class LoginResultModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private String kaptcha; + private String token; + + public String getKaptcha() { + return kaptcha; + } + + public void setKaptcha(String kaptcha) { + this.kaptcha = kaptcha; + } + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java new file mode 100644 index 0000000..09f8d59 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java @@ -0,0 +1,80 @@ +package com.casic.smart.town.sanxi.model; + +public class PublicKeyModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private boolean appKaptcha; + private boolean kaptcha; + private String publicKey; + private String sid; + + public boolean isAppKaptcha() { + return appKaptcha; + } + + public void setAppKaptcha(boolean appKaptcha) { + this.appKaptcha = appKaptcha; + } + + public boolean isKaptcha() { + return kaptcha; + } + + public void setKaptcha(boolean kaptcha) { + this.kaptcha = kaptcha; + } + + public String getPublicKey() { + return publicKey; + } + + public void setPublicKey(String publicKey) { + this.publicKey = publicKey; + } + + public String getSid() { + return sid; + } + + public void setSid(String sid) { + this.sid = sid; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java new file mode 100644 index 0000000..db577fb --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java @@ -0,0 +1,227 @@ +package com.casic.smart.town.sanxi.model; + + +import java.util.List; + +public class UserDetailModel { + private int code; + private Data data; + private String message; + private boolean success; + + public void setCode(int code) { + this.code = code; + } + + public int getCode() { + return code; + } + + public void setData(Data data) { + this.data = data; + } + + public Data getData() { + return data; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getMessage() { + return message; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public boolean getSuccess() { + return success; + } + + public static class Data { + + private String account; + private String attr1; + private String avatar; + private String bizData; + private List dataScope; + private String deptId; + private String deptName; + private List devices; + private String id; + private String ipAddr; + private String name; + private String phone; + private List roleList; + private List roleNames; + private List roleTips; + private String scopeType; + private String sysData; + private String targetId; + private String targetName; + private String tenantId; + + public void setAccount(String account) { + this.account = account; + } + + public String getAccount() { + return account; + } + + public void setAttr1(String attr1) { + this.attr1 = attr1; + } + + public String getAttr1() { + return attr1; + } + + public void setAvatar(String avatar) { + this.avatar = avatar; + } + + public String getAvatar() { + return avatar; + } + + public void setBizData(String bizData) { + this.bizData = bizData; + } + + public String getBizData() { + return bizData; + } + + public void setDataScope(List dataScope) { + this.dataScope = dataScope; + } + + public List getDataScope() { + return dataScope; + } + + public void setDeptId(String deptId) { + this.deptId = deptId; + } + + public String getDeptId() { + return deptId; + } + + public void setDeptName(String deptName) { + this.deptName = deptName; + } + + public String getDeptName() { + return deptName; + } + + public void setDevices(List devices) { + this.devices = devices; + } + + public List getDevices() { + return devices; + } + + public void setId(String id) { + this.id = id; + } + + public String getId() { + return id; + } + + public void setIpAddr(String ipAddr) { + this.ipAddr = ipAddr; + } + + public String getIpAddr() { + return ipAddr; + } + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setPhone(String phone) { + this.phone = phone; + } + + public String getPhone() { + return phone; + } + + public void setRoleList(List roleList) { + this.roleList = roleList; + } + + public List getRoleList() { + return roleList; + } + + public void setRoleNames(List roleNames) { + this.roleNames = roleNames; + } + + public List getRoleNames() { + return roleNames; + } + + public void setRoleTips(List roleTips) { + this.roleTips = roleTips; + } + + public List getRoleTips() { + return roleTips; + } + + public void setScopeType(String scopeType) { + this.scopeType = scopeType; + } + + public String getScopeType() { + return scopeType; + } + + public void setSysData(String sysData) { + this.sysData = sysData; + } + + public String getSysData() { + return sysData; + } + + public void setTargetId(String targetId) { + this.targetId = targetId; + } + + public String getTargetId() { + return targetId; + } + + public void setTargetName(String targetName) { + this.targetName = targetName; + } + + public String getTargetName() { + return targetName; + } + + public void setTenantId(String tenantId) { + this.tenantId = tenantId; + } + + public String getTenantId() { + return tenantId; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/VersionResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/VersionResultModel.java new file mode 100644 index 0000000..79374e0 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/VersionResultModel.java @@ -0,0 +1,61 @@ +package com.casic.smart.town.sanxi.model; + +public class VersionResultModel { + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private String downloadUrl; + private String version; + + public String getDownloadUrl() { + return downloadUrl; + } + + public void setDownloadUrl(String downloadUrl) { + this.downloadUrl = downloadUrl; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/AuthenticationHelper.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/AuthenticationHelper.kt new file mode 100644 index 0000000..f498e4e --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/AuthenticationHelper.kt @@ -0,0 +1,24 @@ +package com.casic.smart.town.sanxi.util + +import com.pengxh.kt.lite.utils.SaveKeyValues + +object AuthenticationHelper { + + fun savePublicKey(key: String) { + SaveKeyValues.putValue("keyString", key) + } + + val publicKey: String? + get() = SaveKeyValues.getValue("keyString", "") as String? + + fun saveToken(token: String?) { + SaveKeyValues.putValue("token", token!!) + } + + val token: String? + get() = SaveKeyValues.getValue("token", "") as String? + + fun removeToken() { + SaveKeyValues.removeKey("token") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt index ab1a36d..99f0d19 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt @@ -14,5 +14,9 @@ const val PAGE_LIMIT = 20 const val DEFAULT_SERVER_CONFIG = "defaultServerConfig" - const val DEFAULT_SERVER = "http://111.198.10.15:11308" + const val DEFAULT_SERVER = "http://36.133.189.112:8082" + const val APP_AUTHORITY = "com.casic.smart.town.sanxi.fileprovider" + const val ACCOUNT = "account" + const val PASSWORD = "password" + const val USER_DETAIL_MODEL = "userDetailModel" } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/RSAUtils.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/RSAUtils.kt new file mode 100644 index 0000000..b9216ba --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/RSAUtils.kt @@ -0,0 +1,55 @@ +package com.casic.smart.town.sanxi.util + +import android.util.Base64 +import java.security.* +import java.security.spec.InvalidKeySpecException +import java.security.spec.X509EncodedKeySpec +import javax.crypto.BadPaddingException +import javax.crypto.Cipher +import javax.crypto.IllegalBlockSizeException +import javax.crypto.NoSuchPaddingException + +object RSAUtils { + //构建Cipher实例时所传入的的字符串,默认为"RSA/NONE/PKCS1Padding" + private fun processData(srcData: ByteArray, key: Key): ByteArray? { //用来保存处理结果 + var resultBytes: ByteArray? = null + try { //获取Cipher实例 + val cipher = Cipher.getInstance("RSA/None/PKCS1Padding") + //初始化Cipher,mode指定是加密还是解密,key为公钥或私钥 + cipher.init(Cipher.ENCRYPT_MODE, key) + //处理数据 + resultBytes = cipher.doFinal(srcData) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: NoSuchPaddingException) { + e.printStackTrace() + } catch (e: InvalidKeyException) { + e.printStackTrace() + } catch (e: BadPaddingException) { + e.printStackTrace() + } catch (e: IllegalBlockSizeException) { + e.printStackTrace() + } + return resultBytes + } + + fun encryptDataByPublicKey(srcData: ByteArray, publicKey: PublicKey): String { + val resultBytes = processData(srcData, publicKey) + return Base64.encodeToString(resultBytes, Base64.DEFAULT) + } + + fun keyStrToPublicKey(publicKeyStr: String?): PublicKey? { + var publicKey: PublicKey? = null + val keyBytes = Base64.decode(publicKeyStr, Base64.DEFAULT) + val keySpec = X509EncodedKeySpec(keyBytes) + try { + val keyFactory = KeyFactory.getInstance("RSA") + publicKey = keyFactory.generatePublic(keySpec) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: InvalidKeySpecException) { + e.printStackTrace() + } + return publicKey + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt index 63db0ec..50d1742 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt @@ -1,12 +1,64 @@ package com.casic.smart.town.sanxi.util.retrofit -import retrofit2.http.GET +import retrofit2.http.* interface RetrofitService { + /** + * PublicKey校验 + */ + @GET("/config/baseConfig") + suspend fun obtainPublicKey(): String + + /** + * 登录并获取Token + * + * @param sid + * @param account 用户名 + * @param secretKey 加密后的密码 + */ + @FormUrlEncoded + @POST("/user/login") + suspend fun login( + @Field("sid") sid: String, + @Field("username") account: String, + @Field("password") secretKey: String + ): String + + /** + * 退出登录 + */ + @GET("/user/logout") + suspend fun loginOut(@Header("token") token: String): String + + /** + * 获取用户信息 + */ + @GET("/user/info") + suspend fun obtainUserDetail(@Header("token") token: String): String + + /** + * 修改密码 + * + * @param oldPwd 旧密码 + * @param newPwd 新密码 + */ + @FormUrlEncoded + @POST("/mgr/changePwd") + suspend fun changePassword( + @Header("token") token: String, + @Field("oldPwd") oldPwd: String, + @Field("newPwd") newPwd: String + ): String /** * 根据布防状态统计闸井数量接口 */ @GET("/well/countByBfzt") - suspend fun countWellByState(): String + suspend fun countWellByState(@Header("token") token: String): String + + /** + * 更新APK版本 + */ + @POST("/app/checkVersion") + suspend fun obtainVersionResult(@Header("token") token: String): String } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt index 46bbf4b..410444b 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt @@ -1,5 +1,6 @@ package com.casic.smart.town.sanxi.util.retrofit +import com.casic.smart.town.sanxi.util.AuthenticationHelper import com.casic.smart.town.sanxi.util.LocaleConstant import com.pengxh.kt.lite.utils.RetrofitFactory import com.pengxh.kt.lite.utils.SaveKeyValues @@ -15,9 +16,52 @@ } /** + * 验证PublicKey + */ + suspend fun authenticate(): String { + return api.obtainPublicKey() + } + + /** + * 登录并获取Token + */ + suspend fun login(sid: String, account: String, secretKey: String): String { + return api.login(sid, account, secretKey) + } + + /** + * 退出登录 + */ + suspend fun loginOut(): String { + return api.loginOut(AuthenticationHelper.token!!) + } + + /** + * 获取用户信息 + */ + suspend fun obtainUserDetail(): String { + return api.obtainUserDetail(AuthenticationHelper.token!!) + } + + /** + * 修改密码 + */ + suspend fun changePassword(oldPwd: String, newPwd: String): String { + return api.changePassword(AuthenticationHelper.token!!, oldPwd, newPwd) + } + + /** * 根据布防状态统计闸井数量接口 */ suspend fun countWellByState(): String { - return api.countWellByState() + return api.countWellByState(AuthenticationHelper.token!!) } + + /** + * 更新APK版本 + */ + suspend fun updateVersion(): String { + return api.obtainVersionResult(AuthenticationHelper.token!!) + } + } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/AboutUsActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/AboutUsActivity.kt new file mode 100644 index 0000000..7e43c47 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/AboutUsActivity.kt @@ -0,0 +1,34 @@ +package com.casic.smart.town.sanxi.view + +import com.casic.smart.town.sanxi.BuildConfig +import com.casic.smart.town.sanxi.R +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.kt.lite.base.KotlinBaseActivity +import com.pengxh.kt.lite.extensions.convertColor +import com.pengxh.kt.lite.utils.ImmerseStatusBarUtil +import kotlinx.android.synthetic.main.activity_about_us.* +import kotlinx.android.synthetic.main.include_base_title.* + +class AboutUsActivity : KotlinBaseActivity() { + + override fun initLayoutView(): Int = R.layout.activity_about_us + + override fun setupTopBarLayout() { + ImmersionBar.with(this).statusBarDarkFont(false).init() + ImmerseStatusBarUtil.setColor(this, R.color.mainThemeColor.convertColor(this)) + titleView.text = "关于我们" + leftBackView.setOnClickListener { finish() } + } + + override fun initData() { + versionCodeView.text = String.format("版本信息:V${BuildConfig.VERSION_NAME}") + } + + override fun observeRequestState() { + + } + + override fun initEvent() { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/LoginActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/LoginActivity.kt new file mode 100644 index 0000000..b5d1d50 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/LoginActivity.kt @@ -0,0 +1,97 @@ +package com.casic.smart.town.sanxi.view + +import androidx.lifecycle.ViewModelProvider +import com.casic.smart.town.sanxi.R +import com.casic.smart.town.sanxi.util.AuthenticationHelper +import com.casic.smart.town.sanxi.util.LoadingDialogHub +import com.casic.smart.town.sanxi.util.LocaleConstant +import com.casic.smart.town.sanxi.util.RSAUtils +import com.casic.smart.town.sanxi.vm.AuthenticateViewModel +import com.casic.smart.town.sanxi.vm.LoginViewModel +import com.casic.smart.town.sanxi.vm.UserViewModel +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.kt.lite.base.KotlinBaseActivity +import com.pengxh.kt.lite.extensions.navigatePageTo +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.utils.SaveKeyValues +import kotlinx.android.synthetic.main.activity_login.* + +class LoginActivity : KotlinBaseActivity() { + + private lateinit var authenticateViewModel: AuthenticateViewModel + private lateinit var loginViewModel: LoginViewModel + private lateinit var userViewModel: UserViewModel + + override fun initLayoutView(): Int = R.layout.activity_login + + override fun setupTopBarLayout() { + ImmersionBar.with(this).statusBarDarkFont(true).init() + } + + override fun initData() { + // 设置默认账号密码 + userNameView.setText(SaveKeyValues.getValue(LocaleConstant.ACCOUNT, "") as String) + userPasswordView.setText(SaveKeyValues.getValue(LocaleConstant.PASSWORD, "") as String) + authenticateViewModel = ViewModelProvider(this)[AuthenticateViewModel::class.java] + loginViewModel = ViewModelProvider(this)[LoginViewModel::class.java] + userViewModel = ViewModelProvider(this)[UserViewModel::class.java] + + authenticateViewModel.keyModel.observe(this, { + if (it.code == 200) {//用code判断,别的判断可能有坑 + val keyString = it.data!!.publicKey!! + /** + * 修改密码需要用到key,先存着 + * */ + AuthenticationHelper.savePublicKey(keyString) + val publicKey = RSAUtils.keyStrToPublicKey(keyString) + + val account = userNameView.text.toString() + val userPassword = userPasswordView.text.toString() + val dataByPublicKey = + RSAUtils.encryptDataByPublicKey(userPassword.toByteArray(), publicKey!!) + //登录并获取Token,POST请求 + loginViewModel.enter(it.data!!.sid!!, account, dataByPublicKey) + loginViewModel.enterResultModel.observe(this, { loginResult -> + if (loginResult.code == 200) { + AuthenticationHelper.saveToken(loginResult.data!!.token!!) + /** + * 获取token之后保存用户信息 + * */ + userViewModel.obtainUserDetail() + //验证成功登录 + navigatePageTo() + finish() + } + }) + } + }) + } + + override fun observeRequestState() { + authenticateViewModel.loadState.observe(this, { + LoadingDialogHub.show(this, "登录中,请稍后") + }) + + loginViewModel.loadState.observe(this, { + LoadingDialogHub.dismiss() + }) + } + + override fun initEvent() { + loginButton.setOnClickListener { + val account = userNameView.text.toString().trim() + val userPassword = userPasswordView.text.toString().trim() + if (account.isBlank()) { + "用户名不能为空".show(this) + return@setOnClickListener + } + if (userPassword.isBlank()) { + "密码不能为空".show(this) + return@setOnClickListener + } + SaveKeyValues.putValue(LocaleConstant.ACCOUNT, account) + SaveKeyValues.putValue(LocaleConstant.PASSWORD, userPassword) + authenticateViewModel.obtainPublicKey() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt index 3bea1c9..a4fa951 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt @@ -7,6 +7,7 @@ import com.casic.smart.town.sanxi.R import com.casic.smart.town.sanxi.adapter.ViewPagerAdapter import com.casic.smart.town.sanxi.fragment.AlarmPageFragment +import com.casic.smart.town.sanxi.fragment.MinePageFragment import com.casic.smart.town.sanxi.fragment.MonitorPageFragment import com.casic.smart.town.sanxi.fragment.StatisticsPageFragment import com.gyf.immersionbar.ImmersionBar @@ -33,6 +34,7 @@ fragmentPages.add(StatisticsPageFragment()) fragmentPages.add(MonitorPageFragment()) fragmentPages.add(AlarmPageFragment()) + fragmentPages.add(MinePageFragment()) } override fun initLayoutView(): Int = R.layout.activity_main @@ -68,6 +70,9 @@ R.id.nav_alarm -> { mainViewPager.currentItem = 2 } + R.id.nav_more -> { + mainViewPager.currentItem = 3 + } } false } diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/PermissionActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/PermissionActivity.kt index c478d83..3238f3c 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/view/PermissionActivity.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/PermissionActivity.kt @@ -32,7 +32,7 @@ //先把导航隐私政策声明,后面导航会用到 MapsInitializer.updatePrivacyAgree(this, true) MapsInitializer.updatePrivacyShow(this, true, true) - this.navigatePageTo() + this.navigatePageTo() finish() } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1df917d..8d4f33b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -6,6 +6,7 @@ + @@ -32,11 +33,14 @@ + + + + + + + diff --git a/app/src/main/assets/svg/mine.svg b/app/src/main/assets/svg/mine.svg new file mode 100644 index 0000000..3fbb0a3 --- /dev/null +++ b/app/src/main/assets/svg/mine.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt b/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt index d730417..b1972c2 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt @@ -1,8 +1,10 @@ package com.casic.smart.town.sanxi.extensions import com.casic.smart.town.sanxi.model.ErrorMessageModel +import com.casic.smart.town.sanxi.util.LocaleConstant import com.google.gson.Gson import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.utils.SaveKeyValues import org.json.JSONObject fun String.separateResponseCode(): Int { @@ -17,4 +19,15 @@ this, object : TypeToken() {}.type ) return errorModel.message.toString() +} + +/** + * 下载路径为 http://xx.com/static/ 拼接downloadUrl + * */ +fun String.appendDownloadUrl(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + LocaleConstant.DEFAULT_SERVER_CONFIG, LocaleConstant.DEFAULT_SERVER + ) as String + return "$defaultValue/static/${this}" } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt b/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt new file mode 100644 index 0000000..1809dc5 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt @@ -0,0 +1,288 @@ +package com.casic.smart.town.sanxi.fragment + +import android.app.ProgressDialog +import android.content.Intent +import android.net.Uri +import android.os.Build +import android.os.CountDownTimer +import androidx.core.content.FileProvider +import androidx.lifecycle.ViewModelProvider +import com.casic.smart.town.sanxi.BuildConfig +import com.casic.smart.town.sanxi.R +import com.casic.smart.town.sanxi.extensions.appendDownloadUrl +import com.casic.smart.town.sanxi.model.UserDetailModel +import com.casic.smart.town.sanxi.util.AuthenticationHelper +import com.casic.smart.town.sanxi.util.LoadingDialogHub +import com.casic.smart.town.sanxi.util.LocaleConstant +import com.casic.smart.town.sanxi.util.RSAUtils +import com.casic.smart.town.sanxi.view.AboutUsActivity +import com.casic.smart.town.sanxi.view.LoginActivity +import com.casic.smart.town.sanxi.vm.LoginViewModel +import com.casic.smart.town.sanxi.vm.UserViewModel +import com.casic.smart.town.sanxi.vm.VersionViewModel +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.base.KotlinBaseFragment +import com.pengxh.kt.lite.callback.OnDownloadListener +import com.pengxh.kt.lite.extensions.* +import com.pengxh.kt.lite.utils.PageNavigationManager +import com.pengxh.kt.lite.utils.SaveKeyValues +import com.pengxh.kt.lite.vm.LoadState +import com.pengxh.kt.lite.widget.dialog.AlertControlDialog +import com.pengxh.kt.lite.widget.dialog.ChangePasswordDialog +import kotlinx.android.synthetic.main.fragment_mine.* +import java.io.File +import java.nio.charset.StandardCharsets + +class MinePageFragment : KotlinBaseFragment() { + + private lateinit var userData: UserDetailModel.Data + private lateinit var userViewModel: UserViewModel + private lateinit var loginViewModel: LoginViewModel + private lateinit var versionViewModel: VersionViewModel + private lateinit var progressDialog: ProgressDialog + + override fun initLayoutView(): Int = R.layout.fragment_mine + + override fun setupTopBarLayout() { + + } + + override fun initData() { + userViewModel = ViewModelProvider(this)[UserViewModel::class.java] + loginViewModel = ViewModelProvider(this)[LoginViewModel::class.java] + versionViewModel = ViewModelProvider(this)[VersionViewModel::class.java] + + //初始化下载对话框 + progressDialog = ProgressDialog(requireContext()) + progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL) + progressDialog.setProgressDrawable(resources.getDrawable(R.drawable.download_progress)) + progressDialog.setCanceledOnTouchOutside(false) + progressDialog.setCancelable(false) + + /** + * 数据监听 + * */ + dataObserve() + } + + override fun observeRequestState() { + userViewModel.loadState.observe(this, { + when (it) { + is LoadState.Loading -> LoadingDialogHub.show(requireActivity(), "修改中,请稍后") + is LoadState.Success -> { + "修改成功,请重新登录".show(requireContext()) + LoadingDialogHub.dismiss() + AuthenticationHelper.removeToken() + requireContext().navigatePageTo() + PageNavigationManager.finishAllActivity() + } + else -> LoadingDialogHub.dismiss() + } + }) + + versionViewModel.loadState.observe(this, { + when (it) { + is LoadState.Loading -> LoadingDialogHub.show(requireActivity(), "检查版本中,请稍后") + else -> LoadingDialogHub.dismiss() + } + }) + } + + override fun initEvent() { + userImageView.setOnClickListener { + "尽情期待~".show(requireContext()) + } + + updateUserButton.setOnClickListener { + LoadingDialogHub.show(requireActivity(), "同步中,请稍后...") + userViewModel.obtainUserDetail() + } + + changePwdLayout.setOnClickListener { + ChangePasswordDialog.Builder() + .setContext(requireContext()) + .setOnDialogButtonClickListener(object : + ChangePasswordDialog.OnDialogButtonClickListener { + override fun onConfirmClick(oldPwd: String, newPwd: String) { + val publicKey = + RSAUtils.keyStrToPublicKey(AuthenticationHelper.publicKey)!! + val oldPassKey = RSAUtils.encryptDataByPublicKey( + oldPwd.toByteArray(StandardCharsets.UTF_8), + publicKey + ) + val newPassKey = RSAUtils.encryptDataByPublicKey( + newPwd.toByteArray(StandardCharsets.UTF_8), + publicKey + ) + userViewModel.changePassword(oldPassKey, newPassKey) + } + }).build().show() + } + + aboutUsLayout.setOnClickListener { + requireContext().navigatePageTo() + } + + updateVersionLayout.setOnClickListener { + versionViewModel.updateVersion() + } + + clearCacheLayout.setOnClickListener { + //删除缓存之后在设置缓存大小 + LoadingDialogHub.show(requireActivity(), "清理中,请稍后") + File(requireContext().cacheDir.path).deleteFile() + object : CountDownTimer(1500, 500) { + override fun onTick(millisUntilFinished: Long) { + + } + + override fun onFinish() { + LoadingDialogHub.dismiss() + cacheSizeView.text = collectApplicationCache().formatFileSize() + } + }.start() + } + + loginOutButton.setOnClickListener { + AlertControlDialog.Builder() + .setContext(requireContext()) + .setTitle("退出登录") + .setMessage("确定要退出吗?") + .setNegativeButton("取消") + .setPositiveButton("确定") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + loginViewModel.out() + } + + override fun onCancelClick() {} + }).build().show() + } + } + + private fun dataObserve() { + userViewModel.userDetailModel.observe(this, { + if (it.code == 200) { + LoadingDialogHub.dismiss() + "同步完成".show(requireContext()) + userData = it.data + updateUserInfo() + } + }) + + versionViewModel.versionResultModel.observe(this, { + if (BuildConfig.VERSION_NAME == it.version) { + "已是最新版本,无需更新".show(requireContext()) + } else { + AlertControlDialog.Builder() + .setContext(requireContext()) + .setTitle("提示") + .setMessage("有新版本,是否更新?") + .setNegativeButton("稍后再说") + .setPositiveButton("立即下载") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + downloadApk(it.downloadUrl) + } + + override fun onCancelClick() { + + } + }).build().show() + } + }) + + loginViewModel.outResultModel.observe(this, { + if (it.code == 200) { + AuthenticationHelper.removeToken() + requireContext().navigatePageTo() + PageNavigationManager.finishAllActivity() + } + }) + } + + private fun collectApplicationCache(): Long { + return File(requireContext().cacheDir.path).calculateSize() + } + + override fun onResume() { + val userDetailJson = SaveKeyValues.getValue(LocaleConstant.USER_DETAIL_MODEL, "") as String + if (userDetailJson.isNotBlank()) { + userData = Gson().fromJson( + userDetailJson, object : TypeToken() {}.type + ) + updateUserInfo() + } + //缓存 + cacheSizeView.text = collectApplicationCache().formatFileSize() + super.onResume() + } + + private fun updateUserInfo() { + //设置头像,圆形,暂时是默认的 +// val roundDrawable = +// BitmapFactory.decodeResource(requireContext().resources, R.mipmap.login_casic) +// .createRoundDrawable( +// requireContext(), +// 3f.dp2px(requireContext()), +// R.color.mainThemeColor.convertColor(requireContext()) +// ) +// userImageView.setImageDrawable(roundDrawable) + userNameView.text = userData.name + userPhoneView.text = String.format("电话:${userData.phone}") + userDeptView.text = String.format("部门:${userData.deptName}") + } + + private fun downloadApk(url: String?) { + progressDialog.setMessage("下载新版本中...") + progressDialog.show() + if (url.toString().isBlank()) { + "抱歉,版本下载失败".show(requireContext()) + return + } + /** + * http://111.198.10.15:11304/static/apk/1.0.1.apk + * */ + val downloadPath = url!!.appendDownloadUrl() + //开始下载 + downloadPath.downloadFile(requireContext().createDownloadFileDir().toString(), + object : OnDownloadListener { + override fun onDownloadStart(totalBytes: Long) { + progressDialog.max = totalBytes.toInt() + } + + override fun onProgressChanged(currentBytes: Long) { + progressDialog.progress = currentBytes.toInt() + } + + override fun onDownloadEnd(file: File?) { + progressDialog.dismiss() + progressDialog.progress = 0 + //安装APK + installApk(file) + } + }) + } + + private fun installApk(apkPackage: File?) { + if (apkPackage == null) { + "文件异常,无法安装".show(requireContext()) + return + } + val intent = Intent(Intent.ACTION_VIEW) + val data: Uri + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { //判断版本大于等于7.0 + data = FileProvider.getUriForFile( + requireContext(), LocaleConstant.APP_AUTHORITY, apkPackage + ) + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) // 给目标应用一个临时授权 + } else { + data = Uri.fromFile(apkPackage) + } + intent.setDataAndType(data, "application/vnd.android.package-archive") + requireContext().startActivity(intent) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java new file mode 100644 index 0000000..467e33d --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java @@ -0,0 +1,43 @@ +package com.casic.smart.town.sanxi.model; + +/** + * 普通实体类,失败/成功数据结构一致 + */ +public class CommonResultModel { + private int code; + private String data; + private String message; + private boolean isSuccess; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return isSuccess; + } + + public void setSuccess(boolean success) { + isSuccess = success; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java new file mode 100644 index 0000000..9bdabef --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java @@ -0,0 +1,62 @@ +package com.casic.smart.town.sanxi.model; + +public class LoginResultModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private String kaptcha; + private String token; + + public String getKaptcha() { + return kaptcha; + } + + public void setKaptcha(String kaptcha) { + this.kaptcha = kaptcha; + } + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java new file mode 100644 index 0000000..09f8d59 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java @@ -0,0 +1,80 @@ +package com.casic.smart.town.sanxi.model; + +public class PublicKeyModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private boolean appKaptcha; + private boolean kaptcha; + private String publicKey; + private String sid; + + public boolean isAppKaptcha() { + return appKaptcha; + } + + public void setAppKaptcha(boolean appKaptcha) { + this.appKaptcha = appKaptcha; + } + + public boolean isKaptcha() { + return kaptcha; + } + + public void setKaptcha(boolean kaptcha) { + this.kaptcha = kaptcha; + } + + public String getPublicKey() { + return publicKey; + } + + public void setPublicKey(String publicKey) { + this.publicKey = publicKey; + } + + public String getSid() { + return sid; + } + + public void setSid(String sid) { + this.sid = sid; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java new file mode 100644 index 0000000..db577fb --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java @@ -0,0 +1,227 @@ +package com.casic.smart.town.sanxi.model; + + +import java.util.List; + +public class UserDetailModel { + private int code; + private Data data; + private String message; + private boolean success; + + public void setCode(int code) { + this.code = code; + } + + public int getCode() { + return code; + } + + public void setData(Data data) { + this.data = data; + } + + public Data getData() { + return data; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getMessage() { + return message; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public boolean getSuccess() { + return success; + } + + public static class Data { + + private String account; + private String attr1; + private String avatar; + private String bizData; + private List dataScope; + private String deptId; + private String deptName; + private List devices; + private String id; + private String ipAddr; + private String name; + private String phone; + private List roleList; + private List roleNames; + private List roleTips; + private String scopeType; + private String sysData; + private String targetId; + private String targetName; + private String tenantId; + + public void setAccount(String account) { + this.account = account; + } + + public String getAccount() { + return account; + } + + public void setAttr1(String attr1) { + this.attr1 = attr1; + } + + public String getAttr1() { + return attr1; + } + + public void setAvatar(String avatar) { + this.avatar = avatar; + } + + public String getAvatar() { + return avatar; + } + + public void setBizData(String bizData) { + this.bizData = bizData; + } + + public String getBizData() { + return bizData; + } + + public void setDataScope(List dataScope) { + this.dataScope = dataScope; + } + + public List getDataScope() { + return dataScope; + } + + public void setDeptId(String deptId) { + this.deptId = deptId; + } + + public String getDeptId() { + return deptId; + } + + public void setDeptName(String deptName) { + this.deptName = deptName; + } + + public String getDeptName() { + return deptName; + } + + public void setDevices(List devices) { + this.devices = devices; + } + + public List getDevices() { + return devices; + } + + public void setId(String id) { + this.id = id; + } + + public String getId() { + return id; + } + + public void setIpAddr(String ipAddr) { + this.ipAddr = ipAddr; + } + + public String getIpAddr() { + return ipAddr; + } + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setPhone(String phone) { + this.phone = phone; + } + + public String getPhone() { + return phone; + } + + public void setRoleList(List roleList) { + this.roleList = roleList; + } + + public List getRoleList() { + return roleList; + } + + public void setRoleNames(List roleNames) { + this.roleNames = roleNames; + } + + public List getRoleNames() { + return roleNames; + } + + public void setRoleTips(List roleTips) { + this.roleTips = roleTips; + } + + public List getRoleTips() { + return roleTips; + } + + public void setScopeType(String scopeType) { + this.scopeType = scopeType; + } + + public String getScopeType() { + return scopeType; + } + + public void setSysData(String sysData) { + this.sysData = sysData; + } + + public String getSysData() { + return sysData; + } + + public void setTargetId(String targetId) { + this.targetId = targetId; + } + + public String getTargetId() { + return targetId; + } + + public void setTargetName(String targetName) { + this.targetName = targetName; + } + + public String getTargetName() { + return targetName; + } + + public void setTenantId(String tenantId) { + this.tenantId = tenantId; + } + + public String getTenantId() { + return tenantId; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/VersionResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/VersionResultModel.java new file mode 100644 index 0000000..79374e0 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/VersionResultModel.java @@ -0,0 +1,61 @@ +package com.casic.smart.town.sanxi.model; + +public class VersionResultModel { + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private String downloadUrl; + private String version; + + public String getDownloadUrl() { + return downloadUrl; + } + + public void setDownloadUrl(String downloadUrl) { + this.downloadUrl = downloadUrl; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/AuthenticationHelper.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/AuthenticationHelper.kt new file mode 100644 index 0000000..f498e4e --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/AuthenticationHelper.kt @@ -0,0 +1,24 @@ +package com.casic.smart.town.sanxi.util + +import com.pengxh.kt.lite.utils.SaveKeyValues + +object AuthenticationHelper { + + fun savePublicKey(key: String) { + SaveKeyValues.putValue("keyString", key) + } + + val publicKey: String? + get() = SaveKeyValues.getValue("keyString", "") as String? + + fun saveToken(token: String?) { + SaveKeyValues.putValue("token", token!!) + } + + val token: String? + get() = SaveKeyValues.getValue("token", "") as String? + + fun removeToken() { + SaveKeyValues.removeKey("token") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt index ab1a36d..99f0d19 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt @@ -14,5 +14,9 @@ const val PAGE_LIMIT = 20 const val DEFAULT_SERVER_CONFIG = "defaultServerConfig" - const val DEFAULT_SERVER = "http://111.198.10.15:11308" + const val DEFAULT_SERVER = "http://36.133.189.112:8082" + const val APP_AUTHORITY = "com.casic.smart.town.sanxi.fileprovider" + const val ACCOUNT = "account" + const val PASSWORD = "password" + const val USER_DETAIL_MODEL = "userDetailModel" } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/RSAUtils.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/RSAUtils.kt new file mode 100644 index 0000000..b9216ba --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/RSAUtils.kt @@ -0,0 +1,55 @@ +package com.casic.smart.town.sanxi.util + +import android.util.Base64 +import java.security.* +import java.security.spec.InvalidKeySpecException +import java.security.spec.X509EncodedKeySpec +import javax.crypto.BadPaddingException +import javax.crypto.Cipher +import javax.crypto.IllegalBlockSizeException +import javax.crypto.NoSuchPaddingException + +object RSAUtils { + //构建Cipher实例时所传入的的字符串,默认为"RSA/NONE/PKCS1Padding" + private fun processData(srcData: ByteArray, key: Key): ByteArray? { //用来保存处理结果 + var resultBytes: ByteArray? = null + try { //获取Cipher实例 + val cipher = Cipher.getInstance("RSA/None/PKCS1Padding") + //初始化Cipher,mode指定是加密还是解密,key为公钥或私钥 + cipher.init(Cipher.ENCRYPT_MODE, key) + //处理数据 + resultBytes = cipher.doFinal(srcData) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: NoSuchPaddingException) { + e.printStackTrace() + } catch (e: InvalidKeyException) { + e.printStackTrace() + } catch (e: BadPaddingException) { + e.printStackTrace() + } catch (e: IllegalBlockSizeException) { + e.printStackTrace() + } + return resultBytes + } + + fun encryptDataByPublicKey(srcData: ByteArray, publicKey: PublicKey): String { + val resultBytes = processData(srcData, publicKey) + return Base64.encodeToString(resultBytes, Base64.DEFAULT) + } + + fun keyStrToPublicKey(publicKeyStr: String?): PublicKey? { + var publicKey: PublicKey? = null + val keyBytes = Base64.decode(publicKeyStr, Base64.DEFAULT) + val keySpec = X509EncodedKeySpec(keyBytes) + try { + val keyFactory = KeyFactory.getInstance("RSA") + publicKey = keyFactory.generatePublic(keySpec) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: InvalidKeySpecException) { + e.printStackTrace() + } + return publicKey + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt index 63db0ec..50d1742 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt @@ -1,12 +1,64 @@ package com.casic.smart.town.sanxi.util.retrofit -import retrofit2.http.GET +import retrofit2.http.* interface RetrofitService { + /** + * PublicKey校验 + */ + @GET("/config/baseConfig") + suspend fun obtainPublicKey(): String + + /** + * 登录并获取Token + * + * @param sid + * @param account 用户名 + * @param secretKey 加密后的密码 + */ + @FormUrlEncoded + @POST("/user/login") + suspend fun login( + @Field("sid") sid: String, + @Field("username") account: String, + @Field("password") secretKey: String + ): String + + /** + * 退出登录 + */ + @GET("/user/logout") + suspend fun loginOut(@Header("token") token: String): String + + /** + * 获取用户信息 + */ + @GET("/user/info") + suspend fun obtainUserDetail(@Header("token") token: String): String + + /** + * 修改密码 + * + * @param oldPwd 旧密码 + * @param newPwd 新密码 + */ + @FormUrlEncoded + @POST("/mgr/changePwd") + suspend fun changePassword( + @Header("token") token: String, + @Field("oldPwd") oldPwd: String, + @Field("newPwd") newPwd: String + ): String /** * 根据布防状态统计闸井数量接口 */ @GET("/well/countByBfzt") - suspend fun countWellByState(): String + suspend fun countWellByState(@Header("token") token: String): String + + /** + * 更新APK版本 + */ + @POST("/app/checkVersion") + suspend fun obtainVersionResult(@Header("token") token: String): String } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt index 46bbf4b..410444b 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt @@ -1,5 +1,6 @@ package com.casic.smart.town.sanxi.util.retrofit +import com.casic.smart.town.sanxi.util.AuthenticationHelper import com.casic.smart.town.sanxi.util.LocaleConstant import com.pengxh.kt.lite.utils.RetrofitFactory import com.pengxh.kt.lite.utils.SaveKeyValues @@ -15,9 +16,52 @@ } /** + * 验证PublicKey + */ + suspend fun authenticate(): String { + return api.obtainPublicKey() + } + + /** + * 登录并获取Token + */ + suspend fun login(sid: String, account: String, secretKey: String): String { + return api.login(sid, account, secretKey) + } + + /** + * 退出登录 + */ + suspend fun loginOut(): String { + return api.loginOut(AuthenticationHelper.token!!) + } + + /** + * 获取用户信息 + */ + suspend fun obtainUserDetail(): String { + return api.obtainUserDetail(AuthenticationHelper.token!!) + } + + /** + * 修改密码 + */ + suspend fun changePassword(oldPwd: String, newPwd: String): String { + return api.changePassword(AuthenticationHelper.token!!, oldPwd, newPwd) + } + + /** * 根据布防状态统计闸井数量接口 */ suspend fun countWellByState(): String { - return api.countWellByState() + return api.countWellByState(AuthenticationHelper.token!!) } + + /** + * 更新APK版本 + */ + suspend fun updateVersion(): String { + return api.obtainVersionResult(AuthenticationHelper.token!!) + } + } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/AboutUsActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/AboutUsActivity.kt new file mode 100644 index 0000000..7e43c47 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/AboutUsActivity.kt @@ -0,0 +1,34 @@ +package com.casic.smart.town.sanxi.view + +import com.casic.smart.town.sanxi.BuildConfig +import com.casic.smart.town.sanxi.R +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.kt.lite.base.KotlinBaseActivity +import com.pengxh.kt.lite.extensions.convertColor +import com.pengxh.kt.lite.utils.ImmerseStatusBarUtil +import kotlinx.android.synthetic.main.activity_about_us.* +import kotlinx.android.synthetic.main.include_base_title.* + +class AboutUsActivity : KotlinBaseActivity() { + + override fun initLayoutView(): Int = R.layout.activity_about_us + + override fun setupTopBarLayout() { + ImmersionBar.with(this).statusBarDarkFont(false).init() + ImmerseStatusBarUtil.setColor(this, R.color.mainThemeColor.convertColor(this)) + titleView.text = "关于我们" + leftBackView.setOnClickListener { finish() } + } + + override fun initData() { + versionCodeView.text = String.format("版本信息:V${BuildConfig.VERSION_NAME}") + } + + override fun observeRequestState() { + + } + + override fun initEvent() { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/LoginActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/LoginActivity.kt new file mode 100644 index 0000000..b5d1d50 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/LoginActivity.kt @@ -0,0 +1,97 @@ +package com.casic.smart.town.sanxi.view + +import androidx.lifecycle.ViewModelProvider +import com.casic.smart.town.sanxi.R +import com.casic.smart.town.sanxi.util.AuthenticationHelper +import com.casic.smart.town.sanxi.util.LoadingDialogHub +import com.casic.smart.town.sanxi.util.LocaleConstant +import com.casic.smart.town.sanxi.util.RSAUtils +import com.casic.smart.town.sanxi.vm.AuthenticateViewModel +import com.casic.smart.town.sanxi.vm.LoginViewModel +import com.casic.smart.town.sanxi.vm.UserViewModel +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.kt.lite.base.KotlinBaseActivity +import com.pengxh.kt.lite.extensions.navigatePageTo +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.utils.SaveKeyValues +import kotlinx.android.synthetic.main.activity_login.* + +class LoginActivity : KotlinBaseActivity() { + + private lateinit var authenticateViewModel: AuthenticateViewModel + private lateinit var loginViewModel: LoginViewModel + private lateinit var userViewModel: UserViewModel + + override fun initLayoutView(): Int = R.layout.activity_login + + override fun setupTopBarLayout() { + ImmersionBar.with(this).statusBarDarkFont(true).init() + } + + override fun initData() { + // 设置默认账号密码 + userNameView.setText(SaveKeyValues.getValue(LocaleConstant.ACCOUNT, "") as String) + userPasswordView.setText(SaveKeyValues.getValue(LocaleConstant.PASSWORD, "") as String) + authenticateViewModel = ViewModelProvider(this)[AuthenticateViewModel::class.java] + loginViewModel = ViewModelProvider(this)[LoginViewModel::class.java] + userViewModel = ViewModelProvider(this)[UserViewModel::class.java] + + authenticateViewModel.keyModel.observe(this, { + if (it.code == 200) {//用code判断,别的判断可能有坑 + val keyString = it.data!!.publicKey!! + /** + * 修改密码需要用到key,先存着 + * */ + AuthenticationHelper.savePublicKey(keyString) + val publicKey = RSAUtils.keyStrToPublicKey(keyString) + + val account = userNameView.text.toString() + val userPassword = userPasswordView.text.toString() + val dataByPublicKey = + RSAUtils.encryptDataByPublicKey(userPassword.toByteArray(), publicKey!!) + //登录并获取Token,POST请求 + loginViewModel.enter(it.data!!.sid!!, account, dataByPublicKey) + loginViewModel.enterResultModel.observe(this, { loginResult -> + if (loginResult.code == 200) { + AuthenticationHelper.saveToken(loginResult.data!!.token!!) + /** + * 获取token之后保存用户信息 + * */ + userViewModel.obtainUserDetail() + //验证成功登录 + navigatePageTo() + finish() + } + }) + } + }) + } + + override fun observeRequestState() { + authenticateViewModel.loadState.observe(this, { + LoadingDialogHub.show(this, "登录中,请稍后") + }) + + loginViewModel.loadState.observe(this, { + LoadingDialogHub.dismiss() + }) + } + + override fun initEvent() { + loginButton.setOnClickListener { + val account = userNameView.text.toString().trim() + val userPassword = userPasswordView.text.toString().trim() + if (account.isBlank()) { + "用户名不能为空".show(this) + return@setOnClickListener + } + if (userPassword.isBlank()) { + "密码不能为空".show(this) + return@setOnClickListener + } + SaveKeyValues.putValue(LocaleConstant.ACCOUNT, account) + SaveKeyValues.putValue(LocaleConstant.PASSWORD, userPassword) + authenticateViewModel.obtainPublicKey() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt index 3bea1c9..a4fa951 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt @@ -7,6 +7,7 @@ import com.casic.smart.town.sanxi.R import com.casic.smart.town.sanxi.adapter.ViewPagerAdapter import com.casic.smart.town.sanxi.fragment.AlarmPageFragment +import com.casic.smart.town.sanxi.fragment.MinePageFragment import com.casic.smart.town.sanxi.fragment.MonitorPageFragment import com.casic.smart.town.sanxi.fragment.StatisticsPageFragment import com.gyf.immersionbar.ImmersionBar @@ -33,6 +34,7 @@ fragmentPages.add(StatisticsPageFragment()) fragmentPages.add(MonitorPageFragment()) fragmentPages.add(AlarmPageFragment()) + fragmentPages.add(MinePageFragment()) } override fun initLayoutView(): Int = R.layout.activity_main @@ -68,6 +70,9 @@ R.id.nav_alarm -> { mainViewPager.currentItem = 2 } + R.id.nav_more -> { + mainViewPager.currentItem = 3 + } } false } diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/PermissionActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/PermissionActivity.kt index c478d83..3238f3c 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/view/PermissionActivity.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/PermissionActivity.kt @@ -32,7 +32,7 @@ //先把导航隐私政策声明,后面导航会用到 MapsInitializer.updatePrivacyAgree(this, true) MapsInitializer.updatePrivacyShow(this, true, true) - this.navigatePageTo() + this.navigatePageTo() finish() } diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/SplashScreenActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/SplashScreenActivity.kt new file mode 100644 index 0000000..fa5a74d --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/SplashScreenActivity.kt @@ -0,0 +1,54 @@ +package com.casic.smart.town.sanxi.view + +import android.annotation.SuppressLint +import android.os.CountDownTimer +import androidx.lifecycle.ViewModelProvider +import com.casic.smart.town.sanxi.R +import com.casic.smart.town.sanxi.vm.UserViewModel +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.kt.lite.base.KotlinBaseActivity +import com.pengxh.kt.lite.extensions.navigatePageTo + +@SuppressLint("CustomSplashScreen") +class SplashScreenActivity : KotlinBaseActivity() { + + private lateinit var userDetailViewModel: UserViewModel + private val countDownTimer = object : CountDownTimer(1000, 500) { + override fun onFinish() { + /** + * 获取token之后保存用户信息 + * */ + userDetailViewModel.obtainUserDetail() + userDetailViewModel.userDetailModel.observe(this@SplashScreenActivity, { + if (it.code == 200) { + navigatePageTo() + } else { + navigatePageTo() + } + finish() + }) + } + + override fun onTick(millisUntilFinished: Long) { + + } + } + + override fun initLayoutView(): Int = R.layout.activity_splash + + override fun setupTopBarLayout() { + ImmersionBar.with(this).statusBarDarkFont(false).init() + } + + override fun initData() { + userDetailViewModel = ViewModelProvider(this).get(UserViewModel::class.java) + } + + override fun observeRequestState() { + + } + + override fun initEvent() { + countDownTimer.start() + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1df917d..8d4f33b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -6,6 +6,7 @@ + @@ -32,11 +33,14 @@ + + + + + + + diff --git a/app/src/main/assets/svg/mine.svg b/app/src/main/assets/svg/mine.svg new file mode 100644 index 0000000..3fbb0a3 --- /dev/null +++ b/app/src/main/assets/svg/mine.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt b/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt index d730417..b1972c2 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt @@ -1,8 +1,10 @@ package com.casic.smart.town.sanxi.extensions import com.casic.smart.town.sanxi.model.ErrorMessageModel +import com.casic.smart.town.sanxi.util.LocaleConstant import com.google.gson.Gson import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.utils.SaveKeyValues import org.json.JSONObject fun String.separateResponseCode(): Int { @@ -17,4 +19,15 @@ this, object : TypeToken() {}.type ) return errorModel.message.toString() +} + +/** + * 下载路径为 http://xx.com/static/ 拼接downloadUrl + * */ +fun String.appendDownloadUrl(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + LocaleConstant.DEFAULT_SERVER_CONFIG, LocaleConstant.DEFAULT_SERVER + ) as String + return "$defaultValue/static/${this}" } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt b/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt new file mode 100644 index 0000000..1809dc5 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt @@ -0,0 +1,288 @@ +package com.casic.smart.town.sanxi.fragment + +import android.app.ProgressDialog +import android.content.Intent +import android.net.Uri +import android.os.Build +import android.os.CountDownTimer +import androidx.core.content.FileProvider +import androidx.lifecycle.ViewModelProvider +import com.casic.smart.town.sanxi.BuildConfig +import com.casic.smart.town.sanxi.R +import com.casic.smart.town.sanxi.extensions.appendDownloadUrl +import com.casic.smart.town.sanxi.model.UserDetailModel +import com.casic.smart.town.sanxi.util.AuthenticationHelper +import com.casic.smart.town.sanxi.util.LoadingDialogHub +import com.casic.smart.town.sanxi.util.LocaleConstant +import com.casic.smart.town.sanxi.util.RSAUtils +import com.casic.smart.town.sanxi.view.AboutUsActivity +import com.casic.smart.town.sanxi.view.LoginActivity +import com.casic.smart.town.sanxi.vm.LoginViewModel +import com.casic.smart.town.sanxi.vm.UserViewModel +import com.casic.smart.town.sanxi.vm.VersionViewModel +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.base.KotlinBaseFragment +import com.pengxh.kt.lite.callback.OnDownloadListener +import com.pengxh.kt.lite.extensions.* +import com.pengxh.kt.lite.utils.PageNavigationManager +import com.pengxh.kt.lite.utils.SaveKeyValues +import com.pengxh.kt.lite.vm.LoadState +import com.pengxh.kt.lite.widget.dialog.AlertControlDialog +import com.pengxh.kt.lite.widget.dialog.ChangePasswordDialog +import kotlinx.android.synthetic.main.fragment_mine.* +import java.io.File +import java.nio.charset.StandardCharsets + +class MinePageFragment : KotlinBaseFragment() { + + private lateinit var userData: UserDetailModel.Data + private lateinit var userViewModel: UserViewModel + private lateinit var loginViewModel: LoginViewModel + private lateinit var versionViewModel: VersionViewModel + private lateinit var progressDialog: ProgressDialog + + override fun initLayoutView(): Int = R.layout.fragment_mine + + override fun setupTopBarLayout() { + + } + + override fun initData() { + userViewModel = ViewModelProvider(this)[UserViewModel::class.java] + loginViewModel = ViewModelProvider(this)[LoginViewModel::class.java] + versionViewModel = ViewModelProvider(this)[VersionViewModel::class.java] + + //初始化下载对话框 + progressDialog = ProgressDialog(requireContext()) + progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL) + progressDialog.setProgressDrawable(resources.getDrawable(R.drawable.download_progress)) + progressDialog.setCanceledOnTouchOutside(false) + progressDialog.setCancelable(false) + + /** + * 数据监听 + * */ + dataObserve() + } + + override fun observeRequestState() { + userViewModel.loadState.observe(this, { + when (it) { + is LoadState.Loading -> LoadingDialogHub.show(requireActivity(), "修改中,请稍后") + is LoadState.Success -> { + "修改成功,请重新登录".show(requireContext()) + LoadingDialogHub.dismiss() + AuthenticationHelper.removeToken() + requireContext().navigatePageTo() + PageNavigationManager.finishAllActivity() + } + else -> LoadingDialogHub.dismiss() + } + }) + + versionViewModel.loadState.observe(this, { + when (it) { + is LoadState.Loading -> LoadingDialogHub.show(requireActivity(), "检查版本中,请稍后") + else -> LoadingDialogHub.dismiss() + } + }) + } + + override fun initEvent() { + userImageView.setOnClickListener { + "尽情期待~".show(requireContext()) + } + + updateUserButton.setOnClickListener { + LoadingDialogHub.show(requireActivity(), "同步中,请稍后...") + userViewModel.obtainUserDetail() + } + + changePwdLayout.setOnClickListener { + ChangePasswordDialog.Builder() + .setContext(requireContext()) + .setOnDialogButtonClickListener(object : + ChangePasswordDialog.OnDialogButtonClickListener { + override fun onConfirmClick(oldPwd: String, newPwd: String) { + val publicKey = + RSAUtils.keyStrToPublicKey(AuthenticationHelper.publicKey)!! + val oldPassKey = RSAUtils.encryptDataByPublicKey( + oldPwd.toByteArray(StandardCharsets.UTF_8), + publicKey + ) + val newPassKey = RSAUtils.encryptDataByPublicKey( + newPwd.toByteArray(StandardCharsets.UTF_8), + publicKey + ) + userViewModel.changePassword(oldPassKey, newPassKey) + } + }).build().show() + } + + aboutUsLayout.setOnClickListener { + requireContext().navigatePageTo() + } + + updateVersionLayout.setOnClickListener { + versionViewModel.updateVersion() + } + + clearCacheLayout.setOnClickListener { + //删除缓存之后在设置缓存大小 + LoadingDialogHub.show(requireActivity(), "清理中,请稍后") + File(requireContext().cacheDir.path).deleteFile() + object : CountDownTimer(1500, 500) { + override fun onTick(millisUntilFinished: Long) { + + } + + override fun onFinish() { + LoadingDialogHub.dismiss() + cacheSizeView.text = collectApplicationCache().formatFileSize() + } + }.start() + } + + loginOutButton.setOnClickListener { + AlertControlDialog.Builder() + .setContext(requireContext()) + .setTitle("退出登录") + .setMessage("确定要退出吗?") + .setNegativeButton("取消") + .setPositiveButton("确定") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + loginViewModel.out() + } + + override fun onCancelClick() {} + }).build().show() + } + } + + private fun dataObserve() { + userViewModel.userDetailModel.observe(this, { + if (it.code == 200) { + LoadingDialogHub.dismiss() + "同步完成".show(requireContext()) + userData = it.data + updateUserInfo() + } + }) + + versionViewModel.versionResultModel.observe(this, { + if (BuildConfig.VERSION_NAME == it.version) { + "已是最新版本,无需更新".show(requireContext()) + } else { + AlertControlDialog.Builder() + .setContext(requireContext()) + .setTitle("提示") + .setMessage("有新版本,是否更新?") + .setNegativeButton("稍后再说") + .setPositiveButton("立即下载") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + downloadApk(it.downloadUrl) + } + + override fun onCancelClick() { + + } + }).build().show() + } + }) + + loginViewModel.outResultModel.observe(this, { + if (it.code == 200) { + AuthenticationHelper.removeToken() + requireContext().navigatePageTo() + PageNavigationManager.finishAllActivity() + } + }) + } + + private fun collectApplicationCache(): Long { + return File(requireContext().cacheDir.path).calculateSize() + } + + override fun onResume() { + val userDetailJson = SaveKeyValues.getValue(LocaleConstant.USER_DETAIL_MODEL, "") as String + if (userDetailJson.isNotBlank()) { + userData = Gson().fromJson( + userDetailJson, object : TypeToken() {}.type + ) + updateUserInfo() + } + //缓存 + cacheSizeView.text = collectApplicationCache().formatFileSize() + super.onResume() + } + + private fun updateUserInfo() { + //设置头像,圆形,暂时是默认的 +// val roundDrawable = +// BitmapFactory.decodeResource(requireContext().resources, R.mipmap.login_casic) +// .createRoundDrawable( +// requireContext(), +// 3f.dp2px(requireContext()), +// R.color.mainThemeColor.convertColor(requireContext()) +// ) +// userImageView.setImageDrawable(roundDrawable) + userNameView.text = userData.name + userPhoneView.text = String.format("电话:${userData.phone}") + userDeptView.text = String.format("部门:${userData.deptName}") + } + + private fun downloadApk(url: String?) { + progressDialog.setMessage("下载新版本中...") + progressDialog.show() + if (url.toString().isBlank()) { + "抱歉,版本下载失败".show(requireContext()) + return + } + /** + * http://111.198.10.15:11304/static/apk/1.0.1.apk + * */ + val downloadPath = url!!.appendDownloadUrl() + //开始下载 + downloadPath.downloadFile(requireContext().createDownloadFileDir().toString(), + object : OnDownloadListener { + override fun onDownloadStart(totalBytes: Long) { + progressDialog.max = totalBytes.toInt() + } + + override fun onProgressChanged(currentBytes: Long) { + progressDialog.progress = currentBytes.toInt() + } + + override fun onDownloadEnd(file: File?) { + progressDialog.dismiss() + progressDialog.progress = 0 + //安装APK + installApk(file) + } + }) + } + + private fun installApk(apkPackage: File?) { + if (apkPackage == null) { + "文件异常,无法安装".show(requireContext()) + return + } + val intent = Intent(Intent.ACTION_VIEW) + val data: Uri + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { //判断版本大于等于7.0 + data = FileProvider.getUriForFile( + requireContext(), LocaleConstant.APP_AUTHORITY, apkPackage + ) + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) // 给目标应用一个临时授权 + } else { + data = Uri.fromFile(apkPackage) + } + intent.setDataAndType(data, "application/vnd.android.package-archive") + requireContext().startActivity(intent) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java new file mode 100644 index 0000000..467e33d --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java @@ -0,0 +1,43 @@ +package com.casic.smart.town.sanxi.model; + +/** + * 普通实体类,失败/成功数据结构一致 + */ +public class CommonResultModel { + private int code; + private String data; + private String message; + private boolean isSuccess; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return isSuccess; + } + + public void setSuccess(boolean success) { + isSuccess = success; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java new file mode 100644 index 0000000..9bdabef --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java @@ -0,0 +1,62 @@ +package com.casic.smart.town.sanxi.model; + +public class LoginResultModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private String kaptcha; + private String token; + + public String getKaptcha() { + return kaptcha; + } + + public void setKaptcha(String kaptcha) { + this.kaptcha = kaptcha; + } + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java new file mode 100644 index 0000000..09f8d59 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java @@ -0,0 +1,80 @@ +package com.casic.smart.town.sanxi.model; + +public class PublicKeyModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private boolean appKaptcha; + private boolean kaptcha; + private String publicKey; + private String sid; + + public boolean isAppKaptcha() { + return appKaptcha; + } + + public void setAppKaptcha(boolean appKaptcha) { + this.appKaptcha = appKaptcha; + } + + public boolean isKaptcha() { + return kaptcha; + } + + public void setKaptcha(boolean kaptcha) { + this.kaptcha = kaptcha; + } + + public String getPublicKey() { + return publicKey; + } + + public void setPublicKey(String publicKey) { + this.publicKey = publicKey; + } + + public String getSid() { + return sid; + } + + public void setSid(String sid) { + this.sid = sid; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java new file mode 100644 index 0000000..db577fb --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java @@ -0,0 +1,227 @@ +package com.casic.smart.town.sanxi.model; + + +import java.util.List; + +public class UserDetailModel { + private int code; + private Data data; + private String message; + private boolean success; + + public void setCode(int code) { + this.code = code; + } + + public int getCode() { + return code; + } + + public void setData(Data data) { + this.data = data; + } + + public Data getData() { + return data; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getMessage() { + return message; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public boolean getSuccess() { + return success; + } + + public static class Data { + + private String account; + private String attr1; + private String avatar; + private String bizData; + private List dataScope; + private String deptId; + private String deptName; + private List devices; + private String id; + private String ipAddr; + private String name; + private String phone; + private List roleList; + private List roleNames; + private List roleTips; + private String scopeType; + private String sysData; + private String targetId; + private String targetName; + private String tenantId; + + public void setAccount(String account) { + this.account = account; + } + + public String getAccount() { + return account; + } + + public void setAttr1(String attr1) { + this.attr1 = attr1; + } + + public String getAttr1() { + return attr1; + } + + public void setAvatar(String avatar) { + this.avatar = avatar; + } + + public String getAvatar() { + return avatar; + } + + public void setBizData(String bizData) { + this.bizData = bizData; + } + + public String getBizData() { + return bizData; + } + + public void setDataScope(List dataScope) { + this.dataScope = dataScope; + } + + public List getDataScope() { + return dataScope; + } + + public void setDeptId(String deptId) { + this.deptId = deptId; + } + + public String getDeptId() { + return deptId; + } + + public void setDeptName(String deptName) { + this.deptName = deptName; + } + + public String getDeptName() { + return deptName; + } + + public void setDevices(List devices) { + this.devices = devices; + } + + public List getDevices() { + return devices; + } + + public void setId(String id) { + this.id = id; + } + + public String getId() { + return id; + } + + public void setIpAddr(String ipAddr) { + this.ipAddr = ipAddr; + } + + public String getIpAddr() { + return ipAddr; + } + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setPhone(String phone) { + this.phone = phone; + } + + public String getPhone() { + return phone; + } + + public void setRoleList(List roleList) { + this.roleList = roleList; + } + + public List getRoleList() { + return roleList; + } + + public void setRoleNames(List roleNames) { + this.roleNames = roleNames; + } + + public List getRoleNames() { + return roleNames; + } + + public void setRoleTips(List roleTips) { + this.roleTips = roleTips; + } + + public List getRoleTips() { + return roleTips; + } + + public void setScopeType(String scopeType) { + this.scopeType = scopeType; + } + + public String getScopeType() { + return scopeType; + } + + public void setSysData(String sysData) { + this.sysData = sysData; + } + + public String getSysData() { + return sysData; + } + + public void setTargetId(String targetId) { + this.targetId = targetId; + } + + public String getTargetId() { + return targetId; + } + + public void setTargetName(String targetName) { + this.targetName = targetName; + } + + public String getTargetName() { + return targetName; + } + + public void setTenantId(String tenantId) { + this.tenantId = tenantId; + } + + public String getTenantId() { + return tenantId; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/VersionResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/VersionResultModel.java new file mode 100644 index 0000000..79374e0 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/VersionResultModel.java @@ -0,0 +1,61 @@ +package com.casic.smart.town.sanxi.model; + +public class VersionResultModel { + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private String downloadUrl; + private String version; + + public String getDownloadUrl() { + return downloadUrl; + } + + public void setDownloadUrl(String downloadUrl) { + this.downloadUrl = downloadUrl; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/AuthenticationHelper.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/AuthenticationHelper.kt new file mode 100644 index 0000000..f498e4e --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/AuthenticationHelper.kt @@ -0,0 +1,24 @@ +package com.casic.smart.town.sanxi.util + +import com.pengxh.kt.lite.utils.SaveKeyValues + +object AuthenticationHelper { + + fun savePublicKey(key: String) { + SaveKeyValues.putValue("keyString", key) + } + + val publicKey: String? + get() = SaveKeyValues.getValue("keyString", "") as String? + + fun saveToken(token: String?) { + SaveKeyValues.putValue("token", token!!) + } + + val token: String? + get() = SaveKeyValues.getValue("token", "") as String? + + fun removeToken() { + SaveKeyValues.removeKey("token") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt index ab1a36d..99f0d19 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt @@ -14,5 +14,9 @@ const val PAGE_LIMIT = 20 const val DEFAULT_SERVER_CONFIG = "defaultServerConfig" - const val DEFAULT_SERVER = "http://111.198.10.15:11308" + const val DEFAULT_SERVER = "http://36.133.189.112:8082" + const val APP_AUTHORITY = "com.casic.smart.town.sanxi.fileprovider" + const val ACCOUNT = "account" + const val PASSWORD = "password" + const val USER_DETAIL_MODEL = "userDetailModel" } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/RSAUtils.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/RSAUtils.kt new file mode 100644 index 0000000..b9216ba --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/RSAUtils.kt @@ -0,0 +1,55 @@ +package com.casic.smart.town.sanxi.util + +import android.util.Base64 +import java.security.* +import java.security.spec.InvalidKeySpecException +import java.security.spec.X509EncodedKeySpec +import javax.crypto.BadPaddingException +import javax.crypto.Cipher +import javax.crypto.IllegalBlockSizeException +import javax.crypto.NoSuchPaddingException + +object RSAUtils { + //构建Cipher实例时所传入的的字符串,默认为"RSA/NONE/PKCS1Padding" + private fun processData(srcData: ByteArray, key: Key): ByteArray? { //用来保存处理结果 + var resultBytes: ByteArray? = null + try { //获取Cipher实例 + val cipher = Cipher.getInstance("RSA/None/PKCS1Padding") + //初始化Cipher,mode指定是加密还是解密,key为公钥或私钥 + cipher.init(Cipher.ENCRYPT_MODE, key) + //处理数据 + resultBytes = cipher.doFinal(srcData) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: NoSuchPaddingException) { + e.printStackTrace() + } catch (e: InvalidKeyException) { + e.printStackTrace() + } catch (e: BadPaddingException) { + e.printStackTrace() + } catch (e: IllegalBlockSizeException) { + e.printStackTrace() + } + return resultBytes + } + + fun encryptDataByPublicKey(srcData: ByteArray, publicKey: PublicKey): String { + val resultBytes = processData(srcData, publicKey) + return Base64.encodeToString(resultBytes, Base64.DEFAULT) + } + + fun keyStrToPublicKey(publicKeyStr: String?): PublicKey? { + var publicKey: PublicKey? = null + val keyBytes = Base64.decode(publicKeyStr, Base64.DEFAULT) + val keySpec = X509EncodedKeySpec(keyBytes) + try { + val keyFactory = KeyFactory.getInstance("RSA") + publicKey = keyFactory.generatePublic(keySpec) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: InvalidKeySpecException) { + e.printStackTrace() + } + return publicKey + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt index 63db0ec..50d1742 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt @@ -1,12 +1,64 @@ package com.casic.smart.town.sanxi.util.retrofit -import retrofit2.http.GET +import retrofit2.http.* interface RetrofitService { + /** + * PublicKey校验 + */ + @GET("/config/baseConfig") + suspend fun obtainPublicKey(): String + + /** + * 登录并获取Token + * + * @param sid + * @param account 用户名 + * @param secretKey 加密后的密码 + */ + @FormUrlEncoded + @POST("/user/login") + suspend fun login( + @Field("sid") sid: String, + @Field("username") account: String, + @Field("password") secretKey: String + ): String + + /** + * 退出登录 + */ + @GET("/user/logout") + suspend fun loginOut(@Header("token") token: String): String + + /** + * 获取用户信息 + */ + @GET("/user/info") + suspend fun obtainUserDetail(@Header("token") token: String): String + + /** + * 修改密码 + * + * @param oldPwd 旧密码 + * @param newPwd 新密码 + */ + @FormUrlEncoded + @POST("/mgr/changePwd") + suspend fun changePassword( + @Header("token") token: String, + @Field("oldPwd") oldPwd: String, + @Field("newPwd") newPwd: String + ): String /** * 根据布防状态统计闸井数量接口 */ @GET("/well/countByBfzt") - suspend fun countWellByState(): String + suspend fun countWellByState(@Header("token") token: String): String + + /** + * 更新APK版本 + */ + @POST("/app/checkVersion") + suspend fun obtainVersionResult(@Header("token") token: String): String } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt index 46bbf4b..410444b 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt @@ -1,5 +1,6 @@ package com.casic.smart.town.sanxi.util.retrofit +import com.casic.smart.town.sanxi.util.AuthenticationHelper import com.casic.smart.town.sanxi.util.LocaleConstant import com.pengxh.kt.lite.utils.RetrofitFactory import com.pengxh.kt.lite.utils.SaveKeyValues @@ -15,9 +16,52 @@ } /** + * 验证PublicKey + */ + suspend fun authenticate(): String { + return api.obtainPublicKey() + } + + /** + * 登录并获取Token + */ + suspend fun login(sid: String, account: String, secretKey: String): String { + return api.login(sid, account, secretKey) + } + + /** + * 退出登录 + */ + suspend fun loginOut(): String { + return api.loginOut(AuthenticationHelper.token!!) + } + + /** + * 获取用户信息 + */ + suspend fun obtainUserDetail(): String { + return api.obtainUserDetail(AuthenticationHelper.token!!) + } + + /** + * 修改密码 + */ + suspend fun changePassword(oldPwd: String, newPwd: String): String { + return api.changePassword(AuthenticationHelper.token!!, oldPwd, newPwd) + } + + /** * 根据布防状态统计闸井数量接口 */ suspend fun countWellByState(): String { - return api.countWellByState() + return api.countWellByState(AuthenticationHelper.token!!) } + + /** + * 更新APK版本 + */ + suspend fun updateVersion(): String { + return api.obtainVersionResult(AuthenticationHelper.token!!) + } + } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/AboutUsActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/AboutUsActivity.kt new file mode 100644 index 0000000..7e43c47 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/AboutUsActivity.kt @@ -0,0 +1,34 @@ +package com.casic.smart.town.sanxi.view + +import com.casic.smart.town.sanxi.BuildConfig +import com.casic.smart.town.sanxi.R +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.kt.lite.base.KotlinBaseActivity +import com.pengxh.kt.lite.extensions.convertColor +import com.pengxh.kt.lite.utils.ImmerseStatusBarUtil +import kotlinx.android.synthetic.main.activity_about_us.* +import kotlinx.android.synthetic.main.include_base_title.* + +class AboutUsActivity : KotlinBaseActivity() { + + override fun initLayoutView(): Int = R.layout.activity_about_us + + override fun setupTopBarLayout() { + ImmersionBar.with(this).statusBarDarkFont(false).init() + ImmerseStatusBarUtil.setColor(this, R.color.mainThemeColor.convertColor(this)) + titleView.text = "关于我们" + leftBackView.setOnClickListener { finish() } + } + + override fun initData() { + versionCodeView.text = String.format("版本信息:V${BuildConfig.VERSION_NAME}") + } + + override fun observeRequestState() { + + } + + override fun initEvent() { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/LoginActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/LoginActivity.kt new file mode 100644 index 0000000..b5d1d50 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/LoginActivity.kt @@ -0,0 +1,97 @@ +package com.casic.smart.town.sanxi.view + +import androidx.lifecycle.ViewModelProvider +import com.casic.smart.town.sanxi.R +import com.casic.smart.town.sanxi.util.AuthenticationHelper +import com.casic.smart.town.sanxi.util.LoadingDialogHub +import com.casic.smart.town.sanxi.util.LocaleConstant +import com.casic.smart.town.sanxi.util.RSAUtils +import com.casic.smart.town.sanxi.vm.AuthenticateViewModel +import com.casic.smart.town.sanxi.vm.LoginViewModel +import com.casic.smart.town.sanxi.vm.UserViewModel +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.kt.lite.base.KotlinBaseActivity +import com.pengxh.kt.lite.extensions.navigatePageTo +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.utils.SaveKeyValues +import kotlinx.android.synthetic.main.activity_login.* + +class LoginActivity : KotlinBaseActivity() { + + private lateinit var authenticateViewModel: AuthenticateViewModel + private lateinit var loginViewModel: LoginViewModel + private lateinit var userViewModel: UserViewModel + + override fun initLayoutView(): Int = R.layout.activity_login + + override fun setupTopBarLayout() { + ImmersionBar.with(this).statusBarDarkFont(true).init() + } + + override fun initData() { + // 设置默认账号密码 + userNameView.setText(SaveKeyValues.getValue(LocaleConstant.ACCOUNT, "") as String) + userPasswordView.setText(SaveKeyValues.getValue(LocaleConstant.PASSWORD, "") as String) + authenticateViewModel = ViewModelProvider(this)[AuthenticateViewModel::class.java] + loginViewModel = ViewModelProvider(this)[LoginViewModel::class.java] + userViewModel = ViewModelProvider(this)[UserViewModel::class.java] + + authenticateViewModel.keyModel.observe(this, { + if (it.code == 200) {//用code判断,别的判断可能有坑 + val keyString = it.data!!.publicKey!! + /** + * 修改密码需要用到key,先存着 + * */ + AuthenticationHelper.savePublicKey(keyString) + val publicKey = RSAUtils.keyStrToPublicKey(keyString) + + val account = userNameView.text.toString() + val userPassword = userPasswordView.text.toString() + val dataByPublicKey = + RSAUtils.encryptDataByPublicKey(userPassword.toByteArray(), publicKey!!) + //登录并获取Token,POST请求 + loginViewModel.enter(it.data!!.sid!!, account, dataByPublicKey) + loginViewModel.enterResultModel.observe(this, { loginResult -> + if (loginResult.code == 200) { + AuthenticationHelper.saveToken(loginResult.data!!.token!!) + /** + * 获取token之后保存用户信息 + * */ + userViewModel.obtainUserDetail() + //验证成功登录 + navigatePageTo() + finish() + } + }) + } + }) + } + + override fun observeRequestState() { + authenticateViewModel.loadState.observe(this, { + LoadingDialogHub.show(this, "登录中,请稍后") + }) + + loginViewModel.loadState.observe(this, { + LoadingDialogHub.dismiss() + }) + } + + override fun initEvent() { + loginButton.setOnClickListener { + val account = userNameView.text.toString().trim() + val userPassword = userPasswordView.text.toString().trim() + if (account.isBlank()) { + "用户名不能为空".show(this) + return@setOnClickListener + } + if (userPassword.isBlank()) { + "密码不能为空".show(this) + return@setOnClickListener + } + SaveKeyValues.putValue(LocaleConstant.ACCOUNT, account) + SaveKeyValues.putValue(LocaleConstant.PASSWORD, userPassword) + authenticateViewModel.obtainPublicKey() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt index 3bea1c9..a4fa951 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt @@ -7,6 +7,7 @@ import com.casic.smart.town.sanxi.R import com.casic.smart.town.sanxi.adapter.ViewPagerAdapter import com.casic.smart.town.sanxi.fragment.AlarmPageFragment +import com.casic.smart.town.sanxi.fragment.MinePageFragment import com.casic.smart.town.sanxi.fragment.MonitorPageFragment import com.casic.smart.town.sanxi.fragment.StatisticsPageFragment import com.gyf.immersionbar.ImmersionBar @@ -33,6 +34,7 @@ fragmentPages.add(StatisticsPageFragment()) fragmentPages.add(MonitorPageFragment()) fragmentPages.add(AlarmPageFragment()) + fragmentPages.add(MinePageFragment()) } override fun initLayoutView(): Int = R.layout.activity_main @@ -68,6 +70,9 @@ R.id.nav_alarm -> { mainViewPager.currentItem = 2 } + R.id.nav_more -> { + mainViewPager.currentItem = 3 + } } false } diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/PermissionActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/PermissionActivity.kt index c478d83..3238f3c 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/view/PermissionActivity.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/PermissionActivity.kt @@ -32,7 +32,7 @@ //先把导航隐私政策声明,后面导航会用到 MapsInitializer.updatePrivacyAgree(this, true) MapsInitializer.updatePrivacyShow(this, true, true) - this.navigatePageTo() + this.navigatePageTo() finish() } diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/SplashScreenActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/SplashScreenActivity.kt new file mode 100644 index 0000000..fa5a74d --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/SplashScreenActivity.kt @@ -0,0 +1,54 @@ +package com.casic.smart.town.sanxi.view + +import android.annotation.SuppressLint +import android.os.CountDownTimer +import androidx.lifecycle.ViewModelProvider +import com.casic.smart.town.sanxi.R +import com.casic.smart.town.sanxi.vm.UserViewModel +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.kt.lite.base.KotlinBaseActivity +import com.pengxh.kt.lite.extensions.navigatePageTo + +@SuppressLint("CustomSplashScreen") +class SplashScreenActivity : KotlinBaseActivity() { + + private lateinit var userDetailViewModel: UserViewModel + private val countDownTimer = object : CountDownTimer(1000, 500) { + override fun onFinish() { + /** + * 获取token之后保存用户信息 + * */ + userDetailViewModel.obtainUserDetail() + userDetailViewModel.userDetailModel.observe(this@SplashScreenActivity, { + if (it.code == 200) { + navigatePageTo() + } else { + navigatePageTo() + } + finish() + }) + } + + override fun onTick(millisUntilFinished: Long) { + + } + } + + override fun initLayoutView(): Int = R.layout.activity_splash + + override fun setupTopBarLayout() { + ImmersionBar.with(this).statusBarDarkFont(false).init() + } + + override fun initData() { + userDetailViewModel = ViewModelProvider(this).get(UserViewModel::class.java) + } + + override fun observeRequestState() { + + } + + override fun initEvent() { + countDownTimer.start() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/vm/AuthenticateViewModel.kt b/app/src/main/java/com/casic/smart/town/sanxi/vm/AuthenticateViewModel.kt new file mode 100644 index 0000000..87ddbf8 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/vm/AuthenticateViewModel.kt @@ -0,0 +1,35 @@ +package com.casic.smart.town.sanxi.vm + +import androidx.lifecycle.MutableLiveData +import com.casic.smart.town.sanxi.base.BaseApplication +import com.casic.smart.town.sanxi.extensions.separateResponseCode +import com.casic.smart.town.sanxi.extensions.toErrorMessage +import com.casic.smart.town.sanxi.model.PublicKeyModel +import com.casic.smart.town.sanxi.util.retrofit.RetrofitServiceManager +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.launch +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.vm.BaseViewModel +import com.pengxh.kt.lite.vm.LoadState + +class AuthenticateViewModel : BaseViewModel() { + + private val gson = Gson() + val keyModel = MutableLiveData() + + fun obtainPublicKey() = launch({ + loadState.value = LoadState.Loading + val response = RetrofitServiceManager.authenticate() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + keyModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ) + } else { + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + it.printStackTrace() + }) +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1df917d..8d4f33b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -6,6 +6,7 @@ + @@ -32,11 +33,14 @@ + + + + + + + diff --git a/app/src/main/assets/svg/mine.svg b/app/src/main/assets/svg/mine.svg new file mode 100644 index 0000000..3fbb0a3 --- /dev/null +++ b/app/src/main/assets/svg/mine.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt b/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt index d730417..b1972c2 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt @@ -1,8 +1,10 @@ package com.casic.smart.town.sanxi.extensions import com.casic.smart.town.sanxi.model.ErrorMessageModel +import com.casic.smart.town.sanxi.util.LocaleConstant import com.google.gson.Gson import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.utils.SaveKeyValues import org.json.JSONObject fun String.separateResponseCode(): Int { @@ -17,4 +19,15 @@ this, object : TypeToken() {}.type ) return errorModel.message.toString() +} + +/** + * 下载路径为 http://xx.com/static/ 拼接downloadUrl + * */ +fun String.appendDownloadUrl(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + LocaleConstant.DEFAULT_SERVER_CONFIG, LocaleConstant.DEFAULT_SERVER + ) as String + return "$defaultValue/static/${this}" } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt b/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt new file mode 100644 index 0000000..1809dc5 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt @@ -0,0 +1,288 @@ +package com.casic.smart.town.sanxi.fragment + +import android.app.ProgressDialog +import android.content.Intent +import android.net.Uri +import android.os.Build +import android.os.CountDownTimer +import androidx.core.content.FileProvider +import androidx.lifecycle.ViewModelProvider +import com.casic.smart.town.sanxi.BuildConfig +import com.casic.smart.town.sanxi.R +import com.casic.smart.town.sanxi.extensions.appendDownloadUrl +import com.casic.smart.town.sanxi.model.UserDetailModel +import com.casic.smart.town.sanxi.util.AuthenticationHelper +import com.casic.smart.town.sanxi.util.LoadingDialogHub +import com.casic.smart.town.sanxi.util.LocaleConstant +import com.casic.smart.town.sanxi.util.RSAUtils +import com.casic.smart.town.sanxi.view.AboutUsActivity +import com.casic.smart.town.sanxi.view.LoginActivity +import com.casic.smart.town.sanxi.vm.LoginViewModel +import com.casic.smart.town.sanxi.vm.UserViewModel +import com.casic.smart.town.sanxi.vm.VersionViewModel +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.base.KotlinBaseFragment +import com.pengxh.kt.lite.callback.OnDownloadListener +import com.pengxh.kt.lite.extensions.* +import com.pengxh.kt.lite.utils.PageNavigationManager +import com.pengxh.kt.lite.utils.SaveKeyValues +import com.pengxh.kt.lite.vm.LoadState +import com.pengxh.kt.lite.widget.dialog.AlertControlDialog +import com.pengxh.kt.lite.widget.dialog.ChangePasswordDialog +import kotlinx.android.synthetic.main.fragment_mine.* +import java.io.File +import java.nio.charset.StandardCharsets + +class MinePageFragment : KotlinBaseFragment() { + + private lateinit var userData: UserDetailModel.Data + private lateinit var userViewModel: UserViewModel + private lateinit var loginViewModel: LoginViewModel + private lateinit var versionViewModel: VersionViewModel + private lateinit var progressDialog: ProgressDialog + + override fun initLayoutView(): Int = R.layout.fragment_mine + + override fun setupTopBarLayout() { + + } + + override fun initData() { + userViewModel = ViewModelProvider(this)[UserViewModel::class.java] + loginViewModel = ViewModelProvider(this)[LoginViewModel::class.java] + versionViewModel = ViewModelProvider(this)[VersionViewModel::class.java] + + //初始化下载对话框 + progressDialog = ProgressDialog(requireContext()) + progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL) + progressDialog.setProgressDrawable(resources.getDrawable(R.drawable.download_progress)) + progressDialog.setCanceledOnTouchOutside(false) + progressDialog.setCancelable(false) + + /** + * 数据监听 + * */ + dataObserve() + } + + override fun observeRequestState() { + userViewModel.loadState.observe(this, { + when (it) { + is LoadState.Loading -> LoadingDialogHub.show(requireActivity(), "修改中,请稍后") + is LoadState.Success -> { + "修改成功,请重新登录".show(requireContext()) + LoadingDialogHub.dismiss() + AuthenticationHelper.removeToken() + requireContext().navigatePageTo() + PageNavigationManager.finishAllActivity() + } + else -> LoadingDialogHub.dismiss() + } + }) + + versionViewModel.loadState.observe(this, { + when (it) { + is LoadState.Loading -> LoadingDialogHub.show(requireActivity(), "检查版本中,请稍后") + else -> LoadingDialogHub.dismiss() + } + }) + } + + override fun initEvent() { + userImageView.setOnClickListener { + "尽情期待~".show(requireContext()) + } + + updateUserButton.setOnClickListener { + LoadingDialogHub.show(requireActivity(), "同步中,请稍后...") + userViewModel.obtainUserDetail() + } + + changePwdLayout.setOnClickListener { + ChangePasswordDialog.Builder() + .setContext(requireContext()) + .setOnDialogButtonClickListener(object : + ChangePasswordDialog.OnDialogButtonClickListener { + override fun onConfirmClick(oldPwd: String, newPwd: String) { + val publicKey = + RSAUtils.keyStrToPublicKey(AuthenticationHelper.publicKey)!! + val oldPassKey = RSAUtils.encryptDataByPublicKey( + oldPwd.toByteArray(StandardCharsets.UTF_8), + publicKey + ) + val newPassKey = RSAUtils.encryptDataByPublicKey( + newPwd.toByteArray(StandardCharsets.UTF_8), + publicKey + ) + userViewModel.changePassword(oldPassKey, newPassKey) + } + }).build().show() + } + + aboutUsLayout.setOnClickListener { + requireContext().navigatePageTo() + } + + updateVersionLayout.setOnClickListener { + versionViewModel.updateVersion() + } + + clearCacheLayout.setOnClickListener { + //删除缓存之后在设置缓存大小 + LoadingDialogHub.show(requireActivity(), "清理中,请稍后") + File(requireContext().cacheDir.path).deleteFile() + object : CountDownTimer(1500, 500) { + override fun onTick(millisUntilFinished: Long) { + + } + + override fun onFinish() { + LoadingDialogHub.dismiss() + cacheSizeView.text = collectApplicationCache().formatFileSize() + } + }.start() + } + + loginOutButton.setOnClickListener { + AlertControlDialog.Builder() + .setContext(requireContext()) + .setTitle("退出登录") + .setMessage("确定要退出吗?") + .setNegativeButton("取消") + .setPositiveButton("确定") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + loginViewModel.out() + } + + override fun onCancelClick() {} + }).build().show() + } + } + + private fun dataObserve() { + userViewModel.userDetailModel.observe(this, { + if (it.code == 200) { + LoadingDialogHub.dismiss() + "同步完成".show(requireContext()) + userData = it.data + updateUserInfo() + } + }) + + versionViewModel.versionResultModel.observe(this, { + if (BuildConfig.VERSION_NAME == it.version) { + "已是最新版本,无需更新".show(requireContext()) + } else { + AlertControlDialog.Builder() + .setContext(requireContext()) + .setTitle("提示") + .setMessage("有新版本,是否更新?") + .setNegativeButton("稍后再说") + .setPositiveButton("立即下载") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + downloadApk(it.downloadUrl) + } + + override fun onCancelClick() { + + } + }).build().show() + } + }) + + loginViewModel.outResultModel.observe(this, { + if (it.code == 200) { + AuthenticationHelper.removeToken() + requireContext().navigatePageTo() + PageNavigationManager.finishAllActivity() + } + }) + } + + private fun collectApplicationCache(): Long { + return File(requireContext().cacheDir.path).calculateSize() + } + + override fun onResume() { + val userDetailJson = SaveKeyValues.getValue(LocaleConstant.USER_DETAIL_MODEL, "") as String + if (userDetailJson.isNotBlank()) { + userData = Gson().fromJson( + userDetailJson, object : TypeToken() {}.type + ) + updateUserInfo() + } + //缓存 + cacheSizeView.text = collectApplicationCache().formatFileSize() + super.onResume() + } + + private fun updateUserInfo() { + //设置头像,圆形,暂时是默认的 +// val roundDrawable = +// BitmapFactory.decodeResource(requireContext().resources, R.mipmap.login_casic) +// .createRoundDrawable( +// requireContext(), +// 3f.dp2px(requireContext()), +// R.color.mainThemeColor.convertColor(requireContext()) +// ) +// userImageView.setImageDrawable(roundDrawable) + userNameView.text = userData.name + userPhoneView.text = String.format("电话:${userData.phone}") + userDeptView.text = String.format("部门:${userData.deptName}") + } + + private fun downloadApk(url: String?) { + progressDialog.setMessage("下载新版本中...") + progressDialog.show() + if (url.toString().isBlank()) { + "抱歉,版本下载失败".show(requireContext()) + return + } + /** + * http://111.198.10.15:11304/static/apk/1.0.1.apk + * */ + val downloadPath = url!!.appendDownloadUrl() + //开始下载 + downloadPath.downloadFile(requireContext().createDownloadFileDir().toString(), + object : OnDownloadListener { + override fun onDownloadStart(totalBytes: Long) { + progressDialog.max = totalBytes.toInt() + } + + override fun onProgressChanged(currentBytes: Long) { + progressDialog.progress = currentBytes.toInt() + } + + override fun onDownloadEnd(file: File?) { + progressDialog.dismiss() + progressDialog.progress = 0 + //安装APK + installApk(file) + } + }) + } + + private fun installApk(apkPackage: File?) { + if (apkPackage == null) { + "文件异常,无法安装".show(requireContext()) + return + } + val intent = Intent(Intent.ACTION_VIEW) + val data: Uri + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { //判断版本大于等于7.0 + data = FileProvider.getUriForFile( + requireContext(), LocaleConstant.APP_AUTHORITY, apkPackage + ) + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) // 给目标应用一个临时授权 + } else { + data = Uri.fromFile(apkPackage) + } + intent.setDataAndType(data, "application/vnd.android.package-archive") + requireContext().startActivity(intent) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java new file mode 100644 index 0000000..467e33d --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java @@ -0,0 +1,43 @@ +package com.casic.smart.town.sanxi.model; + +/** + * 普通实体类,失败/成功数据结构一致 + */ +public class CommonResultModel { + private int code; + private String data; + private String message; + private boolean isSuccess; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return isSuccess; + } + + public void setSuccess(boolean success) { + isSuccess = success; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java new file mode 100644 index 0000000..9bdabef --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java @@ -0,0 +1,62 @@ +package com.casic.smart.town.sanxi.model; + +public class LoginResultModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private String kaptcha; + private String token; + + public String getKaptcha() { + return kaptcha; + } + + public void setKaptcha(String kaptcha) { + this.kaptcha = kaptcha; + } + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java new file mode 100644 index 0000000..09f8d59 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java @@ -0,0 +1,80 @@ +package com.casic.smart.town.sanxi.model; + +public class PublicKeyModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private boolean appKaptcha; + private boolean kaptcha; + private String publicKey; + private String sid; + + public boolean isAppKaptcha() { + return appKaptcha; + } + + public void setAppKaptcha(boolean appKaptcha) { + this.appKaptcha = appKaptcha; + } + + public boolean isKaptcha() { + return kaptcha; + } + + public void setKaptcha(boolean kaptcha) { + this.kaptcha = kaptcha; + } + + public String getPublicKey() { + return publicKey; + } + + public void setPublicKey(String publicKey) { + this.publicKey = publicKey; + } + + public String getSid() { + return sid; + } + + public void setSid(String sid) { + this.sid = sid; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java new file mode 100644 index 0000000..db577fb --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java @@ -0,0 +1,227 @@ +package com.casic.smart.town.sanxi.model; + + +import java.util.List; + +public class UserDetailModel { + private int code; + private Data data; + private String message; + private boolean success; + + public void setCode(int code) { + this.code = code; + } + + public int getCode() { + return code; + } + + public void setData(Data data) { + this.data = data; + } + + public Data getData() { + return data; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getMessage() { + return message; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public boolean getSuccess() { + return success; + } + + public static class Data { + + private String account; + private String attr1; + private String avatar; + private String bizData; + private List dataScope; + private String deptId; + private String deptName; + private List devices; + private String id; + private String ipAddr; + private String name; + private String phone; + private List roleList; + private List roleNames; + private List roleTips; + private String scopeType; + private String sysData; + private String targetId; + private String targetName; + private String tenantId; + + public void setAccount(String account) { + this.account = account; + } + + public String getAccount() { + return account; + } + + public void setAttr1(String attr1) { + this.attr1 = attr1; + } + + public String getAttr1() { + return attr1; + } + + public void setAvatar(String avatar) { + this.avatar = avatar; + } + + public String getAvatar() { + return avatar; + } + + public void setBizData(String bizData) { + this.bizData = bizData; + } + + public String getBizData() { + return bizData; + } + + public void setDataScope(List dataScope) { + this.dataScope = dataScope; + } + + public List getDataScope() { + return dataScope; + } + + public void setDeptId(String deptId) { + this.deptId = deptId; + } + + public String getDeptId() { + return deptId; + } + + public void setDeptName(String deptName) { + this.deptName = deptName; + } + + public String getDeptName() { + return deptName; + } + + public void setDevices(List devices) { + this.devices = devices; + } + + public List getDevices() { + return devices; + } + + public void setId(String id) { + this.id = id; + } + + public String getId() { + return id; + } + + public void setIpAddr(String ipAddr) { + this.ipAddr = ipAddr; + } + + public String getIpAddr() { + return ipAddr; + } + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setPhone(String phone) { + this.phone = phone; + } + + public String getPhone() { + return phone; + } + + public void setRoleList(List roleList) { + this.roleList = roleList; + } + + public List getRoleList() { + return roleList; + } + + public void setRoleNames(List roleNames) { + this.roleNames = roleNames; + } + + public List getRoleNames() { + return roleNames; + } + + public void setRoleTips(List roleTips) { + this.roleTips = roleTips; + } + + public List getRoleTips() { + return roleTips; + } + + public void setScopeType(String scopeType) { + this.scopeType = scopeType; + } + + public String getScopeType() { + return scopeType; + } + + public void setSysData(String sysData) { + this.sysData = sysData; + } + + public String getSysData() { + return sysData; + } + + public void setTargetId(String targetId) { + this.targetId = targetId; + } + + public String getTargetId() { + return targetId; + } + + public void setTargetName(String targetName) { + this.targetName = targetName; + } + + public String getTargetName() { + return targetName; + } + + public void setTenantId(String tenantId) { + this.tenantId = tenantId; + } + + public String getTenantId() { + return tenantId; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/VersionResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/VersionResultModel.java new file mode 100644 index 0000000..79374e0 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/VersionResultModel.java @@ -0,0 +1,61 @@ +package com.casic.smart.town.sanxi.model; + +public class VersionResultModel { + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private String downloadUrl; + private String version; + + public String getDownloadUrl() { + return downloadUrl; + } + + public void setDownloadUrl(String downloadUrl) { + this.downloadUrl = downloadUrl; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/AuthenticationHelper.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/AuthenticationHelper.kt new file mode 100644 index 0000000..f498e4e --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/AuthenticationHelper.kt @@ -0,0 +1,24 @@ +package com.casic.smart.town.sanxi.util + +import com.pengxh.kt.lite.utils.SaveKeyValues + +object AuthenticationHelper { + + fun savePublicKey(key: String) { + SaveKeyValues.putValue("keyString", key) + } + + val publicKey: String? + get() = SaveKeyValues.getValue("keyString", "") as String? + + fun saveToken(token: String?) { + SaveKeyValues.putValue("token", token!!) + } + + val token: String? + get() = SaveKeyValues.getValue("token", "") as String? + + fun removeToken() { + SaveKeyValues.removeKey("token") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt index ab1a36d..99f0d19 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt @@ -14,5 +14,9 @@ const val PAGE_LIMIT = 20 const val DEFAULT_SERVER_CONFIG = "defaultServerConfig" - const val DEFAULT_SERVER = "http://111.198.10.15:11308" + const val DEFAULT_SERVER = "http://36.133.189.112:8082" + const val APP_AUTHORITY = "com.casic.smart.town.sanxi.fileprovider" + const val ACCOUNT = "account" + const val PASSWORD = "password" + const val USER_DETAIL_MODEL = "userDetailModel" } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/RSAUtils.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/RSAUtils.kt new file mode 100644 index 0000000..b9216ba --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/RSAUtils.kt @@ -0,0 +1,55 @@ +package com.casic.smart.town.sanxi.util + +import android.util.Base64 +import java.security.* +import java.security.spec.InvalidKeySpecException +import java.security.spec.X509EncodedKeySpec +import javax.crypto.BadPaddingException +import javax.crypto.Cipher +import javax.crypto.IllegalBlockSizeException +import javax.crypto.NoSuchPaddingException + +object RSAUtils { + //构建Cipher实例时所传入的的字符串,默认为"RSA/NONE/PKCS1Padding" + private fun processData(srcData: ByteArray, key: Key): ByteArray? { //用来保存处理结果 + var resultBytes: ByteArray? = null + try { //获取Cipher实例 + val cipher = Cipher.getInstance("RSA/None/PKCS1Padding") + //初始化Cipher,mode指定是加密还是解密,key为公钥或私钥 + cipher.init(Cipher.ENCRYPT_MODE, key) + //处理数据 + resultBytes = cipher.doFinal(srcData) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: NoSuchPaddingException) { + e.printStackTrace() + } catch (e: InvalidKeyException) { + e.printStackTrace() + } catch (e: BadPaddingException) { + e.printStackTrace() + } catch (e: IllegalBlockSizeException) { + e.printStackTrace() + } + return resultBytes + } + + fun encryptDataByPublicKey(srcData: ByteArray, publicKey: PublicKey): String { + val resultBytes = processData(srcData, publicKey) + return Base64.encodeToString(resultBytes, Base64.DEFAULT) + } + + fun keyStrToPublicKey(publicKeyStr: String?): PublicKey? { + var publicKey: PublicKey? = null + val keyBytes = Base64.decode(publicKeyStr, Base64.DEFAULT) + val keySpec = X509EncodedKeySpec(keyBytes) + try { + val keyFactory = KeyFactory.getInstance("RSA") + publicKey = keyFactory.generatePublic(keySpec) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: InvalidKeySpecException) { + e.printStackTrace() + } + return publicKey + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt index 63db0ec..50d1742 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt @@ -1,12 +1,64 @@ package com.casic.smart.town.sanxi.util.retrofit -import retrofit2.http.GET +import retrofit2.http.* interface RetrofitService { + /** + * PublicKey校验 + */ + @GET("/config/baseConfig") + suspend fun obtainPublicKey(): String + + /** + * 登录并获取Token + * + * @param sid + * @param account 用户名 + * @param secretKey 加密后的密码 + */ + @FormUrlEncoded + @POST("/user/login") + suspend fun login( + @Field("sid") sid: String, + @Field("username") account: String, + @Field("password") secretKey: String + ): String + + /** + * 退出登录 + */ + @GET("/user/logout") + suspend fun loginOut(@Header("token") token: String): String + + /** + * 获取用户信息 + */ + @GET("/user/info") + suspend fun obtainUserDetail(@Header("token") token: String): String + + /** + * 修改密码 + * + * @param oldPwd 旧密码 + * @param newPwd 新密码 + */ + @FormUrlEncoded + @POST("/mgr/changePwd") + suspend fun changePassword( + @Header("token") token: String, + @Field("oldPwd") oldPwd: String, + @Field("newPwd") newPwd: String + ): String /** * 根据布防状态统计闸井数量接口 */ @GET("/well/countByBfzt") - suspend fun countWellByState(): String + suspend fun countWellByState(@Header("token") token: String): String + + /** + * 更新APK版本 + */ + @POST("/app/checkVersion") + suspend fun obtainVersionResult(@Header("token") token: String): String } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt index 46bbf4b..410444b 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt @@ -1,5 +1,6 @@ package com.casic.smart.town.sanxi.util.retrofit +import com.casic.smart.town.sanxi.util.AuthenticationHelper import com.casic.smart.town.sanxi.util.LocaleConstant import com.pengxh.kt.lite.utils.RetrofitFactory import com.pengxh.kt.lite.utils.SaveKeyValues @@ -15,9 +16,52 @@ } /** + * 验证PublicKey + */ + suspend fun authenticate(): String { + return api.obtainPublicKey() + } + + /** + * 登录并获取Token + */ + suspend fun login(sid: String, account: String, secretKey: String): String { + return api.login(sid, account, secretKey) + } + + /** + * 退出登录 + */ + suspend fun loginOut(): String { + return api.loginOut(AuthenticationHelper.token!!) + } + + /** + * 获取用户信息 + */ + suspend fun obtainUserDetail(): String { + return api.obtainUserDetail(AuthenticationHelper.token!!) + } + + /** + * 修改密码 + */ + suspend fun changePassword(oldPwd: String, newPwd: String): String { + return api.changePassword(AuthenticationHelper.token!!, oldPwd, newPwd) + } + + /** * 根据布防状态统计闸井数量接口 */ suspend fun countWellByState(): String { - return api.countWellByState() + return api.countWellByState(AuthenticationHelper.token!!) } + + /** + * 更新APK版本 + */ + suspend fun updateVersion(): String { + return api.obtainVersionResult(AuthenticationHelper.token!!) + } + } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/AboutUsActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/AboutUsActivity.kt new file mode 100644 index 0000000..7e43c47 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/AboutUsActivity.kt @@ -0,0 +1,34 @@ +package com.casic.smart.town.sanxi.view + +import com.casic.smart.town.sanxi.BuildConfig +import com.casic.smart.town.sanxi.R +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.kt.lite.base.KotlinBaseActivity +import com.pengxh.kt.lite.extensions.convertColor +import com.pengxh.kt.lite.utils.ImmerseStatusBarUtil +import kotlinx.android.synthetic.main.activity_about_us.* +import kotlinx.android.synthetic.main.include_base_title.* + +class AboutUsActivity : KotlinBaseActivity() { + + override fun initLayoutView(): Int = R.layout.activity_about_us + + override fun setupTopBarLayout() { + ImmersionBar.with(this).statusBarDarkFont(false).init() + ImmerseStatusBarUtil.setColor(this, R.color.mainThemeColor.convertColor(this)) + titleView.text = "关于我们" + leftBackView.setOnClickListener { finish() } + } + + override fun initData() { + versionCodeView.text = String.format("版本信息:V${BuildConfig.VERSION_NAME}") + } + + override fun observeRequestState() { + + } + + override fun initEvent() { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/LoginActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/LoginActivity.kt new file mode 100644 index 0000000..b5d1d50 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/LoginActivity.kt @@ -0,0 +1,97 @@ +package com.casic.smart.town.sanxi.view + +import androidx.lifecycle.ViewModelProvider +import com.casic.smart.town.sanxi.R +import com.casic.smart.town.sanxi.util.AuthenticationHelper +import com.casic.smart.town.sanxi.util.LoadingDialogHub +import com.casic.smart.town.sanxi.util.LocaleConstant +import com.casic.smart.town.sanxi.util.RSAUtils +import com.casic.smart.town.sanxi.vm.AuthenticateViewModel +import com.casic.smart.town.sanxi.vm.LoginViewModel +import com.casic.smart.town.sanxi.vm.UserViewModel +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.kt.lite.base.KotlinBaseActivity +import com.pengxh.kt.lite.extensions.navigatePageTo +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.utils.SaveKeyValues +import kotlinx.android.synthetic.main.activity_login.* + +class LoginActivity : KotlinBaseActivity() { + + private lateinit var authenticateViewModel: AuthenticateViewModel + private lateinit var loginViewModel: LoginViewModel + private lateinit var userViewModel: UserViewModel + + override fun initLayoutView(): Int = R.layout.activity_login + + override fun setupTopBarLayout() { + ImmersionBar.with(this).statusBarDarkFont(true).init() + } + + override fun initData() { + // 设置默认账号密码 + userNameView.setText(SaveKeyValues.getValue(LocaleConstant.ACCOUNT, "") as String) + userPasswordView.setText(SaveKeyValues.getValue(LocaleConstant.PASSWORD, "") as String) + authenticateViewModel = ViewModelProvider(this)[AuthenticateViewModel::class.java] + loginViewModel = ViewModelProvider(this)[LoginViewModel::class.java] + userViewModel = ViewModelProvider(this)[UserViewModel::class.java] + + authenticateViewModel.keyModel.observe(this, { + if (it.code == 200) {//用code判断,别的判断可能有坑 + val keyString = it.data!!.publicKey!! + /** + * 修改密码需要用到key,先存着 + * */ + AuthenticationHelper.savePublicKey(keyString) + val publicKey = RSAUtils.keyStrToPublicKey(keyString) + + val account = userNameView.text.toString() + val userPassword = userPasswordView.text.toString() + val dataByPublicKey = + RSAUtils.encryptDataByPublicKey(userPassword.toByteArray(), publicKey!!) + //登录并获取Token,POST请求 + loginViewModel.enter(it.data!!.sid!!, account, dataByPublicKey) + loginViewModel.enterResultModel.observe(this, { loginResult -> + if (loginResult.code == 200) { + AuthenticationHelper.saveToken(loginResult.data!!.token!!) + /** + * 获取token之后保存用户信息 + * */ + userViewModel.obtainUserDetail() + //验证成功登录 + navigatePageTo() + finish() + } + }) + } + }) + } + + override fun observeRequestState() { + authenticateViewModel.loadState.observe(this, { + LoadingDialogHub.show(this, "登录中,请稍后") + }) + + loginViewModel.loadState.observe(this, { + LoadingDialogHub.dismiss() + }) + } + + override fun initEvent() { + loginButton.setOnClickListener { + val account = userNameView.text.toString().trim() + val userPassword = userPasswordView.text.toString().trim() + if (account.isBlank()) { + "用户名不能为空".show(this) + return@setOnClickListener + } + if (userPassword.isBlank()) { + "密码不能为空".show(this) + return@setOnClickListener + } + SaveKeyValues.putValue(LocaleConstant.ACCOUNT, account) + SaveKeyValues.putValue(LocaleConstant.PASSWORD, userPassword) + authenticateViewModel.obtainPublicKey() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt index 3bea1c9..a4fa951 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt @@ -7,6 +7,7 @@ import com.casic.smart.town.sanxi.R import com.casic.smart.town.sanxi.adapter.ViewPagerAdapter import com.casic.smart.town.sanxi.fragment.AlarmPageFragment +import com.casic.smart.town.sanxi.fragment.MinePageFragment import com.casic.smart.town.sanxi.fragment.MonitorPageFragment import com.casic.smart.town.sanxi.fragment.StatisticsPageFragment import com.gyf.immersionbar.ImmersionBar @@ -33,6 +34,7 @@ fragmentPages.add(StatisticsPageFragment()) fragmentPages.add(MonitorPageFragment()) fragmentPages.add(AlarmPageFragment()) + fragmentPages.add(MinePageFragment()) } override fun initLayoutView(): Int = R.layout.activity_main @@ -68,6 +70,9 @@ R.id.nav_alarm -> { mainViewPager.currentItem = 2 } + R.id.nav_more -> { + mainViewPager.currentItem = 3 + } } false } diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/PermissionActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/PermissionActivity.kt index c478d83..3238f3c 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/view/PermissionActivity.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/PermissionActivity.kt @@ -32,7 +32,7 @@ //先把导航隐私政策声明,后面导航会用到 MapsInitializer.updatePrivacyAgree(this, true) MapsInitializer.updatePrivacyShow(this, true, true) - this.navigatePageTo() + this.navigatePageTo() finish() } diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/SplashScreenActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/SplashScreenActivity.kt new file mode 100644 index 0000000..fa5a74d --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/SplashScreenActivity.kt @@ -0,0 +1,54 @@ +package com.casic.smart.town.sanxi.view + +import android.annotation.SuppressLint +import android.os.CountDownTimer +import androidx.lifecycle.ViewModelProvider +import com.casic.smart.town.sanxi.R +import com.casic.smart.town.sanxi.vm.UserViewModel +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.kt.lite.base.KotlinBaseActivity +import com.pengxh.kt.lite.extensions.navigatePageTo + +@SuppressLint("CustomSplashScreen") +class SplashScreenActivity : KotlinBaseActivity() { + + private lateinit var userDetailViewModel: UserViewModel + private val countDownTimer = object : CountDownTimer(1000, 500) { + override fun onFinish() { + /** + * 获取token之后保存用户信息 + * */ + userDetailViewModel.obtainUserDetail() + userDetailViewModel.userDetailModel.observe(this@SplashScreenActivity, { + if (it.code == 200) { + navigatePageTo() + } else { + navigatePageTo() + } + finish() + }) + } + + override fun onTick(millisUntilFinished: Long) { + + } + } + + override fun initLayoutView(): Int = R.layout.activity_splash + + override fun setupTopBarLayout() { + ImmersionBar.with(this).statusBarDarkFont(false).init() + } + + override fun initData() { + userDetailViewModel = ViewModelProvider(this).get(UserViewModel::class.java) + } + + override fun observeRequestState() { + + } + + override fun initEvent() { + countDownTimer.start() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/vm/AuthenticateViewModel.kt b/app/src/main/java/com/casic/smart/town/sanxi/vm/AuthenticateViewModel.kt new file mode 100644 index 0000000..87ddbf8 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/vm/AuthenticateViewModel.kt @@ -0,0 +1,35 @@ +package com.casic.smart.town.sanxi.vm + +import androidx.lifecycle.MutableLiveData +import com.casic.smart.town.sanxi.base.BaseApplication +import com.casic.smart.town.sanxi.extensions.separateResponseCode +import com.casic.smart.town.sanxi.extensions.toErrorMessage +import com.casic.smart.town.sanxi.model.PublicKeyModel +import com.casic.smart.town.sanxi.util.retrofit.RetrofitServiceManager +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.launch +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.vm.BaseViewModel +import com.pengxh.kt.lite.vm.LoadState + +class AuthenticateViewModel : BaseViewModel() { + + private val gson = Gson() + val keyModel = MutableLiveData() + + fun obtainPublicKey() = launch({ + loadState.value = LoadState.Loading + val response = RetrofitServiceManager.authenticate() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + keyModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ) + } else { + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + it.printStackTrace() + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/vm/LoginViewModel.kt b/app/src/main/java/com/casic/smart/town/sanxi/vm/LoginViewModel.kt new file mode 100644 index 0000000..c377c61 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/vm/LoginViewModel.kt @@ -0,0 +1,54 @@ +package com.casic.smart.town.sanxi.vm + +import androidx.lifecycle.MutableLiveData +import com.casic.smart.town.sanxi.base.BaseApplication +import com.casic.smart.town.sanxi.extensions.separateResponseCode +import com.casic.smart.town.sanxi.extensions.toErrorMessage +import com.casic.smart.town.sanxi.model.CommonResultModel +import com.casic.smart.town.sanxi.model.LoginResultModel +import com.casic.smart.town.sanxi.util.retrofit.RetrofitServiceManager +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.launch +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.vm.BaseViewModel +import com.pengxh.kt.lite.vm.LoadState + +class LoginViewModel : BaseViewModel() { + + private val gson = Gson() + val enterResultModel = MutableLiveData() + val outResultModel = MutableLiveData() + + fun enter(sid: String, account: String, secretKey: String) = launch({ + val response = RetrofitServiceManager.login(sid, account, secretKey) + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + loadState.value = LoadState.Success + "登录成功".show(BaseApplication.obtainInstance()) + enterResultModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ) + } else { + loadState.value = LoadState.Fail + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + loadState.value = LoadState.Fail + it.printStackTrace() + }) + + fun out() = launch({ + val response = RetrofitServiceManager.loginOut() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + outResultModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ) + } else { + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + it.printStackTrace() + }) +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1df917d..8d4f33b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -6,6 +6,7 @@ + @@ -32,11 +33,14 @@ + + + + + + + diff --git a/app/src/main/assets/svg/mine.svg b/app/src/main/assets/svg/mine.svg new file mode 100644 index 0000000..3fbb0a3 --- /dev/null +++ b/app/src/main/assets/svg/mine.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt b/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt index d730417..b1972c2 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt @@ -1,8 +1,10 @@ package com.casic.smart.town.sanxi.extensions import com.casic.smart.town.sanxi.model.ErrorMessageModel +import com.casic.smart.town.sanxi.util.LocaleConstant import com.google.gson.Gson import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.utils.SaveKeyValues import org.json.JSONObject fun String.separateResponseCode(): Int { @@ -17,4 +19,15 @@ this, object : TypeToken() {}.type ) return errorModel.message.toString() +} + +/** + * 下载路径为 http://xx.com/static/ 拼接downloadUrl + * */ +fun String.appendDownloadUrl(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + LocaleConstant.DEFAULT_SERVER_CONFIG, LocaleConstant.DEFAULT_SERVER + ) as String + return "$defaultValue/static/${this}" } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt b/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt new file mode 100644 index 0000000..1809dc5 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt @@ -0,0 +1,288 @@ +package com.casic.smart.town.sanxi.fragment + +import android.app.ProgressDialog +import android.content.Intent +import android.net.Uri +import android.os.Build +import android.os.CountDownTimer +import androidx.core.content.FileProvider +import androidx.lifecycle.ViewModelProvider +import com.casic.smart.town.sanxi.BuildConfig +import com.casic.smart.town.sanxi.R +import com.casic.smart.town.sanxi.extensions.appendDownloadUrl +import com.casic.smart.town.sanxi.model.UserDetailModel +import com.casic.smart.town.sanxi.util.AuthenticationHelper +import com.casic.smart.town.sanxi.util.LoadingDialogHub +import com.casic.smart.town.sanxi.util.LocaleConstant +import com.casic.smart.town.sanxi.util.RSAUtils +import com.casic.smart.town.sanxi.view.AboutUsActivity +import com.casic.smart.town.sanxi.view.LoginActivity +import com.casic.smart.town.sanxi.vm.LoginViewModel +import com.casic.smart.town.sanxi.vm.UserViewModel +import com.casic.smart.town.sanxi.vm.VersionViewModel +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.base.KotlinBaseFragment +import com.pengxh.kt.lite.callback.OnDownloadListener +import com.pengxh.kt.lite.extensions.* +import com.pengxh.kt.lite.utils.PageNavigationManager +import com.pengxh.kt.lite.utils.SaveKeyValues +import com.pengxh.kt.lite.vm.LoadState +import com.pengxh.kt.lite.widget.dialog.AlertControlDialog +import com.pengxh.kt.lite.widget.dialog.ChangePasswordDialog +import kotlinx.android.synthetic.main.fragment_mine.* +import java.io.File +import java.nio.charset.StandardCharsets + +class MinePageFragment : KotlinBaseFragment() { + + private lateinit var userData: UserDetailModel.Data + private lateinit var userViewModel: UserViewModel + private lateinit var loginViewModel: LoginViewModel + private lateinit var versionViewModel: VersionViewModel + private lateinit var progressDialog: ProgressDialog + + override fun initLayoutView(): Int = R.layout.fragment_mine + + override fun setupTopBarLayout() { + + } + + override fun initData() { + userViewModel = ViewModelProvider(this)[UserViewModel::class.java] + loginViewModel = ViewModelProvider(this)[LoginViewModel::class.java] + versionViewModel = ViewModelProvider(this)[VersionViewModel::class.java] + + //初始化下载对话框 + progressDialog = ProgressDialog(requireContext()) + progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL) + progressDialog.setProgressDrawable(resources.getDrawable(R.drawable.download_progress)) + progressDialog.setCanceledOnTouchOutside(false) + progressDialog.setCancelable(false) + + /** + * 数据监听 + * */ + dataObserve() + } + + override fun observeRequestState() { + userViewModel.loadState.observe(this, { + when (it) { + is LoadState.Loading -> LoadingDialogHub.show(requireActivity(), "修改中,请稍后") + is LoadState.Success -> { + "修改成功,请重新登录".show(requireContext()) + LoadingDialogHub.dismiss() + AuthenticationHelper.removeToken() + requireContext().navigatePageTo() + PageNavigationManager.finishAllActivity() + } + else -> LoadingDialogHub.dismiss() + } + }) + + versionViewModel.loadState.observe(this, { + when (it) { + is LoadState.Loading -> LoadingDialogHub.show(requireActivity(), "检查版本中,请稍后") + else -> LoadingDialogHub.dismiss() + } + }) + } + + override fun initEvent() { + userImageView.setOnClickListener { + "尽情期待~".show(requireContext()) + } + + updateUserButton.setOnClickListener { + LoadingDialogHub.show(requireActivity(), "同步中,请稍后...") + userViewModel.obtainUserDetail() + } + + changePwdLayout.setOnClickListener { + ChangePasswordDialog.Builder() + .setContext(requireContext()) + .setOnDialogButtonClickListener(object : + ChangePasswordDialog.OnDialogButtonClickListener { + override fun onConfirmClick(oldPwd: String, newPwd: String) { + val publicKey = + RSAUtils.keyStrToPublicKey(AuthenticationHelper.publicKey)!! + val oldPassKey = RSAUtils.encryptDataByPublicKey( + oldPwd.toByteArray(StandardCharsets.UTF_8), + publicKey + ) + val newPassKey = RSAUtils.encryptDataByPublicKey( + newPwd.toByteArray(StandardCharsets.UTF_8), + publicKey + ) + userViewModel.changePassword(oldPassKey, newPassKey) + } + }).build().show() + } + + aboutUsLayout.setOnClickListener { + requireContext().navigatePageTo() + } + + updateVersionLayout.setOnClickListener { + versionViewModel.updateVersion() + } + + clearCacheLayout.setOnClickListener { + //删除缓存之后在设置缓存大小 + LoadingDialogHub.show(requireActivity(), "清理中,请稍后") + File(requireContext().cacheDir.path).deleteFile() + object : CountDownTimer(1500, 500) { + override fun onTick(millisUntilFinished: Long) { + + } + + override fun onFinish() { + LoadingDialogHub.dismiss() + cacheSizeView.text = collectApplicationCache().formatFileSize() + } + }.start() + } + + loginOutButton.setOnClickListener { + AlertControlDialog.Builder() + .setContext(requireContext()) + .setTitle("退出登录") + .setMessage("确定要退出吗?") + .setNegativeButton("取消") + .setPositiveButton("确定") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + loginViewModel.out() + } + + override fun onCancelClick() {} + }).build().show() + } + } + + private fun dataObserve() { + userViewModel.userDetailModel.observe(this, { + if (it.code == 200) { + LoadingDialogHub.dismiss() + "同步完成".show(requireContext()) + userData = it.data + updateUserInfo() + } + }) + + versionViewModel.versionResultModel.observe(this, { + if (BuildConfig.VERSION_NAME == it.version) { + "已是最新版本,无需更新".show(requireContext()) + } else { + AlertControlDialog.Builder() + .setContext(requireContext()) + .setTitle("提示") + .setMessage("有新版本,是否更新?") + .setNegativeButton("稍后再说") + .setPositiveButton("立即下载") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + downloadApk(it.downloadUrl) + } + + override fun onCancelClick() { + + } + }).build().show() + } + }) + + loginViewModel.outResultModel.observe(this, { + if (it.code == 200) { + AuthenticationHelper.removeToken() + requireContext().navigatePageTo() + PageNavigationManager.finishAllActivity() + } + }) + } + + private fun collectApplicationCache(): Long { + return File(requireContext().cacheDir.path).calculateSize() + } + + override fun onResume() { + val userDetailJson = SaveKeyValues.getValue(LocaleConstant.USER_DETAIL_MODEL, "") as String + if (userDetailJson.isNotBlank()) { + userData = Gson().fromJson( + userDetailJson, object : TypeToken() {}.type + ) + updateUserInfo() + } + //缓存 + cacheSizeView.text = collectApplicationCache().formatFileSize() + super.onResume() + } + + private fun updateUserInfo() { + //设置头像,圆形,暂时是默认的 +// val roundDrawable = +// BitmapFactory.decodeResource(requireContext().resources, R.mipmap.login_casic) +// .createRoundDrawable( +// requireContext(), +// 3f.dp2px(requireContext()), +// R.color.mainThemeColor.convertColor(requireContext()) +// ) +// userImageView.setImageDrawable(roundDrawable) + userNameView.text = userData.name + userPhoneView.text = String.format("电话:${userData.phone}") + userDeptView.text = String.format("部门:${userData.deptName}") + } + + private fun downloadApk(url: String?) { + progressDialog.setMessage("下载新版本中...") + progressDialog.show() + if (url.toString().isBlank()) { + "抱歉,版本下载失败".show(requireContext()) + return + } + /** + * http://111.198.10.15:11304/static/apk/1.0.1.apk + * */ + val downloadPath = url!!.appendDownloadUrl() + //开始下载 + downloadPath.downloadFile(requireContext().createDownloadFileDir().toString(), + object : OnDownloadListener { + override fun onDownloadStart(totalBytes: Long) { + progressDialog.max = totalBytes.toInt() + } + + override fun onProgressChanged(currentBytes: Long) { + progressDialog.progress = currentBytes.toInt() + } + + override fun onDownloadEnd(file: File?) { + progressDialog.dismiss() + progressDialog.progress = 0 + //安装APK + installApk(file) + } + }) + } + + private fun installApk(apkPackage: File?) { + if (apkPackage == null) { + "文件异常,无法安装".show(requireContext()) + return + } + val intent = Intent(Intent.ACTION_VIEW) + val data: Uri + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { //判断版本大于等于7.0 + data = FileProvider.getUriForFile( + requireContext(), LocaleConstant.APP_AUTHORITY, apkPackage + ) + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) // 给目标应用一个临时授权 + } else { + data = Uri.fromFile(apkPackage) + } + intent.setDataAndType(data, "application/vnd.android.package-archive") + requireContext().startActivity(intent) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java new file mode 100644 index 0000000..467e33d --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java @@ -0,0 +1,43 @@ +package com.casic.smart.town.sanxi.model; + +/** + * 普通实体类,失败/成功数据结构一致 + */ +public class CommonResultModel { + private int code; + private String data; + private String message; + private boolean isSuccess; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return isSuccess; + } + + public void setSuccess(boolean success) { + isSuccess = success; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java new file mode 100644 index 0000000..9bdabef --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java @@ -0,0 +1,62 @@ +package com.casic.smart.town.sanxi.model; + +public class LoginResultModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private String kaptcha; + private String token; + + public String getKaptcha() { + return kaptcha; + } + + public void setKaptcha(String kaptcha) { + this.kaptcha = kaptcha; + } + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java new file mode 100644 index 0000000..09f8d59 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java @@ -0,0 +1,80 @@ +package com.casic.smart.town.sanxi.model; + +public class PublicKeyModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private boolean appKaptcha; + private boolean kaptcha; + private String publicKey; + private String sid; + + public boolean isAppKaptcha() { + return appKaptcha; + } + + public void setAppKaptcha(boolean appKaptcha) { + this.appKaptcha = appKaptcha; + } + + public boolean isKaptcha() { + return kaptcha; + } + + public void setKaptcha(boolean kaptcha) { + this.kaptcha = kaptcha; + } + + public String getPublicKey() { + return publicKey; + } + + public void setPublicKey(String publicKey) { + this.publicKey = publicKey; + } + + public String getSid() { + return sid; + } + + public void setSid(String sid) { + this.sid = sid; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java new file mode 100644 index 0000000..db577fb --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java @@ -0,0 +1,227 @@ +package com.casic.smart.town.sanxi.model; + + +import java.util.List; + +public class UserDetailModel { + private int code; + private Data data; + private String message; + private boolean success; + + public void setCode(int code) { + this.code = code; + } + + public int getCode() { + return code; + } + + public void setData(Data data) { + this.data = data; + } + + public Data getData() { + return data; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getMessage() { + return message; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public boolean getSuccess() { + return success; + } + + public static class Data { + + private String account; + private String attr1; + private String avatar; + private String bizData; + private List dataScope; + private String deptId; + private String deptName; + private List devices; + private String id; + private String ipAddr; + private String name; + private String phone; + private List roleList; + private List roleNames; + private List roleTips; + private String scopeType; + private String sysData; + private String targetId; + private String targetName; + private String tenantId; + + public void setAccount(String account) { + this.account = account; + } + + public String getAccount() { + return account; + } + + public void setAttr1(String attr1) { + this.attr1 = attr1; + } + + public String getAttr1() { + return attr1; + } + + public void setAvatar(String avatar) { + this.avatar = avatar; + } + + public String getAvatar() { + return avatar; + } + + public void setBizData(String bizData) { + this.bizData = bizData; + } + + public String getBizData() { + return bizData; + } + + public void setDataScope(List dataScope) { + this.dataScope = dataScope; + } + + public List getDataScope() { + return dataScope; + } + + public void setDeptId(String deptId) { + this.deptId = deptId; + } + + public String getDeptId() { + return deptId; + } + + public void setDeptName(String deptName) { + this.deptName = deptName; + } + + public String getDeptName() { + return deptName; + } + + public void setDevices(List devices) { + this.devices = devices; + } + + public List getDevices() { + return devices; + } + + public void setId(String id) { + this.id = id; + } + + public String getId() { + return id; + } + + public void setIpAddr(String ipAddr) { + this.ipAddr = ipAddr; + } + + public String getIpAddr() { + return ipAddr; + } + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setPhone(String phone) { + this.phone = phone; + } + + public String getPhone() { + return phone; + } + + public void setRoleList(List roleList) { + this.roleList = roleList; + } + + public List getRoleList() { + return roleList; + } + + public void setRoleNames(List roleNames) { + this.roleNames = roleNames; + } + + public List getRoleNames() { + return roleNames; + } + + public void setRoleTips(List roleTips) { + this.roleTips = roleTips; + } + + public List getRoleTips() { + return roleTips; + } + + public void setScopeType(String scopeType) { + this.scopeType = scopeType; + } + + public String getScopeType() { + return scopeType; + } + + public void setSysData(String sysData) { + this.sysData = sysData; + } + + public String getSysData() { + return sysData; + } + + public void setTargetId(String targetId) { + this.targetId = targetId; + } + + public String getTargetId() { + return targetId; + } + + public void setTargetName(String targetName) { + this.targetName = targetName; + } + + public String getTargetName() { + return targetName; + } + + public void setTenantId(String tenantId) { + this.tenantId = tenantId; + } + + public String getTenantId() { + return tenantId; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/VersionResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/VersionResultModel.java new file mode 100644 index 0000000..79374e0 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/VersionResultModel.java @@ -0,0 +1,61 @@ +package com.casic.smart.town.sanxi.model; + +public class VersionResultModel { + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private String downloadUrl; + private String version; + + public String getDownloadUrl() { + return downloadUrl; + } + + public void setDownloadUrl(String downloadUrl) { + this.downloadUrl = downloadUrl; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/AuthenticationHelper.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/AuthenticationHelper.kt new file mode 100644 index 0000000..f498e4e --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/AuthenticationHelper.kt @@ -0,0 +1,24 @@ +package com.casic.smart.town.sanxi.util + +import com.pengxh.kt.lite.utils.SaveKeyValues + +object AuthenticationHelper { + + fun savePublicKey(key: String) { + SaveKeyValues.putValue("keyString", key) + } + + val publicKey: String? + get() = SaveKeyValues.getValue("keyString", "") as String? + + fun saveToken(token: String?) { + SaveKeyValues.putValue("token", token!!) + } + + val token: String? + get() = SaveKeyValues.getValue("token", "") as String? + + fun removeToken() { + SaveKeyValues.removeKey("token") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt index ab1a36d..99f0d19 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt @@ -14,5 +14,9 @@ const val PAGE_LIMIT = 20 const val DEFAULT_SERVER_CONFIG = "defaultServerConfig" - const val DEFAULT_SERVER = "http://111.198.10.15:11308" + const val DEFAULT_SERVER = "http://36.133.189.112:8082" + const val APP_AUTHORITY = "com.casic.smart.town.sanxi.fileprovider" + const val ACCOUNT = "account" + const val PASSWORD = "password" + const val USER_DETAIL_MODEL = "userDetailModel" } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/RSAUtils.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/RSAUtils.kt new file mode 100644 index 0000000..b9216ba --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/RSAUtils.kt @@ -0,0 +1,55 @@ +package com.casic.smart.town.sanxi.util + +import android.util.Base64 +import java.security.* +import java.security.spec.InvalidKeySpecException +import java.security.spec.X509EncodedKeySpec +import javax.crypto.BadPaddingException +import javax.crypto.Cipher +import javax.crypto.IllegalBlockSizeException +import javax.crypto.NoSuchPaddingException + +object RSAUtils { + //构建Cipher实例时所传入的的字符串,默认为"RSA/NONE/PKCS1Padding" + private fun processData(srcData: ByteArray, key: Key): ByteArray? { //用来保存处理结果 + var resultBytes: ByteArray? = null + try { //获取Cipher实例 + val cipher = Cipher.getInstance("RSA/None/PKCS1Padding") + //初始化Cipher,mode指定是加密还是解密,key为公钥或私钥 + cipher.init(Cipher.ENCRYPT_MODE, key) + //处理数据 + resultBytes = cipher.doFinal(srcData) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: NoSuchPaddingException) { + e.printStackTrace() + } catch (e: InvalidKeyException) { + e.printStackTrace() + } catch (e: BadPaddingException) { + e.printStackTrace() + } catch (e: IllegalBlockSizeException) { + e.printStackTrace() + } + return resultBytes + } + + fun encryptDataByPublicKey(srcData: ByteArray, publicKey: PublicKey): String { + val resultBytes = processData(srcData, publicKey) + return Base64.encodeToString(resultBytes, Base64.DEFAULT) + } + + fun keyStrToPublicKey(publicKeyStr: String?): PublicKey? { + var publicKey: PublicKey? = null + val keyBytes = Base64.decode(publicKeyStr, Base64.DEFAULT) + val keySpec = X509EncodedKeySpec(keyBytes) + try { + val keyFactory = KeyFactory.getInstance("RSA") + publicKey = keyFactory.generatePublic(keySpec) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: InvalidKeySpecException) { + e.printStackTrace() + } + return publicKey + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt index 63db0ec..50d1742 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt @@ -1,12 +1,64 @@ package com.casic.smart.town.sanxi.util.retrofit -import retrofit2.http.GET +import retrofit2.http.* interface RetrofitService { + /** + * PublicKey校验 + */ + @GET("/config/baseConfig") + suspend fun obtainPublicKey(): String + + /** + * 登录并获取Token + * + * @param sid + * @param account 用户名 + * @param secretKey 加密后的密码 + */ + @FormUrlEncoded + @POST("/user/login") + suspend fun login( + @Field("sid") sid: String, + @Field("username") account: String, + @Field("password") secretKey: String + ): String + + /** + * 退出登录 + */ + @GET("/user/logout") + suspend fun loginOut(@Header("token") token: String): String + + /** + * 获取用户信息 + */ + @GET("/user/info") + suspend fun obtainUserDetail(@Header("token") token: String): String + + /** + * 修改密码 + * + * @param oldPwd 旧密码 + * @param newPwd 新密码 + */ + @FormUrlEncoded + @POST("/mgr/changePwd") + suspend fun changePassword( + @Header("token") token: String, + @Field("oldPwd") oldPwd: String, + @Field("newPwd") newPwd: String + ): String /** * 根据布防状态统计闸井数量接口 */ @GET("/well/countByBfzt") - suspend fun countWellByState(): String + suspend fun countWellByState(@Header("token") token: String): String + + /** + * 更新APK版本 + */ + @POST("/app/checkVersion") + suspend fun obtainVersionResult(@Header("token") token: String): String } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt index 46bbf4b..410444b 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt @@ -1,5 +1,6 @@ package com.casic.smart.town.sanxi.util.retrofit +import com.casic.smart.town.sanxi.util.AuthenticationHelper import com.casic.smart.town.sanxi.util.LocaleConstant import com.pengxh.kt.lite.utils.RetrofitFactory import com.pengxh.kt.lite.utils.SaveKeyValues @@ -15,9 +16,52 @@ } /** + * 验证PublicKey + */ + suspend fun authenticate(): String { + return api.obtainPublicKey() + } + + /** + * 登录并获取Token + */ + suspend fun login(sid: String, account: String, secretKey: String): String { + return api.login(sid, account, secretKey) + } + + /** + * 退出登录 + */ + suspend fun loginOut(): String { + return api.loginOut(AuthenticationHelper.token!!) + } + + /** + * 获取用户信息 + */ + suspend fun obtainUserDetail(): String { + return api.obtainUserDetail(AuthenticationHelper.token!!) + } + + /** + * 修改密码 + */ + suspend fun changePassword(oldPwd: String, newPwd: String): String { + return api.changePassword(AuthenticationHelper.token!!, oldPwd, newPwd) + } + + /** * 根据布防状态统计闸井数量接口 */ suspend fun countWellByState(): String { - return api.countWellByState() + return api.countWellByState(AuthenticationHelper.token!!) } + + /** + * 更新APK版本 + */ + suspend fun updateVersion(): String { + return api.obtainVersionResult(AuthenticationHelper.token!!) + } + } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/AboutUsActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/AboutUsActivity.kt new file mode 100644 index 0000000..7e43c47 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/AboutUsActivity.kt @@ -0,0 +1,34 @@ +package com.casic.smart.town.sanxi.view + +import com.casic.smart.town.sanxi.BuildConfig +import com.casic.smart.town.sanxi.R +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.kt.lite.base.KotlinBaseActivity +import com.pengxh.kt.lite.extensions.convertColor +import com.pengxh.kt.lite.utils.ImmerseStatusBarUtil +import kotlinx.android.synthetic.main.activity_about_us.* +import kotlinx.android.synthetic.main.include_base_title.* + +class AboutUsActivity : KotlinBaseActivity() { + + override fun initLayoutView(): Int = R.layout.activity_about_us + + override fun setupTopBarLayout() { + ImmersionBar.with(this).statusBarDarkFont(false).init() + ImmerseStatusBarUtil.setColor(this, R.color.mainThemeColor.convertColor(this)) + titleView.text = "关于我们" + leftBackView.setOnClickListener { finish() } + } + + override fun initData() { + versionCodeView.text = String.format("版本信息:V${BuildConfig.VERSION_NAME}") + } + + override fun observeRequestState() { + + } + + override fun initEvent() { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/LoginActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/LoginActivity.kt new file mode 100644 index 0000000..b5d1d50 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/LoginActivity.kt @@ -0,0 +1,97 @@ +package com.casic.smart.town.sanxi.view + +import androidx.lifecycle.ViewModelProvider +import com.casic.smart.town.sanxi.R +import com.casic.smart.town.sanxi.util.AuthenticationHelper +import com.casic.smart.town.sanxi.util.LoadingDialogHub +import com.casic.smart.town.sanxi.util.LocaleConstant +import com.casic.smart.town.sanxi.util.RSAUtils +import com.casic.smart.town.sanxi.vm.AuthenticateViewModel +import com.casic.smart.town.sanxi.vm.LoginViewModel +import com.casic.smart.town.sanxi.vm.UserViewModel +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.kt.lite.base.KotlinBaseActivity +import com.pengxh.kt.lite.extensions.navigatePageTo +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.utils.SaveKeyValues +import kotlinx.android.synthetic.main.activity_login.* + +class LoginActivity : KotlinBaseActivity() { + + private lateinit var authenticateViewModel: AuthenticateViewModel + private lateinit var loginViewModel: LoginViewModel + private lateinit var userViewModel: UserViewModel + + override fun initLayoutView(): Int = R.layout.activity_login + + override fun setupTopBarLayout() { + ImmersionBar.with(this).statusBarDarkFont(true).init() + } + + override fun initData() { + // 设置默认账号密码 + userNameView.setText(SaveKeyValues.getValue(LocaleConstant.ACCOUNT, "") as String) + userPasswordView.setText(SaveKeyValues.getValue(LocaleConstant.PASSWORD, "") as String) + authenticateViewModel = ViewModelProvider(this)[AuthenticateViewModel::class.java] + loginViewModel = ViewModelProvider(this)[LoginViewModel::class.java] + userViewModel = ViewModelProvider(this)[UserViewModel::class.java] + + authenticateViewModel.keyModel.observe(this, { + if (it.code == 200) {//用code判断,别的判断可能有坑 + val keyString = it.data!!.publicKey!! + /** + * 修改密码需要用到key,先存着 + * */ + AuthenticationHelper.savePublicKey(keyString) + val publicKey = RSAUtils.keyStrToPublicKey(keyString) + + val account = userNameView.text.toString() + val userPassword = userPasswordView.text.toString() + val dataByPublicKey = + RSAUtils.encryptDataByPublicKey(userPassword.toByteArray(), publicKey!!) + //登录并获取Token,POST请求 + loginViewModel.enter(it.data!!.sid!!, account, dataByPublicKey) + loginViewModel.enterResultModel.observe(this, { loginResult -> + if (loginResult.code == 200) { + AuthenticationHelper.saveToken(loginResult.data!!.token!!) + /** + * 获取token之后保存用户信息 + * */ + userViewModel.obtainUserDetail() + //验证成功登录 + navigatePageTo() + finish() + } + }) + } + }) + } + + override fun observeRequestState() { + authenticateViewModel.loadState.observe(this, { + LoadingDialogHub.show(this, "登录中,请稍后") + }) + + loginViewModel.loadState.observe(this, { + LoadingDialogHub.dismiss() + }) + } + + override fun initEvent() { + loginButton.setOnClickListener { + val account = userNameView.text.toString().trim() + val userPassword = userPasswordView.text.toString().trim() + if (account.isBlank()) { + "用户名不能为空".show(this) + return@setOnClickListener + } + if (userPassword.isBlank()) { + "密码不能为空".show(this) + return@setOnClickListener + } + SaveKeyValues.putValue(LocaleConstant.ACCOUNT, account) + SaveKeyValues.putValue(LocaleConstant.PASSWORD, userPassword) + authenticateViewModel.obtainPublicKey() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt index 3bea1c9..a4fa951 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt @@ -7,6 +7,7 @@ import com.casic.smart.town.sanxi.R import com.casic.smart.town.sanxi.adapter.ViewPagerAdapter import com.casic.smart.town.sanxi.fragment.AlarmPageFragment +import com.casic.smart.town.sanxi.fragment.MinePageFragment import com.casic.smart.town.sanxi.fragment.MonitorPageFragment import com.casic.smart.town.sanxi.fragment.StatisticsPageFragment import com.gyf.immersionbar.ImmersionBar @@ -33,6 +34,7 @@ fragmentPages.add(StatisticsPageFragment()) fragmentPages.add(MonitorPageFragment()) fragmentPages.add(AlarmPageFragment()) + fragmentPages.add(MinePageFragment()) } override fun initLayoutView(): Int = R.layout.activity_main @@ -68,6 +70,9 @@ R.id.nav_alarm -> { mainViewPager.currentItem = 2 } + R.id.nav_more -> { + mainViewPager.currentItem = 3 + } } false } diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/PermissionActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/PermissionActivity.kt index c478d83..3238f3c 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/view/PermissionActivity.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/PermissionActivity.kt @@ -32,7 +32,7 @@ //先把导航隐私政策声明,后面导航会用到 MapsInitializer.updatePrivacyAgree(this, true) MapsInitializer.updatePrivacyShow(this, true, true) - this.navigatePageTo() + this.navigatePageTo() finish() } diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/SplashScreenActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/SplashScreenActivity.kt new file mode 100644 index 0000000..fa5a74d --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/SplashScreenActivity.kt @@ -0,0 +1,54 @@ +package com.casic.smart.town.sanxi.view + +import android.annotation.SuppressLint +import android.os.CountDownTimer +import androidx.lifecycle.ViewModelProvider +import com.casic.smart.town.sanxi.R +import com.casic.smart.town.sanxi.vm.UserViewModel +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.kt.lite.base.KotlinBaseActivity +import com.pengxh.kt.lite.extensions.navigatePageTo + +@SuppressLint("CustomSplashScreen") +class SplashScreenActivity : KotlinBaseActivity() { + + private lateinit var userDetailViewModel: UserViewModel + private val countDownTimer = object : CountDownTimer(1000, 500) { + override fun onFinish() { + /** + * 获取token之后保存用户信息 + * */ + userDetailViewModel.obtainUserDetail() + userDetailViewModel.userDetailModel.observe(this@SplashScreenActivity, { + if (it.code == 200) { + navigatePageTo() + } else { + navigatePageTo() + } + finish() + }) + } + + override fun onTick(millisUntilFinished: Long) { + + } + } + + override fun initLayoutView(): Int = R.layout.activity_splash + + override fun setupTopBarLayout() { + ImmersionBar.with(this).statusBarDarkFont(false).init() + } + + override fun initData() { + userDetailViewModel = ViewModelProvider(this).get(UserViewModel::class.java) + } + + override fun observeRequestState() { + + } + + override fun initEvent() { + countDownTimer.start() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/vm/AuthenticateViewModel.kt b/app/src/main/java/com/casic/smart/town/sanxi/vm/AuthenticateViewModel.kt new file mode 100644 index 0000000..87ddbf8 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/vm/AuthenticateViewModel.kt @@ -0,0 +1,35 @@ +package com.casic.smart.town.sanxi.vm + +import androidx.lifecycle.MutableLiveData +import com.casic.smart.town.sanxi.base.BaseApplication +import com.casic.smart.town.sanxi.extensions.separateResponseCode +import com.casic.smart.town.sanxi.extensions.toErrorMessage +import com.casic.smart.town.sanxi.model.PublicKeyModel +import com.casic.smart.town.sanxi.util.retrofit.RetrofitServiceManager +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.launch +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.vm.BaseViewModel +import com.pengxh.kt.lite.vm.LoadState + +class AuthenticateViewModel : BaseViewModel() { + + private val gson = Gson() + val keyModel = MutableLiveData() + + fun obtainPublicKey() = launch({ + loadState.value = LoadState.Loading + val response = RetrofitServiceManager.authenticate() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + keyModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ) + } else { + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + it.printStackTrace() + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/vm/LoginViewModel.kt b/app/src/main/java/com/casic/smart/town/sanxi/vm/LoginViewModel.kt new file mode 100644 index 0000000..c377c61 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/vm/LoginViewModel.kt @@ -0,0 +1,54 @@ +package com.casic.smart.town.sanxi.vm + +import androidx.lifecycle.MutableLiveData +import com.casic.smart.town.sanxi.base.BaseApplication +import com.casic.smart.town.sanxi.extensions.separateResponseCode +import com.casic.smart.town.sanxi.extensions.toErrorMessage +import com.casic.smart.town.sanxi.model.CommonResultModel +import com.casic.smart.town.sanxi.model.LoginResultModel +import com.casic.smart.town.sanxi.util.retrofit.RetrofitServiceManager +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.launch +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.vm.BaseViewModel +import com.pengxh.kt.lite.vm.LoadState + +class LoginViewModel : BaseViewModel() { + + private val gson = Gson() + val enterResultModel = MutableLiveData() + val outResultModel = MutableLiveData() + + fun enter(sid: String, account: String, secretKey: String) = launch({ + val response = RetrofitServiceManager.login(sid, account, secretKey) + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + loadState.value = LoadState.Success + "登录成功".show(BaseApplication.obtainInstance()) + enterResultModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ) + } else { + loadState.value = LoadState.Fail + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + loadState.value = LoadState.Fail + it.printStackTrace() + }) + + fun out() = launch({ + val response = RetrofitServiceManager.loginOut() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + outResultModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ) + } else { + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + it.printStackTrace() + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/vm/UserViewModel.kt b/app/src/main/java/com/casic/smart/town/sanxi/vm/UserViewModel.kt new file mode 100644 index 0000000..29a8756 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/vm/UserViewModel.kt @@ -0,0 +1,57 @@ +package com.casic.smart.town.sanxi.vm + +import androidx.lifecycle.MutableLiveData +import com.casic.smart.town.sanxi.base.BaseApplication +import com.casic.smart.town.sanxi.extensions.separateResponseCode +import com.casic.smart.town.sanxi.extensions.toErrorMessage +import com.casic.smart.town.sanxi.model.UserDetailModel +import com.casic.smart.town.sanxi.util.LocaleConstant +import com.casic.smart.town.sanxi.util.retrofit.RetrofitServiceManager +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.launch +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.utils.SaveKeyValues +import com.pengxh.kt.lite.vm.BaseViewModel +import com.pengxh.kt.lite.vm.LoadState + +class UserViewModel : BaseViewModel() { + + private val gson = Gson() + val userDetailModel = MutableLiveData() + + fun obtainUserDetail() = launch({ + val response = RetrofitServiceManager.obtainUserDetail() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + val userDetail = gson.fromJson( + response, object : TypeToken() {}.type + ) + userDetailModel.value = userDetail + SaveKeyValues.putValue(LocaleConstant.USER_DETAIL_MODEL, gson.toJson(userDetail.data)) + } else { + val errorModel = UserDetailModel() + errorModel.code = responseCode + userDetailModel.value = errorModel + //如果此次获取不到用户信息,那么就清空之前的用户缓存,然后让用户重新登录 + SaveKeyValues.removeKey(LocaleConstant.USER_DETAIL_MODEL) + } + }, { + it.printStackTrace() + }) + + fun changePassword(oldPwd: String, newPwd: String) = launch({ + loadState.value = LoadState.Loading + val response = RetrofitServiceManager.changePassword(oldPwd, newPwd) + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + loadState.value = LoadState.Success + } else { + loadState.value = LoadState.Fail + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + loadState.value = LoadState.Fail + it.printStackTrace() + }) +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1df917d..8d4f33b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -6,6 +6,7 @@ + @@ -32,11 +33,14 @@ + + + + + + + diff --git a/app/src/main/assets/svg/mine.svg b/app/src/main/assets/svg/mine.svg new file mode 100644 index 0000000..3fbb0a3 --- /dev/null +++ b/app/src/main/assets/svg/mine.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt b/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt index d730417..b1972c2 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt @@ -1,8 +1,10 @@ package com.casic.smart.town.sanxi.extensions import com.casic.smart.town.sanxi.model.ErrorMessageModel +import com.casic.smart.town.sanxi.util.LocaleConstant import com.google.gson.Gson import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.utils.SaveKeyValues import org.json.JSONObject fun String.separateResponseCode(): Int { @@ -17,4 +19,15 @@ this, object : TypeToken() {}.type ) return errorModel.message.toString() +} + +/** + * 下载路径为 http://xx.com/static/ 拼接downloadUrl + * */ +fun String.appendDownloadUrl(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + LocaleConstant.DEFAULT_SERVER_CONFIG, LocaleConstant.DEFAULT_SERVER + ) as String + return "$defaultValue/static/${this}" } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt b/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt new file mode 100644 index 0000000..1809dc5 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt @@ -0,0 +1,288 @@ +package com.casic.smart.town.sanxi.fragment + +import android.app.ProgressDialog +import android.content.Intent +import android.net.Uri +import android.os.Build +import android.os.CountDownTimer +import androidx.core.content.FileProvider +import androidx.lifecycle.ViewModelProvider +import com.casic.smart.town.sanxi.BuildConfig +import com.casic.smart.town.sanxi.R +import com.casic.smart.town.sanxi.extensions.appendDownloadUrl +import com.casic.smart.town.sanxi.model.UserDetailModel +import com.casic.smart.town.sanxi.util.AuthenticationHelper +import com.casic.smart.town.sanxi.util.LoadingDialogHub +import com.casic.smart.town.sanxi.util.LocaleConstant +import com.casic.smart.town.sanxi.util.RSAUtils +import com.casic.smart.town.sanxi.view.AboutUsActivity +import com.casic.smart.town.sanxi.view.LoginActivity +import com.casic.smart.town.sanxi.vm.LoginViewModel +import com.casic.smart.town.sanxi.vm.UserViewModel +import com.casic.smart.town.sanxi.vm.VersionViewModel +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.base.KotlinBaseFragment +import com.pengxh.kt.lite.callback.OnDownloadListener +import com.pengxh.kt.lite.extensions.* +import com.pengxh.kt.lite.utils.PageNavigationManager +import com.pengxh.kt.lite.utils.SaveKeyValues +import com.pengxh.kt.lite.vm.LoadState +import com.pengxh.kt.lite.widget.dialog.AlertControlDialog +import com.pengxh.kt.lite.widget.dialog.ChangePasswordDialog +import kotlinx.android.synthetic.main.fragment_mine.* +import java.io.File +import java.nio.charset.StandardCharsets + +class MinePageFragment : KotlinBaseFragment() { + + private lateinit var userData: UserDetailModel.Data + private lateinit var userViewModel: UserViewModel + private lateinit var loginViewModel: LoginViewModel + private lateinit var versionViewModel: VersionViewModel + private lateinit var progressDialog: ProgressDialog + + override fun initLayoutView(): Int = R.layout.fragment_mine + + override fun setupTopBarLayout() { + + } + + override fun initData() { + userViewModel = ViewModelProvider(this)[UserViewModel::class.java] + loginViewModel = ViewModelProvider(this)[LoginViewModel::class.java] + versionViewModel = ViewModelProvider(this)[VersionViewModel::class.java] + + //初始化下载对话框 + progressDialog = ProgressDialog(requireContext()) + progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL) + progressDialog.setProgressDrawable(resources.getDrawable(R.drawable.download_progress)) + progressDialog.setCanceledOnTouchOutside(false) + progressDialog.setCancelable(false) + + /** + * 数据监听 + * */ + dataObserve() + } + + override fun observeRequestState() { + userViewModel.loadState.observe(this, { + when (it) { + is LoadState.Loading -> LoadingDialogHub.show(requireActivity(), "修改中,请稍后") + is LoadState.Success -> { + "修改成功,请重新登录".show(requireContext()) + LoadingDialogHub.dismiss() + AuthenticationHelper.removeToken() + requireContext().navigatePageTo() + PageNavigationManager.finishAllActivity() + } + else -> LoadingDialogHub.dismiss() + } + }) + + versionViewModel.loadState.observe(this, { + when (it) { + is LoadState.Loading -> LoadingDialogHub.show(requireActivity(), "检查版本中,请稍后") + else -> LoadingDialogHub.dismiss() + } + }) + } + + override fun initEvent() { + userImageView.setOnClickListener { + "尽情期待~".show(requireContext()) + } + + updateUserButton.setOnClickListener { + LoadingDialogHub.show(requireActivity(), "同步中,请稍后...") + userViewModel.obtainUserDetail() + } + + changePwdLayout.setOnClickListener { + ChangePasswordDialog.Builder() + .setContext(requireContext()) + .setOnDialogButtonClickListener(object : + ChangePasswordDialog.OnDialogButtonClickListener { + override fun onConfirmClick(oldPwd: String, newPwd: String) { + val publicKey = + RSAUtils.keyStrToPublicKey(AuthenticationHelper.publicKey)!! + val oldPassKey = RSAUtils.encryptDataByPublicKey( + oldPwd.toByteArray(StandardCharsets.UTF_8), + publicKey + ) + val newPassKey = RSAUtils.encryptDataByPublicKey( + newPwd.toByteArray(StandardCharsets.UTF_8), + publicKey + ) + userViewModel.changePassword(oldPassKey, newPassKey) + } + }).build().show() + } + + aboutUsLayout.setOnClickListener { + requireContext().navigatePageTo() + } + + updateVersionLayout.setOnClickListener { + versionViewModel.updateVersion() + } + + clearCacheLayout.setOnClickListener { + //删除缓存之后在设置缓存大小 + LoadingDialogHub.show(requireActivity(), "清理中,请稍后") + File(requireContext().cacheDir.path).deleteFile() + object : CountDownTimer(1500, 500) { + override fun onTick(millisUntilFinished: Long) { + + } + + override fun onFinish() { + LoadingDialogHub.dismiss() + cacheSizeView.text = collectApplicationCache().formatFileSize() + } + }.start() + } + + loginOutButton.setOnClickListener { + AlertControlDialog.Builder() + .setContext(requireContext()) + .setTitle("退出登录") + .setMessage("确定要退出吗?") + .setNegativeButton("取消") + .setPositiveButton("确定") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + loginViewModel.out() + } + + override fun onCancelClick() {} + }).build().show() + } + } + + private fun dataObserve() { + userViewModel.userDetailModel.observe(this, { + if (it.code == 200) { + LoadingDialogHub.dismiss() + "同步完成".show(requireContext()) + userData = it.data + updateUserInfo() + } + }) + + versionViewModel.versionResultModel.observe(this, { + if (BuildConfig.VERSION_NAME == it.version) { + "已是最新版本,无需更新".show(requireContext()) + } else { + AlertControlDialog.Builder() + .setContext(requireContext()) + .setTitle("提示") + .setMessage("有新版本,是否更新?") + .setNegativeButton("稍后再说") + .setPositiveButton("立即下载") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + downloadApk(it.downloadUrl) + } + + override fun onCancelClick() { + + } + }).build().show() + } + }) + + loginViewModel.outResultModel.observe(this, { + if (it.code == 200) { + AuthenticationHelper.removeToken() + requireContext().navigatePageTo() + PageNavigationManager.finishAllActivity() + } + }) + } + + private fun collectApplicationCache(): Long { + return File(requireContext().cacheDir.path).calculateSize() + } + + override fun onResume() { + val userDetailJson = SaveKeyValues.getValue(LocaleConstant.USER_DETAIL_MODEL, "") as String + if (userDetailJson.isNotBlank()) { + userData = Gson().fromJson( + userDetailJson, object : TypeToken() {}.type + ) + updateUserInfo() + } + //缓存 + cacheSizeView.text = collectApplicationCache().formatFileSize() + super.onResume() + } + + private fun updateUserInfo() { + //设置头像,圆形,暂时是默认的 +// val roundDrawable = +// BitmapFactory.decodeResource(requireContext().resources, R.mipmap.login_casic) +// .createRoundDrawable( +// requireContext(), +// 3f.dp2px(requireContext()), +// R.color.mainThemeColor.convertColor(requireContext()) +// ) +// userImageView.setImageDrawable(roundDrawable) + userNameView.text = userData.name + userPhoneView.text = String.format("电话:${userData.phone}") + userDeptView.text = String.format("部门:${userData.deptName}") + } + + private fun downloadApk(url: String?) { + progressDialog.setMessage("下载新版本中...") + progressDialog.show() + if (url.toString().isBlank()) { + "抱歉,版本下载失败".show(requireContext()) + return + } + /** + * http://111.198.10.15:11304/static/apk/1.0.1.apk + * */ + val downloadPath = url!!.appendDownloadUrl() + //开始下载 + downloadPath.downloadFile(requireContext().createDownloadFileDir().toString(), + object : OnDownloadListener { + override fun onDownloadStart(totalBytes: Long) { + progressDialog.max = totalBytes.toInt() + } + + override fun onProgressChanged(currentBytes: Long) { + progressDialog.progress = currentBytes.toInt() + } + + override fun onDownloadEnd(file: File?) { + progressDialog.dismiss() + progressDialog.progress = 0 + //安装APK + installApk(file) + } + }) + } + + private fun installApk(apkPackage: File?) { + if (apkPackage == null) { + "文件异常,无法安装".show(requireContext()) + return + } + val intent = Intent(Intent.ACTION_VIEW) + val data: Uri + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { //判断版本大于等于7.0 + data = FileProvider.getUriForFile( + requireContext(), LocaleConstant.APP_AUTHORITY, apkPackage + ) + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) // 给目标应用一个临时授权 + } else { + data = Uri.fromFile(apkPackage) + } + intent.setDataAndType(data, "application/vnd.android.package-archive") + requireContext().startActivity(intent) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java new file mode 100644 index 0000000..467e33d --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java @@ -0,0 +1,43 @@ +package com.casic.smart.town.sanxi.model; + +/** + * 普通实体类,失败/成功数据结构一致 + */ +public class CommonResultModel { + private int code; + private String data; + private String message; + private boolean isSuccess; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return isSuccess; + } + + public void setSuccess(boolean success) { + isSuccess = success; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java new file mode 100644 index 0000000..9bdabef --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java @@ -0,0 +1,62 @@ +package com.casic.smart.town.sanxi.model; + +public class LoginResultModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private String kaptcha; + private String token; + + public String getKaptcha() { + return kaptcha; + } + + public void setKaptcha(String kaptcha) { + this.kaptcha = kaptcha; + } + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java new file mode 100644 index 0000000..09f8d59 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java @@ -0,0 +1,80 @@ +package com.casic.smart.town.sanxi.model; + +public class PublicKeyModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private boolean appKaptcha; + private boolean kaptcha; + private String publicKey; + private String sid; + + public boolean isAppKaptcha() { + return appKaptcha; + } + + public void setAppKaptcha(boolean appKaptcha) { + this.appKaptcha = appKaptcha; + } + + public boolean isKaptcha() { + return kaptcha; + } + + public void setKaptcha(boolean kaptcha) { + this.kaptcha = kaptcha; + } + + public String getPublicKey() { + return publicKey; + } + + public void setPublicKey(String publicKey) { + this.publicKey = publicKey; + } + + public String getSid() { + return sid; + } + + public void setSid(String sid) { + this.sid = sid; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java new file mode 100644 index 0000000..db577fb --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java @@ -0,0 +1,227 @@ +package com.casic.smart.town.sanxi.model; + + +import java.util.List; + +public class UserDetailModel { + private int code; + private Data data; + private String message; + private boolean success; + + public void setCode(int code) { + this.code = code; + } + + public int getCode() { + return code; + } + + public void setData(Data data) { + this.data = data; + } + + public Data getData() { + return data; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getMessage() { + return message; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public boolean getSuccess() { + return success; + } + + public static class Data { + + private String account; + private String attr1; + private String avatar; + private String bizData; + private List dataScope; + private String deptId; + private String deptName; + private List devices; + private String id; + private String ipAddr; + private String name; + private String phone; + private List roleList; + private List roleNames; + private List roleTips; + private String scopeType; + private String sysData; + private String targetId; + private String targetName; + private String tenantId; + + public void setAccount(String account) { + this.account = account; + } + + public String getAccount() { + return account; + } + + public void setAttr1(String attr1) { + this.attr1 = attr1; + } + + public String getAttr1() { + return attr1; + } + + public void setAvatar(String avatar) { + this.avatar = avatar; + } + + public String getAvatar() { + return avatar; + } + + public void setBizData(String bizData) { + this.bizData = bizData; + } + + public String getBizData() { + return bizData; + } + + public void setDataScope(List dataScope) { + this.dataScope = dataScope; + } + + public List getDataScope() { + return dataScope; + } + + public void setDeptId(String deptId) { + this.deptId = deptId; + } + + public String getDeptId() { + return deptId; + } + + public void setDeptName(String deptName) { + this.deptName = deptName; + } + + public String getDeptName() { + return deptName; + } + + public void setDevices(List devices) { + this.devices = devices; + } + + public List getDevices() { + return devices; + } + + public void setId(String id) { + this.id = id; + } + + public String getId() { + return id; + } + + public void setIpAddr(String ipAddr) { + this.ipAddr = ipAddr; + } + + public String getIpAddr() { + return ipAddr; + } + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setPhone(String phone) { + this.phone = phone; + } + + public String getPhone() { + return phone; + } + + public void setRoleList(List roleList) { + this.roleList = roleList; + } + + public List getRoleList() { + return roleList; + } + + public void setRoleNames(List roleNames) { + this.roleNames = roleNames; + } + + public List getRoleNames() { + return roleNames; + } + + public void setRoleTips(List roleTips) { + this.roleTips = roleTips; + } + + public List getRoleTips() { + return roleTips; + } + + public void setScopeType(String scopeType) { + this.scopeType = scopeType; + } + + public String getScopeType() { + return scopeType; + } + + public void setSysData(String sysData) { + this.sysData = sysData; + } + + public String getSysData() { + return sysData; + } + + public void setTargetId(String targetId) { + this.targetId = targetId; + } + + public String getTargetId() { + return targetId; + } + + public void setTargetName(String targetName) { + this.targetName = targetName; + } + + public String getTargetName() { + return targetName; + } + + public void setTenantId(String tenantId) { + this.tenantId = tenantId; + } + + public String getTenantId() { + return tenantId; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/VersionResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/VersionResultModel.java new file mode 100644 index 0000000..79374e0 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/VersionResultModel.java @@ -0,0 +1,61 @@ +package com.casic.smart.town.sanxi.model; + +public class VersionResultModel { + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private String downloadUrl; + private String version; + + public String getDownloadUrl() { + return downloadUrl; + } + + public void setDownloadUrl(String downloadUrl) { + this.downloadUrl = downloadUrl; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/AuthenticationHelper.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/AuthenticationHelper.kt new file mode 100644 index 0000000..f498e4e --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/AuthenticationHelper.kt @@ -0,0 +1,24 @@ +package com.casic.smart.town.sanxi.util + +import com.pengxh.kt.lite.utils.SaveKeyValues + +object AuthenticationHelper { + + fun savePublicKey(key: String) { + SaveKeyValues.putValue("keyString", key) + } + + val publicKey: String? + get() = SaveKeyValues.getValue("keyString", "") as String? + + fun saveToken(token: String?) { + SaveKeyValues.putValue("token", token!!) + } + + val token: String? + get() = SaveKeyValues.getValue("token", "") as String? + + fun removeToken() { + SaveKeyValues.removeKey("token") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt index ab1a36d..99f0d19 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt @@ -14,5 +14,9 @@ const val PAGE_LIMIT = 20 const val DEFAULT_SERVER_CONFIG = "defaultServerConfig" - const val DEFAULT_SERVER = "http://111.198.10.15:11308" + const val DEFAULT_SERVER = "http://36.133.189.112:8082" + const val APP_AUTHORITY = "com.casic.smart.town.sanxi.fileprovider" + const val ACCOUNT = "account" + const val PASSWORD = "password" + const val USER_DETAIL_MODEL = "userDetailModel" } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/RSAUtils.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/RSAUtils.kt new file mode 100644 index 0000000..b9216ba --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/RSAUtils.kt @@ -0,0 +1,55 @@ +package com.casic.smart.town.sanxi.util + +import android.util.Base64 +import java.security.* +import java.security.spec.InvalidKeySpecException +import java.security.spec.X509EncodedKeySpec +import javax.crypto.BadPaddingException +import javax.crypto.Cipher +import javax.crypto.IllegalBlockSizeException +import javax.crypto.NoSuchPaddingException + +object RSAUtils { + //构建Cipher实例时所传入的的字符串,默认为"RSA/NONE/PKCS1Padding" + private fun processData(srcData: ByteArray, key: Key): ByteArray? { //用来保存处理结果 + var resultBytes: ByteArray? = null + try { //获取Cipher实例 + val cipher = Cipher.getInstance("RSA/None/PKCS1Padding") + //初始化Cipher,mode指定是加密还是解密,key为公钥或私钥 + cipher.init(Cipher.ENCRYPT_MODE, key) + //处理数据 + resultBytes = cipher.doFinal(srcData) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: NoSuchPaddingException) { + e.printStackTrace() + } catch (e: InvalidKeyException) { + e.printStackTrace() + } catch (e: BadPaddingException) { + e.printStackTrace() + } catch (e: IllegalBlockSizeException) { + e.printStackTrace() + } + return resultBytes + } + + fun encryptDataByPublicKey(srcData: ByteArray, publicKey: PublicKey): String { + val resultBytes = processData(srcData, publicKey) + return Base64.encodeToString(resultBytes, Base64.DEFAULT) + } + + fun keyStrToPublicKey(publicKeyStr: String?): PublicKey? { + var publicKey: PublicKey? = null + val keyBytes = Base64.decode(publicKeyStr, Base64.DEFAULT) + val keySpec = X509EncodedKeySpec(keyBytes) + try { + val keyFactory = KeyFactory.getInstance("RSA") + publicKey = keyFactory.generatePublic(keySpec) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: InvalidKeySpecException) { + e.printStackTrace() + } + return publicKey + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt index 63db0ec..50d1742 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt @@ -1,12 +1,64 @@ package com.casic.smart.town.sanxi.util.retrofit -import retrofit2.http.GET +import retrofit2.http.* interface RetrofitService { + /** + * PublicKey校验 + */ + @GET("/config/baseConfig") + suspend fun obtainPublicKey(): String + + /** + * 登录并获取Token + * + * @param sid + * @param account 用户名 + * @param secretKey 加密后的密码 + */ + @FormUrlEncoded + @POST("/user/login") + suspend fun login( + @Field("sid") sid: String, + @Field("username") account: String, + @Field("password") secretKey: String + ): String + + /** + * 退出登录 + */ + @GET("/user/logout") + suspend fun loginOut(@Header("token") token: String): String + + /** + * 获取用户信息 + */ + @GET("/user/info") + suspend fun obtainUserDetail(@Header("token") token: String): String + + /** + * 修改密码 + * + * @param oldPwd 旧密码 + * @param newPwd 新密码 + */ + @FormUrlEncoded + @POST("/mgr/changePwd") + suspend fun changePassword( + @Header("token") token: String, + @Field("oldPwd") oldPwd: String, + @Field("newPwd") newPwd: String + ): String /** * 根据布防状态统计闸井数量接口 */ @GET("/well/countByBfzt") - suspend fun countWellByState(): String + suspend fun countWellByState(@Header("token") token: String): String + + /** + * 更新APK版本 + */ + @POST("/app/checkVersion") + suspend fun obtainVersionResult(@Header("token") token: String): String } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt index 46bbf4b..410444b 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt @@ -1,5 +1,6 @@ package com.casic.smart.town.sanxi.util.retrofit +import com.casic.smart.town.sanxi.util.AuthenticationHelper import com.casic.smart.town.sanxi.util.LocaleConstant import com.pengxh.kt.lite.utils.RetrofitFactory import com.pengxh.kt.lite.utils.SaveKeyValues @@ -15,9 +16,52 @@ } /** + * 验证PublicKey + */ + suspend fun authenticate(): String { + return api.obtainPublicKey() + } + + /** + * 登录并获取Token + */ + suspend fun login(sid: String, account: String, secretKey: String): String { + return api.login(sid, account, secretKey) + } + + /** + * 退出登录 + */ + suspend fun loginOut(): String { + return api.loginOut(AuthenticationHelper.token!!) + } + + /** + * 获取用户信息 + */ + suspend fun obtainUserDetail(): String { + return api.obtainUserDetail(AuthenticationHelper.token!!) + } + + /** + * 修改密码 + */ + suspend fun changePassword(oldPwd: String, newPwd: String): String { + return api.changePassword(AuthenticationHelper.token!!, oldPwd, newPwd) + } + + /** * 根据布防状态统计闸井数量接口 */ suspend fun countWellByState(): String { - return api.countWellByState() + return api.countWellByState(AuthenticationHelper.token!!) } + + /** + * 更新APK版本 + */ + suspend fun updateVersion(): String { + return api.obtainVersionResult(AuthenticationHelper.token!!) + } + } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/AboutUsActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/AboutUsActivity.kt new file mode 100644 index 0000000..7e43c47 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/AboutUsActivity.kt @@ -0,0 +1,34 @@ +package com.casic.smart.town.sanxi.view + +import com.casic.smart.town.sanxi.BuildConfig +import com.casic.smart.town.sanxi.R +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.kt.lite.base.KotlinBaseActivity +import com.pengxh.kt.lite.extensions.convertColor +import com.pengxh.kt.lite.utils.ImmerseStatusBarUtil +import kotlinx.android.synthetic.main.activity_about_us.* +import kotlinx.android.synthetic.main.include_base_title.* + +class AboutUsActivity : KotlinBaseActivity() { + + override fun initLayoutView(): Int = R.layout.activity_about_us + + override fun setupTopBarLayout() { + ImmersionBar.with(this).statusBarDarkFont(false).init() + ImmerseStatusBarUtil.setColor(this, R.color.mainThemeColor.convertColor(this)) + titleView.text = "关于我们" + leftBackView.setOnClickListener { finish() } + } + + override fun initData() { + versionCodeView.text = String.format("版本信息:V${BuildConfig.VERSION_NAME}") + } + + override fun observeRequestState() { + + } + + override fun initEvent() { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/LoginActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/LoginActivity.kt new file mode 100644 index 0000000..b5d1d50 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/LoginActivity.kt @@ -0,0 +1,97 @@ +package com.casic.smart.town.sanxi.view + +import androidx.lifecycle.ViewModelProvider +import com.casic.smart.town.sanxi.R +import com.casic.smart.town.sanxi.util.AuthenticationHelper +import com.casic.smart.town.sanxi.util.LoadingDialogHub +import com.casic.smart.town.sanxi.util.LocaleConstant +import com.casic.smart.town.sanxi.util.RSAUtils +import com.casic.smart.town.sanxi.vm.AuthenticateViewModel +import com.casic.smart.town.sanxi.vm.LoginViewModel +import com.casic.smart.town.sanxi.vm.UserViewModel +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.kt.lite.base.KotlinBaseActivity +import com.pengxh.kt.lite.extensions.navigatePageTo +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.utils.SaveKeyValues +import kotlinx.android.synthetic.main.activity_login.* + +class LoginActivity : KotlinBaseActivity() { + + private lateinit var authenticateViewModel: AuthenticateViewModel + private lateinit var loginViewModel: LoginViewModel + private lateinit var userViewModel: UserViewModel + + override fun initLayoutView(): Int = R.layout.activity_login + + override fun setupTopBarLayout() { + ImmersionBar.with(this).statusBarDarkFont(true).init() + } + + override fun initData() { + // 设置默认账号密码 + userNameView.setText(SaveKeyValues.getValue(LocaleConstant.ACCOUNT, "") as String) + userPasswordView.setText(SaveKeyValues.getValue(LocaleConstant.PASSWORD, "") as String) + authenticateViewModel = ViewModelProvider(this)[AuthenticateViewModel::class.java] + loginViewModel = ViewModelProvider(this)[LoginViewModel::class.java] + userViewModel = ViewModelProvider(this)[UserViewModel::class.java] + + authenticateViewModel.keyModel.observe(this, { + if (it.code == 200) {//用code判断,别的判断可能有坑 + val keyString = it.data!!.publicKey!! + /** + * 修改密码需要用到key,先存着 + * */ + AuthenticationHelper.savePublicKey(keyString) + val publicKey = RSAUtils.keyStrToPublicKey(keyString) + + val account = userNameView.text.toString() + val userPassword = userPasswordView.text.toString() + val dataByPublicKey = + RSAUtils.encryptDataByPublicKey(userPassword.toByteArray(), publicKey!!) + //登录并获取Token,POST请求 + loginViewModel.enter(it.data!!.sid!!, account, dataByPublicKey) + loginViewModel.enterResultModel.observe(this, { loginResult -> + if (loginResult.code == 200) { + AuthenticationHelper.saveToken(loginResult.data!!.token!!) + /** + * 获取token之后保存用户信息 + * */ + userViewModel.obtainUserDetail() + //验证成功登录 + navigatePageTo() + finish() + } + }) + } + }) + } + + override fun observeRequestState() { + authenticateViewModel.loadState.observe(this, { + LoadingDialogHub.show(this, "登录中,请稍后") + }) + + loginViewModel.loadState.observe(this, { + LoadingDialogHub.dismiss() + }) + } + + override fun initEvent() { + loginButton.setOnClickListener { + val account = userNameView.text.toString().trim() + val userPassword = userPasswordView.text.toString().trim() + if (account.isBlank()) { + "用户名不能为空".show(this) + return@setOnClickListener + } + if (userPassword.isBlank()) { + "密码不能为空".show(this) + return@setOnClickListener + } + SaveKeyValues.putValue(LocaleConstant.ACCOUNT, account) + SaveKeyValues.putValue(LocaleConstant.PASSWORD, userPassword) + authenticateViewModel.obtainPublicKey() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt index 3bea1c9..a4fa951 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt @@ -7,6 +7,7 @@ import com.casic.smart.town.sanxi.R import com.casic.smart.town.sanxi.adapter.ViewPagerAdapter import com.casic.smart.town.sanxi.fragment.AlarmPageFragment +import com.casic.smart.town.sanxi.fragment.MinePageFragment import com.casic.smart.town.sanxi.fragment.MonitorPageFragment import com.casic.smart.town.sanxi.fragment.StatisticsPageFragment import com.gyf.immersionbar.ImmersionBar @@ -33,6 +34,7 @@ fragmentPages.add(StatisticsPageFragment()) fragmentPages.add(MonitorPageFragment()) fragmentPages.add(AlarmPageFragment()) + fragmentPages.add(MinePageFragment()) } override fun initLayoutView(): Int = R.layout.activity_main @@ -68,6 +70,9 @@ R.id.nav_alarm -> { mainViewPager.currentItem = 2 } + R.id.nav_more -> { + mainViewPager.currentItem = 3 + } } false } diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/PermissionActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/PermissionActivity.kt index c478d83..3238f3c 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/view/PermissionActivity.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/PermissionActivity.kt @@ -32,7 +32,7 @@ //先把导航隐私政策声明,后面导航会用到 MapsInitializer.updatePrivacyAgree(this, true) MapsInitializer.updatePrivacyShow(this, true, true) - this.navigatePageTo() + this.navigatePageTo() finish() } diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/SplashScreenActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/SplashScreenActivity.kt new file mode 100644 index 0000000..fa5a74d --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/SplashScreenActivity.kt @@ -0,0 +1,54 @@ +package com.casic.smart.town.sanxi.view + +import android.annotation.SuppressLint +import android.os.CountDownTimer +import androidx.lifecycle.ViewModelProvider +import com.casic.smart.town.sanxi.R +import com.casic.smart.town.sanxi.vm.UserViewModel +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.kt.lite.base.KotlinBaseActivity +import com.pengxh.kt.lite.extensions.navigatePageTo + +@SuppressLint("CustomSplashScreen") +class SplashScreenActivity : KotlinBaseActivity() { + + private lateinit var userDetailViewModel: UserViewModel + private val countDownTimer = object : CountDownTimer(1000, 500) { + override fun onFinish() { + /** + * 获取token之后保存用户信息 + * */ + userDetailViewModel.obtainUserDetail() + userDetailViewModel.userDetailModel.observe(this@SplashScreenActivity, { + if (it.code == 200) { + navigatePageTo() + } else { + navigatePageTo() + } + finish() + }) + } + + override fun onTick(millisUntilFinished: Long) { + + } + } + + override fun initLayoutView(): Int = R.layout.activity_splash + + override fun setupTopBarLayout() { + ImmersionBar.with(this).statusBarDarkFont(false).init() + } + + override fun initData() { + userDetailViewModel = ViewModelProvider(this).get(UserViewModel::class.java) + } + + override fun observeRequestState() { + + } + + override fun initEvent() { + countDownTimer.start() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/vm/AuthenticateViewModel.kt b/app/src/main/java/com/casic/smart/town/sanxi/vm/AuthenticateViewModel.kt new file mode 100644 index 0000000..87ddbf8 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/vm/AuthenticateViewModel.kt @@ -0,0 +1,35 @@ +package com.casic.smart.town.sanxi.vm + +import androidx.lifecycle.MutableLiveData +import com.casic.smart.town.sanxi.base.BaseApplication +import com.casic.smart.town.sanxi.extensions.separateResponseCode +import com.casic.smart.town.sanxi.extensions.toErrorMessage +import com.casic.smart.town.sanxi.model.PublicKeyModel +import com.casic.smart.town.sanxi.util.retrofit.RetrofitServiceManager +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.launch +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.vm.BaseViewModel +import com.pengxh.kt.lite.vm.LoadState + +class AuthenticateViewModel : BaseViewModel() { + + private val gson = Gson() + val keyModel = MutableLiveData() + + fun obtainPublicKey() = launch({ + loadState.value = LoadState.Loading + val response = RetrofitServiceManager.authenticate() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + keyModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ) + } else { + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + it.printStackTrace() + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/vm/LoginViewModel.kt b/app/src/main/java/com/casic/smart/town/sanxi/vm/LoginViewModel.kt new file mode 100644 index 0000000..c377c61 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/vm/LoginViewModel.kt @@ -0,0 +1,54 @@ +package com.casic.smart.town.sanxi.vm + +import androidx.lifecycle.MutableLiveData +import com.casic.smart.town.sanxi.base.BaseApplication +import com.casic.smart.town.sanxi.extensions.separateResponseCode +import com.casic.smart.town.sanxi.extensions.toErrorMessage +import com.casic.smart.town.sanxi.model.CommonResultModel +import com.casic.smart.town.sanxi.model.LoginResultModel +import com.casic.smart.town.sanxi.util.retrofit.RetrofitServiceManager +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.launch +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.vm.BaseViewModel +import com.pengxh.kt.lite.vm.LoadState + +class LoginViewModel : BaseViewModel() { + + private val gson = Gson() + val enterResultModel = MutableLiveData() + val outResultModel = MutableLiveData() + + fun enter(sid: String, account: String, secretKey: String) = launch({ + val response = RetrofitServiceManager.login(sid, account, secretKey) + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + loadState.value = LoadState.Success + "登录成功".show(BaseApplication.obtainInstance()) + enterResultModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ) + } else { + loadState.value = LoadState.Fail + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + loadState.value = LoadState.Fail + it.printStackTrace() + }) + + fun out() = launch({ + val response = RetrofitServiceManager.loginOut() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + outResultModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ) + } else { + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + it.printStackTrace() + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/vm/UserViewModel.kt b/app/src/main/java/com/casic/smart/town/sanxi/vm/UserViewModel.kt new file mode 100644 index 0000000..29a8756 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/vm/UserViewModel.kt @@ -0,0 +1,57 @@ +package com.casic.smart.town.sanxi.vm + +import androidx.lifecycle.MutableLiveData +import com.casic.smart.town.sanxi.base.BaseApplication +import com.casic.smart.town.sanxi.extensions.separateResponseCode +import com.casic.smart.town.sanxi.extensions.toErrorMessage +import com.casic.smart.town.sanxi.model.UserDetailModel +import com.casic.smart.town.sanxi.util.LocaleConstant +import com.casic.smart.town.sanxi.util.retrofit.RetrofitServiceManager +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.launch +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.utils.SaveKeyValues +import com.pengxh.kt.lite.vm.BaseViewModel +import com.pengxh.kt.lite.vm.LoadState + +class UserViewModel : BaseViewModel() { + + private val gson = Gson() + val userDetailModel = MutableLiveData() + + fun obtainUserDetail() = launch({ + val response = RetrofitServiceManager.obtainUserDetail() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + val userDetail = gson.fromJson( + response, object : TypeToken() {}.type + ) + userDetailModel.value = userDetail + SaveKeyValues.putValue(LocaleConstant.USER_DETAIL_MODEL, gson.toJson(userDetail.data)) + } else { + val errorModel = UserDetailModel() + errorModel.code = responseCode + userDetailModel.value = errorModel + //如果此次获取不到用户信息,那么就清空之前的用户缓存,然后让用户重新登录 + SaveKeyValues.removeKey(LocaleConstant.USER_DETAIL_MODEL) + } + }, { + it.printStackTrace() + }) + + fun changePassword(oldPwd: String, newPwd: String) = launch({ + loadState.value = LoadState.Loading + val response = RetrofitServiceManager.changePassword(oldPwd, newPwd) + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + loadState.value = LoadState.Success + } else { + loadState.value = LoadState.Fail + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + loadState.value = LoadState.Fail + it.printStackTrace() + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/vm/VersionViewModel.kt b/app/src/main/java/com/casic/smart/town/sanxi/vm/VersionViewModel.kt new file mode 100644 index 0000000..20ea457 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/vm/VersionViewModel.kt @@ -0,0 +1,38 @@ +package com.casic.smart.town.sanxi.vm + +import androidx.lifecycle.MutableLiveData +import com.casic.smart.town.sanxi.base.BaseApplication +import com.casic.smart.town.sanxi.extensions.separateResponseCode +import com.casic.smart.town.sanxi.extensions.toErrorMessage +import com.casic.smart.town.sanxi.model.VersionResultModel +import com.casic.smart.town.sanxi.util.retrofit.RetrofitServiceManager +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.launch +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.vm.BaseViewModel +import com.pengxh.kt.lite.vm.LoadState + +class VersionViewModel : BaseViewModel() { + + private val gson = Gson() + val versionResultModel = MutableLiveData() + + fun updateVersion() = launch({ + loadState.value = LoadState.Loading + val response = RetrofitServiceManager.updateVersion() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + loadState.value = LoadState.Success + versionResultModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ).data + } else { + loadState.value = LoadState.Fail + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + loadState.value = LoadState.Fail + it.printStackTrace() + }) +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1df917d..8d4f33b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -6,6 +6,7 @@ + @@ -32,11 +33,14 @@ + + + + + + + diff --git a/app/src/main/assets/svg/mine.svg b/app/src/main/assets/svg/mine.svg new file mode 100644 index 0000000..3fbb0a3 --- /dev/null +++ b/app/src/main/assets/svg/mine.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt b/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt index d730417..b1972c2 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt @@ -1,8 +1,10 @@ package com.casic.smart.town.sanxi.extensions import com.casic.smart.town.sanxi.model.ErrorMessageModel +import com.casic.smart.town.sanxi.util.LocaleConstant import com.google.gson.Gson import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.utils.SaveKeyValues import org.json.JSONObject fun String.separateResponseCode(): Int { @@ -17,4 +19,15 @@ this, object : TypeToken() {}.type ) return errorModel.message.toString() +} + +/** + * 下载路径为 http://xx.com/static/ 拼接downloadUrl + * */ +fun String.appendDownloadUrl(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + LocaleConstant.DEFAULT_SERVER_CONFIG, LocaleConstant.DEFAULT_SERVER + ) as String + return "$defaultValue/static/${this}" } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt b/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt new file mode 100644 index 0000000..1809dc5 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt @@ -0,0 +1,288 @@ +package com.casic.smart.town.sanxi.fragment + +import android.app.ProgressDialog +import android.content.Intent +import android.net.Uri +import android.os.Build +import android.os.CountDownTimer +import androidx.core.content.FileProvider +import androidx.lifecycle.ViewModelProvider +import com.casic.smart.town.sanxi.BuildConfig +import com.casic.smart.town.sanxi.R +import com.casic.smart.town.sanxi.extensions.appendDownloadUrl +import com.casic.smart.town.sanxi.model.UserDetailModel +import com.casic.smart.town.sanxi.util.AuthenticationHelper +import com.casic.smart.town.sanxi.util.LoadingDialogHub +import com.casic.smart.town.sanxi.util.LocaleConstant +import com.casic.smart.town.sanxi.util.RSAUtils +import com.casic.smart.town.sanxi.view.AboutUsActivity +import com.casic.smart.town.sanxi.view.LoginActivity +import com.casic.smart.town.sanxi.vm.LoginViewModel +import com.casic.smart.town.sanxi.vm.UserViewModel +import com.casic.smart.town.sanxi.vm.VersionViewModel +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.base.KotlinBaseFragment +import com.pengxh.kt.lite.callback.OnDownloadListener +import com.pengxh.kt.lite.extensions.* +import com.pengxh.kt.lite.utils.PageNavigationManager +import com.pengxh.kt.lite.utils.SaveKeyValues +import com.pengxh.kt.lite.vm.LoadState +import com.pengxh.kt.lite.widget.dialog.AlertControlDialog +import com.pengxh.kt.lite.widget.dialog.ChangePasswordDialog +import kotlinx.android.synthetic.main.fragment_mine.* +import java.io.File +import java.nio.charset.StandardCharsets + +class MinePageFragment : KotlinBaseFragment() { + + private lateinit var userData: UserDetailModel.Data + private lateinit var userViewModel: UserViewModel + private lateinit var loginViewModel: LoginViewModel + private lateinit var versionViewModel: VersionViewModel + private lateinit var progressDialog: ProgressDialog + + override fun initLayoutView(): Int = R.layout.fragment_mine + + override fun setupTopBarLayout() { + + } + + override fun initData() { + userViewModel = ViewModelProvider(this)[UserViewModel::class.java] + loginViewModel = ViewModelProvider(this)[LoginViewModel::class.java] + versionViewModel = ViewModelProvider(this)[VersionViewModel::class.java] + + //初始化下载对话框 + progressDialog = ProgressDialog(requireContext()) + progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL) + progressDialog.setProgressDrawable(resources.getDrawable(R.drawable.download_progress)) + progressDialog.setCanceledOnTouchOutside(false) + progressDialog.setCancelable(false) + + /** + * 数据监听 + * */ + dataObserve() + } + + override fun observeRequestState() { + userViewModel.loadState.observe(this, { + when (it) { + is LoadState.Loading -> LoadingDialogHub.show(requireActivity(), "修改中,请稍后") + is LoadState.Success -> { + "修改成功,请重新登录".show(requireContext()) + LoadingDialogHub.dismiss() + AuthenticationHelper.removeToken() + requireContext().navigatePageTo() + PageNavigationManager.finishAllActivity() + } + else -> LoadingDialogHub.dismiss() + } + }) + + versionViewModel.loadState.observe(this, { + when (it) { + is LoadState.Loading -> LoadingDialogHub.show(requireActivity(), "检查版本中,请稍后") + else -> LoadingDialogHub.dismiss() + } + }) + } + + override fun initEvent() { + userImageView.setOnClickListener { + "尽情期待~".show(requireContext()) + } + + updateUserButton.setOnClickListener { + LoadingDialogHub.show(requireActivity(), "同步中,请稍后...") + userViewModel.obtainUserDetail() + } + + changePwdLayout.setOnClickListener { + ChangePasswordDialog.Builder() + .setContext(requireContext()) + .setOnDialogButtonClickListener(object : + ChangePasswordDialog.OnDialogButtonClickListener { + override fun onConfirmClick(oldPwd: String, newPwd: String) { + val publicKey = + RSAUtils.keyStrToPublicKey(AuthenticationHelper.publicKey)!! + val oldPassKey = RSAUtils.encryptDataByPublicKey( + oldPwd.toByteArray(StandardCharsets.UTF_8), + publicKey + ) + val newPassKey = RSAUtils.encryptDataByPublicKey( + newPwd.toByteArray(StandardCharsets.UTF_8), + publicKey + ) + userViewModel.changePassword(oldPassKey, newPassKey) + } + }).build().show() + } + + aboutUsLayout.setOnClickListener { + requireContext().navigatePageTo() + } + + updateVersionLayout.setOnClickListener { + versionViewModel.updateVersion() + } + + clearCacheLayout.setOnClickListener { + //删除缓存之后在设置缓存大小 + LoadingDialogHub.show(requireActivity(), "清理中,请稍后") + File(requireContext().cacheDir.path).deleteFile() + object : CountDownTimer(1500, 500) { + override fun onTick(millisUntilFinished: Long) { + + } + + override fun onFinish() { + LoadingDialogHub.dismiss() + cacheSizeView.text = collectApplicationCache().formatFileSize() + } + }.start() + } + + loginOutButton.setOnClickListener { + AlertControlDialog.Builder() + .setContext(requireContext()) + .setTitle("退出登录") + .setMessage("确定要退出吗?") + .setNegativeButton("取消") + .setPositiveButton("确定") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + loginViewModel.out() + } + + override fun onCancelClick() {} + }).build().show() + } + } + + private fun dataObserve() { + userViewModel.userDetailModel.observe(this, { + if (it.code == 200) { + LoadingDialogHub.dismiss() + "同步完成".show(requireContext()) + userData = it.data + updateUserInfo() + } + }) + + versionViewModel.versionResultModel.observe(this, { + if (BuildConfig.VERSION_NAME == it.version) { + "已是最新版本,无需更新".show(requireContext()) + } else { + AlertControlDialog.Builder() + .setContext(requireContext()) + .setTitle("提示") + .setMessage("有新版本,是否更新?") + .setNegativeButton("稍后再说") + .setPositiveButton("立即下载") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + downloadApk(it.downloadUrl) + } + + override fun onCancelClick() { + + } + }).build().show() + } + }) + + loginViewModel.outResultModel.observe(this, { + if (it.code == 200) { + AuthenticationHelper.removeToken() + requireContext().navigatePageTo() + PageNavigationManager.finishAllActivity() + } + }) + } + + private fun collectApplicationCache(): Long { + return File(requireContext().cacheDir.path).calculateSize() + } + + override fun onResume() { + val userDetailJson = SaveKeyValues.getValue(LocaleConstant.USER_DETAIL_MODEL, "") as String + if (userDetailJson.isNotBlank()) { + userData = Gson().fromJson( + userDetailJson, object : TypeToken() {}.type + ) + updateUserInfo() + } + //缓存 + cacheSizeView.text = collectApplicationCache().formatFileSize() + super.onResume() + } + + private fun updateUserInfo() { + //设置头像,圆形,暂时是默认的 +// val roundDrawable = +// BitmapFactory.decodeResource(requireContext().resources, R.mipmap.login_casic) +// .createRoundDrawable( +// requireContext(), +// 3f.dp2px(requireContext()), +// R.color.mainThemeColor.convertColor(requireContext()) +// ) +// userImageView.setImageDrawable(roundDrawable) + userNameView.text = userData.name + userPhoneView.text = String.format("电话:${userData.phone}") + userDeptView.text = String.format("部门:${userData.deptName}") + } + + private fun downloadApk(url: String?) { + progressDialog.setMessage("下载新版本中...") + progressDialog.show() + if (url.toString().isBlank()) { + "抱歉,版本下载失败".show(requireContext()) + return + } + /** + * http://111.198.10.15:11304/static/apk/1.0.1.apk + * */ + val downloadPath = url!!.appendDownloadUrl() + //开始下载 + downloadPath.downloadFile(requireContext().createDownloadFileDir().toString(), + object : OnDownloadListener { + override fun onDownloadStart(totalBytes: Long) { + progressDialog.max = totalBytes.toInt() + } + + override fun onProgressChanged(currentBytes: Long) { + progressDialog.progress = currentBytes.toInt() + } + + override fun onDownloadEnd(file: File?) { + progressDialog.dismiss() + progressDialog.progress = 0 + //安装APK + installApk(file) + } + }) + } + + private fun installApk(apkPackage: File?) { + if (apkPackage == null) { + "文件异常,无法安装".show(requireContext()) + return + } + val intent = Intent(Intent.ACTION_VIEW) + val data: Uri + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { //判断版本大于等于7.0 + data = FileProvider.getUriForFile( + requireContext(), LocaleConstant.APP_AUTHORITY, apkPackage + ) + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) // 给目标应用一个临时授权 + } else { + data = Uri.fromFile(apkPackage) + } + intent.setDataAndType(data, "application/vnd.android.package-archive") + requireContext().startActivity(intent) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java new file mode 100644 index 0000000..467e33d --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java @@ -0,0 +1,43 @@ +package com.casic.smart.town.sanxi.model; + +/** + * 普通实体类,失败/成功数据结构一致 + */ +public class CommonResultModel { + private int code; + private String data; + private String message; + private boolean isSuccess; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return isSuccess; + } + + public void setSuccess(boolean success) { + isSuccess = success; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java new file mode 100644 index 0000000..9bdabef --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java @@ -0,0 +1,62 @@ +package com.casic.smart.town.sanxi.model; + +public class LoginResultModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private String kaptcha; + private String token; + + public String getKaptcha() { + return kaptcha; + } + + public void setKaptcha(String kaptcha) { + this.kaptcha = kaptcha; + } + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java new file mode 100644 index 0000000..09f8d59 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java @@ -0,0 +1,80 @@ +package com.casic.smart.town.sanxi.model; + +public class PublicKeyModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private boolean appKaptcha; + private boolean kaptcha; + private String publicKey; + private String sid; + + public boolean isAppKaptcha() { + return appKaptcha; + } + + public void setAppKaptcha(boolean appKaptcha) { + this.appKaptcha = appKaptcha; + } + + public boolean isKaptcha() { + return kaptcha; + } + + public void setKaptcha(boolean kaptcha) { + this.kaptcha = kaptcha; + } + + public String getPublicKey() { + return publicKey; + } + + public void setPublicKey(String publicKey) { + this.publicKey = publicKey; + } + + public String getSid() { + return sid; + } + + public void setSid(String sid) { + this.sid = sid; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java new file mode 100644 index 0000000..db577fb --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java @@ -0,0 +1,227 @@ +package com.casic.smart.town.sanxi.model; + + +import java.util.List; + +public class UserDetailModel { + private int code; + private Data data; + private String message; + private boolean success; + + public void setCode(int code) { + this.code = code; + } + + public int getCode() { + return code; + } + + public void setData(Data data) { + this.data = data; + } + + public Data getData() { + return data; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getMessage() { + return message; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public boolean getSuccess() { + return success; + } + + public static class Data { + + private String account; + private String attr1; + private String avatar; + private String bizData; + private List dataScope; + private String deptId; + private String deptName; + private List devices; + private String id; + private String ipAddr; + private String name; + private String phone; + private List roleList; + private List roleNames; + private List roleTips; + private String scopeType; + private String sysData; + private String targetId; + private String targetName; + private String tenantId; + + public void setAccount(String account) { + this.account = account; + } + + public String getAccount() { + return account; + } + + public void setAttr1(String attr1) { + this.attr1 = attr1; + } + + public String getAttr1() { + return attr1; + } + + public void setAvatar(String avatar) { + this.avatar = avatar; + } + + public String getAvatar() { + return avatar; + } + + public void setBizData(String bizData) { + this.bizData = bizData; + } + + public String getBizData() { + return bizData; + } + + public void setDataScope(List dataScope) { + this.dataScope = dataScope; + } + + public List getDataScope() { + return dataScope; + } + + public void setDeptId(String deptId) { + this.deptId = deptId; + } + + public String getDeptId() { + return deptId; + } + + public void setDeptName(String deptName) { + this.deptName = deptName; + } + + public String getDeptName() { + return deptName; + } + + public void setDevices(List devices) { + this.devices = devices; + } + + public List getDevices() { + return devices; + } + + public void setId(String id) { + this.id = id; + } + + public String getId() { + return id; + } + + public void setIpAddr(String ipAddr) { + this.ipAddr = ipAddr; + } + + public String getIpAddr() { + return ipAddr; + } + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setPhone(String phone) { + this.phone = phone; + } + + public String getPhone() { + return phone; + } + + public void setRoleList(List roleList) { + this.roleList = roleList; + } + + public List getRoleList() { + return roleList; + } + + public void setRoleNames(List roleNames) { + this.roleNames = roleNames; + } + + public List getRoleNames() { + return roleNames; + } + + public void setRoleTips(List roleTips) { + this.roleTips = roleTips; + } + + public List getRoleTips() { + return roleTips; + } + + public void setScopeType(String scopeType) { + this.scopeType = scopeType; + } + + public String getScopeType() { + return scopeType; + } + + public void setSysData(String sysData) { + this.sysData = sysData; + } + + public String getSysData() { + return sysData; + } + + public void setTargetId(String targetId) { + this.targetId = targetId; + } + + public String getTargetId() { + return targetId; + } + + public void setTargetName(String targetName) { + this.targetName = targetName; + } + + public String getTargetName() { + return targetName; + } + + public void setTenantId(String tenantId) { + this.tenantId = tenantId; + } + + public String getTenantId() { + return tenantId; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/VersionResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/VersionResultModel.java new file mode 100644 index 0000000..79374e0 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/VersionResultModel.java @@ -0,0 +1,61 @@ +package com.casic.smart.town.sanxi.model; + +public class VersionResultModel { + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private String downloadUrl; + private String version; + + public String getDownloadUrl() { + return downloadUrl; + } + + public void setDownloadUrl(String downloadUrl) { + this.downloadUrl = downloadUrl; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/AuthenticationHelper.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/AuthenticationHelper.kt new file mode 100644 index 0000000..f498e4e --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/AuthenticationHelper.kt @@ -0,0 +1,24 @@ +package com.casic.smart.town.sanxi.util + +import com.pengxh.kt.lite.utils.SaveKeyValues + +object AuthenticationHelper { + + fun savePublicKey(key: String) { + SaveKeyValues.putValue("keyString", key) + } + + val publicKey: String? + get() = SaveKeyValues.getValue("keyString", "") as String? + + fun saveToken(token: String?) { + SaveKeyValues.putValue("token", token!!) + } + + val token: String? + get() = SaveKeyValues.getValue("token", "") as String? + + fun removeToken() { + SaveKeyValues.removeKey("token") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt index ab1a36d..99f0d19 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt @@ -14,5 +14,9 @@ const val PAGE_LIMIT = 20 const val DEFAULT_SERVER_CONFIG = "defaultServerConfig" - const val DEFAULT_SERVER = "http://111.198.10.15:11308" + const val DEFAULT_SERVER = "http://36.133.189.112:8082" + const val APP_AUTHORITY = "com.casic.smart.town.sanxi.fileprovider" + const val ACCOUNT = "account" + const val PASSWORD = "password" + const val USER_DETAIL_MODEL = "userDetailModel" } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/RSAUtils.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/RSAUtils.kt new file mode 100644 index 0000000..b9216ba --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/RSAUtils.kt @@ -0,0 +1,55 @@ +package com.casic.smart.town.sanxi.util + +import android.util.Base64 +import java.security.* +import java.security.spec.InvalidKeySpecException +import java.security.spec.X509EncodedKeySpec +import javax.crypto.BadPaddingException +import javax.crypto.Cipher +import javax.crypto.IllegalBlockSizeException +import javax.crypto.NoSuchPaddingException + +object RSAUtils { + //构建Cipher实例时所传入的的字符串,默认为"RSA/NONE/PKCS1Padding" + private fun processData(srcData: ByteArray, key: Key): ByteArray? { //用来保存处理结果 + var resultBytes: ByteArray? = null + try { //获取Cipher实例 + val cipher = Cipher.getInstance("RSA/None/PKCS1Padding") + //初始化Cipher,mode指定是加密还是解密,key为公钥或私钥 + cipher.init(Cipher.ENCRYPT_MODE, key) + //处理数据 + resultBytes = cipher.doFinal(srcData) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: NoSuchPaddingException) { + e.printStackTrace() + } catch (e: InvalidKeyException) { + e.printStackTrace() + } catch (e: BadPaddingException) { + e.printStackTrace() + } catch (e: IllegalBlockSizeException) { + e.printStackTrace() + } + return resultBytes + } + + fun encryptDataByPublicKey(srcData: ByteArray, publicKey: PublicKey): String { + val resultBytes = processData(srcData, publicKey) + return Base64.encodeToString(resultBytes, Base64.DEFAULT) + } + + fun keyStrToPublicKey(publicKeyStr: String?): PublicKey? { + var publicKey: PublicKey? = null + val keyBytes = Base64.decode(publicKeyStr, Base64.DEFAULT) + val keySpec = X509EncodedKeySpec(keyBytes) + try { + val keyFactory = KeyFactory.getInstance("RSA") + publicKey = keyFactory.generatePublic(keySpec) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: InvalidKeySpecException) { + e.printStackTrace() + } + return publicKey + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt index 63db0ec..50d1742 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt @@ -1,12 +1,64 @@ package com.casic.smart.town.sanxi.util.retrofit -import retrofit2.http.GET +import retrofit2.http.* interface RetrofitService { + /** + * PublicKey校验 + */ + @GET("/config/baseConfig") + suspend fun obtainPublicKey(): String + + /** + * 登录并获取Token + * + * @param sid + * @param account 用户名 + * @param secretKey 加密后的密码 + */ + @FormUrlEncoded + @POST("/user/login") + suspend fun login( + @Field("sid") sid: String, + @Field("username") account: String, + @Field("password") secretKey: String + ): String + + /** + * 退出登录 + */ + @GET("/user/logout") + suspend fun loginOut(@Header("token") token: String): String + + /** + * 获取用户信息 + */ + @GET("/user/info") + suspend fun obtainUserDetail(@Header("token") token: String): String + + /** + * 修改密码 + * + * @param oldPwd 旧密码 + * @param newPwd 新密码 + */ + @FormUrlEncoded + @POST("/mgr/changePwd") + suspend fun changePassword( + @Header("token") token: String, + @Field("oldPwd") oldPwd: String, + @Field("newPwd") newPwd: String + ): String /** * 根据布防状态统计闸井数量接口 */ @GET("/well/countByBfzt") - suspend fun countWellByState(): String + suspend fun countWellByState(@Header("token") token: String): String + + /** + * 更新APK版本 + */ + @POST("/app/checkVersion") + suspend fun obtainVersionResult(@Header("token") token: String): String } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt index 46bbf4b..410444b 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt @@ -1,5 +1,6 @@ package com.casic.smart.town.sanxi.util.retrofit +import com.casic.smart.town.sanxi.util.AuthenticationHelper import com.casic.smart.town.sanxi.util.LocaleConstant import com.pengxh.kt.lite.utils.RetrofitFactory import com.pengxh.kt.lite.utils.SaveKeyValues @@ -15,9 +16,52 @@ } /** + * 验证PublicKey + */ + suspend fun authenticate(): String { + return api.obtainPublicKey() + } + + /** + * 登录并获取Token + */ + suspend fun login(sid: String, account: String, secretKey: String): String { + return api.login(sid, account, secretKey) + } + + /** + * 退出登录 + */ + suspend fun loginOut(): String { + return api.loginOut(AuthenticationHelper.token!!) + } + + /** + * 获取用户信息 + */ + suspend fun obtainUserDetail(): String { + return api.obtainUserDetail(AuthenticationHelper.token!!) + } + + /** + * 修改密码 + */ + suspend fun changePassword(oldPwd: String, newPwd: String): String { + return api.changePassword(AuthenticationHelper.token!!, oldPwd, newPwd) + } + + /** * 根据布防状态统计闸井数量接口 */ suspend fun countWellByState(): String { - return api.countWellByState() + return api.countWellByState(AuthenticationHelper.token!!) } + + /** + * 更新APK版本 + */ + suspend fun updateVersion(): String { + return api.obtainVersionResult(AuthenticationHelper.token!!) + } + } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/AboutUsActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/AboutUsActivity.kt new file mode 100644 index 0000000..7e43c47 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/AboutUsActivity.kt @@ -0,0 +1,34 @@ +package com.casic.smart.town.sanxi.view + +import com.casic.smart.town.sanxi.BuildConfig +import com.casic.smart.town.sanxi.R +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.kt.lite.base.KotlinBaseActivity +import com.pengxh.kt.lite.extensions.convertColor +import com.pengxh.kt.lite.utils.ImmerseStatusBarUtil +import kotlinx.android.synthetic.main.activity_about_us.* +import kotlinx.android.synthetic.main.include_base_title.* + +class AboutUsActivity : KotlinBaseActivity() { + + override fun initLayoutView(): Int = R.layout.activity_about_us + + override fun setupTopBarLayout() { + ImmersionBar.with(this).statusBarDarkFont(false).init() + ImmerseStatusBarUtil.setColor(this, R.color.mainThemeColor.convertColor(this)) + titleView.text = "关于我们" + leftBackView.setOnClickListener { finish() } + } + + override fun initData() { + versionCodeView.text = String.format("版本信息:V${BuildConfig.VERSION_NAME}") + } + + override fun observeRequestState() { + + } + + override fun initEvent() { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/LoginActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/LoginActivity.kt new file mode 100644 index 0000000..b5d1d50 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/LoginActivity.kt @@ -0,0 +1,97 @@ +package com.casic.smart.town.sanxi.view + +import androidx.lifecycle.ViewModelProvider +import com.casic.smart.town.sanxi.R +import com.casic.smart.town.sanxi.util.AuthenticationHelper +import com.casic.smart.town.sanxi.util.LoadingDialogHub +import com.casic.smart.town.sanxi.util.LocaleConstant +import com.casic.smart.town.sanxi.util.RSAUtils +import com.casic.smart.town.sanxi.vm.AuthenticateViewModel +import com.casic.smart.town.sanxi.vm.LoginViewModel +import com.casic.smart.town.sanxi.vm.UserViewModel +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.kt.lite.base.KotlinBaseActivity +import com.pengxh.kt.lite.extensions.navigatePageTo +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.utils.SaveKeyValues +import kotlinx.android.synthetic.main.activity_login.* + +class LoginActivity : KotlinBaseActivity() { + + private lateinit var authenticateViewModel: AuthenticateViewModel + private lateinit var loginViewModel: LoginViewModel + private lateinit var userViewModel: UserViewModel + + override fun initLayoutView(): Int = R.layout.activity_login + + override fun setupTopBarLayout() { + ImmersionBar.with(this).statusBarDarkFont(true).init() + } + + override fun initData() { + // 设置默认账号密码 + userNameView.setText(SaveKeyValues.getValue(LocaleConstant.ACCOUNT, "") as String) + userPasswordView.setText(SaveKeyValues.getValue(LocaleConstant.PASSWORD, "") as String) + authenticateViewModel = ViewModelProvider(this)[AuthenticateViewModel::class.java] + loginViewModel = ViewModelProvider(this)[LoginViewModel::class.java] + userViewModel = ViewModelProvider(this)[UserViewModel::class.java] + + authenticateViewModel.keyModel.observe(this, { + if (it.code == 200) {//用code判断,别的判断可能有坑 + val keyString = it.data!!.publicKey!! + /** + * 修改密码需要用到key,先存着 + * */ + AuthenticationHelper.savePublicKey(keyString) + val publicKey = RSAUtils.keyStrToPublicKey(keyString) + + val account = userNameView.text.toString() + val userPassword = userPasswordView.text.toString() + val dataByPublicKey = + RSAUtils.encryptDataByPublicKey(userPassword.toByteArray(), publicKey!!) + //登录并获取Token,POST请求 + loginViewModel.enter(it.data!!.sid!!, account, dataByPublicKey) + loginViewModel.enterResultModel.observe(this, { loginResult -> + if (loginResult.code == 200) { + AuthenticationHelper.saveToken(loginResult.data!!.token!!) + /** + * 获取token之后保存用户信息 + * */ + userViewModel.obtainUserDetail() + //验证成功登录 + navigatePageTo() + finish() + } + }) + } + }) + } + + override fun observeRequestState() { + authenticateViewModel.loadState.observe(this, { + LoadingDialogHub.show(this, "登录中,请稍后") + }) + + loginViewModel.loadState.observe(this, { + LoadingDialogHub.dismiss() + }) + } + + override fun initEvent() { + loginButton.setOnClickListener { + val account = userNameView.text.toString().trim() + val userPassword = userPasswordView.text.toString().trim() + if (account.isBlank()) { + "用户名不能为空".show(this) + return@setOnClickListener + } + if (userPassword.isBlank()) { + "密码不能为空".show(this) + return@setOnClickListener + } + SaveKeyValues.putValue(LocaleConstant.ACCOUNT, account) + SaveKeyValues.putValue(LocaleConstant.PASSWORD, userPassword) + authenticateViewModel.obtainPublicKey() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt index 3bea1c9..a4fa951 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt @@ -7,6 +7,7 @@ import com.casic.smart.town.sanxi.R import com.casic.smart.town.sanxi.adapter.ViewPagerAdapter import com.casic.smart.town.sanxi.fragment.AlarmPageFragment +import com.casic.smart.town.sanxi.fragment.MinePageFragment import com.casic.smart.town.sanxi.fragment.MonitorPageFragment import com.casic.smart.town.sanxi.fragment.StatisticsPageFragment import com.gyf.immersionbar.ImmersionBar @@ -33,6 +34,7 @@ fragmentPages.add(StatisticsPageFragment()) fragmentPages.add(MonitorPageFragment()) fragmentPages.add(AlarmPageFragment()) + fragmentPages.add(MinePageFragment()) } override fun initLayoutView(): Int = R.layout.activity_main @@ -68,6 +70,9 @@ R.id.nav_alarm -> { mainViewPager.currentItem = 2 } + R.id.nav_more -> { + mainViewPager.currentItem = 3 + } } false } diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/PermissionActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/PermissionActivity.kt index c478d83..3238f3c 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/view/PermissionActivity.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/PermissionActivity.kt @@ -32,7 +32,7 @@ //先把导航隐私政策声明,后面导航会用到 MapsInitializer.updatePrivacyAgree(this, true) MapsInitializer.updatePrivacyShow(this, true, true) - this.navigatePageTo() + this.navigatePageTo() finish() } diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/SplashScreenActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/SplashScreenActivity.kt new file mode 100644 index 0000000..fa5a74d --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/SplashScreenActivity.kt @@ -0,0 +1,54 @@ +package com.casic.smart.town.sanxi.view + +import android.annotation.SuppressLint +import android.os.CountDownTimer +import androidx.lifecycle.ViewModelProvider +import com.casic.smart.town.sanxi.R +import com.casic.smart.town.sanxi.vm.UserViewModel +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.kt.lite.base.KotlinBaseActivity +import com.pengxh.kt.lite.extensions.navigatePageTo + +@SuppressLint("CustomSplashScreen") +class SplashScreenActivity : KotlinBaseActivity() { + + private lateinit var userDetailViewModel: UserViewModel + private val countDownTimer = object : CountDownTimer(1000, 500) { + override fun onFinish() { + /** + * 获取token之后保存用户信息 + * */ + userDetailViewModel.obtainUserDetail() + userDetailViewModel.userDetailModel.observe(this@SplashScreenActivity, { + if (it.code == 200) { + navigatePageTo() + } else { + navigatePageTo() + } + finish() + }) + } + + override fun onTick(millisUntilFinished: Long) { + + } + } + + override fun initLayoutView(): Int = R.layout.activity_splash + + override fun setupTopBarLayout() { + ImmersionBar.with(this).statusBarDarkFont(false).init() + } + + override fun initData() { + userDetailViewModel = ViewModelProvider(this).get(UserViewModel::class.java) + } + + override fun observeRequestState() { + + } + + override fun initEvent() { + countDownTimer.start() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/vm/AuthenticateViewModel.kt b/app/src/main/java/com/casic/smart/town/sanxi/vm/AuthenticateViewModel.kt new file mode 100644 index 0000000..87ddbf8 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/vm/AuthenticateViewModel.kt @@ -0,0 +1,35 @@ +package com.casic.smart.town.sanxi.vm + +import androidx.lifecycle.MutableLiveData +import com.casic.smart.town.sanxi.base.BaseApplication +import com.casic.smart.town.sanxi.extensions.separateResponseCode +import com.casic.smart.town.sanxi.extensions.toErrorMessage +import com.casic.smart.town.sanxi.model.PublicKeyModel +import com.casic.smart.town.sanxi.util.retrofit.RetrofitServiceManager +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.launch +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.vm.BaseViewModel +import com.pengxh.kt.lite.vm.LoadState + +class AuthenticateViewModel : BaseViewModel() { + + private val gson = Gson() + val keyModel = MutableLiveData() + + fun obtainPublicKey() = launch({ + loadState.value = LoadState.Loading + val response = RetrofitServiceManager.authenticate() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + keyModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ) + } else { + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + it.printStackTrace() + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/vm/LoginViewModel.kt b/app/src/main/java/com/casic/smart/town/sanxi/vm/LoginViewModel.kt new file mode 100644 index 0000000..c377c61 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/vm/LoginViewModel.kt @@ -0,0 +1,54 @@ +package com.casic.smart.town.sanxi.vm + +import androidx.lifecycle.MutableLiveData +import com.casic.smart.town.sanxi.base.BaseApplication +import com.casic.smart.town.sanxi.extensions.separateResponseCode +import com.casic.smart.town.sanxi.extensions.toErrorMessage +import com.casic.smart.town.sanxi.model.CommonResultModel +import com.casic.smart.town.sanxi.model.LoginResultModel +import com.casic.smart.town.sanxi.util.retrofit.RetrofitServiceManager +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.launch +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.vm.BaseViewModel +import com.pengxh.kt.lite.vm.LoadState + +class LoginViewModel : BaseViewModel() { + + private val gson = Gson() + val enterResultModel = MutableLiveData() + val outResultModel = MutableLiveData() + + fun enter(sid: String, account: String, secretKey: String) = launch({ + val response = RetrofitServiceManager.login(sid, account, secretKey) + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + loadState.value = LoadState.Success + "登录成功".show(BaseApplication.obtainInstance()) + enterResultModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ) + } else { + loadState.value = LoadState.Fail + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + loadState.value = LoadState.Fail + it.printStackTrace() + }) + + fun out() = launch({ + val response = RetrofitServiceManager.loginOut() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + outResultModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ) + } else { + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + it.printStackTrace() + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/vm/UserViewModel.kt b/app/src/main/java/com/casic/smart/town/sanxi/vm/UserViewModel.kt new file mode 100644 index 0000000..29a8756 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/vm/UserViewModel.kt @@ -0,0 +1,57 @@ +package com.casic.smart.town.sanxi.vm + +import androidx.lifecycle.MutableLiveData +import com.casic.smart.town.sanxi.base.BaseApplication +import com.casic.smart.town.sanxi.extensions.separateResponseCode +import com.casic.smart.town.sanxi.extensions.toErrorMessage +import com.casic.smart.town.sanxi.model.UserDetailModel +import com.casic.smart.town.sanxi.util.LocaleConstant +import com.casic.smart.town.sanxi.util.retrofit.RetrofitServiceManager +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.launch +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.utils.SaveKeyValues +import com.pengxh.kt.lite.vm.BaseViewModel +import com.pengxh.kt.lite.vm.LoadState + +class UserViewModel : BaseViewModel() { + + private val gson = Gson() + val userDetailModel = MutableLiveData() + + fun obtainUserDetail() = launch({ + val response = RetrofitServiceManager.obtainUserDetail() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + val userDetail = gson.fromJson( + response, object : TypeToken() {}.type + ) + userDetailModel.value = userDetail + SaveKeyValues.putValue(LocaleConstant.USER_DETAIL_MODEL, gson.toJson(userDetail.data)) + } else { + val errorModel = UserDetailModel() + errorModel.code = responseCode + userDetailModel.value = errorModel + //如果此次获取不到用户信息,那么就清空之前的用户缓存,然后让用户重新登录 + SaveKeyValues.removeKey(LocaleConstant.USER_DETAIL_MODEL) + } + }, { + it.printStackTrace() + }) + + fun changePassword(oldPwd: String, newPwd: String) = launch({ + loadState.value = LoadState.Loading + val response = RetrofitServiceManager.changePassword(oldPwd, newPwd) + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + loadState.value = LoadState.Success + } else { + loadState.value = LoadState.Fail + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + loadState.value = LoadState.Fail + it.printStackTrace() + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/vm/VersionViewModel.kt b/app/src/main/java/com/casic/smart/town/sanxi/vm/VersionViewModel.kt new file mode 100644 index 0000000..20ea457 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/vm/VersionViewModel.kt @@ -0,0 +1,38 @@ +package com.casic.smart.town.sanxi.vm + +import androidx.lifecycle.MutableLiveData +import com.casic.smart.town.sanxi.base.BaseApplication +import com.casic.smart.town.sanxi.extensions.separateResponseCode +import com.casic.smart.town.sanxi.extensions.toErrorMessage +import com.casic.smart.town.sanxi.model.VersionResultModel +import com.casic.smart.town.sanxi.util.retrofit.RetrofitServiceManager +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.launch +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.vm.BaseViewModel +import com.pengxh.kt.lite.vm.LoadState + +class VersionViewModel : BaseViewModel() { + + private val gson = Gson() + val versionResultModel = MutableLiveData() + + fun updateVersion() = launch({ + loadState.value = LoadState.Loading + val response = RetrofitServiceManager.updateVersion() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + loadState.value = LoadState.Success + versionResultModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ).data + } else { + loadState.value = LoadState.Fail + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + loadState.value = LoadState.Fail + it.printStackTrace() + }) +} \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_solid_layout_gray_radius_5.xml b/app/src/main/res/drawable/bg_solid_layout_gray_radius_5.xml new file mode 100644 index 0000000..96eb93e --- /dev/null +++ b/app/src/main/res/drawable/bg_solid_layout_gray_radius_5.xml @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1df917d..8d4f33b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -6,6 +6,7 @@ + @@ -32,11 +33,14 @@ + + + + + + + diff --git a/app/src/main/assets/svg/mine.svg b/app/src/main/assets/svg/mine.svg new file mode 100644 index 0000000..3fbb0a3 --- /dev/null +++ b/app/src/main/assets/svg/mine.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt b/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt index d730417..b1972c2 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt @@ -1,8 +1,10 @@ package com.casic.smart.town.sanxi.extensions import com.casic.smart.town.sanxi.model.ErrorMessageModel +import com.casic.smart.town.sanxi.util.LocaleConstant import com.google.gson.Gson import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.utils.SaveKeyValues import org.json.JSONObject fun String.separateResponseCode(): Int { @@ -17,4 +19,15 @@ this, object : TypeToken() {}.type ) return errorModel.message.toString() +} + +/** + * 下载路径为 http://xx.com/static/ 拼接downloadUrl + * */ +fun String.appendDownloadUrl(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + LocaleConstant.DEFAULT_SERVER_CONFIG, LocaleConstant.DEFAULT_SERVER + ) as String + return "$defaultValue/static/${this}" } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt b/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt new file mode 100644 index 0000000..1809dc5 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt @@ -0,0 +1,288 @@ +package com.casic.smart.town.sanxi.fragment + +import android.app.ProgressDialog +import android.content.Intent +import android.net.Uri +import android.os.Build +import android.os.CountDownTimer +import androidx.core.content.FileProvider +import androidx.lifecycle.ViewModelProvider +import com.casic.smart.town.sanxi.BuildConfig +import com.casic.smart.town.sanxi.R +import com.casic.smart.town.sanxi.extensions.appendDownloadUrl +import com.casic.smart.town.sanxi.model.UserDetailModel +import com.casic.smart.town.sanxi.util.AuthenticationHelper +import com.casic.smart.town.sanxi.util.LoadingDialogHub +import com.casic.smart.town.sanxi.util.LocaleConstant +import com.casic.smart.town.sanxi.util.RSAUtils +import com.casic.smart.town.sanxi.view.AboutUsActivity +import com.casic.smart.town.sanxi.view.LoginActivity +import com.casic.smart.town.sanxi.vm.LoginViewModel +import com.casic.smart.town.sanxi.vm.UserViewModel +import com.casic.smart.town.sanxi.vm.VersionViewModel +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.base.KotlinBaseFragment +import com.pengxh.kt.lite.callback.OnDownloadListener +import com.pengxh.kt.lite.extensions.* +import com.pengxh.kt.lite.utils.PageNavigationManager +import com.pengxh.kt.lite.utils.SaveKeyValues +import com.pengxh.kt.lite.vm.LoadState +import com.pengxh.kt.lite.widget.dialog.AlertControlDialog +import com.pengxh.kt.lite.widget.dialog.ChangePasswordDialog +import kotlinx.android.synthetic.main.fragment_mine.* +import java.io.File +import java.nio.charset.StandardCharsets + +class MinePageFragment : KotlinBaseFragment() { + + private lateinit var userData: UserDetailModel.Data + private lateinit var userViewModel: UserViewModel + private lateinit var loginViewModel: LoginViewModel + private lateinit var versionViewModel: VersionViewModel + private lateinit var progressDialog: ProgressDialog + + override fun initLayoutView(): Int = R.layout.fragment_mine + + override fun setupTopBarLayout() { + + } + + override fun initData() { + userViewModel = ViewModelProvider(this)[UserViewModel::class.java] + loginViewModel = ViewModelProvider(this)[LoginViewModel::class.java] + versionViewModel = ViewModelProvider(this)[VersionViewModel::class.java] + + //初始化下载对话框 + progressDialog = ProgressDialog(requireContext()) + progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL) + progressDialog.setProgressDrawable(resources.getDrawable(R.drawable.download_progress)) + progressDialog.setCanceledOnTouchOutside(false) + progressDialog.setCancelable(false) + + /** + * 数据监听 + * */ + dataObserve() + } + + override fun observeRequestState() { + userViewModel.loadState.observe(this, { + when (it) { + is LoadState.Loading -> LoadingDialogHub.show(requireActivity(), "修改中,请稍后") + is LoadState.Success -> { + "修改成功,请重新登录".show(requireContext()) + LoadingDialogHub.dismiss() + AuthenticationHelper.removeToken() + requireContext().navigatePageTo() + PageNavigationManager.finishAllActivity() + } + else -> LoadingDialogHub.dismiss() + } + }) + + versionViewModel.loadState.observe(this, { + when (it) { + is LoadState.Loading -> LoadingDialogHub.show(requireActivity(), "检查版本中,请稍后") + else -> LoadingDialogHub.dismiss() + } + }) + } + + override fun initEvent() { + userImageView.setOnClickListener { + "尽情期待~".show(requireContext()) + } + + updateUserButton.setOnClickListener { + LoadingDialogHub.show(requireActivity(), "同步中,请稍后...") + userViewModel.obtainUserDetail() + } + + changePwdLayout.setOnClickListener { + ChangePasswordDialog.Builder() + .setContext(requireContext()) + .setOnDialogButtonClickListener(object : + ChangePasswordDialog.OnDialogButtonClickListener { + override fun onConfirmClick(oldPwd: String, newPwd: String) { + val publicKey = + RSAUtils.keyStrToPublicKey(AuthenticationHelper.publicKey)!! + val oldPassKey = RSAUtils.encryptDataByPublicKey( + oldPwd.toByteArray(StandardCharsets.UTF_8), + publicKey + ) + val newPassKey = RSAUtils.encryptDataByPublicKey( + newPwd.toByteArray(StandardCharsets.UTF_8), + publicKey + ) + userViewModel.changePassword(oldPassKey, newPassKey) + } + }).build().show() + } + + aboutUsLayout.setOnClickListener { + requireContext().navigatePageTo() + } + + updateVersionLayout.setOnClickListener { + versionViewModel.updateVersion() + } + + clearCacheLayout.setOnClickListener { + //删除缓存之后在设置缓存大小 + LoadingDialogHub.show(requireActivity(), "清理中,请稍后") + File(requireContext().cacheDir.path).deleteFile() + object : CountDownTimer(1500, 500) { + override fun onTick(millisUntilFinished: Long) { + + } + + override fun onFinish() { + LoadingDialogHub.dismiss() + cacheSizeView.text = collectApplicationCache().formatFileSize() + } + }.start() + } + + loginOutButton.setOnClickListener { + AlertControlDialog.Builder() + .setContext(requireContext()) + .setTitle("退出登录") + .setMessage("确定要退出吗?") + .setNegativeButton("取消") + .setPositiveButton("确定") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + loginViewModel.out() + } + + override fun onCancelClick() {} + }).build().show() + } + } + + private fun dataObserve() { + userViewModel.userDetailModel.observe(this, { + if (it.code == 200) { + LoadingDialogHub.dismiss() + "同步完成".show(requireContext()) + userData = it.data + updateUserInfo() + } + }) + + versionViewModel.versionResultModel.observe(this, { + if (BuildConfig.VERSION_NAME == it.version) { + "已是最新版本,无需更新".show(requireContext()) + } else { + AlertControlDialog.Builder() + .setContext(requireContext()) + .setTitle("提示") + .setMessage("有新版本,是否更新?") + .setNegativeButton("稍后再说") + .setPositiveButton("立即下载") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + downloadApk(it.downloadUrl) + } + + override fun onCancelClick() { + + } + }).build().show() + } + }) + + loginViewModel.outResultModel.observe(this, { + if (it.code == 200) { + AuthenticationHelper.removeToken() + requireContext().navigatePageTo() + PageNavigationManager.finishAllActivity() + } + }) + } + + private fun collectApplicationCache(): Long { + return File(requireContext().cacheDir.path).calculateSize() + } + + override fun onResume() { + val userDetailJson = SaveKeyValues.getValue(LocaleConstant.USER_DETAIL_MODEL, "") as String + if (userDetailJson.isNotBlank()) { + userData = Gson().fromJson( + userDetailJson, object : TypeToken() {}.type + ) + updateUserInfo() + } + //缓存 + cacheSizeView.text = collectApplicationCache().formatFileSize() + super.onResume() + } + + private fun updateUserInfo() { + //设置头像,圆形,暂时是默认的 +// val roundDrawable = +// BitmapFactory.decodeResource(requireContext().resources, R.mipmap.login_casic) +// .createRoundDrawable( +// requireContext(), +// 3f.dp2px(requireContext()), +// R.color.mainThemeColor.convertColor(requireContext()) +// ) +// userImageView.setImageDrawable(roundDrawable) + userNameView.text = userData.name + userPhoneView.text = String.format("电话:${userData.phone}") + userDeptView.text = String.format("部门:${userData.deptName}") + } + + private fun downloadApk(url: String?) { + progressDialog.setMessage("下载新版本中...") + progressDialog.show() + if (url.toString().isBlank()) { + "抱歉,版本下载失败".show(requireContext()) + return + } + /** + * http://111.198.10.15:11304/static/apk/1.0.1.apk + * */ + val downloadPath = url!!.appendDownloadUrl() + //开始下载 + downloadPath.downloadFile(requireContext().createDownloadFileDir().toString(), + object : OnDownloadListener { + override fun onDownloadStart(totalBytes: Long) { + progressDialog.max = totalBytes.toInt() + } + + override fun onProgressChanged(currentBytes: Long) { + progressDialog.progress = currentBytes.toInt() + } + + override fun onDownloadEnd(file: File?) { + progressDialog.dismiss() + progressDialog.progress = 0 + //安装APK + installApk(file) + } + }) + } + + private fun installApk(apkPackage: File?) { + if (apkPackage == null) { + "文件异常,无法安装".show(requireContext()) + return + } + val intent = Intent(Intent.ACTION_VIEW) + val data: Uri + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { //判断版本大于等于7.0 + data = FileProvider.getUriForFile( + requireContext(), LocaleConstant.APP_AUTHORITY, apkPackage + ) + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) // 给目标应用一个临时授权 + } else { + data = Uri.fromFile(apkPackage) + } + intent.setDataAndType(data, "application/vnd.android.package-archive") + requireContext().startActivity(intent) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java new file mode 100644 index 0000000..467e33d --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java @@ -0,0 +1,43 @@ +package com.casic.smart.town.sanxi.model; + +/** + * 普通实体类,失败/成功数据结构一致 + */ +public class CommonResultModel { + private int code; + private String data; + private String message; + private boolean isSuccess; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return isSuccess; + } + + public void setSuccess(boolean success) { + isSuccess = success; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java new file mode 100644 index 0000000..9bdabef --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java @@ -0,0 +1,62 @@ +package com.casic.smart.town.sanxi.model; + +public class LoginResultModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private String kaptcha; + private String token; + + public String getKaptcha() { + return kaptcha; + } + + public void setKaptcha(String kaptcha) { + this.kaptcha = kaptcha; + } + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java new file mode 100644 index 0000000..09f8d59 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java @@ -0,0 +1,80 @@ +package com.casic.smart.town.sanxi.model; + +public class PublicKeyModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private boolean appKaptcha; + private boolean kaptcha; + private String publicKey; + private String sid; + + public boolean isAppKaptcha() { + return appKaptcha; + } + + public void setAppKaptcha(boolean appKaptcha) { + this.appKaptcha = appKaptcha; + } + + public boolean isKaptcha() { + return kaptcha; + } + + public void setKaptcha(boolean kaptcha) { + this.kaptcha = kaptcha; + } + + public String getPublicKey() { + return publicKey; + } + + public void setPublicKey(String publicKey) { + this.publicKey = publicKey; + } + + public String getSid() { + return sid; + } + + public void setSid(String sid) { + this.sid = sid; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java new file mode 100644 index 0000000..db577fb --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java @@ -0,0 +1,227 @@ +package com.casic.smart.town.sanxi.model; + + +import java.util.List; + +public class UserDetailModel { + private int code; + private Data data; + private String message; + private boolean success; + + public void setCode(int code) { + this.code = code; + } + + public int getCode() { + return code; + } + + public void setData(Data data) { + this.data = data; + } + + public Data getData() { + return data; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getMessage() { + return message; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public boolean getSuccess() { + return success; + } + + public static class Data { + + private String account; + private String attr1; + private String avatar; + private String bizData; + private List dataScope; + private String deptId; + private String deptName; + private List devices; + private String id; + private String ipAddr; + private String name; + private String phone; + private List roleList; + private List roleNames; + private List roleTips; + private String scopeType; + private String sysData; + private String targetId; + private String targetName; + private String tenantId; + + public void setAccount(String account) { + this.account = account; + } + + public String getAccount() { + return account; + } + + public void setAttr1(String attr1) { + this.attr1 = attr1; + } + + public String getAttr1() { + return attr1; + } + + public void setAvatar(String avatar) { + this.avatar = avatar; + } + + public String getAvatar() { + return avatar; + } + + public void setBizData(String bizData) { + this.bizData = bizData; + } + + public String getBizData() { + return bizData; + } + + public void setDataScope(List dataScope) { + this.dataScope = dataScope; + } + + public List getDataScope() { + return dataScope; + } + + public void setDeptId(String deptId) { + this.deptId = deptId; + } + + public String getDeptId() { + return deptId; + } + + public void setDeptName(String deptName) { + this.deptName = deptName; + } + + public String getDeptName() { + return deptName; + } + + public void setDevices(List devices) { + this.devices = devices; + } + + public List getDevices() { + return devices; + } + + public void setId(String id) { + this.id = id; + } + + public String getId() { + return id; + } + + public void setIpAddr(String ipAddr) { + this.ipAddr = ipAddr; + } + + public String getIpAddr() { + return ipAddr; + } + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setPhone(String phone) { + this.phone = phone; + } + + public String getPhone() { + return phone; + } + + public void setRoleList(List roleList) { + this.roleList = roleList; + } + + public List getRoleList() { + return roleList; + } + + public void setRoleNames(List roleNames) { + this.roleNames = roleNames; + } + + public List getRoleNames() { + return roleNames; + } + + public void setRoleTips(List roleTips) { + this.roleTips = roleTips; + } + + public List getRoleTips() { + return roleTips; + } + + public void setScopeType(String scopeType) { + this.scopeType = scopeType; + } + + public String getScopeType() { + return scopeType; + } + + public void setSysData(String sysData) { + this.sysData = sysData; + } + + public String getSysData() { + return sysData; + } + + public void setTargetId(String targetId) { + this.targetId = targetId; + } + + public String getTargetId() { + return targetId; + } + + public void setTargetName(String targetName) { + this.targetName = targetName; + } + + public String getTargetName() { + return targetName; + } + + public void setTenantId(String tenantId) { + this.tenantId = tenantId; + } + + public String getTenantId() { + return tenantId; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/VersionResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/VersionResultModel.java new file mode 100644 index 0000000..79374e0 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/VersionResultModel.java @@ -0,0 +1,61 @@ +package com.casic.smart.town.sanxi.model; + +public class VersionResultModel { + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private String downloadUrl; + private String version; + + public String getDownloadUrl() { + return downloadUrl; + } + + public void setDownloadUrl(String downloadUrl) { + this.downloadUrl = downloadUrl; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/AuthenticationHelper.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/AuthenticationHelper.kt new file mode 100644 index 0000000..f498e4e --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/AuthenticationHelper.kt @@ -0,0 +1,24 @@ +package com.casic.smart.town.sanxi.util + +import com.pengxh.kt.lite.utils.SaveKeyValues + +object AuthenticationHelper { + + fun savePublicKey(key: String) { + SaveKeyValues.putValue("keyString", key) + } + + val publicKey: String? + get() = SaveKeyValues.getValue("keyString", "") as String? + + fun saveToken(token: String?) { + SaveKeyValues.putValue("token", token!!) + } + + val token: String? + get() = SaveKeyValues.getValue("token", "") as String? + + fun removeToken() { + SaveKeyValues.removeKey("token") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt index ab1a36d..99f0d19 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt @@ -14,5 +14,9 @@ const val PAGE_LIMIT = 20 const val DEFAULT_SERVER_CONFIG = "defaultServerConfig" - const val DEFAULT_SERVER = "http://111.198.10.15:11308" + const val DEFAULT_SERVER = "http://36.133.189.112:8082" + const val APP_AUTHORITY = "com.casic.smart.town.sanxi.fileprovider" + const val ACCOUNT = "account" + const val PASSWORD = "password" + const val USER_DETAIL_MODEL = "userDetailModel" } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/RSAUtils.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/RSAUtils.kt new file mode 100644 index 0000000..b9216ba --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/RSAUtils.kt @@ -0,0 +1,55 @@ +package com.casic.smart.town.sanxi.util + +import android.util.Base64 +import java.security.* +import java.security.spec.InvalidKeySpecException +import java.security.spec.X509EncodedKeySpec +import javax.crypto.BadPaddingException +import javax.crypto.Cipher +import javax.crypto.IllegalBlockSizeException +import javax.crypto.NoSuchPaddingException + +object RSAUtils { + //构建Cipher实例时所传入的的字符串,默认为"RSA/NONE/PKCS1Padding" + private fun processData(srcData: ByteArray, key: Key): ByteArray? { //用来保存处理结果 + var resultBytes: ByteArray? = null + try { //获取Cipher实例 + val cipher = Cipher.getInstance("RSA/None/PKCS1Padding") + //初始化Cipher,mode指定是加密还是解密,key为公钥或私钥 + cipher.init(Cipher.ENCRYPT_MODE, key) + //处理数据 + resultBytes = cipher.doFinal(srcData) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: NoSuchPaddingException) { + e.printStackTrace() + } catch (e: InvalidKeyException) { + e.printStackTrace() + } catch (e: BadPaddingException) { + e.printStackTrace() + } catch (e: IllegalBlockSizeException) { + e.printStackTrace() + } + return resultBytes + } + + fun encryptDataByPublicKey(srcData: ByteArray, publicKey: PublicKey): String { + val resultBytes = processData(srcData, publicKey) + return Base64.encodeToString(resultBytes, Base64.DEFAULT) + } + + fun keyStrToPublicKey(publicKeyStr: String?): PublicKey? { + var publicKey: PublicKey? = null + val keyBytes = Base64.decode(publicKeyStr, Base64.DEFAULT) + val keySpec = X509EncodedKeySpec(keyBytes) + try { + val keyFactory = KeyFactory.getInstance("RSA") + publicKey = keyFactory.generatePublic(keySpec) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: InvalidKeySpecException) { + e.printStackTrace() + } + return publicKey + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt index 63db0ec..50d1742 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt @@ -1,12 +1,64 @@ package com.casic.smart.town.sanxi.util.retrofit -import retrofit2.http.GET +import retrofit2.http.* interface RetrofitService { + /** + * PublicKey校验 + */ + @GET("/config/baseConfig") + suspend fun obtainPublicKey(): String + + /** + * 登录并获取Token + * + * @param sid + * @param account 用户名 + * @param secretKey 加密后的密码 + */ + @FormUrlEncoded + @POST("/user/login") + suspend fun login( + @Field("sid") sid: String, + @Field("username") account: String, + @Field("password") secretKey: String + ): String + + /** + * 退出登录 + */ + @GET("/user/logout") + suspend fun loginOut(@Header("token") token: String): String + + /** + * 获取用户信息 + */ + @GET("/user/info") + suspend fun obtainUserDetail(@Header("token") token: String): String + + /** + * 修改密码 + * + * @param oldPwd 旧密码 + * @param newPwd 新密码 + */ + @FormUrlEncoded + @POST("/mgr/changePwd") + suspend fun changePassword( + @Header("token") token: String, + @Field("oldPwd") oldPwd: String, + @Field("newPwd") newPwd: String + ): String /** * 根据布防状态统计闸井数量接口 */ @GET("/well/countByBfzt") - suspend fun countWellByState(): String + suspend fun countWellByState(@Header("token") token: String): String + + /** + * 更新APK版本 + */ + @POST("/app/checkVersion") + suspend fun obtainVersionResult(@Header("token") token: String): String } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt index 46bbf4b..410444b 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt @@ -1,5 +1,6 @@ package com.casic.smart.town.sanxi.util.retrofit +import com.casic.smart.town.sanxi.util.AuthenticationHelper import com.casic.smart.town.sanxi.util.LocaleConstant import com.pengxh.kt.lite.utils.RetrofitFactory import com.pengxh.kt.lite.utils.SaveKeyValues @@ -15,9 +16,52 @@ } /** + * 验证PublicKey + */ + suspend fun authenticate(): String { + return api.obtainPublicKey() + } + + /** + * 登录并获取Token + */ + suspend fun login(sid: String, account: String, secretKey: String): String { + return api.login(sid, account, secretKey) + } + + /** + * 退出登录 + */ + suspend fun loginOut(): String { + return api.loginOut(AuthenticationHelper.token!!) + } + + /** + * 获取用户信息 + */ + suspend fun obtainUserDetail(): String { + return api.obtainUserDetail(AuthenticationHelper.token!!) + } + + /** + * 修改密码 + */ + suspend fun changePassword(oldPwd: String, newPwd: String): String { + return api.changePassword(AuthenticationHelper.token!!, oldPwd, newPwd) + } + + /** * 根据布防状态统计闸井数量接口 */ suspend fun countWellByState(): String { - return api.countWellByState() + return api.countWellByState(AuthenticationHelper.token!!) } + + /** + * 更新APK版本 + */ + suspend fun updateVersion(): String { + return api.obtainVersionResult(AuthenticationHelper.token!!) + } + } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/AboutUsActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/AboutUsActivity.kt new file mode 100644 index 0000000..7e43c47 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/AboutUsActivity.kt @@ -0,0 +1,34 @@ +package com.casic.smart.town.sanxi.view + +import com.casic.smart.town.sanxi.BuildConfig +import com.casic.smart.town.sanxi.R +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.kt.lite.base.KotlinBaseActivity +import com.pengxh.kt.lite.extensions.convertColor +import com.pengxh.kt.lite.utils.ImmerseStatusBarUtil +import kotlinx.android.synthetic.main.activity_about_us.* +import kotlinx.android.synthetic.main.include_base_title.* + +class AboutUsActivity : KotlinBaseActivity() { + + override fun initLayoutView(): Int = R.layout.activity_about_us + + override fun setupTopBarLayout() { + ImmersionBar.with(this).statusBarDarkFont(false).init() + ImmerseStatusBarUtil.setColor(this, R.color.mainThemeColor.convertColor(this)) + titleView.text = "关于我们" + leftBackView.setOnClickListener { finish() } + } + + override fun initData() { + versionCodeView.text = String.format("版本信息:V${BuildConfig.VERSION_NAME}") + } + + override fun observeRequestState() { + + } + + override fun initEvent() { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/LoginActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/LoginActivity.kt new file mode 100644 index 0000000..b5d1d50 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/LoginActivity.kt @@ -0,0 +1,97 @@ +package com.casic.smart.town.sanxi.view + +import androidx.lifecycle.ViewModelProvider +import com.casic.smart.town.sanxi.R +import com.casic.smart.town.sanxi.util.AuthenticationHelper +import com.casic.smart.town.sanxi.util.LoadingDialogHub +import com.casic.smart.town.sanxi.util.LocaleConstant +import com.casic.smart.town.sanxi.util.RSAUtils +import com.casic.smart.town.sanxi.vm.AuthenticateViewModel +import com.casic.smart.town.sanxi.vm.LoginViewModel +import com.casic.smart.town.sanxi.vm.UserViewModel +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.kt.lite.base.KotlinBaseActivity +import com.pengxh.kt.lite.extensions.navigatePageTo +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.utils.SaveKeyValues +import kotlinx.android.synthetic.main.activity_login.* + +class LoginActivity : KotlinBaseActivity() { + + private lateinit var authenticateViewModel: AuthenticateViewModel + private lateinit var loginViewModel: LoginViewModel + private lateinit var userViewModel: UserViewModel + + override fun initLayoutView(): Int = R.layout.activity_login + + override fun setupTopBarLayout() { + ImmersionBar.with(this).statusBarDarkFont(true).init() + } + + override fun initData() { + // 设置默认账号密码 + userNameView.setText(SaveKeyValues.getValue(LocaleConstant.ACCOUNT, "") as String) + userPasswordView.setText(SaveKeyValues.getValue(LocaleConstant.PASSWORD, "") as String) + authenticateViewModel = ViewModelProvider(this)[AuthenticateViewModel::class.java] + loginViewModel = ViewModelProvider(this)[LoginViewModel::class.java] + userViewModel = ViewModelProvider(this)[UserViewModel::class.java] + + authenticateViewModel.keyModel.observe(this, { + if (it.code == 200) {//用code判断,别的判断可能有坑 + val keyString = it.data!!.publicKey!! + /** + * 修改密码需要用到key,先存着 + * */ + AuthenticationHelper.savePublicKey(keyString) + val publicKey = RSAUtils.keyStrToPublicKey(keyString) + + val account = userNameView.text.toString() + val userPassword = userPasswordView.text.toString() + val dataByPublicKey = + RSAUtils.encryptDataByPublicKey(userPassword.toByteArray(), publicKey!!) + //登录并获取Token,POST请求 + loginViewModel.enter(it.data!!.sid!!, account, dataByPublicKey) + loginViewModel.enterResultModel.observe(this, { loginResult -> + if (loginResult.code == 200) { + AuthenticationHelper.saveToken(loginResult.data!!.token!!) + /** + * 获取token之后保存用户信息 + * */ + userViewModel.obtainUserDetail() + //验证成功登录 + navigatePageTo() + finish() + } + }) + } + }) + } + + override fun observeRequestState() { + authenticateViewModel.loadState.observe(this, { + LoadingDialogHub.show(this, "登录中,请稍后") + }) + + loginViewModel.loadState.observe(this, { + LoadingDialogHub.dismiss() + }) + } + + override fun initEvent() { + loginButton.setOnClickListener { + val account = userNameView.text.toString().trim() + val userPassword = userPasswordView.text.toString().trim() + if (account.isBlank()) { + "用户名不能为空".show(this) + return@setOnClickListener + } + if (userPassword.isBlank()) { + "密码不能为空".show(this) + return@setOnClickListener + } + SaveKeyValues.putValue(LocaleConstant.ACCOUNT, account) + SaveKeyValues.putValue(LocaleConstant.PASSWORD, userPassword) + authenticateViewModel.obtainPublicKey() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt index 3bea1c9..a4fa951 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt @@ -7,6 +7,7 @@ import com.casic.smart.town.sanxi.R import com.casic.smart.town.sanxi.adapter.ViewPagerAdapter import com.casic.smart.town.sanxi.fragment.AlarmPageFragment +import com.casic.smart.town.sanxi.fragment.MinePageFragment import com.casic.smart.town.sanxi.fragment.MonitorPageFragment import com.casic.smart.town.sanxi.fragment.StatisticsPageFragment import com.gyf.immersionbar.ImmersionBar @@ -33,6 +34,7 @@ fragmentPages.add(StatisticsPageFragment()) fragmentPages.add(MonitorPageFragment()) fragmentPages.add(AlarmPageFragment()) + fragmentPages.add(MinePageFragment()) } override fun initLayoutView(): Int = R.layout.activity_main @@ -68,6 +70,9 @@ R.id.nav_alarm -> { mainViewPager.currentItem = 2 } + R.id.nav_more -> { + mainViewPager.currentItem = 3 + } } false } diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/PermissionActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/PermissionActivity.kt index c478d83..3238f3c 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/view/PermissionActivity.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/PermissionActivity.kt @@ -32,7 +32,7 @@ //先把导航隐私政策声明,后面导航会用到 MapsInitializer.updatePrivacyAgree(this, true) MapsInitializer.updatePrivacyShow(this, true, true) - this.navigatePageTo() + this.navigatePageTo() finish() } diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/SplashScreenActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/SplashScreenActivity.kt new file mode 100644 index 0000000..fa5a74d --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/SplashScreenActivity.kt @@ -0,0 +1,54 @@ +package com.casic.smart.town.sanxi.view + +import android.annotation.SuppressLint +import android.os.CountDownTimer +import androidx.lifecycle.ViewModelProvider +import com.casic.smart.town.sanxi.R +import com.casic.smart.town.sanxi.vm.UserViewModel +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.kt.lite.base.KotlinBaseActivity +import com.pengxh.kt.lite.extensions.navigatePageTo + +@SuppressLint("CustomSplashScreen") +class SplashScreenActivity : KotlinBaseActivity() { + + private lateinit var userDetailViewModel: UserViewModel + private val countDownTimer = object : CountDownTimer(1000, 500) { + override fun onFinish() { + /** + * 获取token之后保存用户信息 + * */ + userDetailViewModel.obtainUserDetail() + userDetailViewModel.userDetailModel.observe(this@SplashScreenActivity, { + if (it.code == 200) { + navigatePageTo() + } else { + navigatePageTo() + } + finish() + }) + } + + override fun onTick(millisUntilFinished: Long) { + + } + } + + override fun initLayoutView(): Int = R.layout.activity_splash + + override fun setupTopBarLayout() { + ImmersionBar.with(this).statusBarDarkFont(false).init() + } + + override fun initData() { + userDetailViewModel = ViewModelProvider(this).get(UserViewModel::class.java) + } + + override fun observeRequestState() { + + } + + override fun initEvent() { + countDownTimer.start() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/vm/AuthenticateViewModel.kt b/app/src/main/java/com/casic/smart/town/sanxi/vm/AuthenticateViewModel.kt new file mode 100644 index 0000000..87ddbf8 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/vm/AuthenticateViewModel.kt @@ -0,0 +1,35 @@ +package com.casic.smart.town.sanxi.vm + +import androidx.lifecycle.MutableLiveData +import com.casic.smart.town.sanxi.base.BaseApplication +import com.casic.smart.town.sanxi.extensions.separateResponseCode +import com.casic.smart.town.sanxi.extensions.toErrorMessage +import com.casic.smart.town.sanxi.model.PublicKeyModel +import com.casic.smart.town.sanxi.util.retrofit.RetrofitServiceManager +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.launch +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.vm.BaseViewModel +import com.pengxh.kt.lite.vm.LoadState + +class AuthenticateViewModel : BaseViewModel() { + + private val gson = Gson() + val keyModel = MutableLiveData() + + fun obtainPublicKey() = launch({ + loadState.value = LoadState.Loading + val response = RetrofitServiceManager.authenticate() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + keyModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ) + } else { + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + it.printStackTrace() + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/vm/LoginViewModel.kt b/app/src/main/java/com/casic/smart/town/sanxi/vm/LoginViewModel.kt new file mode 100644 index 0000000..c377c61 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/vm/LoginViewModel.kt @@ -0,0 +1,54 @@ +package com.casic.smart.town.sanxi.vm + +import androidx.lifecycle.MutableLiveData +import com.casic.smart.town.sanxi.base.BaseApplication +import com.casic.smart.town.sanxi.extensions.separateResponseCode +import com.casic.smart.town.sanxi.extensions.toErrorMessage +import com.casic.smart.town.sanxi.model.CommonResultModel +import com.casic.smart.town.sanxi.model.LoginResultModel +import com.casic.smart.town.sanxi.util.retrofit.RetrofitServiceManager +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.launch +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.vm.BaseViewModel +import com.pengxh.kt.lite.vm.LoadState + +class LoginViewModel : BaseViewModel() { + + private val gson = Gson() + val enterResultModel = MutableLiveData() + val outResultModel = MutableLiveData() + + fun enter(sid: String, account: String, secretKey: String) = launch({ + val response = RetrofitServiceManager.login(sid, account, secretKey) + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + loadState.value = LoadState.Success + "登录成功".show(BaseApplication.obtainInstance()) + enterResultModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ) + } else { + loadState.value = LoadState.Fail + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + loadState.value = LoadState.Fail + it.printStackTrace() + }) + + fun out() = launch({ + val response = RetrofitServiceManager.loginOut() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + outResultModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ) + } else { + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + it.printStackTrace() + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/vm/UserViewModel.kt b/app/src/main/java/com/casic/smart/town/sanxi/vm/UserViewModel.kt new file mode 100644 index 0000000..29a8756 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/vm/UserViewModel.kt @@ -0,0 +1,57 @@ +package com.casic.smart.town.sanxi.vm + +import androidx.lifecycle.MutableLiveData +import com.casic.smart.town.sanxi.base.BaseApplication +import com.casic.smart.town.sanxi.extensions.separateResponseCode +import com.casic.smart.town.sanxi.extensions.toErrorMessage +import com.casic.smart.town.sanxi.model.UserDetailModel +import com.casic.smart.town.sanxi.util.LocaleConstant +import com.casic.smart.town.sanxi.util.retrofit.RetrofitServiceManager +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.launch +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.utils.SaveKeyValues +import com.pengxh.kt.lite.vm.BaseViewModel +import com.pengxh.kt.lite.vm.LoadState + +class UserViewModel : BaseViewModel() { + + private val gson = Gson() + val userDetailModel = MutableLiveData() + + fun obtainUserDetail() = launch({ + val response = RetrofitServiceManager.obtainUserDetail() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + val userDetail = gson.fromJson( + response, object : TypeToken() {}.type + ) + userDetailModel.value = userDetail + SaveKeyValues.putValue(LocaleConstant.USER_DETAIL_MODEL, gson.toJson(userDetail.data)) + } else { + val errorModel = UserDetailModel() + errorModel.code = responseCode + userDetailModel.value = errorModel + //如果此次获取不到用户信息,那么就清空之前的用户缓存,然后让用户重新登录 + SaveKeyValues.removeKey(LocaleConstant.USER_DETAIL_MODEL) + } + }, { + it.printStackTrace() + }) + + fun changePassword(oldPwd: String, newPwd: String) = launch({ + loadState.value = LoadState.Loading + val response = RetrofitServiceManager.changePassword(oldPwd, newPwd) + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + loadState.value = LoadState.Success + } else { + loadState.value = LoadState.Fail + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + loadState.value = LoadState.Fail + it.printStackTrace() + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/vm/VersionViewModel.kt b/app/src/main/java/com/casic/smart/town/sanxi/vm/VersionViewModel.kt new file mode 100644 index 0000000..20ea457 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/vm/VersionViewModel.kt @@ -0,0 +1,38 @@ +package com.casic.smart.town.sanxi.vm + +import androidx.lifecycle.MutableLiveData +import com.casic.smart.town.sanxi.base.BaseApplication +import com.casic.smart.town.sanxi.extensions.separateResponseCode +import com.casic.smart.town.sanxi.extensions.toErrorMessage +import com.casic.smart.town.sanxi.model.VersionResultModel +import com.casic.smart.town.sanxi.util.retrofit.RetrofitServiceManager +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.launch +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.vm.BaseViewModel +import com.pengxh.kt.lite.vm.LoadState + +class VersionViewModel : BaseViewModel() { + + private val gson = Gson() + val versionResultModel = MutableLiveData() + + fun updateVersion() = launch({ + loadState.value = LoadState.Loading + val response = RetrofitServiceManager.updateVersion() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + loadState.value = LoadState.Success + versionResultModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ).data + } else { + loadState.value = LoadState.Fail + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + loadState.value = LoadState.Fail + it.printStackTrace() + }) +} \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_solid_layout_gray_radius_5.xml b/app/src/main/res/drawable/bg_solid_layout_gray_radius_5.xml new file mode 100644 index 0000000..96eb93e --- /dev/null +++ b/app/src/main/res/drawable/bg_solid_layout_gray_radius_5.xml @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/button_main_selector.xml b/app/src/main/res/drawable/button_main_selector.xml new file mode 100644 index 0000000..5875998 --- /dev/null +++ b/app/src/main/res/drawable/button_main_selector.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1df917d..8d4f33b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -6,6 +6,7 @@ + @@ -32,11 +33,14 @@ + + + + + + + diff --git a/app/src/main/assets/svg/mine.svg b/app/src/main/assets/svg/mine.svg new file mode 100644 index 0000000..3fbb0a3 --- /dev/null +++ b/app/src/main/assets/svg/mine.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt b/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt index d730417..b1972c2 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt @@ -1,8 +1,10 @@ package com.casic.smart.town.sanxi.extensions import com.casic.smart.town.sanxi.model.ErrorMessageModel +import com.casic.smart.town.sanxi.util.LocaleConstant import com.google.gson.Gson import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.utils.SaveKeyValues import org.json.JSONObject fun String.separateResponseCode(): Int { @@ -17,4 +19,15 @@ this, object : TypeToken() {}.type ) return errorModel.message.toString() +} + +/** + * 下载路径为 http://xx.com/static/ 拼接downloadUrl + * */ +fun String.appendDownloadUrl(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + LocaleConstant.DEFAULT_SERVER_CONFIG, LocaleConstant.DEFAULT_SERVER + ) as String + return "$defaultValue/static/${this}" } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt b/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt new file mode 100644 index 0000000..1809dc5 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt @@ -0,0 +1,288 @@ +package com.casic.smart.town.sanxi.fragment + +import android.app.ProgressDialog +import android.content.Intent +import android.net.Uri +import android.os.Build +import android.os.CountDownTimer +import androidx.core.content.FileProvider +import androidx.lifecycle.ViewModelProvider +import com.casic.smart.town.sanxi.BuildConfig +import com.casic.smart.town.sanxi.R +import com.casic.smart.town.sanxi.extensions.appendDownloadUrl +import com.casic.smart.town.sanxi.model.UserDetailModel +import com.casic.smart.town.sanxi.util.AuthenticationHelper +import com.casic.smart.town.sanxi.util.LoadingDialogHub +import com.casic.smart.town.sanxi.util.LocaleConstant +import com.casic.smart.town.sanxi.util.RSAUtils +import com.casic.smart.town.sanxi.view.AboutUsActivity +import com.casic.smart.town.sanxi.view.LoginActivity +import com.casic.smart.town.sanxi.vm.LoginViewModel +import com.casic.smart.town.sanxi.vm.UserViewModel +import com.casic.smart.town.sanxi.vm.VersionViewModel +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.base.KotlinBaseFragment +import com.pengxh.kt.lite.callback.OnDownloadListener +import com.pengxh.kt.lite.extensions.* +import com.pengxh.kt.lite.utils.PageNavigationManager +import com.pengxh.kt.lite.utils.SaveKeyValues +import com.pengxh.kt.lite.vm.LoadState +import com.pengxh.kt.lite.widget.dialog.AlertControlDialog +import com.pengxh.kt.lite.widget.dialog.ChangePasswordDialog +import kotlinx.android.synthetic.main.fragment_mine.* +import java.io.File +import java.nio.charset.StandardCharsets + +class MinePageFragment : KotlinBaseFragment() { + + private lateinit var userData: UserDetailModel.Data + private lateinit var userViewModel: UserViewModel + private lateinit var loginViewModel: LoginViewModel + private lateinit var versionViewModel: VersionViewModel + private lateinit var progressDialog: ProgressDialog + + override fun initLayoutView(): Int = R.layout.fragment_mine + + override fun setupTopBarLayout() { + + } + + override fun initData() { + userViewModel = ViewModelProvider(this)[UserViewModel::class.java] + loginViewModel = ViewModelProvider(this)[LoginViewModel::class.java] + versionViewModel = ViewModelProvider(this)[VersionViewModel::class.java] + + //初始化下载对话框 + progressDialog = ProgressDialog(requireContext()) + progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL) + progressDialog.setProgressDrawable(resources.getDrawable(R.drawable.download_progress)) + progressDialog.setCanceledOnTouchOutside(false) + progressDialog.setCancelable(false) + + /** + * 数据监听 + * */ + dataObserve() + } + + override fun observeRequestState() { + userViewModel.loadState.observe(this, { + when (it) { + is LoadState.Loading -> LoadingDialogHub.show(requireActivity(), "修改中,请稍后") + is LoadState.Success -> { + "修改成功,请重新登录".show(requireContext()) + LoadingDialogHub.dismiss() + AuthenticationHelper.removeToken() + requireContext().navigatePageTo() + PageNavigationManager.finishAllActivity() + } + else -> LoadingDialogHub.dismiss() + } + }) + + versionViewModel.loadState.observe(this, { + when (it) { + is LoadState.Loading -> LoadingDialogHub.show(requireActivity(), "检查版本中,请稍后") + else -> LoadingDialogHub.dismiss() + } + }) + } + + override fun initEvent() { + userImageView.setOnClickListener { + "尽情期待~".show(requireContext()) + } + + updateUserButton.setOnClickListener { + LoadingDialogHub.show(requireActivity(), "同步中,请稍后...") + userViewModel.obtainUserDetail() + } + + changePwdLayout.setOnClickListener { + ChangePasswordDialog.Builder() + .setContext(requireContext()) + .setOnDialogButtonClickListener(object : + ChangePasswordDialog.OnDialogButtonClickListener { + override fun onConfirmClick(oldPwd: String, newPwd: String) { + val publicKey = + RSAUtils.keyStrToPublicKey(AuthenticationHelper.publicKey)!! + val oldPassKey = RSAUtils.encryptDataByPublicKey( + oldPwd.toByteArray(StandardCharsets.UTF_8), + publicKey + ) + val newPassKey = RSAUtils.encryptDataByPublicKey( + newPwd.toByteArray(StandardCharsets.UTF_8), + publicKey + ) + userViewModel.changePassword(oldPassKey, newPassKey) + } + }).build().show() + } + + aboutUsLayout.setOnClickListener { + requireContext().navigatePageTo() + } + + updateVersionLayout.setOnClickListener { + versionViewModel.updateVersion() + } + + clearCacheLayout.setOnClickListener { + //删除缓存之后在设置缓存大小 + LoadingDialogHub.show(requireActivity(), "清理中,请稍后") + File(requireContext().cacheDir.path).deleteFile() + object : CountDownTimer(1500, 500) { + override fun onTick(millisUntilFinished: Long) { + + } + + override fun onFinish() { + LoadingDialogHub.dismiss() + cacheSizeView.text = collectApplicationCache().formatFileSize() + } + }.start() + } + + loginOutButton.setOnClickListener { + AlertControlDialog.Builder() + .setContext(requireContext()) + .setTitle("退出登录") + .setMessage("确定要退出吗?") + .setNegativeButton("取消") + .setPositiveButton("确定") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + loginViewModel.out() + } + + override fun onCancelClick() {} + }).build().show() + } + } + + private fun dataObserve() { + userViewModel.userDetailModel.observe(this, { + if (it.code == 200) { + LoadingDialogHub.dismiss() + "同步完成".show(requireContext()) + userData = it.data + updateUserInfo() + } + }) + + versionViewModel.versionResultModel.observe(this, { + if (BuildConfig.VERSION_NAME == it.version) { + "已是最新版本,无需更新".show(requireContext()) + } else { + AlertControlDialog.Builder() + .setContext(requireContext()) + .setTitle("提示") + .setMessage("有新版本,是否更新?") + .setNegativeButton("稍后再说") + .setPositiveButton("立即下载") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + downloadApk(it.downloadUrl) + } + + override fun onCancelClick() { + + } + }).build().show() + } + }) + + loginViewModel.outResultModel.observe(this, { + if (it.code == 200) { + AuthenticationHelper.removeToken() + requireContext().navigatePageTo() + PageNavigationManager.finishAllActivity() + } + }) + } + + private fun collectApplicationCache(): Long { + return File(requireContext().cacheDir.path).calculateSize() + } + + override fun onResume() { + val userDetailJson = SaveKeyValues.getValue(LocaleConstant.USER_DETAIL_MODEL, "") as String + if (userDetailJson.isNotBlank()) { + userData = Gson().fromJson( + userDetailJson, object : TypeToken() {}.type + ) + updateUserInfo() + } + //缓存 + cacheSizeView.text = collectApplicationCache().formatFileSize() + super.onResume() + } + + private fun updateUserInfo() { + //设置头像,圆形,暂时是默认的 +// val roundDrawable = +// BitmapFactory.decodeResource(requireContext().resources, R.mipmap.login_casic) +// .createRoundDrawable( +// requireContext(), +// 3f.dp2px(requireContext()), +// R.color.mainThemeColor.convertColor(requireContext()) +// ) +// userImageView.setImageDrawable(roundDrawable) + userNameView.text = userData.name + userPhoneView.text = String.format("电话:${userData.phone}") + userDeptView.text = String.format("部门:${userData.deptName}") + } + + private fun downloadApk(url: String?) { + progressDialog.setMessage("下载新版本中...") + progressDialog.show() + if (url.toString().isBlank()) { + "抱歉,版本下载失败".show(requireContext()) + return + } + /** + * http://111.198.10.15:11304/static/apk/1.0.1.apk + * */ + val downloadPath = url!!.appendDownloadUrl() + //开始下载 + downloadPath.downloadFile(requireContext().createDownloadFileDir().toString(), + object : OnDownloadListener { + override fun onDownloadStart(totalBytes: Long) { + progressDialog.max = totalBytes.toInt() + } + + override fun onProgressChanged(currentBytes: Long) { + progressDialog.progress = currentBytes.toInt() + } + + override fun onDownloadEnd(file: File?) { + progressDialog.dismiss() + progressDialog.progress = 0 + //安装APK + installApk(file) + } + }) + } + + private fun installApk(apkPackage: File?) { + if (apkPackage == null) { + "文件异常,无法安装".show(requireContext()) + return + } + val intent = Intent(Intent.ACTION_VIEW) + val data: Uri + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { //判断版本大于等于7.0 + data = FileProvider.getUriForFile( + requireContext(), LocaleConstant.APP_AUTHORITY, apkPackage + ) + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) // 给目标应用一个临时授权 + } else { + data = Uri.fromFile(apkPackage) + } + intent.setDataAndType(data, "application/vnd.android.package-archive") + requireContext().startActivity(intent) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java new file mode 100644 index 0000000..467e33d --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java @@ -0,0 +1,43 @@ +package com.casic.smart.town.sanxi.model; + +/** + * 普通实体类,失败/成功数据结构一致 + */ +public class CommonResultModel { + private int code; + private String data; + private String message; + private boolean isSuccess; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return isSuccess; + } + + public void setSuccess(boolean success) { + isSuccess = success; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java new file mode 100644 index 0000000..9bdabef --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java @@ -0,0 +1,62 @@ +package com.casic.smart.town.sanxi.model; + +public class LoginResultModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private String kaptcha; + private String token; + + public String getKaptcha() { + return kaptcha; + } + + public void setKaptcha(String kaptcha) { + this.kaptcha = kaptcha; + } + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java new file mode 100644 index 0000000..09f8d59 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java @@ -0,0 +1,80 @@ +package com.casic.smart.town.sanxi.model; + +public class PublicKeyModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private boolean appKaptcha; + private boolean kaptcha; + private String publicKey; + private String sid; + + public boolean isAppKaptcha() { + return appKaptcha; + } + + public void setAppKaptcha(boolean appKaptcha) { + this.appKaptcha = appKaptcha; + } + + public boolean isKaptcha() { + return kaptcha; + } + + public void setKaptcha(boolean kaptcha) { + this.kaptcha = kaptcha; + } + + public String getPublicKey() { + return publicKey; + } + + public void setPublicKey(String publicKey) { + this.publicKey = publicKey; + } + + public String getSid() { + return sid; + } + + public void setSid(String sid) { + this.sid = sid; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java new file mode 100644 index 0000000..db577fb --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java @@ -0,0 +1,227 @@ +package com.casic.smart.town.sanxi.model; + + +import java.util.List; + +public class UserDetailModel { + private int code; + private Data data; + private String message; + private boolean success; + + public void setCode(int code) { + this.code = code; + } + + public int getCode() { + return code; + } + + public void setData(Data data) { + this.data = data; + } + + public Data getData() { + return data; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getMessage() { + return message; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public boolean getSuccess() { + return success; + } + + public static class Data { + + private String account; + private String attr1; + private String avatar; + private String bizData; + private List dataScope; + private String deptId; + private String deptName; + private List devices; + private String id; + private String ipAddr; + private String name; + private String phone; + private List roleList; + private List roleNames; + private List roleTips; + private String scopeType; + private String sysData; + private String targetId; + private String targetName; + private String tenantId; + + public void setAccount(String account) { + this.account = account; + } + + public String getAccount() { + return account; + } + + public void setAttr1(String attr1) { + this.attr1 = attr1; + } + + public String getAttr1() { + return attr1; + } + + public void setAvatar(String avatar) { + this.avatar = avatar; + } + + public String getAvatar() { + return avatar; + } + + public void setBizData(String bizData) { + this.bizData = bizData; + } + + public String getBizData() { + return bizData; + } + + public void setDataScope(List dataScope) { + this.dataScope = dataScope; + } + + public List getDataScope() { + return dataScope; + } + + public void setDeptId(String deptId) { + this.deptId = deptId; + } + + public String getDeptId() { + return deptId; + } + + public void setDeptName(String deptName) { + this.deptName = deptName; + } + + public String getDeptName() { + return deptName; + } + + public void setDevices(List devices) { + this.devices = devices; + } + + public List getDevices() { + return devices; + } + + public void setId(String id) { + this.id = id; + } + + public String getId() { + return id; + } + + public void setIpAddr(String ipAddr) { + this.ipAddr = ipAddr; + } + + public String getIpAddr() { + return ipAddr; + } + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setPhone(String phone) { + this.phone = phone; + } + + public String getPhone() { + return phone; + } + + public void setRoleList(List roleList) { + this.roleList = roleList; + } + + public List getRoleList() { + return roleList; + } + + public void setRoleNames(List roleNames) { + this.roleNames = roleNames; + } + + public List getRoleNames() { + return roleNames; + } + + public void setRoleTips(List roleTips) { + this.roleTips = roleTips; + } + + public List getRoleTips() { + return roleTips; + } + + public void setScopeType(String scopeType) { + this.scopeType = scopeType; + } + + public String getScopeType() { + return scopeType; + } + + public void setSysData(String sysData) { + this.sysData = sysData; + } + + public String getSysData() { + return sysData; + } + + public void setTargetId(String targetId) { + this.targetId = targetId; + } + + public String getTargetId() { + return targetId; + } + + public void setTargetName(String targetName) { + this.targetName = targetName; + } + + public String getTargetName() { + return targetName; + } + + public void setTenantId(String tenantId) { + this.tenantId = tenantId; + } + + public String getTenantId() { + return tenantId; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/VersionResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/VersionResultModel.java new file mode 100644 index 0000000..79374e0 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/VersionResultModel.java @@ -0,0 +1,61 @@ +package com.casic.smart.town.sanxi.model; + +public class VersionResultModel { + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private String downloadUrl; + private String version; + + public String getDownloadUrl() { + return downloadUrl; + } + + public void setDownloadUrl(String downloadUrl) { + this.downloadUrl = downloadUrl; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/AuthenticationHelper.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/AuthenticationHelper.kt new file mode 100644 index 0000000..f498e4e --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/AuthenticationHelper.kt @@ -0,0 +1,24 @@ +package com.casic.smart.town.sanxi.util + +import com.pengxh.kt.lite.utils.SaveKeyValues + +object AuthenticationHelper { + + fun savePublicKey(key: String) { + SaveKeyValues.putValue("keyString", key) + } + + val publicKey: String? + get() = SaveKeyValues.getValue("keyString", "") as String? + + fun saveToken(token: String?) { + SaveKeyValues.putValue("token", token!!) + } + + val token: String? + get() = SaveKeyValues.getValue("token", "") as String? + + fun removeToken() { + SaveKeyValues.removeKey("token") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt index ab1a36d..99f0d19 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt @@ -14,5 +14,9 @@ const val PAGE_LIMIT = 20 const val DEFAULT_SERVER_CONFIG = "defaultServerConfig" - const val DEFAULT_SERVER = "http://111.198.10.15:11308" + const val DEFAULT_SERVER = "http://36.133.189.112:8082" + const val APP_AUTHORITY = "com.casic.smart.town.sanxi.fileprovider" + const val ACCOUNT = "account" + const val PASSWORD = "password" + const val USER_DETAIL_MODEL = "userDetailModel" } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/RSAUtils.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/RSAUtils.kt new file mode 100644 index 0000000..b9216ba --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/RSAUtils.kt @@ -0,0 +1,55 @@ +package com.casic.smart.town.sanxi.util + +import android.util.Base64 +import java.security.* +import java.security.spec.InvalidKeySpecException +import java.security.spec.X509EncodedKeySpec +import javax.crypto.BadPaddingException +import javax.crypto.Cipher +import javax.crypto.IllegalBlockSizeException +import javax.crypto.NoSuchPaddingException + +object RSAUtils { + //构建Cipher实例时所传入的的字符串,默认为"RSA/NONE/PKCS1Padding" + private fun processData(srcData: ByteArray, key: Key): ByteArray? { //用来保存处理结果 + var resultBytes: ByteArray? = null + try { //获取Cipher实例 + val cipher = Cipher.getInstance("RSA/None/PKCS1Padding") + //初始化Cipher,mode指定是加密还是解密,key为公钥或私钥 + cipher.init(Cipher.ENCRYPT_MODE, key) + //处理数据 + resultBytes = cipher.doFinal(srcData) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: NoSuchPaddingException) { + e.printStackTrace() + } catch (e: InvalidKeyException) { + e.printStackTrace() + } catch (e: BadPaddingException) { + e.printStackTrace() + } catch (e: IllegalBlockSizeException) { + e.printStackTrace() + } + return resultBytes + } + + fun encryptDataByPublicKey(srcData: ByteArray, publicKey: PublicKey): String { + val resultBytes = processData(srcData, publicKey) + return Base64.encodeToString(resultBytes, Base64.DEFAULT) + } + + fun keyStrToPublicKey(publicKeyStr: String?): PublicKey? { + var publicKey: PublicKey? = null + val keyBytes = Base64.decode(publicKeyStr, Base64.DEFAULT) + val keySpec = X509EncodedKeySpec(keyBytes) + try { + val keyFactory = KeyFactory.getInstance("RSA") + publicKey = keyFactory.generatePublic(keySpec) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: InvalidKeySpecException) { + e.printStackTrace() + } + return publicKey + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt index 63db0ec..50d1742 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt @@ -1,12 +1,64 @@ package com.casic.smart.town.sanxi.util.retrofit -import retrofit2.http.GET +import retrofit2.http.* interface RetrofitService { + /** + * PublicKey校验 + */ + @GET("/config/baseConfig") + suspend fun obtainPublicKey(): String + + /** + * 登录并获取Token + * + * @param sid + * @param account 用户名 + * @param secretKey 加密后的密码 + */ + @FormUrlEncoded + @POST("/user/login") + suspend fun login( + @Field("sid") sid: String, + @Field("username") account: String, + @Field("password") secretKey: String + ): String + + /** + * 退出登录 + */ + @GET("/user/logout") + suspend fun loginOut(@Header("token") token: String): String + + /** + * 获取用户信息 + */ + @GET("/user/info") + suspend fun obtainUserDetail(@Header("token") token: String): String + + /** + * 修改密码 + * + * @param oldPwd 旧密码 + * @param newPwd 新密码 + */ + @FormUrlEncoded + @POST("/mgr/changePwd") + suspend fun changePassword( + @Header("token") token: String, + @Field("oldPwd") oldPwd: String, + @Field("newPwd") newPwd: String + ): String /** * 根据布防状态统计闸井数量接口 */ @GET("/well/countByBfzt") - suspend fun countWellByState(): String + suspend fun countWellByState(@Header("token") token: String): String + + /** + * 更新APK版本 + */ + @POST("/app/checkVersion") + suspend fun obtainVersionResult(@Header("token") token: String): String } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt index 46bbf4b..410444b 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt @@ -1,5 +1,6 @@ package com.casic.smart.town.sanxi.util.retrofit +import com.casic.smart.town.sanxi.util.AuthenticationHelper import com.casic.smart.town.sanxi.util.LocaleConstant import com.pengxh.kt.lite.utils.RetrofitFactory import com.pengxh.kt.lite.utils.SaveKeyValues @@ -15,9 +16,52 @@ } /** + * 验证PublicKey + */ + suspend fun authenticate(): String { + return api.obtainPublicKey() + } + + /** + * 登录并获取Token + */ + suspend fun login(sid: String, account: String, secretKey: String): String { + return api.login(sid, account, secretKey) + } + + /** + * 退出登录 + */ + suspend fun loginOut(): String { + return api.loginOut(AuthenticationHelper.token!!) + } + + /** + * 获取用户信息 + */ + suspend fun obtainUserDetail(): String { + return api.obtainUserDetail(AuthenticationHelper.token!!) + } + + /** + * 修改密码 + */ + suspend fun changePassword(oldPwd: String, newPwd: String): String { + return api.changePassword(AuthenticationHelper.token!!, oldPwd, newPwd) + } + + /** * 根据布防状态统计闸井数量接口 */ suspend fun countWellByState(): String { - return api.countWellByState() + return api.countWellByState(AuthenticationHelper.token!!) } + + /** + * 更新APK版本 + */ + suspend fun updateVersion(): String { + return api.obtainVersionResult(AuthenticationHelper.token!!) + } + } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/AboutUsActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/AboutUsActivity.kt new file mode 100644 index 0000000..7e43c47 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/AboutUsActivity.kt @@ -0,0 +1,34 @@ +package com.casic.smart.town.sanxi.view + +import com.casic.smart.town.sanxi.BuildConfig +import com.casic.smart.town.sanxi.R +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.kt.lite.base.KotlinBaseActivity +import com.pengxh.kt.lite.extensions.convertColor +import com.pengxh.kt.lite.utils.ImmerseStatusBarUtil +import kotlinx.android.synthetic.main.activity_about_us.* +import kotlinx.android.synthetic.main.include_base_title.* + +class AboutUsActivity : KotlinBaseActivity() { + + override fun initLayoutView(): Int = R.layout.activity_about_us + + override fun setupTopBarLayout() { + ImmersionBar.with(this).statusBarDarkFont(false).init() + ImmerseStatusBarUtil.setColor(this, R.color.mainThemeColor.convertColor(this)) + titleView.text = "关于我们" + leftBackView.setOnClickListener { finish() } + } + + override fun initData() { + versionCodeView.text = String.format("版本信息:V${BuildConfig.VERSION_NAME}") + } + + override fun observeRequestState() { + + } + + override fun initEvent() { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/LoginActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/LoginActivity.kt new file mode 100644 index 0000000..b5d1d50 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/LoginActivity.kt @@ -0,0 +1,97 @@ +package com.casic.smart.town.sanxi.view + +import androidx.lifecycle.ViewModelProvider +import com.casic.smart.town.sanxi.R +import com.casic.smart.town.sanxi.util.AuthenticationHelper +import com.casic.smart.town.sanxi.util.LoadingDialogHub +import com.casic.smart.town.sanxi.util.LocaleConstant +import com.casic.smart.town.sanxi.util.RSAUtils +import com.casic.smart.town.sanxi.vm.AuthenticateViewModel +import com.casic.smart.town.sanxi.vm.LoginViewModel +import com.casic.smart.town.sanxi.vm.UserViewModel +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.kt.lite.base.KotlinBaseActivity +import com.pengxh.kt.lite.extensions.navigatePageTo +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.utils.SaveKeyValues +import kotlinx.android.synthetic.main.activity_login.* + +class LoginActivity : KotlinBaseActivity() { + + private lateinit var authenticateViewModel: AuthenticateViewModel + private lateinit var loginViewModel: LoginViewModel + private lateinit var userViewModel: UserViewModel + + override fun initLayoutView(): Int = R.layout.activity_login + + override fun setupTopBarLayout() { + ImmersionBar.with(this).statusBarDarkFont(true).init() + } + + override fun initData() { + // 设置默认账号密码 + userNameView.setText(SaveKeyValues.getValue(LocaleConstant.ACCOUNT, "") as String) + userPasswordView.setText(SaveKeyValues.getValue(LocaleConstant.PASSWORD, "") as String) + authenticateViewModel = ViewModelProvider(this)[AuthenticateViewModel::class.java] + loginViewModel = ViewModelProvider(this)[LoginViewModel::class.java] + userViewModel = ViewModelProvider(this)[UserViewModel::class.java] + + authenticateViewModel.keyModel.observe(this, { + if (it.code == 200) {//用code判断,别的判断可能有坑 + val keyString = it.data!!.publicKey!! + /** + * 修改密码需要用到key,先存着 + * */ + AuthenticationHelper.savePublicKey(keyString) + val publicKey = RSAUtils.keyStrToPublicKey(keyString) + + val account = userNameView.text.toString() + val userPassword = userPasswordView.text.toString() + val dataByPublicKey = + RSAUtils.encryptDataByPublicKey(userPassword.toByteArray(), publicKey!!) + //登录并获取Token,POST请求 + loginViewModel.enter(it.data!!.sid!!, account, dataByPublicKey) + loginViewModel.enterResultModel.observe(this, { loginResult -> + if (loginResult.code == 200) { + AuthenticationHelper.saveToken(loginResult.data!!.token!!) + /** + * 获取token之后保存用户信息 + * */ + userViewModel.obtainUserDetail() + //验证成功登录 + navigatePageTo() + finish() + } + }) + } + }) + } + + override fun observeRequestState() { + authenticateViewModel.loadState.observe(this, { + LoadingDialogHub.show(this, "登录中,请稍后") + }) + + loginViewModel.loadState.observe(this, { + LoadingDialogHub.dismiss() + }) + } + + override fun initEvent() { + loginButton.setOnClickListener { + val account = userNameView.text.toString().trim() + val userPassword = userPasswordView.text.toString().trim() + if (account.isBlank()) { + "用户名不能为空".show(this) + return@setOnClickListener + } + if (userPassword.isBlank()) { + "密码不能为空".show(this) + return@setOnClickListener + } + SaveKeyValues.putValue(LocaleConstant.ACCOUNT, account) + SaveKeyValues.putValue(LocaleConstant.PASSWORD, userPassword) + authenticateViewModel.obtainPublicKey() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt index 3bea1c9..a4fa951 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt @@ -7,6 +7,7 @@ import com.casic.smart.town.sanxi.R import com.casic.smart.town.sanxi.adapter.ViewPagerAdapter import com.casic.smart.town.sanxi.fragment.AlarmPageFragment +import com.casic.smart.town.sanxi.fragment.MinePageFragment import com.casic.smart.town.sanxi.fragment.MonitorPageFragment import com.casic.smart.town.sanxi.fragment.StatisticsPageFragment import com.gyf.immersionbar.ImmersionBar @@ -33,6 +34,7 @@ fragmentPages.add(StatisticsPageFragment()) fragmentPages.add(MonitorPageFragment()) fragmentPages.add(AlarmPageFragment()) + fragmentPages.add(MinePageFragment()) } override fun initLayoutView(): Int = R.layout.activity_main @@ -68,6 +70,9 @@ R.id.nav_alarm -> { mainViewPager.currentItem = 2 } + R.id.nav_more -> { + mainViewPager.currentItem = 3 + } } false } diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/PermissionActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/PermissionActivity.kt index c478d83..3238f3c 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/view/PermissionActivity.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/PermissionActivity.kt @@ -32,7 +32,7 @@ //先把导航隐私政策声明,后面导航会用到 MapsInitializer.updatePrivacyAgree(this, true) MapsInitializer.updatePrivacyShow(this, true, true) - this.navigatePageTo() + this.navigatePageTo() finish() } diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/SplashScreenActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/SplashScreenActivity.kt new file mode 100644 index 0000000..fa5a74d --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/SplashScreenActivity.kt @@ -0,0 +1,54 @@ +package com.casic.smart.town.sanxi.view + +import android.annotation.SuppressLint +import android.os.CountDownTimer +import androidx.lifecycle.ViewModelProvider +import com.casic.smart.town.sanxi.R +import com.casic.smart.town.sanxi.vm.UserViewModel +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.kt.lite.base.KotlinBaseActivity +import com.pengxh.kt.lite.extensions.navigatePageTo + +@SuppressLint("CustomSplashScreen") +class SplashScreenActivity : KotlinBaseActivity() { + + private lateinit var userDetailViewModel: UserViewModel + private val countDownTimer = object : CountDownTimer(1000, 500) { + override fun onFinish() { + /** + * 获取token之后保存用户信息 + * */ + userDetailViewModel.obtainUserDetail() + userDetailViewModel.userDetailModel.observe(this@SplashScreenActivity, { + if (it.code == 200) { + navigatePageTo() + } else { + navigatePageTo() + } + finish() + }) + } + + override fun onTick(millisUntilFinished: Long) { + + } + } + + override fun initLayoutView(): Int = R.layout.activity_splash + + override fun setupTopBarLayout() { + ImmersionBar.with(this).statusBarDarkFont(false).init() + } + + override fun initData() { + userDetailViewModel = ViewModelProvider(this).get(UserViewModel::class.java) + } + + override fun observeRequestState() { + + } + + override fun initEvent() { + countDownTimer.start() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/vm/AuthenticateViewModel.kt b/app/src/main/java/com/casic/smart/town/sanxi/vm/AuthenticateViewModel.kt new file mode 100644 index 0000000..87ddbf8 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/vm/AuthenticateViewModel.kt @@ -0,0 +1,35 @@ +package com.casic.smart.town.sanxi.vm + +import androidx.lifecycle.MutableLiveData +import com.casic.smart.town.sanxi.base.BaseApplication +import com.casic.smart.town.sanxi.extensions.separateResponseCode +import com.casic.smart.town.sanxi.extensions.toErrorMessage +import com.casic.smart.town.sanxi.model.PublicKeyModel +import com.casic.smart.town.sanxi.util.retrofit.RetrofitServiceManager +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.launch +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.vm.BaseViewModel +import com.pengxh.kt.lite.vm.LoadState + +class AuthenticateViewModel : BaseViewModel() { + + private val gson = Gson() + val keyModel = MutableLiveData() + + fun obtainPublicKey() = launch({ + loadState.value = LoadState.Loading + val response = RetrofitServiceManager.authenticate() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + keyModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ) + } else { + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + it.printStackTrace() + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/vm/LoginViewModel.kt b/app/src/main/java/com/casic/smart/town/sanxi/vm/LoginViewModel.kt new file mode 100644 index 0000000..c377c61 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/vm/LoginViewModel.kt @@ -0,0 +1,54 @@ +package com.casic.smart.town.sanxi.vm + +import androidx.lifecycle.MutableLiveData +import com.casic.smart.town.sanxi.base.BaseApplication +import com.casic.smart.town.sanxi.extensions.separateResponseCode +import com.casic.smart.town.sanxi.extensions.toErrorMessage +import com.casic.smart.town.sanxi.model.CommonResultModel +import com.casic.smart.town.sanxi.model.LoginResultModel +import com.casic.smart.town.sanxi.util.retrofit.RetrofitServiceManager +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.launch +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.vm.BaseViewModel +import com.pengxh.kt.lite.vm.LoadState + +class LoginViewModel : BaseViewModel() { + + private val gson = Gson() + val enterResultModel = MutableLiveData() + val outResultModel = MutableLiveData() + + fun enter(sid: String, account: String, secretKey: String) = launch({ + val response = RetrofitServiceManager.login(sid, account, secretKey) + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + loadState.value = LoadState.Success + "登录成功".show(BaseApplication.obtainInstance()) + enterResultModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ) + } else { + loadState.value = LoadState.Fail + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + loadState.value = LoadState.Fail + it.printStackTrace() + }) + + fun out() = launch({ + val response = RetrofitServiceManager.loginOut() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + outResultModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ) + } else { + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + it.printStackTrace() + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/vm/UserViewModel.kt b/app/src/main/java/com/casic/smart/town/sanxi/vm/UserViewModel.kt new file mode 100644 index 0000000..29a8756 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/vm/UserViewModel.kt @@ -0,0 +1,57 @@ +package com.casic.smart.town.sanxi.vm + +import androidx.lifecycle.MutableLiveData +import com.casic.smart.town.sanxi.base.BaseApplication +import com.casic.smart.town.sanxi.extensions.separateResponseCode +import com.casic.smart.town.sanxi.extensions.toErrorMessage +import com.casic.smart.town.sanxi.model.UserDetailModel +import com.casic.smart.town.sanxi.util.LocaleConstant +import com.casic.smart.town.sanxi.util.retrofit.RetrofitServiceManager +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.launch +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.utils.SaveKeyValues +import com.pengxh.kt.lite.vm.BaseViewModel +import com.pengxh.kt.lite.vm.LoadState + +class UserViewModel : BaseViewModel() { + + private val gson = Gson() + val userDetailModel = MutableLiveData() + + fun obtainUserDetail() = launch({ + val response = RetrofitServiceManager.obtainUserDetail() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + val userDetail = gson.fromJson( + response, object : TypeToken() {}.type + ) + userDetailModel.value = userDetail + SaveKeyValues.putValue(LocaleConstant.USER_DETAIL_MODEL, gson.toJson(userDetail.data)) + } else { + val errorModel = UserDetailModel() + errorModel.code = responseCode + userDetailModel.value = errorModel + //如果此次获取不到用户信息,那么就清空之前的用户缓存,然后让用户重新登录 + SaveKeyValues.removeKey(LocaleConstant.USER_DETAIL_MODEL) + } + }, { + it.printStackTrace() + }) + + fun changePassword(oldPwd: String, newPwd: String) = launch({ + loadState.value = LoadState.Loading + val response = RetrofitServiceManager.changePassword(oldPwd, newPwd) + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + loadState.value = LoadState.Success + } else { + loadState.value = LoadState.Fail + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + loadState.value = LoadState.Fail + it.printStackTrace() + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/vm/VersionViewModel.kt b/app/src/main/java/com/casic/smart/town/sanxi/vm/VersionViewModel.kt new file mode 100644 index 0000000..20ea457 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/vm/VersionViewModel.kt @@ -0,0 +1,38 @@ +package com.casic.smart.town.sanxi.vm + +import androidx.lifecycle.MutableLiveData +import com.casic.smart.town.sanxi.base.BaseApplication +import com.casic.smart.town.sanxi.extensions.separateResponseCode +import com.casic.smart.town.sanxi.extensions.toErrorMessage +import com.casic.smart.town.sanxi.model.VersionResultModel +import com.casic.smart.town.sanxi.util.retrofit.RetrofitServiceManager +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.launch +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.vm.BaseViewModel +import com.pengxh.kt.lite.vm.LoadState + +class VersionViewModel : BaseViewModel() { + + private val gson = Gson() + val versionResultModel = MutableLiveData() + + fun updateVersion() = launch({ + loadState.value = LoadState.Loading + val response = RetrofitServiceManager.updateVersion() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + loadState.value = LoadState.Success + versionResultModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ).data + } else { + loadState.value = LoadState.Fail + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + loadState.value = LoadState.Fail + it.printStackTrace() + }) +} \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_solid_layout_gray_radius_5.xml b/app/src/main/res/drawable/bg_solid_layout_gray_radius_5.xml new file mode 100644 index 0000000..96eb93e --- /dev/null +++ b/app/src/main/res/drawable/bg_solid_layout_gray_radius_5.xml @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/button_main_selector.xml b/app/src/main/res/drawable/button_main_selector.xml new file mode 100644 index 0000000..5875998 --- /dev/null +++ b/app/src/main/res/drawable/button_main_selector.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/button_white_selector.xml b/app/src/main/res/drawable/button_white_selector.xml new file mode 100644 index 0000000..4dd3c0b --- /dev/null +++ b/app/src/main/res/drawable/button_white_selector.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1df917d..8d4f33b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -6,6 +6,7 @@ + @@ -32,11 +33,14 @@ + + + + + + + diff --git a/app/src/main/assets/svg/mine.svg b/app/src/main/assets/svg/mine.svg new file mode 100644 index 0000000..3fbb0a3 --- /dev/null +++ b/app/src/main/assets/svg/mine.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt b/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt index d730417..b1972c2 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt @@ -1,8 +1,10 @@ package com.casic.smart.town.sanxi.extensions import com.casic.smart.town.sanxi.model.ErrorMessageModel +import com.casic.smart.town.sanxi.util.LocaleConstant import com.google.gson.Gson import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.utils.SaveKeyValues import org.json.JSONObject fun String.separateResponseCode(): Int { @@ -17,4 +19,15 @@ this, object : TypeToken() {}.type ) return errorModel.message.toString() +} + +/** + * 下载路径为 http://xx.com/static/ 拼接downloadUrl + * */ +fun String.appendDownloadUrl(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + LocaleConstant.DEFAULT_SERVER_CONFIG, LocaleConstant.DEFAULT_SERVER + ) as String + return "$defaultValue/static/${this}" } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt b/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt new file mode 100644 index 0000000..1809dc5 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt @@ -0,0 +1,288 @@ +package com.casic.smart.town.sanxi.fragment + +import android.app.ProgressDialog +import android.content.Intent +import android.net.Uri +import android.os.Build +import android.os.CountDownTimer +import androidx.core.content.FileProvider +import androidx.lifecycle.ViewModelProvider +import com.casic.smart.town.sanxi.BuildConfig +import com.casic.smart.town.sanxi.R +import com.casic.smart.town.sanxi.extensions.appendDownloadUrl +import com.casic.smart.town.sanxi.model.UserDetailModel +import com.casic.smart.town.sanxi.util.AuthenticationHelper +import com.casic.smart.town.sanxi.util.LoadingDialogHub +import com.casic.smart.town.sanxi.util.LocaleConstant +import com.casic.smart.town.sanxi.util.RSAUtils +import com.casic.smart.town.sanxi.view.AboutUsActivity +import com.casic.smart.town.sanxi.view.LoginActivity +import com.casic.smart.town.sanxi.vm.LoginViewModel +import com.casic.smart.town.sanxi.vm.UserViewModel +import com.casic.smart.town.sanxi.vm.VersionViewModel +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.base.KotlinBaseFragment +import com.pengxh.kt.lite.callback.OnDownloadListener +import com.pengxh.kt.lite.extensions.* +import com.pengxh.kt.lite.utils.PageNavigationManager +import com.pengxh.kt.lite.utils.SaveKeyValues +import com.pengxh.kt.lite.vm.LoadState +import com.pengxh.kt.lite.widget.dialog.AlertControlDialog +import com.pengxh.kt.lite.widget.dialog.ChangePasswordDialog +import kotlinx.android.synthetic.main.fragment_mine.* +import java.io.File +import java.nio.charset.StandardCharsets + +class MinePageFragment : KotlinBaseFragment() { + + private lateinit var userData: UserDetailModel.Data + private lateinit var userViewModel: UserViewModel + private lateinit var loginViewModel: LoginViewModel + private lateinit var versionViewModel: VersionViewModel + private lateinit var progressDialog: ProgressDialog + + override fun initLayoutView(): Int = R.layout.fragment_mine + + override fun setupTopBarLayout() { + + } + + override fun initData() { + userViewModel = ViewModelProvider(this)[UserViewModel::class.java] + loginViewModel = ViewModelProvider(this)[LoginViewModel::class.java] + versionViewModel = ViewModelProvider(this)[VersionViewModel::class.java] + + //初始化下载对话框 + progressDialog = ProgressDialog(requireContext()) + progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL) + progressDialog.setProgressDrawable(resources.getDrawable(R.drawable.download_progress)) + progressDialog.setCanceledOnTouchOutside(false) + progressDialog.setCancelable(false) + + /** + * 数据监听 + * */ + dataObserve() + } + + override fun observeRequestState() { + userViewModel.loadState.observe(this, { + when (it) { + is LoadState.Loading -> LoadingDialogHub.show(requireActivity(), "修改中,请稍后") + is LoadState.Success -> { + "修改成功,请重新登录".show(requireContext()) + LoadingDialogHub.dismiss() + AuthenticationHelper.removeToken() + requireContext().navigatePageTo() + PageNavigationManager.finishAllActivity() + } + else -> LoadingDialogHub.dismiss() + } + }) + + versionViewModel.loadState.observe(this, { + when (it) { + is LoadState.Loading -> LoadingDialogHub.show(requireActivity(), "检查版本中,请稍后") + else -> LoadingDialogHub.dismiss() + } + }) + } + + override fun initEvent() { + userImageView.setOnClickListener { + "尽情期待~".show(requireContext()) + } + + updateUserButton.setOnClickListener { + LoadingDialogHub.show(requireActivity(), "同步中,请稍后...") + userViewModel.obtainUserDetail() + } + + changePwdLayout.setOnClickListener { + ChangePasswordDialog.Builder() + .setContext(requireContext()) + .setOnDialogButtonClickListener(object : + ChangePasswordDialog.OnDialogButtonClickListener { + override fun onConfirmClick(oldPwd: String, newPwd: String) { + val publicKey = + RSAUtils.keyStrToPublicKey(AuthenticationHelper.publicKey)!! + val oldPassKey = RSAUtils.encryptDataByPublicKey( + oldPwd.toByteArray(StandardCharsets.UTF_8), + publicKey + ) + val newPassKey = RSAUtils.encryptDataByPublicKey( + newPwd.toByteArray(StandardCharsets.UTF_8), + publicKey + ) + userViewModel.changePassword(oldPassKey, newPassKey) + } + }).build().show() + } + + aboutUsLayout.setOnClickListener { + requireContext().navigatePageTo() + } + + updateVersionLayout.setOnClickListener { + versionViewModel.updateVersion() + } + + clearCacheLayout.setOnClickListener { + //删除缓存之后在设置缓存大小 + LoadingDialogHub.show(requireActivity(), "清理中,请稍后") + File(requireContext().cacheDir.path).deleteFile() + object : CountDownTimer(1500, 500) { + override fun onTick(millisUntilFinished: Long) { + + } + + override fun onFinish() { + LoadingDialogHub.dismiss() + cacheSizeView.text = collectApplicationCache().formatFileSize() + } + }.start() + } + + loginOutButton.setOnClickListener { + AlertControlDialog.Builder() + .setContext(requireContext()) + .setTitle("退出登录") + .setMessage("确定要退出吗?") + .setNegativeButton("取消") + .setPositiveButton("确定") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + loginViewModel.out() + } + + override fun onCancelClick() {} + }).build().show() + } + } + + private fun dataObserve() { + userViewModel.userDetailModel.observe(this, { + if (it.code == 200) { + LoadingDialogHub.dismiss() + "同步完成".show(requireContext()) + userData = it.data + updateUserInfo() + } + }) + + versionViewModel.versionResultModel.observe(this, { + if (BuildConfig.VERSION_NAME == it.version) { + "已是最新版本,无需更新".show(requireContext()) + } else { + AlertControlDialog.Builder() + .setContext(requireContext()) + .setTitle("提示") + .setMessage("有新版本,是否更新?") + .setNegativeButton("稍后再说") + .setPositiveButton("立即下载") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + downloadApk(it.downloadUrl) + } + + override fun onCancelClick() { + + } + }).build().show() + } + }) + + loginViewModel.outResultModel.observe(this, { + if (it.code == 200) { + AuthenticationHelper.removeToken() + requireContext().navigatePageTo() + PageNavigationManager.finishAllActivity() + } + }) + } + + private fun collectApplicationCache(): Long { + return File(requireContext().cacheDir.path).calculateSize() + } + + override fun onResume() { + val userDetailJson = SaveKeyValues.getValue(LocaleConstant.USER_DETAIL_MODEL, "") as String + if (userDetailJson.isNotBlank()) { + userData = Gson().fromJson( + userDetailJson, object : TypeToken() {}.type + ) + updateUserInfo() + } + //缓存 + cacheSizeView.text = collectApplicationCache().formatFileSize() + super.onResume() + } + + private fun updateUserInfo() { + //设置头像,圆形,暂时是默认的 +// val roundDrawable = +// BitmapFactory.decodeResource(requireContext().resources, R.mipmap.login_casic) +// .createRoundDrawable( +// requireContext(), +// 3f.dp2px(requireContext()), +// R.color.mainThemeColor.convertColor(requireContext()) +// ) +// userImageView.setImageDrawable(roundDrawable) + userNameView.text = userData.name + userPhoneView.text = String.format("电话:${userData.phone}") + userDeptView.text = String.format("部门:${userData.deptName}") + } + + private fun downloadApk(url: String?) { + progressDialog.setMessage("下载新版本中...") + progressDialog.show() + if (url.toString().isBlank()) { + "抱歉,版本下载失败".show(requireContext()) + return + } + /** + * http://111.198.10.15:11304/static/apk/1.0.1.apk + * */ + val downloadPath = url!!.appendDownloadUrl() + //开始下载 + downloadPath.downloadFile(requireContext().createDownloadFileDir().toString(), + object : OnDownloadListener { + override fun onDownloadStart(totalBytes: Long) { + progressDialog.max = totalBytes.toInt() + } + + override fun onProgressChanged(currentBytes: Long) { + progressDialog.progress = currentBytes.toInt() + } + + override fun onDownloadEnd(file: File?) { + progressDialog.dismiss() + progressDialog.progress = 0 + //安装APK + installApk(file) + } + }) + } + + private fun installApk(apkPackage: File?) { + if (apkPackage == null) { + "文件异常,无法安装".show(requireContext()) + return + } + val intent = Intent(Intent.ACTION_VIEW) + val data: Uri + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { //判断版本大于等于7.0 + data = FileProvider.getUriForFile( + requireContext(), LocaleConstant.APP_AUTHORITY, apkPackage + ) + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) // 给目标应用一个临时授权 + } else { + data = Uri.fromFile(apkPackage) + } + intent.setDataAndType(data, "application/vnd.android.package-archive") + requireContext().startActivity(intent) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java new file mode 100644 index 0000000..467e33d --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java @@ -0,0 +1,43 @@ +package com.casic.smart.town.sanxi.model; + +/** + * 普通实体类,失败/成功数据结构一致 + */ +public class CommonResultModel { + private int code; + private String data; + private String message; + private boolean isSuccess; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return isSuccess; + } + + public void setSuccess(boolean success) { + isSuccess = success; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java new file mode 100644 index 0000000..9bdabef --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java @@ -0,0 +1,62 @@ +package com.casic.smart.town.sanxi.model; + +public class LoginResultModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private String kaptcha; + private String token; + + public String getKaptcha() { + return kaptcha; + } + + public void setKaptcha(String kaptcha) { + this.kaptcha = kaptcha; + } + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java new file mode 100644 index 0000000..09f8d59 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java @@ -0,0 +1,80 @@ +package com.casic.smart.town.sanxi.model; + +public class PublicKeyModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private boolean appKaptcha; + private boolean kaptcha; + private String publicKey; + private String sid; + + public boolean isAppKaptcha() { + return appKaptcha; + } + + public void setAppKaptcha(boolean appKaptcha) { + this.appKaptcha = appKaptcha; + } + + public boolean isKaptcha() { + return kaptcha; + } + + public void setKaptcha(boolean kaptcha) { + this.kaptcha = kaptcha; + } + + public String getPublicKey() { + return publicKey; + } + + public void setPublicKey(String publicKey) { + this.publicKey = publicKey; + } + + public String getSid() { + return sid; + } + + public void setSid(String sid) { + this.sid = sid; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java new file mode 100644 index 0000000..db577fb --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java @@ -0,0 +1,227 @@ +package com.casic.smart.town.sanxi.model; + + +import java.util.List; + +public class UserDetailModel { + private int code; + private Data data; + private String message; + private boolean success; + + public void setCode(int code) { + this.code = code; + } + + public int getCode() { + return code; + } + + public void setData(Data data) { + this.data = data; + } + + public Data getData() { + return data; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getMessage() { + return message; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public boolean getSuccess() { + return success; + } + + public static class Data { + + private String account; + private String attr1; + private String avatar; + private String bizData; + private List dataScope; + private String deptId; + private String deptName; + private List devices; + private String id; + private String ipAddr; + private String name; + private String phone; + private List roleList; + private List roleNames; + private List roleTips; + private String scopeType; + private String sysData; + private String targetId; + private String targetName; + private String tenantId; + + public void setAccount(String account) { + this.account = account; + } + + public String getAccount() { + return account; + } + + public void setAttr1(String attr1) { + this.attr1 = attr1; + } + + public String getAttr1() { + return attr1; + } + + public void setAvatar(String avatar) { + this.avatar = avatar; + } + + public String getAvatar() { + return avatar; + } + + public void setBizData(String bizData) { + this.bizData = bizData; + } + + public String getBizData() { + return bizData; + } + + public void setDataScope(List dataScope) { + this.dataScope = dataScope; + } + + public List getDataScope() { + return dataScope; + } + + public void setDeptId(String deptId) { + this.deptId = deptId; + } + + public String getDeptId() { + return deptId; + } + + public void setDeptName(String deptName) { + this.deptName = deptName; + } + + public String getDeptName() { + return deptName; + } + + public void setDevices(List devices) { + this.devices = devices; + } + + public List getDevices() { + return devices; + } + + public void setId(String id) { + this.id = id; + } + + public String getId() { + return id; + } + + public void setIpAddr(String ipAddr) { + this.ipAddr = ipAddr; + } + + public String getIpAddr() { + return ipAddr; + } + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setPhone(String phone) { + this.phone = phone; + } + + public String getPhone() { + return phone; + } + + public void setRoleList(List roleList) { + this.roleList = roleList; + } + + public List getRoleList() { + return roleList; + } + + public void setRoleNames(List roleNames) { + this.roleNames = roleNames; + } + + public List getRoleNames() { + return roleNames; + } + + public void setRoleTips(List roleTips) { + this.roleTips = roleTips; + } + + public List getRoleTips() { + return roleTips; + } + + public void setScopeType(String scopeType) { + this.scopeType = scopeType; + } + + public String getScopeType() { + return scopeType; + } + + public void setSysData(String sysData) { + this.sysData = sysData; + } + + public String getSysData() { + return sysData; + } + + public void setTargetId(String targetId) { + this.targetId = targetId; + } + + public String getTargetId() { + return targetId; + } + + public void setTargetName(String targetName) { + this.targetName = targetName; + } + + public String getTargetName() { + return targetName; + } + + public void setTenantId(String tenantId) { + this.tenantId = tenantId; + } + + public String getTenantId() { + return tenantId; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/VersionResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/VersionResultModel.java new file mode 100644 index 0000000..79374e0 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/VersionResultModel.java @@ -0,0 +1,61 @@ +package com.casic.smart.town.sanxi.model; + +public class VersionResultModel { + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private String downloadUrl; + private String version; + + public String getDownloadUrl() { + return downloadUrl; + } + + public void setDownloadUrl(String downloadUrl) { + this.downloadUrl = downloadUrl; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/AuthenticationHelper.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/AuthenticationHelper.kt new file mode 100644 index 0000000..f498e4e --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/AuthenticationHelper.kt @@ -0,0 +1,24 @@ +package com.casic.smart.town.sanxi.util + +import com.pengxh.kt.lite.utils.SaveKeyValues + +object AuthenticationHelper { + + fun savePublicKey(key: String) { + SaveKeyValues.putValue("keyString", key) + } + + val publicKey: String? + get() = SaveKeyValues.getValue("keyString", "") as String? + + fun saveToken(token: String?) { + SaveKeyValues.putValue("token", token!!) + } + + val token: String? + get() = SaveKeyValues.getValue("token", "") as String? + + fun removeToken() { + SaveKeyValues.removeKey("token") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt index ab1a36d..99f0d19 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt @@ -14,5 +14,9 @@ const val PAGE_LIMIT = 20 const val DEFAULT_SERVER_CONFIG = "defaultServerConfig" - const val DEFAULT_SERVER = "http://111.198.10.15:11308" + const val DEFAULT_SERVER = "http://36.133.189.112:8082" + const val APP_AUTHORITY = "com.casic.smart.town.sanxi.fileprovider" + const val ACCOUNT = "account" + const val PASSWORD = "password" + const val USER_DETAIL_MODEL = "userDetailModel" } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/RSAUtils.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/RSAUtils.kt new file mode 100644 index 0000000..b9216ba --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/RSAUtils.kt @@ -0,0 +1,55 @@ +package com.casic.smart.town.sanxi.util + +import android.util.Base64 +import java.security.* +import java.security.spec.InvalidKeySpecException +import java.security.spec.X509EncodedKeySpec +import javax.crypto.BadPaddingException +import javax.crypto.Cipher +import javax.crypto.IllegalBlockSizeException +import javax.crypto.NoSuchPaddingException + +object RSAUtils { + //构建Cipher实例时所传入的的字符串,默认为"RSA/NONE/PKCS1Padding" + private fun processData(srcData: ByteArray, key: Key): ByteArray? { //用来保存处理结果 + var resultBytes: ByteArray? = null + try { //获取Cipher实例 + val cipher = Cipher.getInstance("RSA/None/PKCS1Padding") + //初始化Cipher,mode指定是加密还是解密,key为公钥或私钥 + cipher.init(Cipher.ENCRYPT_MODE, key) + //处理数据 + resultBytes = cipher.doFinal(srcData) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: NoSuchPaddingException) { + e.printStackTrace() + } catch (e: InvalidKeyException) { + e.printStackTrace() + } catch (e: BadPaddingException) { + e.printStackTrace() + } catch (e: IllegalBlockSizeException) { + e.printStackTrace() + } + return resultBytes + } + + fun encryptDataByPublicKey(srcData: ByteArray, publicKey: PublicKey): String { + val resultBytes = processData(srcData, publicKey) + return Base64.encodeToString(resultBytes, Base64.DEFAULT) + } + + fun keyStrToPublicKey(publicKeyStr: String?): PublicKey? { + var publicKey: PublicKey? = null + val keyBytes = Base64.decode(publicKeyStr, Base64.DEFAULT) + val keySpec = X509EncodedKeySpec(keyBytes) + try { + val keyFactory = KeyFactory.getInstance("RSA") + publicKey = keyFactory.generatePublic(keySpec) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: InvalidKeySpecException) { + e.printStackTrace() + } + return publicKey + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt index 63db0ec..50d1742 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt @@ -1,12 +1,64 @@ package com.casic.smart.town.sanxi.util.retrofit -import retrofit2.http.GET +import retrofit2.http.* interface RetrofitService { + /** + * PublicKey校验 + */ + @GET("/config/baseConfig") + suspend fun obtainPublicKey(): String + + /** + * 登录并获取Token + * + * @param sid + * @param account 用户名 + * @param secretKey 加密后的密码 + */ + @FormUrlEncoded + @POST("/user/login") + suspend fun login( + @Field("sid") sid: String, + @Field("username") account: String, + @Field("password") secretKey: String + ): String + + /** + * 退出登录 + */ + @GET("/user/logout") + suspend fun loginOut(@Header("token") token: String): String + + /** + * 获取用户信息 + */ + @GET("/user/info") + suspend fun obtainUserDetail(@Header("token") token: String): String + + /** + * 修改密码 + * + * @param oldPwd 旧密码 + * @param newPwd 新密码 + */ + @FormUrlEncoded + @POST("/mgr/changePwd") + suspend fun changePassword( + @Header("token") token: String, + @Field("oldPwd") oldPwd: String, + @Field("newPwd") newPwd: String + ): String /** * 根据布防状态统计闸井数量接口 */ @GET("/well/countByBfzt") - suspend fun countWellByState(): String + suspend fun countWellByState(@Header("token") token: String): String + + /** + * 更新APK版本 + */ + @POST("/app/checkVersion") + suspend fun obtainVersionResult(@Header("token") token: String): String } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt index 46bbf4b..410444b 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt @@ -1,5 +1,6 @@ package com.casic.smart.town.sanxi.util.retrofit +import com.casic.smart.town.sanxi.util.AuthenticationHelper import com.casic.smart.town.sanxi.util.LocaleConstant import com.pengxh.kt.lite.utils.RetrofitFactory import com.pengxh.kt.lite.utils.SaveKeyValues @@ -15,9 +16,52 @@ } /** + * 验证PublicKey + */ + suspend fun authenticate(): String { + return api.obtainPublicKey() + } + + /** + * 登录并获取Token + */ + suspend fun login(sid: String, account: String, secretKey: String): String { + return api.login(sid, account, secretKey) + } + + /** + * 退出登录 + */ + suspend fun loginOut(): String { + return api.loginOut(AuthenticationHelper.token!!) + } + + /** + * 获取用户信息 + */ + suspend fun obtainUserDetail(): String { + return api.obtainUserDetail(AuthenticationHelper.token!!) + } + + /** + * 修改密码 + */ + suspend fun changePassword(oldPwd: String, newPwd: String): String { + return api.changePassword(AuthenticationHelper.token!!, oldPwd, newPwd) + } + + /** * 根据布防状态统计闸井数量接口 */ suspend fun countWellByState(): String { - return api.countWellByState() + return api.countWellByState(AuthenticationHelper.token!!) } + + /** + * 更新APK版本 + */ + suspend fun updateVersion(): String { + return api.obtainVersionResult(AuthenticationHelper.token!!) + } + } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/AboutUsActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/AboutUsActivity.kt new file mode 100644 index 0000000..7e43c47 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/AboutUsActivity.kt @@ -0,0 +1,34 @@ +package com.casic.smart.town.sanxi.view + +import com.casic.smart.town.sanxi.BuildConfig +import com.casic.smart.town.sanxi.R +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.kt.lite.base.KotlinBaseActivity +import com.pengxh.kt.lite.extensions.convertColor +import com.pengxh.kt.lite.utils.ImmerseStatusBarUtil +import kotlinx.android.synthetic.main.activity_about_us.* +import kotlinx.android.synthetic.main.include_base_title.* + +class AboutUsActivity : KotlinBaseActivity() { + + override fun initLayoutView(): Int = R.layout.activity_about_us + + override fun setupTopBarLayout() { + ImmersionBar.with(this).statusBarDarkFont(false).init() + ImmerseStatusBarUtil.setColor(this, R.color.mainThemeColor.convertColor(this)) + titleView.text = "关于我们" + leftBackView.setOnClickListener { finish() } + } + + override fun initData() { + versionCodeView.text = String.format("版本信息:V${BuildConfig.VERSION_NAME}") + } + + override fun observeRequestState() { + + } + + override fun initEvent() { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/LoginActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/LoginActivity.kt new file mode 100644 index 0000000..b5d1d50 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/LoginActivity.kt @@ -0,0 +1,97 @@ +package com.casic.smart.town.sanxi.view + +import androidx.lifecycle.ViewModelProvider +import com.casic.smart.town.sanxi.R +import com.casic.smart.town.sanxi.util.AuthenticationHelper +import com.casic.smart.town.sanxi.util.LoadingDialogHub +import com.casic.smart.town.sanxi.util.LocaleConstant +import com.casic.smart.town.sanxi.util.RSAUtils +import com.casic.smart.town.sanxi.vm.AuthenticateViewModel +import com.casic.smart.town.sanxi.vm.LoginViewModel +import com.casic.smart.town.sanxi.vm.UserViewModel +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.kt.lite.base.KotlinBaseActivity +import com.pengxh.kt.lite.extensions.navigatePageTo +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.utils.SaveKeyValues +import kotlinx.android.synthetic.main.activity_login.* + +class LoginActivity : KotlinBaseActivity() { + + private lateinit var authenticateViewModel: AuthenticateViewModel + private lateinit var loginViewModel: LoginViewModel + private lateinit var userViewModel: UserViewModel + + override fun initLayoutView(): Int = R.layout.activity_login + + override fun setupTopBarLayout() { + ImmersionBar.with(this).statusBarDarkFont(true).init() + } + + override fun initData() { + // 设置默认账号密码 + userNameView.setText(SaveKeyValues.getValue(LocaleConstant.ACCOUNT, "") as String) + userPasswordView.setText(SaveKeyValues.getValue(LocaleConstant.PASSWORD, "") as String) + authenticateViewModel = ViewModelProvider(this)[AuthenticateViewModel::class.java] + loginViewModel = ViewModelProvider(this)[LoginViewModel::class.java] + userViewModel = ViewModelProvider(this)[UserViewModel::class.java] + + authenticateViewModel.keyModel.observe(this, { + if (it.code == 200) {//用code判断,别的判断可能有坑 + val keyString = it.data!!.publicKey!! + /** + * 修改密码需要用到key,先存着 + * */ + AuthenticationHelper.savePublicKey(keyString) + val publicKey = RSAUtils.keyStrToPublicKey(keyString) + + val account = userNameView.text.toString() + val userPassword = userPasswordView.text.toString() + val dataByPublicKey = + RSAUtils.encryptDataByPublicKey(userPassword.toByteArray(), publicKey!!) + //登录并获取Token,POST请求 + loginViewModel.enter(it.data!!.sid!!, account, dataByPublicKey) + loginViewModel.enterResultModel.observe(this, { loginResult -> + if (loginResult.code == 200) { + AuthenticationHelper.saveToken(loginResult.data!!.token!!) + /** + * 获取token之后保存用户信息 + * */ + userViewModel.obtainUserDetail() + //验证成功登录 + navigatePageTo() + finish() + } + }) + } + }) + } + + override fun observeRequestState() { + authenticateViewModel.loadState.observe(this, { + LoadingDialogHub.show(this, "登录中,请稍后") + }) + + loginViewModel.loadState.observe(this, { + LoadingDialogHub.dismiss() + }) + } + + override fun initEvent() { + loginButton.setOnClickListener { + val account = userNameView.text.toString().trim() + val userPassword = userPasswordView.text.toString().trim() + if (account.isBlank()) { + "用户名不能为空".show(this) + return@setOnClickListener + } + if (userPassword.isBlank()) { + "密码不能为空".show(this) + return@setOnClickListener + } + SaveKeyValues.putValue(LocaleConstant.ACCOUNT, account) + SaveKeyValues.putValue(LocaleConstant.PASSWORD, userPassword) + authenticateViewModel.obtainPublicKey() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt index 3bea1c9..a4fa951 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt @@ -7,6 +7,7 @@ import com.casic.smart.town.sanxi.R import com.casic.smart.town.sanxi.adapter.ViewPagerAdapter import com.casic.smart.town.sanxi.fragment.AlarmPageFragment +import com.casic.smart.town.sanxi.fragment.MinePageFragment import com.casic.smart.town.sanxi.fragment.MonitorPageFragment import com.casic.smart.town.sanxi.fragment.StatisticsPageFragment import com.gyf.immersionbar.ImmersionBar @@ -33,6 +34,7 @@ fragmentPages.add(StatisticsPageFragment()) fragmentPages.add(MonitorPageFragment()) fragmentPages.add(AlarmPageFragment()) + fragmentPages.add(MinePageFragment()) } override fun initLayoutView(): Int = R.layout.activity_main @@ -68,6 +70,9 @@ R.id.nav_alarm -> { mainViewPager.currentItem = 2 } + R.id.nav_more -> { + mainViewPager.currentItem = 3 + } } false } diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/PermissionActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/PermissionActivity.kt index c478d83..3238f3c 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/view/PermissionActivity.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/PermissionActivity.kt @@ -32,7 +32,7 @@ //先把导航隐私政策声明,后面导航会用到 MapsInitializer.updatePrivacyAgree(this, true) MapsInitializer.updatePrivacyShow(this, true, true) - this.navigatePageTo() + this.navigatePageTo() finish() } diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/SplashScreenActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/SplashScreenActivity.kt new file mode 100644 index 0000000..fa5a74d --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/SplashScreenActivity.kt @@ -0,0 +1,54 @@ +package com.casic.smart.town.sanxi.view + +import android.annotation.SuppressLint +import android.os.CountDownTimer +import androidx.lifecycle.ViewModelProvider +import com.casic.smart.town.sanxi.R +import com.casic.smart.town.sanxi.vm.UserViewModel +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.kt.lite.base.KotlinBaseActivity +import com.pengxh.kt.lite.extensions.navigatePageTo + +@SuppressLint("CustomSplashScreen") +class SplashScreenActivity : KotlinBaseActivity() { + + private lateinit var userDetailViewModel: UserViewModel + private val countDownTimer = object : CountDownTimer(1000, 500) { + override fun onFinish() { + /** + * 获取token之后保存用户信息 + * */ + userDetailViewModel.obtainUserDetail() + userDetailViewModel.userDetailModel.observe(this@SplashScreenActivity, { + if (it.code == 200) { + navigatePageTo() + } else { + navigatePageTo() + } + finish() + }) + } + + override fun onTick(millisUntilFinished: Long) { + + } + } + + override fun initLayoutView(): Int = R.layout.activity_splash + + override fun setupTopBarLayout() { + ImmersionBar.with(this).statusBarDarkFont(false).init() + } + + override fun initData() { + userDetailViewModel = ViewModelProvider(this).get(UserViewModel::class.java) + } + + override fun observeRequestState() { + + } + + override fun initEvent() { + countDownTimer.start() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/vm/AuthenticateViewModel.kt b/app/src/main/java/com/casic/smart/town/sanxi/vm/AuthenticateViewModel.kt new file mode 100644 index 0000000..87ddbf8 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/vm/AuthenticateViewModel.kt @@ -0,0 +1,35 @@ +package com.casic.smart.town.sanxi.vm + +import androidx.lifecycle.MutableLiveData +import com.casic.smart.town.sanxi.base.BaseApplication +import com.casic.smart.town.sanxi.extensions.separateResponseCode +import com.casic.smart.town.sanxi.extensions.toErrorMessage +import com.casic.smart.town.sanxi.model.PublicKeyModel +import com.casic.smart.town.sanxi.util.retrofit.RetrofitServiceManager +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.launch +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.vm.BaseViewModel +import com.pengxh.kt.lite.vm.LoadState + +class AuthenticateViewModel : BaseViewModel() { + + private val gson = Gson() + val keyModel = MutableLiveData() + + fun obtainPublicKey() = launch({ + loadState.value = LoadState.Loading + val response = RetrofitServiceManager.authenticate() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + keyModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ) + } else { + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + it.printStackTrace() + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/vm/LoginViewModel.kt b/app/src/main/java/com/casic/smart/town/sanxi/vm/LoginViewModel.kt new file mode 100644 index 0000000..c377c61 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/vm/LoginViewModel.kt @@ -0,0 +1,54 @@ +package com.casic.smart.town.sanxi.vm + +import androidx.lifecycle.MutableLiveData +import com.casic.smart.town.sanxi.base.BaseApplication +import com.casic.smart.town.sanxi.extensions.separateResponseCode +import com.casic.smart.town.sanxi.extensions.toErrorMessage +import com.casic.smart.town.sanxi.model.CommonResultModel +import com.casic.smart.town.sanxi.model.LoginResultModel +import com.casic.smart.town.sanxi.util.retrofit.RetrofitServiceManager +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.launch +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.vm.BaseViewModel +import com.pengxh.kt.lite.vm.LoadState + +class LoginViewModel : BaseViewModel() { + + private val gson = Gson() + val enterResultModel = MutableLiveData() + val outResultModel = MutableLiveData() + + fun enter(sid: String, account: String, secretKey: String) = launch({ + val response = RetrofitServiceManager.login(sid, account, secretKey) + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + loadState.value = LoadState.Success + "登录成功".show(BaseApplication.obtainInstance()) + enterResultModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ) + } else { + loadState.value = LoadState.Fail + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + loadState.value = LoadState.Fail + it.printStackTrace() + }) + + fun out() = launch({ + val response = RetrofitServiceManager.loginOut() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + outResultModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ) + } else { + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + it.printStackTrace() + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/vm/UserViewModel.kt b/app/src/main/java/com/casic/smart/town/sanxi/vm/UserViewModel.kt new file mode 100644 index 0000000..29a8756 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/vm/UserViewModel.kt @@ -0,0 +1,57 @@ +package com.casic.smart.town.sanxi.vm + +import androidx.lifecycle.MutableLiveData +import com.casic.smart.town.sanxi.base.BaseApplication +import com.casic.smart.town.sanxi.extensions.separateResponseCode +import com.casic.smart.town.sanxi.extensions.toErrorMessage +import com.casic.smart.town.sanxi.model.UserDetailModel +import com.casic.smart.town.sanxi.util.LocaleConstant +import com.casic.smart.town.sanxi.util.retrofit.RetrofitServiceManager +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.launch +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.utils.SaveKeyValues +import com.pengxh.kt.lite.vm.BaseViewModel +import com.pengxh.kt.lite.vm.LoadState + +class UserViewModel : BaseViewModel() { + + private val gson = Gson() + val userDetailModel = MutableLiveData() + + fun obtainUserDetail() = launch({ + val response = RetrofitServiceManager.obtainUserDetail() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + val userDetail = gson.fromJson( + response, object : TypeToken() {}.type + ) + userDetailModel.value = userDetail + SaveKeyValues.putValue(LocaleConstant.USER_DETAIL_MODEL, gson.toJson(userDetail.data)) + } else { + val errorModel = UserDetailModel() + errorModel.code = responseCode + userDetailModel.value = errorModel + //如果此次获取不到用户信息,那么就清空之前的用户缓存,然后让用户重新登录 + SaveKeyValues.removeKey(LocaleConstant.USER_DETAIL_MODEL) + } + }, { + it.printStackTrace() + }) + + fun changePassword(oldPwd: String, newPwd: String) = launch({ + loadState.value = LoadState.Loading + val response = RetrofitServiceManager.changePassword(oldPwd, newPwd) + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + loadState.value = LoadState.Success + } else { + loadState.value = LoadState.Fail + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + loadState.value = LoadState.Fail + it.printStackTrace() + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/vm/VersionViewModel.kt b/app/src/main/java/com/casic/smart/town/sanxi/vm/VersionViewModel.kt new file mode 100644 index 0000000..20ea457 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/vm/VersionViewModel.kt @@ -0,0 +1,38 @@ +package com.casic.smart.town.sanxi.vm + +import androidx.lifecycle.MutableLiveData +import com.casic.smart.town.sanxi.base.BaseApplication +import com.casic.smart.town.sanxi.extensions.separateResponseCode +import com.casic.smart.town.sanxi.extensions.toErrorMessage +import com.casic.smart.town.sanxi.model.VersionResultModel +import com.casic.smart.town.sanxi.util.retrofit.RetrofitServiceManager +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.launch +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.vm.BaseViewModel +import com.pengxh.kt.lite.vm.LoadState + +class VersionViewModel : BaseViewModel() { + + private val gson = Gson() + val versionResultModel = MutableLiveData() + + fun updateVersion() = launch({ + loadState.value = LoadState.Loading + val response = RetrofitServiceManager.updateVersion() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + loadState.value = LoadState.Success + versionResultModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ).data + } else { + loadState.value = LoadState.Fail + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + loadState.value = LoadState.Fail + it.printStackTrace() + }) +} \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_solid_layout_gray_radius_5.xml b/app/src/main/res/drawable/bg_solid_layout_gray_radius_5.xml new file mode 100644 index 0000000..96eb93e --- /dev/null +++ b/app/src/main/res/drawable/bg_solid_layout_gray_radius_5.xml @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/button_main_selector.xml b/app/src/main/res/drawable/button_main_selector.xml new file mode 100644 index 0000000..5875998 --- /dev/null +++ b/app/src/main/res/drawable/button_main_selector.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/button_white_selector.xml b/app/src/main/res/drawable/button_white_selector.xml new file mode 100644 index 0000000..4dd3c0b --- /dev/null +++ b/app/src/main/res/drawable/button_white_selector.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/download_progress.xml b/app/src/main/res/drawable/download_progress.xml new file mode 100644 index 0000000..493d923 --- /dev/null +++ b/app/src/main/res/drawable/download_progress.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1df917d..8d4f33b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -6,6 +6,7 @@ + @@ -32,11 +33,14 @@ + + + + + + + diff --git a/app/src/main/assets/svg/mine.svg b/app/src/main/assets/svg/mine.svg new file mode 100644 index 0000000..3fbb0a3 --- /dev/null +++ b/app/src/main/assets/svg/mine.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt b/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt index d730417..b1972c2 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt @@ -1,8 +1,10 @@ package com.casic.smart.town.sanxi.extensions import com.casic.smart.town.sanxi.model.ErrorMessageModel +import com.casic.smart.town.sanxi.util.LocaleConstant import com.google.gson.Gson import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.utils.SaveKeyValues import org.json.JSONObject fun String.separateResponseCode(): Int { @@ -17,4 +19,15 @@ this, object : TypeToken() {}.type ) return errorModel.message.toString() +} + +/** + * 下载路径为 http://xx.com/static/ 拼接downloadUrl + * */ +fun String.appendDownloadUrl(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + LocaleConstant.DEFAULT_SERVER_CONFIG, LocaleConstant.DEFAULT_SERVER + ) as String + return "$defaultValue/static/${this}" } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt b/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt new file mode 100644 index 0000000..1809dc5 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt @@ -0,0 +1,288 @@ +package com.casic.smart.town.sanxi.fragment + +import android.app.ProgressDialog +import android.content.Intent +import android.net.Uri +import android.os.Build +import android.os.CountDownTimer +import androidx.core.content.FileProvider +import androidx.lifecycle.ViewModelProvider +import com.casic.smart.town.sanxi.BuildConfig +import com.casic.smart.town.sanxi.R +import com.casic.smart.town.sanxi.extensions.appendDownloadUrl +import com.casic.smart.town.sanxi.model.UserDetailModel +import com.casic.smart.town.sanxi.util.AuthenticationHelper +import com.casic.smart.town.sanxi.util.LoadingDialogHub +import com.casic.smart.town.sanxi.util.LocaleConstant +import com.casic.smart.town.sanxi.util.RSAUtils +import com.casic.smart.town.sanxi.view.AboutUsActivity +import com.casic.smart.town.sanxi.view.LoginActivity +import com.casic.smart.town.sanxi.vm.LoginViewModel +import com.casic.smart.town.sanxi.vm.UserViewModel +import com.casic.smart.town.sanxi.vm.VersionViewModel +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.base.KotlinBaseFragment +import com.pengxh.kt.lite.callback.OnDownloadListener +import com.pengxh.kt.lite.extensions.* +import com.pengxh.kt.lite.utils.PageNavigationManager +import com.pengxh.kt.lite.utils.SaveKeyValues +import com.pengxh.kt.lite.vm.LoadState +import com.pengxh.kt.lite.widget.dialog.AlertControlDialog +import com.pengxh.kt.lite.widget.dialog.ChangePasswordDialog +import kotlinx.android.synthetic.main.fragment_mine.* +import java.io.File +import java.nio.charset.StandardCharsets + +class MinePageFragment : KotlinBaseFragment() { + + private lateinit var userData: UserDetailModel.Data + private lateinit var userViewModel: UserViewModel + private lateinit var loginViewModel: LoginViewModel + private lateinit var versionViewModel: VersionViewModel + private lateinit var progressDialog: ProgressDialog + + override fun initLayoutView(): Int = R.layout.fragment_mine + + override fun setupTopBarLayout() { + + } + + override fun initData() { + userViewModel = ViewModelProvider(this)[UserViewModel::class.java] + loginViewModel = ViewModelProvider(this)[LoginViewModel::class.java] + versionViewModel = ViewModelProvider(this)[VersionViewModel::class.java] + + //初始化下载对话框 + progressDialog = ProgressDialog(requireContext()) + progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL) + progressDialog.setProgressDrawable(resources.getDrawable(R.drawable.download_progress)) + progressDialog.setCanceledOnTouchOutside(false) + progressDialog.setCancelable(false) + + /** + * 数据监听 + * */ + dataObserve() + } + + override fun observeRequestState() { + userViewModel.loadState.observe(this, { + when (it) { + is LoadState.Loading -> LoadingDialogHub.show(requireActivity(), "修改中,请稍后") + is LoadState.Success -> { + "修改成功,请重新登录".show(requireContext()) + LoadingDialogHub.dismiss() + AuthenticationHelper.removeToken() + requireContext().navigatePageTo() + PageNavigationManager.finishAllActivity() + } + else -> LoadingDialogHub.dismiss() + } + }) + + versionViewModel.loadState.observe(this, { + when (it) { + is LoadState.Loading -> LoadingDialogHub.show(requireActivity(), "检查版本中,请稍后") + else -> LoadingDialogHub.dismiss() + } + }) + } + + override fun initEvent() { + userImageView.setOnClickListener { + "尽情期待~".show(requireContext()) + } + + updateUserButton.setOnClickListener { + LoadingDialogHub.show(requireActivity(), "同步中,请稍后...") + userViewModel.obtainUserDetail() + } + + changePwdLayout.setOnClickListener { + ChangePasswordDialog.Builder() + .setContext(requireContext()) + .setOnDialogButtonClickListener(object : + ChangePasswordDialog.OnDialogButtonClickListener { + override fun onConfirmClick(oldPwd: String, newPwd: String) { + val publicKey = + RSAUtils.keyStrToPublicKey(AuthenticationHelper.publicKey)!! + val oldPassKey = RSAUtils.encryptDataByPublicKey( + oldPwd.toByteArray(StandardCharsets.UTF_8), + publicKey + ) + val newPassKey = RSAUtils.encryptDataByPublicKey( + newPwd.toByteArray(StandardCharsets.UTF_8), + publicKey + ) + userViewModel.changePassword(oldPassKey, newPassKey) + } + }).build().show() + } + + aboutUsLayout.setOnClickListener { + requireContext().navigatePageTo() + } + + updateVersionLayout.setOnClickListener { + versionViewModel.updateVersion() + } + + clearCacheLayout.setOnClickListener { + //删除缓存之后在设置缓存大小 + LoadingDialogHub.show(requireActivity(), "清理中,请稍后") + File(requireContext().cacheDir.path).deleteFile() + object : CountDownTimer(1500, 500) { + override fun onTick(millisUntilFinished: Long) { + + } + + override fun onFinish() { + LoadingDialogHub.dismiss() + cacheSizeView.text = collectApplicationCache().formatFileSize() + } + }.start() + } + + loginOutButton.setOnClickListener { + AlertControlDialog.Builder() + .setContext(requireContext()) + .setTitle("退出登录") + .setMessage("确定要退出吗?") + .setNegativeButton("取消") + .setPositiveButton("确定") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + loginViewModel.out() + } + + override fun onCancelClick() {} + }).build().show() + } + } + + private fun dataObserve() { + userViewModel.userDetailModel.observe(this, { + if (it.code == 200) { + LoadingDialogHub.dismiss() + "同步完成".show(requireContext()) + userData = it.data + updateUserInfo() + } + }) + + versionViewModel.versionResultModel.observe(this, { + if (BuildConfig.VERSION_NAME == it.version) { + "已是最新版本,无需更新".show(requireContext()) + } else { + AlertControlDialog.Builder() + .setContext(requireContext()) + .setTitle("提示") + .setMessage("有新版本,是否更新?") + .setNegativeButton("稍后再说") + .setPositiveButton("立即下载") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + downloadApk(it.downloadUrl) + } + + override fun onCancelClick() { + + } + }).build().show() + } + }) + + loginViewModel.outResultModel.observe(this, { + if (it.code == 200) { + AuthenticationHelper.removeToken() + requireContext().navigatePageTo() + PageNavigationManager.finishAllActivity() + } + }) + } + + private fun collectApplicationCache(): Long { + return File(requireContext().cacheDir.path).calculateSize() + } + + override fun onResume() { + val userDetailJson = SaveKeyValues.getValue(LocaleConstant.USER_DETAIL_MODEL, "") as String + if (userDetailJson.isNotBlank()) { + userData = Gson().fromJson( + userDetailJson, object : TypeToken() {}.type + ) + updateUserInfo() + } + //缓存 + cacheSizeView.text = collectApplicationCache().formatFileSize() + super.onResume() + } + + private fun updateUserInfo() { + //设置头像,圆形,暂时是默认的 +// val roundDrawable = +// BitmapFactory.decodeResource(requireContext().resources, R.mipmap.login_casic) +// .createRoundDrawable( +// requireContext(), +// 3f.dp2px(requireContext()), +// R.color.mainThemeColor.convertColor(requireContext()) +// ) +// userImageView.setImageDrawable(roundDrawable) + userNameView.text = userData.name + userPhoneView.text = String.format("电话:${userData.phone}") + userDeptView.text = String.format("部门:${userData.deptName}") + } + + private fun downloadApk(url: String?) { + progressDialog.setMessage("下载新版本中...") + progressDialog.show() + if (url.toString().isBlank()) { + "抱歉,版本下载失败".show(requireContext()) + return + } + /** + * http://111.198.10.15:11304/static/apk/1.0.1.apk + * */ + val downloadPath = url!!.appendDownloadUrl() + //开始下载 + downloadPath.downloadFile(requireContext().createDownloadFileDir().toString(), + object : OnDownloadListener { + override fun onDownloadStart(totalBytes: Long) { + progressDialog.max = totalBytes.toInt() + } + + override fun onProgressChanged(currentBytes: Long) { + progressDialog.progress = currentBytes.toInt() + } + + override fun onDownloadEnd(file: File?) { + progressDialog.dismiss() + progressDialog.progress = 0 + //安装APK + installApk(file) + } + }) + } + + private fun installApk(apkPackage: File?) { + if (apkPackage == null) { + "文件异常,无法安装".show(requireContext()) + return + } + val intent = Intent(Intent.ACTION_VIEW) + val data: Uri + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { //判断版本大于等于7.0 + data = FileProvider.getUriForFile( + requireContext(), LocaleConstant.APP_AUTHORITY, apkPackage + ) + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) // 给目标应用一个临时授权 + } else { + data = Uri.fromFile(apkPackage) + } + intent.setDataAndType(data, "application/vnd.android.package-archive") + requireContext().startActivity(intent) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java new file mode 100644 index 0000000..467e33d --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java @@ -0,0 +1,43 @@ +package com.casic.smart.town.sanxi.model; + +/** + * 普通实体类,失败/成功数据结构一致 + */ +public class CommonResultModel { + private int code; + private String data; + private String message; + private boolean isSuccess; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return isSuccess; + } + + public void setSuccess(boolean success) { + isSuccess = success; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java new file mode 100644 index 0000000..9bdabef --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java @@ -0,0 +1,62 @@ +package com.casic.smart.town.sanxi.model; + +public class LoginResultModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private String kaptcha; + private String token; + + public String getKaptcha() { + return kaptcha; + } + + public void setKaptcha(String kaptcha) { + this.kaptcha = kaptcha; + } + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java new file mode 100644 index 0000000..09f8d59 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java @@ -0,0 +1,80 @@ +package com.casic.smart.town.sanxi.model; + +public class PublicKeyModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private boolean appKaptcha; + private boolean kaptcha; + private String publicKey; + private String sid; + + public boolean isAppKaptcha() { + return appKaptcha; + } + + public void setAppKaptcha(boolean appKaptcha) { + this.appKaptcha = appKaptcha; + } + + public boolean isKaptcha() { + return kaptcha; + } + + public void setKaptcha(boolean kaptcha) { + this.kaptcha = kaptcha; + } + + public String getPublicKey() { + return publicKey; + } + + public void setPublicKey(String publicKey) { + this.publicKey = publicKey; + } + + public String getSid() { + return sid; + } + + public void setSid(String sid) { + this.sid = sid; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java new file mode 100644 index 0000000..db577fb --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java @@ -0,0 +1,227 @@ +package com.casic.smart.town.sanxi.model; + + +import java.util.List; + +public class UserDetailModel { + private int code; + private Data data; + private String message; + private boolean success; + + public void setCode(int code) { + this.code = code; + } + + public int getCode() { + return code; + } + + public void setData(Data data) { + this.data = data; + } + + public Data getData() { + return data; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getMessage() { + return message; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public boolean getSuccess() { + return success; + } + + public static class Data { + + private String account; + private String attr1; + private String avatar; + private String bizData; + private List dataScope; + private String deptId; + private String deptName; + private List devices; + private String id; + private String ipAddr; + private String name; + private String phone; + private List roleList; + private List roleNames; + private List roleTips; + private String scopeType; + private String sysData; + private String targetId; + private String targetName; + private String tenantId; + + public void setAccount(String account) { + this.account = account; + } + + public String getAccount() { + return account; + } + + public void setAttr1(String attr1) { + this.attr1 = attr1; + } + + public String getAttr1() { + return attr1; + } + + public void setAvatar(String avatar) { + this.avatar = avatar; + } + + public String getAvatar() { + return avatar; + } + + public void setBizData(String bizData) { + this.bizData = bizData; + } + + public String getBizData() { + return bizData; + } + + public void setDataScope(List dataScope) { + this.dataScope = dataScope; + } + + public List getDataScope() { + return dataScope; + } + + public void setDeptId(String deptId) { + this.deptId = deptId; + } + + public String getDeptId() { + return deptId; + } + + public void setDeptName(String deptName) { + this.deptName = deptName; + } + + public String getDeptName() { + return deptName; + } + + public void setDevices(List devices) { + this.devices = devices; + } + + public List getDevices() { + return devices; + } + + public void setId(String id) { + this.id = id; + } + + public String getId() { + return id; + } + + public void setIpAddr(String ipAddr) { + this.ipAddr = ipAddr; + } + + public String getIpAddr() { + return ipAddr; + } + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setPhone(String phone) { + this.phone = phone; + } + + public String getPhone() { + return phone; + } + + public void setRoleList(List roleList) { + this.roleList = roleList; + } + + public List getRoleList() { + return roleList; + } + + public void setRoleNames(List roleNames) { + this.roleNames = roleNames; + } + + public List getRoleNames() { + return roleNames; + } + + public void setRoleTips(List roleTips) { + this.roleTips = roleTips; + } + + public List getRoleTips() { + return roleTips; + } + + public void setScopeType(String scopeType) { + this.scopeType = scopeType; + } + + public String getScopeType() { + return scopeType; + } + + public void setSysData(String sysData) { + this.sysData = sysData; + } + + public String getSysData() { + return sysData; + } + + public void setTargetId(String targetId) { + this.targetId = targetId; + } + + public String getTargetId() { + return targetId; + } + + public void setTargetName(String targetName) { + this.targetName = targetName; + } + + public String getTargetName() { + return targetName; + } + + public void setTenantId(String tenantId) { + this.tenantId = tenantId; + } + + public String getTenantId() { + return tenantId; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/VersionResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/VersionResultModel.java new file mode 100644 index 0000000..79374e0 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/VersionResultModel.java @@ -0,0 +1,61 @@ +package com.casic.smart.town.sanxi.model; + +public class VersionResultModel { + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private String downloadUrl; + private String version; + + public String getDownloadUrl() { + return downloadUrl; + } + + public void setDownloadUrl(String downloadUrl) { + this.downloadUrl = downloadUrl; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/AuthenticationHelper.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/AuthenticationHelper.kt new file mode 100644 index 0000000..f498e4e --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/AuthenticationHelper.kt @@ -0,0 +1,24 @@ +package com.casic.smart.town.sanxi.util + +import com.pengxh.kt.lite.utils.SaveKeyValues + +object AuthenticationHelper { + + fun savePublicKey(key: String) { + SaveKeyValues.putValue("keyString", key) + } + + val publicKey: String? + get() = SaveKeyValues.getValue("keyString", "") as String? + + fun saveToken(token: String?) { + SaveKeyValues.putValue("token", token!!) + } + + val token: String? + get() = SaveKeyValues.getValue("token", "") as String? + + fun removeToken() { + SaveKeyValues.removeKey("token") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt index ab1a36d..99f0d19 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt @@ -14,5 +14,9 @@ const val PAGE_LIMIT = 20 const val DEFAULT_SERVER_CONFIG = "defaultServerConfig" - const val DEFAULT_SERVER = "http://111.198.10.15:11308" + const val DEFAULT_SERVER = "http://36.133.189.112:8082" + const val APP_AUTHORITY = "com.casic.smart.town.sanxi.fileprovider" + const val ACCOUNT = "account" + const val PASSWORD = "password" + const val USER_DETAIL_MODEL = "userDetailModel" } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/RSAUtils.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/RSAUtils.kt new file mode 100644 index 0000000..b9216ba --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/RSAUtils.kt @@ -0,0 +1,55 @@ +package com.casic.smart.town.sanxi.util + +import android.util.Base64 +import java.security.* +import java.security.spec.InvalidKeySpecException +import java.security.spec.X509EncodedKeySpec +import javax.crypto.BadPaddingException +import javax.crypto.Cipher +import javax.crypto.IllegalBlockSizeException +import javax.crypto.NoSuchPaddingException + +object RSAUtils { + //构建Cipher实例时所传入的的字符串,默认为"RSA/NONE/PKCS1Padding" + private fun processData(srcData: ByteArray, key: Key): ByteArray? { //用来保存处理结果 + var resultBytes: ByteArray? = null + try { //获取Cipher实例 + val cipher = Cipher.getInstance("RSA/None/PKCS1Padding") + //初始化Cipher,mode指定是加密还是解密,key为公钥或私钥 + cipher.init(Cipher.ENCRYPT_MODE, key) + //处理数据 + resultBytes = cipher.doFinal(srcData) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: NoSuchPaddingException) { + e.printStackTrace() + } catch (e: InvalidKeyException) { + e.printStackTrace() + } catch (e: BadPaddingException) { + e.printStackTrace() + } catch (e: IllegalBlockSizeException) { + e.printStackTrace() + } + return resultBytes + } + + fun encryptDataByPublicKey(srcData: ByteArray, publicKey: PublicKey): String { + val resultBytes = processData(srcData, publicKey) + return Base64.encodeToString(resultBytes, Base64.DEFAULT) + } + + fun keyStrToPublicKey(publicKeyStr: String?): PublicKey? { + var publicKey: PublicKey? = null + val keyBytes = Base64.decode(publicKeyStr, Base64.DEFAULT) + val keySpec = X509EncodedKeySpec(keyBytes) + try { + val keyFactory = KeyFactory.getInstance("RSA") + publicKey = keyFactory.generatePublic(keySpec) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: InvalidKeySpecException) { + e.printStackTrace() + } + return publicKey + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt index 63db0ec..50d1742 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt @@ -1,12 +1,64 @@ package com.casic.smart.town.sanxi.util.retrofit -import retrofit2.http.GET +import retrofit2.http.* interface RetrofitService { + /** + * PublicKey校验 + */ + @GET("/config/baseConfig") + suspend fun obtainPublicKey(): String + + /** + * 登录并获取Token + * + * @param sid + * @param account 用户名 + * @param secretKey 加密后的密码 + */ + @FormUrlEncoded + @POST("/user/login") + suspend fun login( + @Field("sid") sid: String, + @Field("username") account: String, + @Field("password") secretKey: String + ): String + + /** + * 退出登录 + */ + @GET("/user/logout") + suspend fun loginOut(@Header("token") token: String): String + + /** + * 获取用户信息 + */ + @GET("/user/info") + suspend fun obtainUserDetail(@Header("token") token: String): String + + /** + * 修改密码 + * + * @param oldPwd 旧密码 + * @param newPwd 新密码 + */ + @FormUrlEncoded + @POST("/mgr/changePwd") + suspend fun changePassword( + @Header("token") token: String, + @Field("oldPwd") oldPwd: String, + @Field("newPwd") newPwd: String + ): String /** * 根据布防状态统计闸井数量接口 */ @GET("/well/countByBfzt") - suspend fun countWellByState(): String + suspend fun countWellByState(@Header("token") token: String): String + + /** + * 更新APK版本 + */ + @POST("/app/checkVersion") + suspend fun obtainVersionResult(@Header("token") token: String): String } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt index 46bbf4b..410444b 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt @@ -1,5 +1,6 @@ package com.casic.smart.town.sanxi.util.retrofit +import com.casic.smart.town.sanxi.util.AuthenticationHelper import com.casic.smart.town.sanxi.util.LocaleConstant import com.pengxh.kt.lite.utils.RetrofitFactory import com.pengxh.kt.lite.utils.SaveKeyValues @@ -15,9 +16,52 @@ } /** + * 验证PublicKey + */ + suspend fun authenticate(): String { + return api.obtainPublicKey() + } + + /** + * 登录并获取Token + */ + suspend fun login(sid: String, account: String, secretKey: String): String { + return api.login(sid, account, secretKey) + } + + /** + * 退出登录 + */ + suspend fun loginOut(): String { + return api.loginOut(AuthenticationHelper.token!!) + } + + /** + * 获取用户信息 + */ + suspend fun obtainUserDetail(): String { + return api.obtainUserDetail(AuthenticationHelper.token!!) + } + + /** + * 修改密码 + */ + suspend fun changePassword(oldPwd: String, newPwd: String): String { + return api.changePassword(AuthenticationHelper.token!!, oldPwd, newPwd) + } + + /** * 根据布防状态统计闸井数量接口 */ suspend fun countWellByState(): String { - return api.countWellByState() + return api.countWellByState(AuthenticationHelper.token!!) } + + /** + * 更新APK版本 + */ + suspend fun updateVersion(): String { + return api.obtainVersionResult(AuthenticationHelper.token!!) + } + } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/AboutUsActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/AboutUsActivity.kt new file mode 100644 index 0000000..7e43c47 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/AboutUsActivity.kt @@ -0,0 +1,34 @@ +package com.casic.smart.town.sanxi.view + +import com.casic.smart.town.sanxi.BuildConfig +import com.casic.smart.town.sanxi.R +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.kt.lite.base.KotlinBaseActivity +import com.pengxh.kt.lite.extensions.convertColor +import com.pengxh.kt.lite.utils.ImmerseStatusBarUtil +import kotlinx.android.synthetic.main.activity_about_us.* +import kotlinx.android.synthetic.main.include_base_title.* + +class AboutUsActivity : KotlinBaseActivity() { + + override fun initLayoutView(): Int = R.layout.activity_about_us + + override fun setupTopBarLayout() { + ImmersionBar.with(this).statusBarDarkFont(false).init() + ImmerseStatusBarUtil.setColor(this, R.color.mainThemeColor.convertColor(this)) + titleView.text = "关于我们" + leftBackView.setOnClickListener { finish() } + } + + override fun initData() { + versionCodeView.text = String.format("版本信息:V${BuildConfig.VERSION_NAME}") + } + + override fun observeRequestState() { + + } + + override fun initEvent() { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/LoginActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/LoginActivity.kt new file mode 100644 index 0000000..b5d1d50 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/LoginActivity.kt @@ -0,0 +1,97 @@ +package com.casic.smart.town.sanxi.view + +import androidx.lifecycle.ViewModelProvider +import com.casic.smart.town.sanxi.R +import com.casic.smart.town.sanxi.util.AuthenticationHelper +import com.casic.smart.town.sanxi.util.LoadingDialogHub +import com.casic.smart.town.sanxi.util.LocaleConstant +import com.casic.smart.town.sanxi.util.RSAUtils +import com.casic.smart.town.sanxi.vm.AuthenticateViewModel +import com.casic.smart.town.sanxi.vm.LoginViewModel +import com.casic.smart.town.sanxi.vm.UserViewModel +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.kt.lite.base.KotlinBaseActivity +import com.pengxh.kt.lite.extensions.navigatePageTo +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.utils.SaveKeyValues +import kotlinx.android.synthetic.main.activity_login.* + +class LoginActivity : KotlinBaseActivity() { + + private lateinit var authenticateViewModel: AuthenticateViewModel + private lateinit var loginViewModel: LoginViewModel + private lateinit var userViewModel: UserViewModel + + override fun initLayoutView(): Int = R.layout.activity_login + + override fun setupTopBarLayout() { + ImmersionBar.with(this).statusBarDarkFont(true).init() + } + + override fun initData() { + // 设置默认账号密码 + userNameView.setText(SaveKeyValues.getValue(LocaleConstant.ACCOUNT, "") as String) + userPasswordView.setText(SaveKeyValues.getValue(LocaleConstant.PASSWORD, "") as String) + authenticateViewModel = ViewModelProvider(this)[AuthenticateViewModel::class.java] + loginViewModel = ViewModelProvider(this)[LoginViewModel::class.java] + userViewModel = ViewModelProvider(this)[UserViewModel::class.java] + + authenticateViewModel.keyModel.observe(this, { + if (it.code == 200) {//用code判断,别的判断可能有坑 + val keyString = it.data!!.publicKey!! + /** + * 修改密码需要用到key,先存着 + * */ + AuthenticationHelper.savePublicKey(keyString) + val publicKey = RSAUtils.keyStrToPublicKey(keyString) + + val account = userNameView.text.toString() + val userPassword = userPasswordView.text.toString() + val dataByPublicKey = + RSAUtils.encryptDataByPublicKey(userPassword.toByteArray(), publicKey!!) + //登录并获取Token,POST请求 + loginViewModel.enter(it.data!!.sid!!, account, dataByPublicKey) + loginViewModel.enterResultModel.observe(this, { loginResult -> + if (loginResult.code == 200) { + AuthenticationHelper.saveToken(loginResult.data!!.token!!) + /** + * 获取token之后保存用户信息 + * */ + userViewModel.obtainUserDetail() + //验证成功登录 + navigatePageTo() + finish() + } + }) + } + }) + } + + override fun observeRequestState() { + authenticateViewModel.loadState.observe(this, { + LoadingDialogHub.show(this, "登录中,请稍后") + }) + + loginViewModel.loadState.observe(this, { + LoadingDialogHub.dismiss() + }) + } + + override fun initEvent() { + loginButton.setOnClickListener { + val account = userNameView.text.toString().trim() + val userPassword = userPasswordView.text.toString().trim() + if (account.isBlank()) { + "用户名不能为空".show(this) + return@setOnClickListener + } + if (userPassword.isBlank()) { + "密码不能为空".show(this) + return@setOnClickListener + } + SaveKeyValues.putValue(LocaleConstant.ACCOUNT, account) + SaveKeyValues.putValue(LocaleConstant.PASSWORD, userPassword) + authenticateViewModel.obtainPublicKey() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt index 3bea1c9..a4fa951 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt @@ -7,6 +7,7 @@ import com.casic.smart.town.sanxi.R import com.casic.smart.town.sanxi.adapter.ViewPagerAdapter import com.casic.smart.town.sanxi.fragment.AlarmPageFragment +import com.casic.smart.town.sanxi.fragment.MinePageFragment import com.casic.smart.town.sanxi.fragment.MonitorPageFragment import com.casic.smart.town.sanxi.fragment.StatisticsPageFragment import com.gyf.immersionbar.ImmersionBar @@ -33,6 +34,7 @@ fragmentPages.add(StatisticsPageFragment()) fragmentPages.add(MonitorPageFragment()) fragmentPages.add(AlarmPageFragment()) + fragmentPages.add(MinePageFragment()) } override fun initLayoutView(): Int = R.layout.activity_main @@ -68,6 +70,9 @@ R.id.nav_alarm -> { mainViewPager.currentItem = 2 } + R.id.nav_more -> { + mainViewPager.currentItem = 3 + } } false } diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/PermissionActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/PermissionActivity.kt index c478d83..3238f3c 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/view/PermissionActivity.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/PermissionActivity.kt @@ -32,7 +32,7 @@ //先把导航隐私政策声明,后面导航会用到 MapsInitializer.updatePrivacyAgree(this, true) MapsInitializer.updatePrivacyShow(this, true, true) - this.navigatePageTo() + this.navigatePageTo() finish() } diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/SplashScreenActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/SplashScreenActivity.kt new file mode 100644 index 0000000..fa5a74d --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/SplashScreenActivity.kt @@ -0,0 +1,54 @@ +package com.casic.smart.town.sanxi.view + +import android.annotation.SuppressLint +import android.os.CountDownTimer +import androidx.lifecycle.ViewModelProvider +import com.casic.smart.town.sanxi.R +import com.casic.smart.town.sanxi.vm.UserViewModel +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.kt.lite.base.KotlinBaseActivity +import com.pengxh.kt.lite.extensions.navigatePageTo + +@SuppressLint("CustomSplashScreen") +class SplashScreenActivity : KotlinBaseActivity() { + + private lateinit var userDetailViewModel: UserViewModel + private val countDownTimer = object : CountDownTimer(1000, 500) { + override fun onFinish() { + /** + * 获取token之后保存用户信息 + * */ + userDetailViewModel.obtainUserDetail() + userDetailViewModel.userDetailModel.observe(this@SplashScreenActivity, { + if (it.code == 200) { + navigatePageTo() + } else { + navigatePageTo() + } + finish() + }) + } + + override fun onTick(millisUntilFinished: Long) { + + } + } + + override fun initLayoutView(): Int = R.layout.activity_splash + + override fun setupTopBarLayout() { + ImmersionBar.with(this).statusBarDarkFont(false).init() + } + + override fun initData() { + userDetailViewModel = ViewModelProvider(this).get(UserViewModel::class.java) + } + + override fun observeRequestState() { + + } + + override fun initEvent() { + countDownTimer.start() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/vm/AuthenticateViewModel.kt b/app/src/main/java/com/casic/smart/town/sanxi/vm/AuthenticateViewModel.kt new file mode 100644 index 0000000..87ddbf8 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/vm/AuthenticateViewModel.kt @@ -0,0 +1,35 @@ +package com.casic.smart.town.sanxi.vm + +import androidx.lifecycle.MutableLiveData +import com.casic.smart.town.sanxi.base.BaseApplication +import com.casic.smart.town.sanxi.extensions.separateResponseCode +import com.casic.smart.town.sanxi.extensions.toErrorMessage +import com.casic.smart.town.sanxi.model.PublicKeyModel +import com.casic.smart.town.sanxi.util.retrofit.RetrofitServiceManager +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.launch +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.vm.BaseViewModel +import com.pengxh.kt.lite.vm.LoadState + +class AuthenticateViewModel : BaseViewModel() { + + private val gson = Gson() + val keyModel = MutableLiveData() + + fun obtainPublicKey() = launch({ + loadState.value = LoadState.Loading + val response = RetrofitServiceManager.authenticate() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + keyModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ) + } else { + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + it.printStackTrace() + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/vm/LoginViewModel.kt b/app/src/main/java/com/casic/smart/town/sanxi/vm/LoginViewModel.kt new file mode 100644 index 0000000..c377c61 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/vm/LoginViewModel.kt @@ -0,0 +1,54 @@ +package com.casic.smart.town.sanxi.vm + +import androidx.lifecycle.MutableLiveData +import com.casic.smart.town.sanxi.base.BaseApplication +import com.casic.smart.town.sanxi.extensions.separateResponseCode +import com.casic.smart.town.sanxi.extensions.toErrorMessage +import com.casic.smart.town.sanxi.model.CommonResultModel +import com.casic.smart.town.sanxi.model.LoginResultModel +import com.casic.smart.town.sanxi.util.retrofit.RetrofitServiceManager +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.launch +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.vm.BaseViewModel +import com.pengxh.kt.lite.vm.LoadState + +class LoginViewModel : BaseViewModel() { + + private val gson = Gson() + val enterResultModel = MutableLiveData() + val outResultModel = MutableLiveData() + + fun enter(sid: String, account: String, secretKey: String) = launch({ + val response = RetrofitServiceManager.login(sid, account, secretKey) + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + loadState.value = LoadState.Success + "登录成功".show(BaseApplication.obtainInstance()) + enterResultModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ) + } else { + loadState.value = LoadState.Fail + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + loadState.value = LoadState.Fail + it.printStackTrace() + }) + + fun out() = launch({ + val response = RetrofitServiceManager.loginOut() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + outResultModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ) + } else { + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + it.printStackTrace() + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/vm/UserViewModel.kt b/app/src/main/java/com/casic/smart/town/sanxi/vm/UserViewModel.kt new file mode 100644 index 0000000..29a8756 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/vm/UserViewModel.kt @@ -0,0 +1,57 @@ +package com.casic.smart.town.sanxi.vm + +import androidx.lifecycle.MutableLiveData +import com.casic.smart.town.sanxi.base.BaseApplication +import com.casic.smart.town.sanxi.extensions.separateResponseCode +import com.casic.smart.town.sanxi.extensions.toErrorMessage +import com.casic.smart.town.sanxi.model.UserDetailModel +import com.casic.smart.town.sanxi.util.LocaleConstant +import com.casic.smart.town.sanxi.util.retrofit.RetrofitServiceManager +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.launch +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.utils.SaveKeyValues +import com.pengxh.kt.lite.vm.BaseViewModel +import com.pengxh.kt.lite.vm.LoadState + +class UserViewModel : BaseViewModel() { + + private val gson = Gson() + val userDetailModel = MutableLiveData() + + fun obtainUserDetail() = launch({ + val response = RetrofitServiceManager.obtainUserDetail() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + val userDetail = gson.fromJson( + response, object : TypeToken() {}.type + ) + userDetailModel.value = userDetail + SaveKeyValues.putValue(LocaleConstant.USER_DETAIL_MODEL, gson.toJson(userDetail.data)) + } else { + val errorModel = UserDetailModel() + errorModel.code = responseCode + userDetailModel.value = errorModel + //如果此次获取不到用户信息,那么就清空之前的用户缓存,然后让用户重新登录 + SaveKeyValues.removeKey(LocaleConstant.USER_DETAIL_MODEL) + } + }, { + it.printStackTrace() + }) + + fun changePassword(oldPwd: String, newPwd: String) = launch({ + loadState.value = LoadState.Loading + val response = RetrofitServiceManager.changePassword(oldPwd, newPwd) + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + loadState.value = LoadState.Success + } else { + loadState.value = LoadState.Fail + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + loadState.value = LoadState.Fail + it.printStackTrace() + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/vm/VersionViewModel.kt b/app/src/main/java/com/casic/smart/town/sanxi/vm/VersionViewModel.kt new file mode 100644 index 0000000..20ea457 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/vm/VersionViewModel.kt @@ -0,0 +1,38 @@ +package com.casic.smart.town.sanxi.vm + +import androidx.lifecycle.MutableLiveData +import com.casic.smart.town.sanxi.base.BaseApplication +import com.casic.smart.town.sanxi.extensions.separateResponseCode +import com.casic.smart.town.sanxi.extensions.toErrorMessage +import com.casic.smart.town.sanxi.model.VersionResultModel +import com.casic.smart.town.sanxi.util.retrofit.RetrofitServiceManager +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.launch +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.vm.BaseViewModel +import com.pengxh.kt.lite.vm.LoadState + +class VersionViewModel : BaseViewModel() { + + private val gson = Gson() + val versionResultModel = MutableLiveData() + + fun updateVersion() = launch({ + loadState.value = LoadState.Loading + val response = RetrofitServiceManager.updateVersion() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + loadState.value = LoadState.Success + versionResultModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ).data + } else { + loadState.value = LoadState.Fail + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + loadState.value = LoadState.Fail + it.printStackTrace() + }) +} \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_solid_layout_gray_radius_5.xml b/app/src/main/res/drawable/bg_solid_layout_gray_radius_5.xml new file mode 100644 index 0000000..96eb93e --- /dev/null +++ b/app/src/main/res/drawable/bg_solid_layout_gray_radius_5.xml @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/button_main_selector.xml b/app/src/main/res/drawable/button_main_selector.xml new file mode 100644 index 0000000..5875998 --- /dev/null +++ b/app/src/main/res/drawable/button_main_selector.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/button_white_selector.xml b/app/src/main/res/drawable/button_white_selector.xml new file mode 100644 index 0000000..4dd3c0b --- /dev/null +++ b/app/src/main/res/drawable/button_white_selector.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/download_progress.xml b/app/src/main/res/drawable/download_progress.xml new file mode 100644 index 0000000..493d923 --- /dev/null +++ b/app/src/main/res/drawable/download_progress.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_about.xml b/app/src/main/res/drawable/ic_about.xml new file mode 100644 index 0000000..8469319 --- /dev/null +++ b/app/src/main/res/drawable/ic_about.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1df917d..8d4f33b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -6,6 +6,7 @@ + @@ -32,11 +33,14 @@ + + + + + + + diff --git a/app/src/main/assets/svg/mine.svg b/app/src/main/assets/svg/mine.svg new file mode 100644 index 0000000..3fbb0a3 --- /dev/null +++ b/app/src/main/assets/svg/mine.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt b/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt index d730417..b1972c2 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt @@ -1,8 +1,10 @@ package com.casic.smart.town.sanxi.extensions import com.casic.smart.town.sanxi.model.ErrorMessageModel +import com.casic.smart.town.sanxi.util.LocaleConstant import com.google.gson.Gson import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.utils.SaveKeyValues import org.json.JSONObject fun String.separateResponseCode(): Int { @@ -17,4 +19,15 @@ this, object : TypeToken() {}.type ) return errorModel.message.toString() +} + +/** + * 下载路径为 http://xx.com/static/ 拼接downloadUrl + * */ +fun String.appendDownloadUrl(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + LocaleConstant.DEFAULT_SERVER_CONFIG, LocaleConstant.DEFAULT_SERVER + ) as String + return "$defaultValue/static/${this}" } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt b/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt new file mode 100644 index 0000000..1809dc5 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt @@ -0,0 +1,288 @@ +package com.casic.smart.town.sanxi.fragment + +import android.app.ProgressDialog +import android.content.Intent +import android.net.Uri +import android.os.Build +import android.os.CountDownTimer +import androidx.core.content.FileProvider +import androidx.lifecycle.ViewModelProvider +import com.casic.smart.town.sanxi.BuildConfig +import com.casic.smart.town.sanxi.R +import com.casic.smart.town.sanxi.extensions.appendDownloadUrl +import com.casic.smart.town.sanxi.model.UserDetailModel +import com.casic.smart.town.sanxi.util.AuthenticationHelper +import com.casic.smart.town.sanxi.util.LoadingDialogHub +import com.casic.smart.town.sanxi.util.LocaleConstant +import com.casic.smart.town.sanxi.util.RSAUtils +import com.casic.smart.town.sanxi.view.AboutUsActivity +import com.casic.smart.town.sanxi.view.LoginActivity +import com.casic.smart.town.sanxi.vm.LoginViewModel +import com.casic.smart.town.sanxi.vm.UserViewModel +import com.casic.smart.town.sanxi.vm.VersionViewModel +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.base.KotlinBaseFragment +import com.pengxh.kt.lite.callback.OnDownloadListener +import com.pengxh.kt.lite.extensions.* +import com.pengxh.kt.lite.utils.PageNavigationManager +import com.pengxh.kt.lite.utils.SaveKeyValues +import com.pengxh.kt.lite.vm.LoadState +import com.pengxh.kt.lite.widget.dialog.AlertControlDialog +import com.pengxh.kt.lite.widget.dialog.ChangePasswordDialog +import kotlinx.android.synthetic.main.fragment_mine.* +import java.io.File +import java.nio.charset.StandardCharsets + +class MinePageFragment : KotlinBaseFragment() { + + private lateinit var userData: UserDetailModel.Data + private lateinit var userViewModel: UserViewModel + private lateinit var loginViewModel: LoginViewModel + private lateinit var versionViewModel: VersionViewModel + private lateinit var progressDialog: ProgressDialog + + override fun initLayoutView(): Int = R.layout.fragment_mine + + override fun setupTopBarLayout() { + + } + + override fun initData() { + userViewModel = ViewModelProvider(this)[UserViewModel::class.java] + loginViewModel = ViewModelProvider(this)[LoginViewModel::class.java] + versionViewModel = ViewModelProvider(this)[VersionViewModel::class.java] + + //初始化下载对话框 + progressDialog = ProgressDialog(requireContext()) + progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL) + progressDialog.setProgressDrawable(resources.getDrawable(R.drawable.download_progress)) + progressDialog.setCanceledOnTouchOutside(false) + progressDialog.setCancelable(false) + + /** + * 数据监听 + * */ + dataObserve() + } + + override fun observeRequestState() { + userViewModel.loadState.observe(this, { + when (it) { + is LoadState.Loading -> LoadingDialogHub.show(requireActivity(), "修改中,请稍后") + is LoadState.Success -> { + "修改成功,请重新登录".show(requireContext()) + LoadingDialogHub.dismiss() + AuthenticationHelper.removeToken() + requireContext().navigatePageTo() + PageNavigationManager.finishAllActivity() + } + else -> LoadingDialogHub.dismiss() + } + }) + + versionViewModel.loadState.observe(this, { + when (it) { + is LoadState.Loading -> LoadingDialogHub.show(requireActivity(), "检查版本中,请稍后") + else -> LoadingDialogHub.dismiss() + } + }) + } + + override fun initEvent() { + userImageView.setOnClickListener { + "尽情期待~".show(requireContext()) + } + + updateUserButton.setOnClickListener { + LoadingDialogHub.show(requireActivity(), "同步中,请稍后...") + userViewModel.obtainUserDetail() + } + + changePwdLayout.setOnClickListener { + ChangePasswordDialog.Builder() + .setContext(requireContext()) + .setOnDialogButtonClickListener(object : + ChangePasswordDialog.OnDialogButtonClickListener { + override fun onConfirmClick(oldPwd: String, newPwd: String) { + val publicKey = + RSAUtils.keyStrToPublicKey(AuthenticationHelper.publicKey)!! + val oldPassKey = RSAUtils.encryptDataByPublicKey( + oldPwd.toByteArray(StandardCharsets.UTF_8), + publicKey + ) + val newPassKey = RSAUtils.encryptDataByPublicKey( + newPwd.toByteArray(StandardCharsets.UTF_8), + publicKey + ) + userViewModel.changePassword(oldPassKey, newPassKey) + } + }).build().show() + } + + aboutUsLayout.setOnClickListener { + requireContext().navigatePageTo() + } + + updateVersionLayout.setOnClickListener { + versionViewModel.updateVersion() + } + + clearCacheLayout.setOnClickListener { + //删除缓存之后在设置缓存大小 + LoadingDialogHub.show(requireActivity(), "清理中,请稍后") + File(requireContext().cacheDir.path).deleteFile() + object : CountDownTimer(1500, 500) { + override fun onTick(millisUntilFinished: Long) { + + } + + override fun onFinish() { + LoadingDialogHub.dismiss() + cacheSizeView.text = collectApplicationCache().formatFileSize() + } + }.start() + } + + loginOutButton.setOnClickListener { + AlertControlDialog.Builder() + .setContext(requireContext()) + .setTitle("退出登录") + .setMessage("确定要退出吗?") + .setNegativeButton("取消") + .setPositiveButton("确定") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + loginViewModel.out() + } + + override fun onCancelClick() {} + }).build().show() + } + } + + private fun dataObserve() { + userViewModel.userDetailModel.observe(this, { + if (it.code == 200) { + LoadingDialogHub.dismiss() + "同步完成".show(requireContext()) + userData = it.data + updateUserInfo() + } + }) + + versionViewModel.versionResultModel.observe(this, { + if (BuildConfig.VERSION_NAME == it.version) { + "已是最新版本,无需更新".show(requireContext()) + } else { + AlertControlDialog.Builder() + .setContext(requireContext()) + .setTitle("提示") + .setMessage("有新版本,是否更新?") + .setNegativeButton("稍后再说") + .setPositiveButton("立即下载") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + downloadApk(it.downloadUrl) + } + + override fun onCancelClick() { + + } + }).build().show() + } + }) + + loginViewModel.outResultModel.observe(this, { + if (it.code == 200) { + AuthenticationHelper.removeToken() + requireContext().navigatePageTo() + PageNavigationManager.finishAllActivity() + } + }) + } + + private fun collectApplicationCache(): Long { + return File(requireContext().cacheDir.path).calculateSize() + } + + override fun onResume() { + val userDetailJson = SaveKeyValues.getValue(LocaleConstant.USER_DETAIL_MODEL, "") as String + if (userDetailJson.isNotBlank()) { + userData = Gson().fromJson( + userDetailJson, object : TypeToken() {}.type + ) + updateUserInfo() + } + //缓存 + cacheSizeView.text = collectApplicationCache().formatFileSize() + super.onResume() + } + + private fun updateUserInfo() { + //设置头像,圆形,暂时是默认的 +// val roundDrawable = +// BitmapFactory.decodeResource(requireContext().resources, R.mipmap.login_casic) +// .createRoundDrawable( +// requireContext(), +// 3f.dp2px(requireContext()), +// R.color.mainThemeColor.convertColor(requireContext()) +// ) +// userImageView.setImageDrawable(roundDrawable) + userNameView.text = userData.name + userPhoneView.text = String.format("电话:${userData.phone}") + userDeptView.text = String.format("部门:${userData.deptName}") + } + + private fun downloadApk(url: String?) { + progressDialog.setMessage("下载新版本中...") + progressDialog.show() + if (url.toString().isBlank()) { + "抱歉,版本下载失败".show(requireContext()) + return + } + /** + * http://111.198.10.15:11304/static/apk/1.0.1.apk + * */ + val downloadPath = url!!.appendDownloadUrl() + //开始下载 + downloadPath.downloadFile(requireContext().createDownloadFileDir().toString(), + object : OnDownloadListener { + override fun onDownloadStart(totalBytes: Long) { + progressDialog.max = totalBytes.toInt() + } + + override fun onProgressChanged(currentBytes: Long) { + progressDialog.progress = currentBytes.toInt() + } + + override fun onDownloadEnd(file: File?) { + progressDialog.dismiss() + progressDialog.progress = 0 + //安装APK + installApk(file) + } + }) + } + + private fun installApk(apkPackage: File?) { + if (apkPackage == null) { + "文件异常,无法安装".show(requireContext()) + return + } + val intent = Intent(Intent.ACTION_VIEW) + val data: Uri + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { //判断版本大于等于7.0 + data = FileProvider.getUriForFile( + requireContext(), LocaleConstant.APP_AUTHORITY, apkPackage + ) + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) // 给目标应用一个临时授权 + } else { + data = Uri.fromFile(apkPackage) + } + intent.setDataAndType(data, "application/vnd.android.package-archive") + requireContext().startActivity(intent) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java new file mode 100644 index 0000000..467e33d --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java @@ -0,0 +1,43 @@ +package com.casic.smart.town.sanxi.model; + +/** + * 普通实体类,失败/成功数据结构一致 + */ +public class CommonResultModel { + private int code; + private String data; + private String message; + private boolean isSuccess; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return isSuccess; + } + + public void setSuccess(boolean success) { + isSuccess = success; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java new file mode 100644 index 0000000..9bdabef --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java @@ -0,0 +1,62 @@ +package com.casic.smart.town.sanxi.model; + +public class LoginResultModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private String kaptcha; + private String token; + + public String getKaptcha() { + return kaptcha; + } + + public void setKaptcha(String kaptcha) { + this.kaptcha = kaptcha; + } + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java new file mode 100644 index 0000000..09f8d59 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java @@ -0,0 +1,80 @@ +package com.casic.smart.town.sanxi.model; + +public class PublicKeyModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private boolean appKaptcha; + private boolean kaptcha; + private String publicKey; + private String sid; + + public boolean isAppKaptcha() { + return appKaptcha; + } + + public void setAppKaptcha(boolean appKaptcha) { + this.appKaptcha = appKaptcha; + } + + public boolean isKaptcha() { + return kaptcha; + } + + public void setKaptcha(boolean kaptcha) { + this.kaptcha = kaptcha; + } + + public String getPublicKey() { + return publicKey; + } + + public void setPublicKey(String publicKey) { + this.publicKey = publicKey; + } + + public String getSid() { + return sid; + } + + public void setSid(String sid) { + this.sid = sid; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java new file mode 100644 index 0000000..db577fb --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java @@ -0,0 +1,227 @@ +package com.casic.smart.town.sanxi.model; + + +import java.util.List; + +public class UserDetailModel { + private int code; + private Data data; + private String message; + private boolean success; + + public void setCode(int code) { + this.code = code; + } + + public int getCode() { + return code; + } + + public void setData(Data data) { + this.data = data; + } + + public Data getData() { + return data; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getMessage() { + return message; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public boolean getSuccess() { + return success; + } + + public static class Data { + + private String account; + private String attr1; + private String avatar; + private String bizData; + private List dataScope; + private String deptId; + private String deptName; + private List devices; + private String id; + private String ipAddr; + private String name; + private String phone; + private List roleList; + private List roleNames; + private List roleTips; + private String scopeType; + private String sysData; + private String targetId; + private String targetName; + private String tenantId; + + public void setAccount(String account) { + this.account = account; + } + + public String getAccount() { + return account; + } + + public void setAttr1(String attr1) { + this.attr1 = attr1; + } + + public String getAttr1() { + return attr1; + } + + public void setAvatar(String avatar) { + this.avatar = avatar; + } + + public String getAvatar() { + return avatar; + } + + public void setBizData(String bizData) { + this.bizData = bizData; + } + + public String getBizData() { + return bizData; + } + + public void setDataScope(List dataScope) { + this.dataScope = dataScope; + } + + public List getDataScope() { + return dataScope; + } + + public void setDeptId(String deptId) { + this.deptId = deptId; + } + + public String getDeptId() { + return deptId; + } + + public void setDeptName(String deptName) { + this.deptName = deptName; + } + + public String getDeptName() { + return deptName; + } + + public void setDevices(List devices) { + this.devices = devices; + } + + public List getDevices() { + return devices; + } + + public void setId(String id) { + this.id = id; + } + + public String getId() { + return id; + } + + public void setIpAddr(String ipAddr) { + this.ipAddr = ipAddr; + } + + public String getIpAddr() { + return ipAddr; + } + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setPhone(String phone) { + this.phone = phone; + } + + public String getPhone() { + return phone; + } + + public void setRoleList(List roleList) { + this.roleList = roleList; + } + + public List getRoleList() { + return roleList; + } + + public void setRoleNames(List roleNames) { + this.roleNames = roleNames; + } + + public List getRoleNames() { + return roleNames; + } + + public void setRoleTips(List roleTips) { + this.roleTips = roleTips; + } + + public List getRoleTips() { + return roleTips; + } + + public void setScopeType(String scopeType) { + this.scopeType = scopeType; + } + + public String getScopeType() { + return scopeType; + } + + public void setSysData(String sysData) { + this.sysData = sysData; + } + + public String getSysData() { + return sysData; + } + + public void setTargetId(String targetId) { + this.targetId = targetId; + } + + public String getTargetId() { + return targetId; + } + + public void setTargetName(String targetName) { + this.targetName = targetName; + } + + public String getTargetName() { + return targetName; + } + + public void setTenantId(String tenantId) { + this.tenantId = tenantId; + } + + public String getTenantId() { + return tenantId; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/VersionResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/VersionResultModel.java new file mode 100644 index 0000000..79374e0 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/VersionResultModel.java @@ -0,0 +1,61 @@ +package com.casic.smart.town.sanxi.model; + +public class VersionResultModel { + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private String downloadUrl; + private String version; + + public String getDownloadUrl() { + return downloadUrl; + } + + public void setDownloadUrl(String downloadUrl) { + this.downloadUrl = downloadUrl; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/AuthenticationHelper.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/AuthenticationHelper.kt new file mode 100644 index 0000000..f498e4e --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/AuthenticationHelper.kt @@ -0,0 +1,24 @@ +package com.casic.smart.town.sanxi.util + +import com.pengxh.kt.lite.utils.SaveKeyValues + +object AuthenticationHelper { + + fun savePublicKey(key: String) { + SaveKeyValues.putValue("keyString", key) + } + + val publicKey: String? + get() = SaveKeyValues.getValue("keyString", "") as String? + + fun saveToken(token: String?) { + SaveKeyValues.putValue("token", token!!) + } + + val token: String? + get() = SaveKeyValues.getValue("token", "") as String? + + fun removeToken() { + SaveKeyValues.removeKey("token") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt index ab1a36d..99f0d19 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt @@ -14,5 +14,9 @@ const val PAGE_LIMIT = 20 const val DEFAULT_SERVER_CONFIG = "defaultServerConfig" - const val DEFAULT_SERVER = "http://111.198.10.15:11308" + const val DEFAULT_SERVER = "http://36.133.189.112:8082" + const val APP_AUTHORITY = "com.casic.smart.town.sanxi.fileprovider" + const val ACCOUNT = "account" + const val PASSWORD = "password" + const val USER_DETAIL_MODEL = "userDetailModel" } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/RSAUtils.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/RSAUtils.kt new file mode 100644 index 0000000..b9216ba --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/RSAUtils.kt @@ -0,0 +1,55 @@ +package com.casic.smart.town.sanxi.util + +import android.util.Base64 +import java.security.* +import java.security.spec.InvalidKeySpecException +import java.security.spec.X509EncodedKeySpec +import javax.crypto.BadPaddingException +import javax.crypto.Cipher +import javax.crypto.IllegalBlockSizeException +import javax.crypto.NoSuchPaddingException + +object RSAUtils { + //构建Cipher实例时所传入的的字符串,默认为"RSA/NONE/PKCS1Padding" + private fun processData(srcData: ByteArray, key: Key): ByteArray? { //用来保存处理结果 + var resultBytes: ByteArray? = null + try { //获取Cipher实例 + val cipher = Cipher.getInstance("RSA/None/PKCS1Padding") + //初始化Cipher,mode指定是加密还是解密,key为公钥或私钥 + cipher.init(Cipher.ENCRYPT_MODE, key) + //处理数据 + resultBytes = cipher.doFinal(srcData) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: NoSuchPaddingException) { + e.printStackTrace() + } catch (e: InvalidKeyException) { + e.printStackTrace() + } catch (e: BadPaddingException) { + e.printStackTrace() + } catch (e: IllegalBlockSizeException) { + e.printStackTrace() + } + return resultBytes + } + + fun encryptDataByPublicKey(srcData: ByteArray, publicKey: PublicKey): String { + val resultBytes = processData(srcData, publicKey) + return Base64.encodeToString(resultBytes, Base64.DEFAULT) + } + + fun keyStrToPublicKey(publicKeyStr: String?): PublicKey? { + var publicKey: PublicKey? = null + val keyBytes = Base64.decode(publicKeyStr, Base64.DEFAULT) + val keySpec = X509EncodedKeySpec(keyBytes) + try { + val keyFactory = KeyFactory.getInstance("RSA") + publicKey = keyFactory.generatePublic(keySpec) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: InvalidKeySpecException) { + e.printStackTrace() + } + return publicKey + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt index 63db0ec..50d1742 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt @@ -1,12 +1,64 @@ package com.casic.smart.town.sanxi.util.retrofit -import retrofit2.http.GET +import retrofit2.http.* interface RetrofitService { + /** + * PublicKey校验 + */ + @GET("/config/baseConfig") + suspend fun obtainPublicKey(): String + + /** + * 登录并获取Token + * + * @param sid + * @param account 用户名 + * @param secretKey 加密后的密码 + */ + @FormUrlEncoded + @POST("/user/login") + suspend fun login( + @Field("sid") sid: String, + @Field("username") account: String, + @Field("password") secretKey: String + ): String + + /** + * 退出登录 + */ + @GET("/user/logout") + suspend fun loginOut(@Header("token") token: String): String + + /** + * 获取用户信息 + */ + @GET("/user/info") + suspend fun obtainUserDetail(@Header("token") token: String): String + + /** + * 修改密码 + * + * @param oldPwd 旧密码 + * @param newPwd 新密码 + */ + @FormUrlEncoded + @POST("/mgr/changePwd") + suspend fun changePassword( + @Header("token") token: String, + @Field("oldPwd") oldPwd: String, + @Field("newPwd") newPwd: String + ): String /** * 根据布防状态统计闸井数量接口 */ @GET("/well/countByBfzt") - suspend fun countWellByState(): String + suspend fun countWellByState(@Header("token") token: String): String + + /** + * 更新APK版本 + */ + @POST("/app/checkVersion") + suspend fun obtainVersionResult(@Header("token") token: String): String } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt index 46bbf4b..410444b 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt @@ -1,5 +1,6 @@ package com.casic.smart.town.sanxi.util.retrofit +import com.casic.smart.town.sanxi.util.AuthenticationHelper import com.casic.smart.town.sanxi.util.LocaleConstant import com.pengxh.kt.lite.utils.RetrofitFactory import com.pengxh.kt.lite.utils.SaveKeyValues @@ -15,9 +16,52 @@ } /** + * 验证PublicKey + */ + suspend fun authenticate(): String { + return api.obtainPublicKey() + } + + /** + * 登录并获取Token + */ + suspend fun login(sid: String, account: String, secretKey: String): String { + return api.login(sid, account, secretKey) + } + + /** + * 退出登录 + */ + suspend fun loginOut(): String { + return api.loginOut(AuthenticationHelper.token!!) + } + + /** + * 获取用户信息 + */ + suspend fun obtainUserDetail(): String { + return api.obtainUserDetail(AuthenticationHelper.token!!) + } + + /** + * 修改密码 + */ + suspend fun changePassword(oldPwd: String, newPwd: String): String { + return api.changePassword(AuthenticationHelper.token!!, oldPwd, newPwd) + } + + /** * 根据布防状态统计闸井数量接口 */ suspend fun countWellByState(): String { - return api.countWellByState() + return api.countWellByState(AuthenticationHelper.token!!) } + + /** + * 更新APK版本 + */ + suspend fun updateVersion(): String { + return api.obtainVersionResult(AuthenticationHelper.token!!) + } + } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/AboutUsActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/AboutUsActivity.kt new file mode 100644 index 0000000..7e43c47 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/AboutUsActivity.kt @@ -0,0 +1,34 @@ +package com.casic.smart.town.sanxi.view + +import com.casic.smart.town.sanxi.BuildConfig +import com.casic.smart.town.sanxi.R +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.kt.lite.base.KotlinBaseActivity +import com.pengxh.kt.lite.extensions.convertColor +import com.pengxh.kt.lite.utils.ImmerseStatusBarUtil +import kotlinx.android.synthetic.main.activity_about_us.* +import kotlinx.android.synthetic.main.include_base_title.* + +class AboutUsActivity : KotlinBaseActivity() { + + override fun initLayoutView(): Int = R.layout.activity_about_us + + override fun setupTopBarLayout() { + ImmersionBar.with(this).statusBarDarkFont(false).init() + ImmerseStatusBarUtil.setColor(this, R.color.mainThemeColor.convertColor(this)) + titleView.text = "关于我们" + leftBackView.setOnClickListener { finish() } + } + + override fun initData() { + versionCodeView.text = String.format("版本信息:V${BuildConfig.VERSION_NAME}") + } + + override fun observeRequestState() { + + } + + override fun initEvent() { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/LoginActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/LoginActivity.kt new file mode 100644 index 0000000..b5d1d50 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/LoginActivity.kt @@ -0,0 +1,97 @@ +package com.casic.smart.town.sanxi.view + +import androidx.lifecycle.ViewModelProvider +import com.casic.smart.town.sanxi.R +import com.casic.smart.town.sanxi.util.AuthenticationHelper +import com.casic.smart.town.sanxi.util.LoadingDialogHub +import com.casic.smart.town.sanxi.util.LocaleConstant +import com.casic.smart.town.sanxi.util.RSAUtils +import com.casic.smart.town.sanxi.vm.AuthenticateViewModel +import com.casic.smart.town.sanxi.vm.LoginViewModel +import com.casic.smart.town.sanxi.vm.UserViewModel +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.kt.lite.base.KotlinBaseActivity +import com.pengxh.kt.lite.extensions.navigatePageTo +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.utils.SaveKeyValues +import kotlinx.android.synthetic.main.activity_login.* + +class LoginActivity : KotlinBaseActivity() { + + private lateinit var authenticateViewModel: AuthenticateViewModel + private lateinit var loginViewModel: LoginViewModel + private lateinit var userViewModel: UserViewModel + + override fun initLayoutView(): Int = R.layout.activity_login + + override fun setupTopBarLayout() { + ImmersionBar.with(this).statusBarDarkFont(true).init() + } + + override fun initData() { + // 设置默认账号密码 + userNameView.setText(SaveKeyValues.getValue(LocaleConstant.ACCOUNT, "") as String) + userPasswordView.setText(SaveKeyValues.getValue(LocaleConstant.PASSWORD, "") as String) + authenticateViewModel = ViewModelProvider(this)[AuthenticateViewModel::class.java] + loginViewModel = ViewModelProvider(this)[LoginViewModel::class.java] + userViewModel = ViewModelProvider(this)[UserViewModel::class.java] + + authenticateViewModel.keyModel.observe(this, { + if (it.code == 200) {//用code判断,别的判断可能有坑 + val keyString = it.data!!.publicKey!! + /** + * 修改密码需要用到key,先存着 + * */ + AuthenticationHelper.savePublicKey(keyString) + val publicKey = RSAUtils.keyStrToPublicKey(keyString) + + val account = userNameView.text.toString() + val userPassword = userPasswordView.text.toString() + val dataByPublicKey = + RSAUtils.encryptDataByPublicKey(userPassword.toByteArray(), publicKey!!) + //登录并获取Token,POST请求 + loginViewModel.enter(it.data!!.sid!!, account, dataByPublicKey) + loginViewModel.enterResultModel.observe(this, { loginResult -> + if (loginResult.code == 200) { + AuthenticationHelper.saveToken(loginResult.data!!.token!!) + /** + * 获取token之后保存用户信息 + * */ + userViewModel.obtainUserDetail() + //验证成功登录 + navigatePageTo() + finish() + } + }) + } + }) + } + + override fun observeRequestState() { + authenticateViewModel.loadState.observe(this, { + LoadingDialogHub.show(this, "登录中,请稍后") + }) + + loginViewModel.loadState.observe(this, { + LoadingDialogHub.dismiss() + }) + } + + override fun initEvent() { + loginButton.setOnClickListener { + val account = userNameView.text.toString().trim() + val userPassword = userPasswordView.text.toString().trim() + if (account.isBlank()) { + "用户名不能为空".show(this) + return@setOnClickListener + } + if (userPassword.isBlank()) { + "密码不能为空".show(this) + return@setOnClickListener + } + SaveKeyValues.putValue(LocaleConstant.ACCOUNT, account) + SaveKeyValues.putValue(LocaleConstant.PASSWORD, userPassword) + authenticateViewModel.obtainPublicKey() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt index 3bea1c9..a4fa951 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt @@ -7,6 +7,7 @@ import com.casic.smart.town.sanxi.R import com.casic.smart.town.sanxi.adapter.ViewPagerAdapter import com.casic.smart.town.sanxi.fragment.AlarmPageFragment +import com.casic.smart.town.sanxi.fragment.MinePageFragment import com.casic.smart.town.sanxi.fragment.MonitorPageFragment import com.casic.smart.town.sanxi.fragment.StatisticsPageFragment import com.gyf.immersionbar.ImmersionBar @@ -33,6 +34,7 @@ fragmentPages.add(StatisticsPageFragment()) fragmentPages.add(MonitorPageFragment()) fragmentPages.add(AlarmPageFragment()) + fragmentPages.add(MinePageFragment()) } override fun initLayoutView(): Int = R.layout.activity_main @@ -68,6 +70,9 @@ R.id.nav_alarm -> { mainViewPager.currentItem = 2 } + R.id.nav_more -> { + mainViewPager.currentItem = 3 + } } false } diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/PermissionActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/PermissionActivity.kt index c478d83..3238f3c 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/view/PermissionActivity.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/PermissionActivity.kt @@ -32,7 +32,7 @@ //先把导航隐私政策声明,后面导航会用到 MapsInitializer.updatePrivacyAgree(this, true) MapsInitializer.updatePrivacyShow(this, true, true) - this.navigatePageTo() + this.navigatePageTo() finish() } diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/SplashScreenActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/SplashScreenActivity.kt new file mode 100644 index 0000000..fa5a74d --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/SplashScreenActivity.kt @@ -0,0 +1,54 @@ +package com.casic.smart.town.sanxi.view + +import android.annotation.SuppressLint +import android.os.CountDownTimer +import androidx.lifecycle.ViewModelProvider +import com.casic.smart.town.sanxi.R +import com.casic.smart.town.sanxi.vm.UserViewModel +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.kt.lite.base.KotlinBaseActivity +import com.pengxh.kt.lite.extensions.navigatePageTo + +@SuppressLint("CustomSplashScreen") +class SplashScreenActivity : KotlinBaseActivity() { + + private lateinit var userDetailViewModel: UserViewModel + private val countDownTimer = object : CountDownTimer(1000, 500) { + override fun onFinish() { + /** + * 获取token之后保存用户信息 + * */ + userDetailViewModel.obtainUserDetail() + userDetailViewModel.userDetailModel.observe(this@SplashScreenActivity, { + if (it.code == 200) { + navigatePageTo() + } else { + navigatePageTo() + } + finish() + }) + } + + override fun onTick(millisUntilFinished: Long) { + + } + } + + override fun initLayoutView(): Int = R.layout.activity_splash + + override fun setupTopBarLayout() { + ImmersionBar.with(this).statusBarDarkFont(false).init() + } + + override fun initData() { + userDetailViewModel = ViewModelProvider(this).get(UserViewModel::class.java) + } + + override fun observeRequestState() { + + } + + override fun initEvent() { + countDownTimer.start() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/vm/AuthenticateViewModel.kt b/app/src/main/java/com/casic/smart/town/sanxi/vm/AuthenticateViewModel.kt new file mode 100644 index 0000000..87ddbf8 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/vm/AuthenticateViewModel.kt @@ -0,0 +1,35 @@ +package com.casic.smart.town.sanxi.vm + +import androidx.lifecycle.MutableLiveData +import com.casic.smart.town.sanxi.base.BaseApplication +import com.casic.smart.town.sanxi.extensions.separateResponseCode +import com.casic.smart.town.sanxi.extensions.toErrorMessage +import com.casic.smart.town.sanxi.model.PublicKeyModel +import com.casic.smart.town.sanxi.util.retrofit.RetrofitServiceManager +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.launch +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.vm.BaseViewModel +import com.pengxh.kt.lite.vm.LoadState + +class AuthenticateViewModel : BaseViewModel() { + + private val gson = Gson() + val keyModel = MutableLiveData() + + fun obtainPublicKey() = launch({ + loadState.value = LoadState.Loading + val response = RetrofitServiceManager.authenticate() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + keyModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ) + } else { + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + it.printStackTrace() + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/vm/LoginViewModel.kt b/app/src/main/java/com/casic/smart/town/sanxi/vm/LoginViewModel.kt new file mode 100644 index 0000000..c377c61 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/vm/LoginViewModel.kt @@ -0,0 +1,54 @@ +package com.casic.smart.town.sanxi.vm + +import androidx.lifecycle.MutableLiveData +import com.casic.smart.town.sanxi.base.BaseApplication +import com.casic.smart.town.sanxi.extensions.separateResponseCode +import com.casic.smart.town.sanxi.extensions.toErrorMessage +import com.casic.smart.town.sanxi.model.CommonResultModel +import com.casic.smart.town.sanxi.model.LoginResultModel +import com.casic.smart.town.sanxi.util.retrofit.RetrofitServiceManager +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.launch +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.vm.BaseViewModel +import com.pengxh.kt.lite.vm.LoadState + +class LoginViewModel : BaseViewModel() { + + private val gson = Gson() + val enterResultModel = MutableLiveData() + val outResultModel = MutableLiveData() + + fun enter(sid: String, account: String, secretKey: String) = launch({ + val response = RetrofitServiceManager.login(sid, account, secretKey) + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + loadState.value = LoadState.Success + "登录成功".show(BaseApplication.obtainInstance()) + enterResultModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ) + } else { + loadState.value = LoadState.Fail + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + loadState.value = LoadState.Fail + it.printStackTrace() + }) + + fun out() = launch({ + val response = RetrofitServiceManager.loginOut() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + outResultModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ) + } else { + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + it.printStackTrace() + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/vm/UserViewModel.kt b/app/src/main/java/com/casic/smart/town/sanxi/vm/UserViewModel.kt new file mode 100644 index 0000000..29a8756 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/vm/UserViewModel.kt @@ -0,0 +1,57 @@ +package com.casic.smart.town.sanxi.vm + +import androidx.lifecycle.MutableLiveData +import com.casic.smart.town.sanxi.base.BaseApplication +import com.casic.smart.town.sanxi.extensions.separateResponseCode +import com.casic.smart.town.sanxi.extensions.toErrorMessage +import com.casic.smart.town.sanxi.model.UserDetailModel +import com.casic.smart.town.sanxi.util.LocaleConstant +import com.casic.smart.town.sanxi.util.retrofit.RetrofitServiceManager +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.launch +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.utils.SaveKeyValues +import com.pengxh.kt.lite.vm.BaseViewModel +import com.pengxh.kt.lite.vm.LoadState + +class UserViewModel : BaseViewModel() { + + private val gson = Gson() + val userDetailModel = MutableLiveData() + + fun obtainUserDetail() = launch({ + val response = RetrofitServiceManager.obtainUserDetail() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + val userDetail = gson.fromJson( + response, object : TypeToken() {}.type + ) + userDetailModel.value = userDetail + SaveKeyValues.putValue(LocaleConstant.USER_DETAIL_MODEL, gson.toJson(userDetail.data)) + } else { + val errorModel = UserDetailModel() + errorModel.code = responseCode + userDetailModel.value = errorModel + //如果此次获取不到用户信息,那么就清空之前的用户缓存,然后让用户重新登录 + SaveKeyValues.removeKey(LocaleConstant.USER_DETAIL_MODEL) + } + }, { + it.printStackTrace() + }) + + fun changePassword(oldPwd: String, newPwd: String) = launch({ + loadState.value = LoadState.Loading + val response = RetrofitServiceManager.changePassword(oldPwd, newPwd) + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + loadState.value = LoadState.Success + } else { + loadState.value = LoadState.Fail + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + loadState.value = LoadState.Fail + it.printStackTrace() + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/vm/VersionViewModel.kt b/app/src/main/java/com/casic/smart/town/sanxi/vm/VersionViewModel.kt new file mode 100644 index 0000000..20ea457 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/vm/VersionViewModel.kt @@ -0,0 +1,38 @@ +package com.casic.smart.town.sanxi.vm + +import androidx.lifecycle.MutableLiveData +import com.casic.smart.town.sanxi.base.BaseApplication +import com.casic.smart.town.sanxi.extensions.separateResponseCode +import com.casic.smart.town.sanxi.extensions.toErrorMessage +import com.casic.smart.town.sanxi.model.VersionResultModel +import com.casic.smart.town.sanxi.util.retrofit.RetrofitServiceManager +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.launch +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.vm.BaseViewModel +import com.pengxh.kt.lite.vm.LoadState + +class VersionViewModel : BaseViewModel() { + + private val gson = Gson() + val versionResultModel = MutableLiveData() + + fun updateVersion() = launch({ + loadState.value = LoadState.Loading + val response = RetrofitServiceManager.updateVersion() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + loadState.value = LoadState.Success + versionResultModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ).data + } else { + loadState.value = LoadState.Fail + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + loadState.value = LoadState.Fail + it.printStackTrace() + }) +} \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_solid_layout_gray_radius_5.xml b/app/src/main/res/drawable/bg_solid_layout_gray_radius_5.xml new file mode 100644 index 0000000..96eb93e --- /dev/null +++ b/app/src/main/res/drawable/bg_solid_layout_gray_radius_5.xml @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/button_main_selector.xml b/app/src/main/res/drawable/button_main_selector.xml new file mode 100644 index 0000000..5875998 --- /dev/null +++ b/app/src/main/res/drawable/button_main_selector.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/button_white_selector.xml b/app/src/main/res/drawable/button_white_selector.xml new file mode 100644 index 0000000..4dd3c0b --- /dev/null +++ b/app/src/main/res/drawable/button_white_selector.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/download_progress.xml b/app/src/main/res/drawable/download_progress.xml new file mode 100644 index 0000000..493d923 --- /dev/null +++ b/app/src/main/res/drawable/download_progress.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_about.xml b/app/src/main/res/drawable/ic_about.xml new file mode 100644 index 0000000..8469319 --- /dev/null +++ b/app/src/main/res/drawable/ic_about.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_change_pwd.xml b/app/src/main/res/drawable/ic_change_pwd.xml new file mode 100644 index 0000000..75caa39 --- /dev/null +++ b/app/src/main/res/drawable/ic_change_pwd.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1df917d..8d4f33b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -6,6 +6,7 @@ + @@ -32,11 +33,14 @@ + + + + + + + diff --git a/app/src/main/assets/svg/mine.svg b/app/src/main/assets/svg/mine.svg new file mode 100644 index 0000000..3fbb0a3 --- /dev/null +++ b/app/src/main/assets/svg/mine.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt b/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt index d730417..b1972c2 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt @@ -1,8 +1,10 @@ package com.casic.smart.town.sanxi.extensions import com.casic.smart.town.sanxi.model.ErrorMessageModel +import com.casic.smart.town.sanxi.util.LocaleConstant import com.google.gson.Gson import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.utils.SaveKeyValues import org.json.JSONObject fun String.separateResponseCode(): Int { @@ -17,4 +19,15 @@ this, object : TypeToken() {}.type ) return errorModel.message.toString() +} + +/** + * 下载路径为 http://xx.com/static/ 拼接downloadUrl + * */ +fun String.appendDownloadUrl(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + LocaleConstant.DEFAULT_SERVER_CONFIG, LocaleConstant.DEFAULT_SERVER + ) as String + return "$defaultValue/static/${this}" } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt b/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt new file mode 100644 index 0000000..1809dc5 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt @@ -0,0 +1,288 @@ +package com.casic.smart.town.sanxi.fragment + +import android.app.ProgressDialog +import android.content.Intent +import android.net.Uri +import android.os.Build +import android.os.CountDownTimer +import androidx.core.content.FileProvider +import androidx.lifecycle.ViewModelProvider +import com.casic.smart.town.sanxi.BuildConfig +import com.casic.smart.town.sanxi.R +import com.casic.smart.town.sanxi.extensions.appendDownloadUrl +import com.casic.smart.town.sanxi.model.UserDetailModel +import com.casic.smart.town.sanxi.util.AuthenticationHelper +import com.casic.smart.town.sanxi.util.LoadingDialogHub +import com.casic.smart.town.sanxi.util.LocaleConstant +import com.casic.smart.town.sanxi.util.RSAUtils +import com.casic.smart.town.sanxi.view.AboutUsActivity +import com.casic.smart.town.sanxi.view.LoginActivity +import com.casic.smart.town.sanxi.vm.LoginViewModel +import com.casic.smart.town.sanxi.vm.UserViewModel +import com.casic.smart.town.sanxi.vm.VersionViewModel +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.base.KotlinBaseFragment +import com.pengxh.kt.lite.callback.OnDownloadListener +import com.pengxh.kt.lite.extensions.* +import com.pengxh.kt.lite.utils.PageNavigationManager +import com.pengxh.kt.lite.utils.SaveKeyValues +import com.pengxh.kt.lite.vm.LoadState +import com.pengxh.kt.lite.widget.dialog.AlertControlDialog +import com.pengxh.kt.lite.widget.dialog.ChangePasswordDialog +import kotlinx.android.synthetic.main.fragment_mine.* +import java.io.File +import java.nio.charset.StandardCharsets + +class MinePageFragment : KotlinBaseFragment() { + + private lateinit var userData: UserDetailModel.Data + private lateinit var userViewModel: UserViewModel + private lateinit var loginViewModel: LoginViewModel + private lateinit var versionViewModel: VersionViewModel + private lateinit var progressDialog: ProgressDialog + + override fun initLayoutView(): Int = R.layout.fragment_mine + + override fun setupTopBarLayout() { + + } + + override fun initData() { + userViewModel = ViewModelProvider(this)[UserViewModel::class.java] + loginViewModel = ViewModelProvider(this)[LoginViewModel::class.java] + versionViewModel = ViewModelProvider(this)[VersionViewModel::class.java] + + //初始化下载对话框 + progressDialog = ProgressDialog(requireContext()) + progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL) + progressDialog.setProgressDrawable(resources.getDrawable(R.drawable.download_progress)) + progressDialog.setCanceledOnTouchOutside(false) + progressDialog.setCancelable(false) + + /** + * 数据监听 + * */ + dataObserve() + } + + override fun observeRequestState() { + userViewModel.loadState.observe(this, { + when (it) { + is LoadState.Loading -> LoadingDialogHub.show(requireActivity(), "修改中,请稍后") + is LoadState.Success -> { + "修改成功,请重新登录".show(requireContext()) + LoadingDialogHub.dismiss() + AuthenticationHelper.removeToken() + requireContext().navigatePageTo() + PageNavigationManager.finishAllActivity() + } + else -> LoadingDialogHub.dismiss() + } + }) + + versionViewModel.loadState.observe(this, { + when (it) { + is LoadState.Loading -> LoadingDialogHub.show(requireActivity(), "检查版本中,请稍后") + else -> LoadingDialogHub.dismiss() + } + }) + } + + override fun initEvent() { + userImageView.setOnClickListener { + "尽情期待~".show(requireContext()) + } + + updateUserButton.setOnClickListener { + LoadingDialogHub.show(requireActivity(), "同步中,请稍后...") + userViewModel.obtainUserDetail() + } + + changePwdLayout.setOnClickListener { + ChangePasswordDialog.Builder() + .setContext(requireContext()) + .setOnDialogButtonClickListener(object : + ChangePasswordDialog.OnDialogButtonClickListener { + override fun onConfirmClick(oldPwd: String, newPwd: String) { + val publicKey = + RSAUtils.keyStrToPublicKey(AuthenticationHelper.publicKey)!! + val oldPassKey = RSAUtils.encryptDataByPublicKey( + oldPwd.toByteArray(StandardCharsets.UTF_8), + publicKey + ) + val newPassKey = RSAUtils.encryptDataByPublicKey( + newPwd.toByteArray(StandardCharsets.UTF_8), + publicKey + ) + userViewModel.changePassword(oldPassKey, newPassKey) + } + }).build().show() + } + + aboutUsLayout.setOnClickListener { + requireContext().navigatePageTo() + } + + updateVersionLayout.setOnClickListener { + versionViewModel.updateVersion() + } + + clearCacheLayout.setOnClickListener { + //删除缓存之后在设置缓存大小 + LoadingDialogHub.show(requireActivity(), "清理中,请稍后") + File(requireContext().cacheDir.path).deleteFile() + object : CountDownTimer(1500, 500) { + override fun onTick(millisUntilFinished: Long) { + + } + + override fun onFinish() { + LoadingDialogHub.dismiss() + cacheSizeView.text = collectApplicationCache().formatFileSize() + } + }.start() + } + + loginOutButton.setOnClickListener { + AlertControlDialog.Builder() + .setContext(requireContext()) + .setTitle("退出登录") + .setMessage("确定要退出吗?") + .setNegativeButton("取消") + .setPositiveButton("确定") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + loginViewModel.out() + } + + override fun onCancelClick() {} + }).build().show() + } + } + + private fun dataObserve() { + userViewModel.userDetailModel.observe(this, { + if (it.code == 200) { + LoadingDialogHub.dismiss() + "同步完成".show(requireContext()) + userData = it.data + updateUserInfo() + } + }) + + versionViewModel.versionResultModel.observe(this, { + if (BuildConfig.VERSION_NAME == it.version) { + "已是最新版本,无需更新".show(requireContext()) + } else { + AlertControlDialog.Builder() + .setContext(requireContext()) + .setTitle("提示") + .setMessage("有新版本,是否更新?") + .setNegativeButton("稍后再说") + .setPositiveButton("立即下载") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + downloadApk(it.downloadUrl) + } + + override fun onCancelClick() { + + } + }).build().show() + } + }) + + loginViewModel.outResultModel.observe(this, { + if (it.code == 200) { + AuthenticationHelper.removeToken() + requireContext().navigatePageTo() + PageNavigationManager.finishAllActivity() + } + }) + } + + private fun collectApplicationCache(): Long { + return File(requireContext().cacheDir.path).calculateSize() + } + + override fun onResume() { + val userDetailJson = SaveKeyValues.getValue(LocaleConstant.USER_DETAIL_MODEL, "") as String + if (userDetailJson.isNotBlank()) { + userData = Gson().fromJson( + userDetailJson, object : TypeToken() {}.type + ) + updateUserInfo() + } + //缓存 + cacheSizeView.text = collectApplicationCache().formatFileSize() + super.onResume() + } + + private fun updateUserInfo() { + //设置头像,圆形,暂时是默认的 +// val roundDrawable = +// BitmapFactory.decodeResource(requireContext().resources, R.mipmap.login_casic) +// .createRoundDrawable( +// requireContext(), +// 3f.dp2px(requireContext()), +// R.color.mainThemeColor.convertColor(requireContext()) +// ) +// userImageView.setImageDrawable(roundDrawable) + userNameView.text = userData.name + userPhoneView.text = String.format("电话:${userData.phone}") + userDeptView.text = String.format("部门:${userData.deptName}") + } + + private fun downloadApk(url: String?) { + progressDialog.setMessage("下载新版本中...") + progressDialog.show() + if (url.toString().isBlank()) { + "抱歉,版本下载失败".show(requireContext()) + return + } + /** + * http://111.198.10.15:11304/static/apk/1.0.1.apk + * */ + val downloadPath = url!!.appendDownloadUrl() + //开始下载 + downloadPath.downloadFile(requireContext().createDownloadFileDir().toString(), + object : OnDownloadListener { + override fun onDownloadStart(totalBytes: Long) { + progressDialog.max = totalBytes.toInt() + } + + override fun onProgressChanged(currentBytes: Long) { + progressDialog.progress = currentBytes.toInt() + } + + override fun onDownloadEnd(file: File?) { + progressDialog.dismiss() + progressDialog.progress = 0 + //安装APK + installApk(file) + } + }) + } + + private fun installApk(apkPackage: File?) { + if (apkPackage == null) { + "文件异常,无法安装".show(requireContext()) + return + } + val intent = Intent(Intent.ACTION_VIEW) + val data: Uri + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { //判断版本大于等于7.0 + data = FileProvider.getUriForFile( + requireContext(), LocaleConstant.APP_AUTHORITY, apkPackage + ) + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) // 给目标应用一个临时授权 + } else { + data = Uri.fromFile(apkPackage) + } + intent.setDataAndType(data, "application/vnd.android.package-archive") + requireContext().startActivity(intent) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java new file mode 100644 index 0000000..467e33d --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java @@ -0,0 +1,43 @@ +package com.casic.smart.town.sanxi.model; + +/** + * 普通实体类,失败/成功数据结构一致 + */ +public class CommonResultModel { + private int code; + private String data; + private String message; + private boolean isSuccess; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return isSuccess; + } + + public void setSuccess(boolean success) { + isSuccess = success; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java new file mode 100644 index 0000000..9bdabef --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java @@ -0,0 +1,62 @@ +package com.casic.smart.town.sanxi.model; + +public class LoginResultModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private String kaptcha; + private String token; + + public String getKaptcha() { + return kaptcha; + } + + public void setKaptcha(String kaptcha) { + this.kaptcha = kaptcha; + } + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java new file mode 100644 index 0000000..09f8d59 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java @@ -0,0 +1,80 @@ +package com.casic.smart.town.sanxi.model; + +public class PublicKeyModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private boolean appKaptcha; + private boolean kaptcha; + private String publicKey; + private String sid; + + public boolean isAppKaptcha() { + return appKaptcha; + } + + public void setAppKaptcha(boolean appKaptcha) { + this.appKaptcha = appKaptcha; + } + + public boolean isKaptcha() { + return kaptcha; + } + + public void setKaptcha(boolean kaptcha) { + this.kaptcha = kaptcha; + } + + public String getPublicKey() { + return publicKey; + } + + public void setPublicKey(String publicKey) { + this.publicKey = publicKey; + } + + public String getSid() { + return sid; + } + + public void setSid(String sid) { + this.sid = sid; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java new file mode 100644 index 0000000..db577fb --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java @@ -0,0 +1,227 @@ +package com.casic.smart.town.sanxi.model; + + +import java.util.List; + +public class UserDetailModel { + private int code; + private Data data; + private String message; + private boolean success; + + public void setCode(int code) { + this.code = code; + } + + public int getCode() { + return code; + } + + public void setData(Data data) { + this.data = data; + } + + public Data getData() { + return data; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getMessage() { + return message; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public boolean getSuccess() { + return success; + } + + public static class Data { + + private String account; + private String attr1; + private String avatar; + private String bizData; + private List dataScope; + private String deptId; + private String deptName; + private List devices; + private String id; + private String ipAddr; + private String name; + private String phone; + private List roleList; + private List roleNames; + private List roleTips; + private String scopeType; + private String sysData; + private String targetId; + private String targetName; + private String tenantId; + + public void setAccount(String account) { + this.account = account; + } + + public String getAccount() { + return account; + } + + public void setAttr1(String attr1) { + this.attr1 = attr1; + } + + public String getAttr1() { + return attr1; + } + + public void setAvatar(String avatar) { + this.avatar = avatar; + } + + public String getAvatar() { + return avatar; + } + + public void setBizData(String bizData) { + this.bizData = bizData; + } + + public String getBizData() { + return bizData; + } + + public void setDataScope(List dataScope) { + this.dataScope = dataScope; + } + + public List getDataScope() { + return dataScope; + } + + public void setDeptId(String deptId) { + this.deptId = deptId; + } + + public String getDeptId() { + return deptId; + } + + public void setDeptName(String deptName) { + this.deptName = deptName; + } + + public String getDeptName() { + return deptName; + } + + public void setDevices(List devices) { + this.devices = devices; + } + + public List getDevices() { + return devices; + } + + public void setId(String id) { + this.id = id; + } + + public String getId() { + return id; + } + + public void setIpAddr(String ipAddr) { + this.ipAddr = ipAddr; + } + + public String getIpAddr() { + return ipAddr; + } + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setPhone(String phone) { + this.phone = phone; + } + + public String getPhone() { + return phone; + } + + public void setRoleList(List roleList) { + this.roleList = roleList; + } + + public List getRoleList() { + return roleList; + } + + public void setRoleNames(List roleNames) { + this.roleNames = roleNames; + } + + public List getRoleNames() { + return roleNames; + } + + public void setRoleTips(List roleTips) { + this.roleTips = roleTips; + } + + public List getRoleTips() { + return roleTips; + } + + public void setScopeType(String scopeType) { + this.scopeType = scopeType; + } + + public String getScopeType() { + return scopeType; + } + + public void setSysData(String sysData) { + this.sysData = sysData; + } + + public String getSysData() { + return sysData; + } + + public void setTargetId(String targetId) { + this.targetId = targetId; + } + + public String getTargetId() { + return targetId; + } + + public void setTargetName(String targetName) { + this.targetName = targetName; + } + + public String getTargetName() { + return targetName; + } + + public void setTenantId(String tenantId) { + this.tenantId = tenantId; + } + + public String getTenantId() { + return tenantId; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/VersionResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/VersionResultModel.java new file mode 100644 index 0000000..79374e0 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/VersionResultModel.java @@ -0,0 +1,61 @@ +package com.casic.smart.town.sanxi.model; + +public class VersionResultModel { + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private String downloadUrl; + private String version; + + public String getDownloadUrl() { + return downloadUrl; + } + + public void setDownloadUrl(String downloadUrl) { + this.downloadUrl = downloadUrl; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/AuthenticationHelper.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/AuthenticationHelper.kt new file mode 100644 index 0000000..f498e4e --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/AuthenticationHelper.kt @@ -0,0 +1,24 @@ +package com.casic.smart.town.sanxi.util + +import com.pengxh.kt.lite.utils.SaveKeyValues + +object AuthenticationHelper { + + fun savePublicKey(key: String) { + SaveKeyValues.putValue("keyString", key) + } + + val publicKey: String? + get() = SaveKeyValues.getValue("keyString", "") as String? + + fun saveToken(token: String?) { + SaveKeyValues.putValue("token", token!!) + } + + val token: String? + get() = SaveKeyValues.getValue("token", "") as String? + + fun removeToken() { + SaveKeyValues.removeKey("token") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt index ab1a36d..99f0d19 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt @@ -14,5 +14,9 @@ const val PAGE_LIMIT = 20 const val DEFAULT_SERVER_CONFIG = "defaultServerConfig" - const val DEFAULT_SERVER = "http://111.198.10.15:11308" + const val DEFAULT_SERVER = "http://36.133.189.112:8082" + const val APP_AUTHORITY = "com.casic.smart.town.sanxi.fileprovider" + const val ACCOUNT = "account" + const val PASSWORD = "password" + const val USER_DETAIL_MODEL = "userDetailModel" } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/RSAUtils.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/RSAUtils.kt new file mode 100644 index 0000000..b9216ba --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/RSAUtils.kt @@ -0,0 +1,55 @@ +package com.casic.smart.town.sanxi.util + +import android.util.Base64 +import java.security.* +import java.security.spec.InvalidKeySpecException +import java.security.spec.X509EncodedKeySpec +import javax.crypto.BadPaddingException +import javax.crypto.Cipher +import javax.crypto.IllegalBlockSizeException +import javax.crypto.NoSuchPaddingException + +object RSAUtils { + //构建Cipher实例时所传入的的字符串,默认为"RSA/NONE/PKCS1Padding" + private fun processData(srcData: ByteArray, key: Key): ByteArray? { //用来保存处理结果 + var resultBytes: ByteArray? = null + try { //获取Cipher实例 + val cipher = Cipher.getInstance("RSA/None/PKCS1Padding") + //初始化Cipher,mode指定是加密还是解密,key为公钥或私钥 + cipher.init(Cipher.ENCRYPT_MODE, key) + //处理数据 + resultBytes = cipher.doFinal(srcData) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: NoSuchPaddingException) { + e.printStackTrace() + } catch (e: InvalidKeyException) { + e.printStackTrace() + } catch (e: BadPaddingException) { + e.printStackTrace() + } catch (e: IllegalBlockSizeException) { + e.printStackTrace() + } + return resultBytes + } + + fun encryptDataByPublicKey(srcData: ByteArray, publicKey: PublicKey): String { + val resultBytes = processData(srcData, publicKey) + return Base64.encodeToString(resultBytes, Base64.DEFAULT) + } + + fun keyStrToPublicKey(publicKeyStr: String?): PublicKey? { + var publicKey: PublicKey? = null + val keyBytes = Base64.decode(publicKeyStr, Base64.DEFAULT) + val keySpec = X509EncodedKeySpec(keyBytes) + try { + val keyFactory = KeyFactory.getInstance("RSA") + publicKey = keyFactory.generatePublic(keySpec) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: InvalidKeySpecException) { + e.printStackTrace() + } + return publicKey + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt index 63db0ec..50d1742 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt @@ -1,12 +1,64 @@ package com.casic.smart.town.sanxi.util.retrofit -import retrofit2.http.GET +import retrofit2.http.* interface RetrofitService { + /** + * PublicKey校验 + */ + @GET("/config/baseConfig") + suspend fun obtainPublicKey(): String + + /** + * 登录并获取Token + * + * @param sid + * @param account 用户名 + * @param secretKey 加密后的密码 + */ + @FormUrlEncoded + @POST("/user/login") + suspend fun login( + @Field("sid") sid: String, + @Field("username") account: String, + @Field("password") secretKey: String + ): String + + /** + * 退出登录 + */ + @GET("/user/logout") + suspend fun loginOut(@Header("token") token: String): String + + /** + * 获取用户信息 + */ + @GET("/user/info") + suspend fun obtainUserDetail(@Header("token") token: String): String + + /** + * 修改密码 + * + * @param oldPwd 旧密码 + * @param newPwd 新密码 + */ + @FormUrlEncoded + @POST("/mgr/changePwd") + suspend fun changePassword( + @Header("token") token: String, + @Field("oldPwd") oldPwd: String, + @Field("newPwd") newPwd: String + ): String /** * 根据布防状态统计闸井数量接口 */ @GET("/well/countByBfzt") - suspend fun countWellByState(): String + suspend fun countWellByState(@Header("token") token: String): String + + /** + * 更新APK版本 + */ + @POST("/app/checkVersion") + suspend fun obtainVersionResult(@Header("token") token: String): String } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt index 46bbf4b..410444b 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt @@ -1,5 +1,6 @@ package com.casic.smart.town.sanxi.util.retrofit +import com.casic.smart.town.sanxi.util.AuthenticationHelper import com.casic.smart.town.sanxi.util.LocaleConstant import com.pengxh.kt.lite.utils.RetrofitFactory import com.pengxh.kt.lite.utils.SaveKeyValues @@ -15,9 +16,52 @@ } /** + * 验证PublicKey + */ + suspend fun authenticate(): String { + return api.obtainPublicKey() + } + + /** + * 登录并获取Token + */ + suspend fun login(sid: String, account: String, secretKey: String): String { + return api.login(sid, account, secretKey) + } + + /** + * 退出登录 + */ + suspend fun loginOut(): String { + return api.loginOut(AuthenticationHelper.token!!) + } + + /** + * 获取用户信息 + */ + suspend fun obtainUserDetail(): String { + return api.obtainUserDetail(AuthenticationHelper.token!!) + } + + /** + * 修改密码 + */ + suspend fun changePassword(oldPwd: String, newPwd: String): String { + return api.changePassword(AuthenticationHelper.token!!, oldPwd, newPwd) + } + + /** * 根据布防状态统计闸井数量接口 */ suspend fun countWellByState(): String { - return api.countWellByState() + return api.countWellByState(AuthenticationHelper.token!!) } + + /** + * 更新APK版本 + */ + suspend fun updateVersion(): String { + return api.obtainVersionResult(AuthenticationHelper.token!!) + } + } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/AboutUsActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/AboutUsActivity.kt new file mode 100644 index 0000000..7e43c47 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/AboutUsActivity.kt @@ -0,0 +1,34 @@ +package com.casic.smart.town.sanxi.view + +import com.casic.smart.town.sanxi.BuildConfig +import com.casic.smart.town.sanxi.R +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.kt.lite.base.KotlinBaseActivity +import com.pengxh.kt.lite.extensions.convertColor +import com.pengxh.kt.lite.utils.ImmerseStatusBarUtil +import kotlinx.android.synthetic.main.activity_about_us.* +import kotlinx.android.synthetic.main.include_base_title.* + +class AboutUsActivity : KotlinBaseActivity() { + + override fun initLayoutView(): Int = R.layout.activity_about_us + + override fun setupTopBarLayout() { + ImmersionBar.with(this).statusBarDarkFont(false).init() + ImmerseStatusBarUtil.setColor(this, R.color.mainThemeColor.convertColor(this)) + titleView.text = "关于我们" + leftBackView.setOnClickListener { finish() } + } + + override fun initData() { + versionCodeView.text = String.format("版本信息:V${BuildConfig.VERSION_NAME}") + } + + override fun observeRequestState() { + + } + + override fun initEvent() { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/LoginActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/LoginActivity.kt new file mode 100644 index 0000000..b5d1d50 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/LoginActivity.kt @@ -0,0 +1,97 @@ +package com.casic.smart.town.sanxi.view + +import androidx.lifecycle.ViewModelProvider +import com.casic.smart.town.sanxi.R +import com.casic.smart.town.sanxi.util.AuthenticationHelper +import com.casic.smart.town.sanxi.util.LoadingDialogHub +import com.casic.smart.town.sanxi.util.LocaleConstant +import com.casic.smart.town.sanxi.util.RSAUtils +import com.casic.smart.town.sanxi.vm.AuthenticateViewModel +import com.casic.smart.town.sanxi.vm.LoginViewModel +import com.casic.smart.town.sanxi.vm.UserViewModel +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.kt.lite.base.KotlinBaseActivity +import com.pengxh.kt.lite.extensions.navigatePageTo +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.utils.SaveKeyValues +import kotlinx.android.synthetic.main.activity_login.* + +class LoginActivity : KotlinBaseActivity() { + + private lateinit var authenticateViewModel: AuthenticateViewModel + private lateinit var loginViewModel: LoginViewModel + private lateinit var userViewModel: UserViewModel + + override fun initLayoutView(): Int = R.layout.activity_login + + override fun setupTopBarLayout() { + ImmersionBar.with(this).statusBarDarkFont(true).init() + } + + override fun initData() { + // 设置默认账号密码 + userNameView.setText(SaveKeyValues.getValue(LocaleConstant.ACCOUNT, "") as String) + userPasswordView.setText(SaveKeyValues.getValue(LocaleConstant.PASSWORD, "") as String) + authenticateViewModel = ViewModelProvider(this)[AuthenticateViewModel::class.java] + loginViewModel = ViewModelProvider(this)[LoginViewModel::class.java] + userViewModel = ViewModelProvider(this)[UserViewModel::class.java] + + authenticateViewModel.keyModel.observe(this, { + if (it.code == 200) {//用code判断,别的判断可能有坑 + val keyString = it.data!!.publicKey!! + /** + * 修改密码需要用到key,先存着 + * */ + AuthenticationHelper.savePublicKey(keyString) + val publicKey = RSAUtils.keyStrToPublicKey(keyString) + + val account = userNameView.text.toString() + val userPassword = userPasswordView.text.toString() + val dataByPublicKey = + RSAUtils.encryptDataByPublicKey(userPassword.toByteArray(), publicKey!!) + //登录并获取Token,POST请求 + loginViewModel.enter(it.data!!.sid!!, account, dataByPublicKey) + loginViewModel.enterResultModel.observe(this, { loginResult -> + if (loginResult.code == 200) { + AuthenticationHelper.saveToken(loginResult.data!!.token!!) + /** + * 获取token之后保存用户信息 + * */ + userViewModel.obtainUserDetail() + //验证成功登录 + navigatePageTo() + finish() + } + }) + } + }) + } + + override fun observeRequestState() { + authenticateViewModel.loadState.observe(this, { + LoadingDialogHub.show(this, "登录中,请稍后") + }) + + loginViewModel.loadState.observe(this, { + LoadingDialogHub.dismiss() + }) + } + + override fun initEvent() { + loginButton.setOnClickListener { + val account = userNameView.text.toString().trim() + val userPassword = userPasswordView.text.toString().trim() + if (account.isBlank()) { + "用户名不能为空".show(this) + return@setOnClickListener + } + if (userPassword.isBlank()) { + "密码不能为空".show(this) + return@setOnClickListener + } + SaveKeyValues.putValue(LocaleConstant.ACCOUNT, account) + SaveKeyValues.putValue(LocaleConstant.PASSWORD, userPassword) + authenticateViewModel.obtainPublicKey() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt index 3bea1c9..a4fa951 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt @@ -7,6 +7,7 @@ import com.casic.smart.town.sanxi.R import com.casic.smart.town.sanxi.adapter.ViewPagerAdapter import com.casic.smart.town.sanxi.fragment.AlarmPageFragment +import com.casic.smart.town.sanxi.fragment.MinePageFragment import com.casic.smart.town.sanxi.fragment.MonitorPageFragment import com.casic.smart.town.sanxi.fragment.StatisticsPageFragment import com.gyf.immersionbar.ImmersionBar @@ -33,6 +34,7 @@ fragmentPages.add(StatisticsPageFragment()) fragmentPages.add(MonitorPageFragment()) fragmentPages.add(AlarmPageFragment()) + fragmentPages.add(MinePageFragment()) } override fun initLayoutView(): Int = R.layout.activity_main @@ -68,6 +70,9 @@ R.id.nav_alarm -> { mainViewPager.currentItem = 2 } + R.id.nav_more -> { + mainViewPager.currentItem = 3 + } } false } diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/PermissionActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/PermissionActivity.kt index c478d83..3238f3c 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/view/PermissionActivity.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/PermissionActivity.kt @@ -32,7 +32,7 @@ //先把导航隐私政策声明,后面导航会用到 MapsInitializer.updatePrivacyAgree(this, true) MapsInitializer.updatePrivacyShow(this, true, true) - this.navigatePageTo() + this.navigatePageTo() finish() } diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/SplashScreenActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/SplashScreenActivity.kt new file mode 100644 index 0000000..fa5a74d --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/SplashScreenActivity.kt @@ -0,0 +1,54 @@ +package com.casic.smart.town.sanxi.view + +import android.annotation.SuppressLint +import android.os.CountDownTimer +import androidx.lifecycle.ViewModelProvider +import com.casic.smart.town.sanxi.R +import com.casic.smart.town.sanxi.vm.UserViewModel +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.kt.lite.base.KotlinBaseActivity +import com.pengxh.kt.lite.extensions.navigatePageTo + +@SuppressLint("CustomSplashScreen") +class SplashScreenActivity : KotlinBaseActivity() { + + private lateinit var userDetailViewModel: UserViewModel + private val countDownTimer = object : CountDownTimer(1000, 500) { + override fun onFinish() { + /** + * 获取token之后保存用户信息 + * */ + userDetailViewModel.obtainUserDetail() + userDetailViewModel.userDetailModel.observe(this@SplashScreenActivity, { + if (it.code == 200) { + navigatePageTo() + } else { + navigatePageTo() + } + finish() + }) + } + + override fun onTick(millisUntilFinished: Long) { + + } + } + + override fun initLayoutView(): Int = R.layout.activity_splash + + override fun setupTopBarLayout() { + ImmersionBar.with(this).statusBarDarkFont(false).init() + } + + override fun initData() { + userDetailViewModel = ViewModelProvider(this).get(UserViewModel::class.java) + } + + override fun observeRequestState() { + + } + + override fun initEvent() { + countDownTimer.start() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/vm/AuthenticateViewModel.kt b/app/src/main/java/com/casic/smart/town/sanxi/vm/AuthenticateViewModel.kt new file mode 100644 index 0000000..87ddbf8 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/vm/AuthenticateViewModel.kt @@ -0,0 +1,35 @@ +package com.casic.smart.town.sanxi.vm + +import androidx.lifecycle.MutableLiveData +import com.casic.smart.town.sanxi.base.BaseApplication +import com.casic.smart.town.sanxi.extensions.separateResponseCode +import com.casic.smart.town.sanxi.extensions.toErrorMessage +import com.casic.smart.town.sanxi.model.PublicKeyModel +import com.casic.smart.town.sanxi.util.retrofit.RetrofitServiceManager +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.launch +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.vm.BaseViewModel +import com.pengxh.kt.lite.vm.LoadState + +class AuthenticateViewModel : BaseViewModel() { + + private val gson = Gson() + val keyModel = MutableLiveData() + + fun obtainPublicKey() = launch({ + loadState.value = LoadState.Loading + val response = RetrofitServiceManager.authenticate() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + keyModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ) + } else { + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + it.printStackTrace() + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/vm/LoginViewModel.kt b/app/src/main/java/com/casic/smart/town/sanxi/vm/LoginViewModel.kt new file mode 100644 index 0000000..c377c61 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/vm/LoginViewModel.kt @@ -0,0 +1,54 @@ +package com.casic.smart.town.sanxi.vm + +import androidx.lifecycle.MutableLiveData +import com.casic.smart.town.sanxi.base.BaseApplication +import com.casic.smart.town.sanxi.extensions.separateResponseCode +import com.casic.smart.town.sanxi.extensions.toErrorMessage +import com.casic.smart.town.sanxi.model.CommonResultModel +import com.casic.smart.town.sanxi.model.LoginResultModel +import com.casic.smart.town.sanxi.util.retrofit.RetrofitServiceManager +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.launch +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.vm.BaseViewModel +import com.pengxh.kt.lite.vm.LoadState + +class LoginViewModel : BaseViewModel() { + + private val gson = Gson() + val enterResultModel = MutableLiveData() + val outResultModel = MutableLiveData() + + fun enter(sid: String, account: String, secretKey: String) = launch({ + val response = RetrofitServiceManager.login(sid, account, secretKey) + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + loadState.value = LoadState.Success + "登录成功".show(BaseApplication.obtainInstance()) + enterResultModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ) + } else { + loadState.value = LoadState.Fail + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + loadState.value = LoadState.Fail + it.printStackTrace() + }) + + fun out() = launch({ + val response = RetrofitServiceManager.loginOut() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + outResultModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ) + } else { + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + it.printStackTrace() + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/vm/UserViewModel.kt b/app/src/main/java/com/casic/smart/town/sanxi/vm/UserViewModel.kt new file mode 100644 index 0000000..29a8756 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/vm/UserViewModel.kt @@ -0,0 +1,57 @@ +package com.casic.smart.town.sanxi.vm + +import androidx.lifecycle.MutableLiveData +import com.casic.smart.town.sanxi.base.BaseApplication +import com.casic.smart.town.sanxi.extensions.separateResponseCode +import com.casic.smart.town.sanxi.extensions.toErrorMessage +import com.casic.smart.town.sanxi.model.UserDetailModel +import com.casic.smart.town.sanxi.util.LocaleConstant +import com.casic.smart.town.sanxi.util.retrofit.RetrofitServiceManager +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.launch +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.utils.SaveKeyValues +import com.pengxh.kt.lite.vm.BaseViewModel +import com.pengxh.kt.lite.vm.LoadState + +class UserViewModel : BaseViewModel() { + + private val gson = Gson() + val userDetailModel = MutableLiveData() + + fun obtainUserDetail() = launch({ + val response = RetrofitServiceManager.obtainUserDetail() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + val userDetail = gson.fromJson( + response, object : TypeToken() {}.type + ) + userDetailModel.value = userDetail + SaveKeyValues.putValue(LocaleConstant.USER_DETAIL_MODEL, gson.toJson(userDetail.data)) + } else { + val errorModel = UserDetailModel() + errorModel.code = responseCode + userDetailModel.value = errorModel + //如果此次获取不到用户信息,那么就清空之前的用户缓存,然后让用户重新登录 + SaveKeyValues.removeKey(LocaleConstant.USER_DETAIL_MODEL) + } + }, { + it.printStackTrace() + }) + + fun changePassword(oldPwd: String, newPwd: String) = launch({ + loadState.value = LoadState.Loading + val response = RetrofitServiceManager.changePassword(oldPwd, newPwd) + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + loadState.value = LoadState.Success + } else { + loadState.value = LoadState.Fail + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + loadState.value = LoadState.Fail + it.printStackTrace() + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/vm/VersionViewModel.kt b/app/src/main/java/com/casic/smart/town/sanxi/vm/VersionViewModel.kt new file mode 100644 index 0000000..20ea457 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/vm/VersionViewModel.kt @@ -0,0 +1,38 @@ +package com.casic.smart.town.sanxi.vm + +import androidx.lifecycle.MutableLiveData +import com.casic.smart.town.sanxi.base.BaseApplication +import com.casic.smart.town.sanxi.extensions.separateResponseCode +import com.casic.smart.town.sanxi.extensions.toErrorMessage +import com.casic.smart.town.sanxi.model.VersionResultModel +import com.casic.smart.town.sanxi.util.retrofit.RetrofitServiceManager +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.launch +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.vm.BaseViewModel +import com.pengxh.kt.lite.vm.LoadState + +class VersionViewModel : BaseViewModel() { + + private val gson = Gson() + val versionResultModel = MutableLiveData() + + fun updateVersion() = launch({ + loadState.value = LoadState.Loading + val response = RetrofitServiceManager.updateVersion() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + loadState.value = LoadState.Success + versionResultModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ).data + } else { + loadState.value = LoadState.Fail + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + loadState.value = LoadState.Fail + it.printStackTrace() + }) +} \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_solid_layout_gray_radius_5.xml b/app/src/main/res/drawable/bg_solid_layout_gray_radius_5.xml new file mode 100644 index 0000000..96eb93e --- /dev/null +++ b/app/src/main/res/drawable/bg_solid_layout_gray_radius_5.xml @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/button_main_selector.xml b/app/src/main/res/drawable/button_main_selector.xml new file mode 100644 index 0000000..5875998 --- /dev/null +++ b/app/src/main/res/drawable/button_main_selector.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/button_white_selector.xml b/app/src/main/res/drawable/button_white_selector.xml new file mode 100644 index 0000000..4dd3c0b --- /dev/null +++ b/app/src/main/res/drawable/button_white_selector.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/download_progress.xml b/app/src/main/res/drawable/download_progress.xml new file mode 100644 index 0000000..493d923 --- /dev/null +++ b/app/src/main/res/drawable/download_progress.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_about.xml b/app/src/main/res/drawable/ic_about.xml new file mode 100644 index 0000000..8469319 --- /dev/null +++ b/app/src/main/res/drawable/ic_about.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_change_pwd.xml b/app/src/main/res/drawable/ic_change_pwd.xml new file mode 100644 index 0000000..75caa39 --- /dev/null +++ b/app/src/main/res/drawable/ic_change_pwd.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/ic_clean.xml b/app/src/main/res/drawable/ic_clean.xml new file mode 100644 index 0000000..027f809 --- /dev/null +++ b/app/src/main/res/drawable/ic_clean.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1df917d..8d4f33b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -6,6 +6,7 @@ + @@ -32,11 +33,14 @@ + + + + + + + diff --git a/app/src/main/assets/svg/mine.svg b/app/src/main/assets/svg/mine.svg new file mode 100644 index 0000000..3fbb0a3 --- /dev/null +++ b/app/src/main/assets/svg/mine.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt b/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt index d730417..b1972c2 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt @@ -1,8 +1,10 @@ package com.casic.smart.town.sanxi.extensions import com.casic.smart.town.sanxi.model.ErrorMessageModel +import com.casic.smart.town.sanxi.util.LocaleConstant import com.google.gson.Gson import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.utils.SaveKeyValues import org.json.JSONObject fun String.separateResponseCode(): Int { @@ -17,4 +19,15 @@ this, object : TypeToken() {}.type ) return errorModel.message.toString() +} + +/** + * 下载路径为 http://xx.com/static/ 拼接downloadUrl + * */ +fun String.appendDownloadUrl(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + LocaleConstant.DEFAULT_SERVER_CONFIG, LocaleConstant.DEFAULT_SERVER + ) as String + return "$defaultValue/static/${this}" } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt b/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt new file mode 100644 index 0000000..1809dc5 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt @@ -0,0 +1,288 @@ +package com.casic.smart.town.sanxi.fragment + +import android.app.ProgressDialog +import android.content.Intent +import android.net.Uri +import android.os.Build +import android.os.CountDownTimer +import androidx.core.content.FileProvider +import androidx.lifecycle.ViewModelProvider +import com.casic.smart.town.sanxi.BuildConfig +import com.casic.smart.town.sanxi.R +import com.casic.smart.town.sanxi.extensions.appendDownloadUrl +import com.casic.smart.town.sanxi.model.UserDetailModel +import com.casic.smart.town.sanxi.util.AuthenticationHelper +import com.casic.smart.town.sanxi.util.LoadingDialogHub +import com.casic.smart.town.sanxi.util.LocaleConstant +import com.casic.smart.town.sanxi.util.RSAUtils +import com.casic.smart.town.sanxi.view.AboutUsActivity +import com.casic.smart.town.sanxi.view.LoginActivity +import com.casic.smart.town.sanxi.vm.LoginViewModel +import com.casic.smart.town.sanxi.vm.UserViewModel +import com.casic.smart.town.sanxi.vm.VersionViewModel +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.base.KotlinBaseFragment +import com.pengxh.kt.lite.callback.OnDownloadListener +import com.pengxh.kt.lite.extensions.* +import com.pengxh.kt.lite.utils.PageNavigationManager +import com.pengxh.kt.lite.utils.SaveKeyValues +import com.pengxh.kt.lite.vm.LoadState +import com.pengxh.kt.lite.widget.dialog.AlertControlDialog +import com.pengxh.kt.lite.widget.dialog.ChangePasswordDialog +import kotlinx.android.synthetic.main.fragment_mine.* +import java.io.File +import java.nio.charset.StandardCharsets + +class MinePageFragment : KotlinBaseFragment() { + + private lateinit var userData: UserDetailModel.Data + private lateinit var userViewModel: UserViewModel + private lateinit var loginViewModel: LoginViewModel + private lateinit var versionViewModel: VersionViewModel + private lateinit var progressDialog: ProgressDialog + + override fun initLayoutView(): Int = R.layout.fragment_mine + + override fun setupTopBarLayout() { + + } + + override fun initData() { + userViewModel = ViewModelProvider(this)[UserViewModel::class.java] + loginViewModel = ViewModelProvider(this)[LoginViewModel::class.java] + versionViewModel = ViewModelProvider(this)[VersionViewModel::class.java] + + //初始化下载对话框 + progressDialog = ProgressDialog(requireContext()) + progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL) + progressDialog.setProgressDrawable(resources.getDrawable(R.drawable.download_progress)) + progressDialog.setCanceledOnTouchOutside(false) + progressDialog.setCancelable(false) + + /** + * 数据监听 + * */ + dataObserve() + } + + override fun observeRequestState() { + userViewModel.loadState.observe(this, { + when (it) { + is LoadState.Loading -> LoadingDialogHub.show(requireActivity(), "修改中,请稍后") + is LoadState.Success -> { + "修改成功,请重新登录".show(requireContext()) + LoadingDialogHub.dismiss() + AuthenticationHelper.removeToken() + requireContext().navigatePageTo() + PageNavigationManager.finishAllActivity() + } + else -> LoadingDialogHub.dismiss() + } + }) + + versionViewModel.loadState.observe(this, { + when (it) { + is LoadState.Loading -> LoadingDialogHub.show(requireActivity(), "检查版本中,请稍后") + else -> LoadingDialogHub.dismiss() + } + }) + } + + override fun initEvent() { + userImageView.setOnClickListener { + "尽情期待~".show(requireContext()) + } + + updateUserButton.setOnClickListener { + LoadingDialogHub.show(requireActivity(), "同步中,请稍后...") + userViewModel.obtainUserDetail() + } + + changePwdLayout.setOnClickListener { + ChangePasswordDialog.Builder() + .setContext(requireContext()) + .setOnDialogButtonClickListener(object : + ChangePasswordDialog.OnDialogButtonClickListener { + override fun onConfirmClick(oldPwd: String, newPwd: String) { + val publicKey = + RSAUtils.keyStrToPublicKey(AuthenticationHelper.publicKey)!! + val oldPassKey = RSAUtils.encryptDataByPublicKey( + oldPwd.toByteArray(StandardCharsets.UTF_8), + publicKey + ) + val newPassKey = RSAUtils.encryptDataByPublicKey( + newPwd.toByteArray(StandardCharsets.UTF_8), + publicKey + ) + userViewModel.changePassword(oldPassKey, newPassKey) + } + }).build().show() + } + + aboutUsLayout.setOnClickListener { + requireContext().navigatePageTo() + } + + updateVersionLayout.setOnClickListener { + versionViewModel.updateVersion() + } + + clearCacheLayout.setOnClickListener { + //删除缓存之后在设置缓存大小 + LoadingDialogHub.show(requireActivity(), "清理中,请稍后") + File(requireContext().cacheDir.path).deleteFile() + object : CountDownTimer(1500, 500) { + override fun onTick(millisUntilFinished: Long) { + + } + + override fun onFinish() { + LoadingDialogHub.dismiss() + cacheSizeView.text = collectApplicationCache().formatFileSize() + } + }.start() + } + + loginOutButton.setOnClickListener { + AlertControlDialog.Builder() + .setContext(requireContext()) + .setTitle("退出登录") + .setMessage("确定要退出吗?") + .setNegativeButton("取消") + .setPositiveButton("确定") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + loginViewModel.out() + } + + override fun onCancelClick() {} + }).build().show() + } + } + + private fun dataObserve() { + userViewModel.userDetailModel.observe(this, { + if (it.code == 200) { + LoadingDialogHub.dismiss() + "同步完成".show(requireContext()) + userData = it.data + updateUserInfo() + } + }) + + versionViewModel.versionResultModel.observe(this, { + if (BuildConfig.VERSION_NAME == it.version) { + "已是最新版本,无需更新".show(requireContext()) + } else { + AlertControlDialog.Builder() + .setContext(requireContext()) + .setTitle("提示") + .setMessage("有新版本,是否更新?") + .setNegativeButton("稍后再说") + .setPositiveButton("立即下载") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + downloadApk(it.downloadUrl) + } + + override fun onCancelClick() { + + } + }).build().show() + } + }) + + loginViewModel.outResultModel.observe(this, { + if (it.code == 200) { + AuthenticationHelper.removeToken() + requireContext().navigatePageTo() + PageNavigationManager.finishAllActivity() + } + }) + } + + private fun collectApplicationCache(): Long { + return File(requireContext().cacheDir.path).calculateSize() + } + + override fun onResume() { + val userDetailJson = SaveKeyValues.getValue(LocaleConstant.USER_DETAIL_MODEL, "") as String + if (userDetailJson.isNotBlank()) { + userData = Gson().fromJson( + userDetailJson, object : TypeToken() {}.type + ) + updateUserInfo() + } + //缓存 + cacheSizeView.text = collectApplicationCache().formatFileSize() + super.onResume() + } + + private fun updateUserInfo() { + //设置头像,圆形,暂时是默认的 +// val roundDrawable = +// BitmapFactory.decodeResource(requireContext().resources, R.mipmap.login_casic) +// .createRoundDrawable( +// requireContext(), +// 3f.dp2px(requireContext()), +// R.color.mainThemeColor.convertColor(requireContext()) +// ) +// userImageView.setImageDrawable(roundDrawable) + userNameView.text = userData.name + userPhoneView.text = String.format("电话:${userData.phone}") + userDeptView.text = String.format("部门:${userData.deptName}") + } + + private fun downloadApk(url: String?) { + progressDialog.setMessage("下载新版本中...") + progressDialog.show() + if (url.toString().isBlank()) { + "抱歉,版本下载失败".show(requireContext()) + return + } + /** + * http://111.198.10.15:11304/static/apk/1.0.1.apk + * */ + val downloadPath = url!!.appendDownloadUrl() + //开始下载 + downloadPath.downloadFile(requireContext().createDownloadFileDir().toString(), + object : OnDownloadListener { + override fun onDownloadStart(totalBytes: Long) { + progressDialog.max = totalBytes.toInt() + } + + override fun onProgressChanged(currentBytes: Long) { + progressDialog.progress = currentBytes.toInt() + } + + override fun onDownloadEnd(file: File?) { + progressDialog.dismiss() + progressDialog.progress = 0 + //安装APK + installApk(file) + } + }) + } + + private fun installApk(apkPackage: File?) { + if (apkPackage == null) { + "文件异常,无法安装".show(requireContext()) + return + } + val intent = Intent(Intent.ACTION_VIEW) + val data: Uri + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { //判断版本大于等于7.0 + data = FileProvider.getUriForFile( + requireContext(), LocaleConstant.APP_AUTHORITY, apkPackage + ) + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) // 给目标应用一个临时授权 + } else { + data = Uri.fromFile(apkPackage) + } + intent.setDataAndType(data, "application/vnd.android.package-archive") + requireContext().startActivity(intent) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java new file mode 100644 index 0000000..467e33d --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java @@ -0,0 +1,43 @@ +package com.casic.smart.town.sanxi.model; + +/** + * 普通实体类,失败/成功数据结构一致 + */ +public class CommonResultModel { + private int code; + private String data; + private String message; + private boolean isSuccess; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return isSuccess; + } + + public void setSuccess(boolean success) { + isSuccess = success; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java new file mode 100644 index 0000000..9bdabef --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java @@ -0,0 +1,62 @@ +package com.casic.smart.town.sanxi.model; + +public class LoginResultModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private String kaptcha; + private String token; + + public String getKaptcha() { + return kaptcha; + } + + public void setKaptcha(String kaptcha) { + this.kaptcha = kaptcha; + } + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java new file mode 100644 index 0000000..09f8d59 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java @@ -0,0 +1,80 @@ +package com.casic.smart.town.sanxi.model; + +public class PublicKeyModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private boolean appKaptcha; + private boolean kaptcha; + private String publicKey; + private String sid; + + public boolean isAppKaptcha() { + return appKaptcha; + } + + public void setAppKaptcha(boolean appKaptcha) { + this.appKaptcha = appKaptcha; + } + + public boolean isKaptcha() { + return kaptcha; + } + + public void setKaptcha(boolean kaptcha) { + this.kaptcha = kaptcha; + } + + public String getPublicKey() { + return publicKey; + } + + public void setPublicKey(String publicKey) { + this.publicKey = publicKey; + } + + public String getSid() { + return sid; + } + + public void setSid(String sid) { + this.sid = sid; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java new file mode 100644 index 0000000..db577fb --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java @@ -0,0 +1,227 @@ +package com.casic.smart.town.sanxi.model; + + +import java.util.List; + +public class UserDetailModel { + private int code; + private Data data; + private String message; + private boolean success; + + public void setCode(int code) { + this.code = code; + } + + public int getCode() { + return code; + } + + public void setData(Data data) { + this.data = data; + } + + public Data getData() { + return data; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getMessage() { + return message; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public boolean getSuccess() { + return success; + } + + public static class Data { + + private String account; + private String attr1; + private String avatar; + private String bizData; + private List dataScope; + private String deptId; + private String deptName; + private List devices; + private String id; + private String ipAddr; + private String name; + private String phone; + private List roleList; + private List roleNames; + private List roleTips; + private String scopeType; + private String sysData; + private String targetId; + private String targetName; + private String tenantId; + + public void setAccount(String account) { + this.account = account; + } + + public String getAccount() { + return account; + } + + public void setAttr1(String attr1) { + this.attr1 = attr1; + } + + public String getAttr1() { + return attr1; + } + + public void setAvatar(String avatar) { + this.avatar = avatar; + } + + public String getAvatar() { + return avatar; + } + + public void setBizData(String bizData) { + this.bizData = bizData; + } + + public String getBizData() { + return bizData; + } + + public void setDataScope(List dataScope) { + this.dataScope = dataScope; + } + + public List getDataScope() { + return dataScope; + } + + public void setDeptId(String deptId) { + this.deptId = deptId; + } + + public String getDeptId() { + return deptId; + } + + public void setDeptName(String deptName) { + this.deptName = deptName; + } + + public String getDeptName() { + return deptName; + } + + public void setDevices(List devices) { + this.devices = devices; + } + + public List getDevices() { + return devices; + } + + public void setId(String id) { + this.id = id; + } + + public String getId() { + return id; + } + + public void setIpAddr(String ipAddr) { + this.ipAddr = ipAddr; + } + + public String getIpAddr() { + return ipAddr; + } + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setPhone(String phone) { + this.phone = phone; + } + + public String getPhone() { + return phone; + } + + public void setRoleList(List roleList) { + this.roleList = roleList; + } + + public List getRoleList() { + return roleList; + } + + public void setRoleNames(List roleNames) { + this.roleNames = roleNames; + } + + public List getRoleNames() { + return roleNames; + } + + public void setRoleTips(List roleTips) { + this.roleTips = roleTips; + } + + public List getRoleTips() { + return roleTips; + } + + public void setScopeType(String scopeType) { + this.scopeType = scopeType; + } + + public String getScopeType() { + return scopeType; + } + + public void setSysData(String sysData) { + this.sysData = sysData; + } + + public String getSysData() { + return sysData; + } + + public void setTargetId(String targetId) { + this.targetId = targetId; + } + + public String getTargetId() { + return targetId; + } + + public void setTargetName(String targetName) { + this.targetName = targetName; + } + + public String getTargetName() { + return targetName; + } + + public void setTenantId(String tenantId) { + this.tenantId = tenantId; + } + + public String getTenantId() { + return tenantId; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/VersionResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/VersionResultModel.java new file mode 100644 index 0000000..79374e0 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/VersionResultModel.java @@ -0,0 +1,61 @@ +package com.casic.smart.town.sanxi.model; + +public class VersionResultModel { + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private String downloadUrl; + private String version; + + public String getDownloadUrl() { + return downloadUrl; + } + + public void setDownloadUrl(String downloadUrl) { + this.downloadUrl = downloadUrl; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/AuthenticationHelper.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/AuthenticationHelper.kt new file mode 100644 index 0000000..f498e4e --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/AuthenticationHelper.kt @@ -0,0 +1,24 @@ +package com.casic.smart.town.sanxi.util + +import com.pengxh.kt.lite.utils.SaveKeyValues + +object AuthenticationHelper { + + fun savePublicKey(key: String) { + SaveKeyValues.putValue("keyString", key) + } + + val publicKey: String? + get() = SaveKeyValues.getValue("keyString", "") as String? + + fun saveToken(token: String?) { + SaveKeyValues.putValue("token", token!!) + } + + val token: String? + get() = SaveKeyValues.getValue("token", "") as String? + + fun removeToken() { + SaveKeyValues.removeKey("token") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt index ab1a36d..99f0d19 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt @@ -14,5 +14,9 @@ const val PAGE_LIMIT = 20 const val DEFAULT_SERVER_CONFIG = "defaultServerConfig" - const val DEFAULT_SERVER = "http://111.198.10.15:11308" + const val DEFAULT_SERVER = "http://36.133.189.112:8082" + const val APP_AUTHORITY = "com.casic.smart.town.sanxi.fileprovider" + const val ACCOUNT = "account" + const val PASSWORD = "password" + const val USER_DETAIL_MODEL = "userDetailModel" } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/RSAUtils.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/RSAUtils.kt new file mode 100644 index 0000000..b9216ba --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/RSAUtils.kt @@ -0,0 +1,55 @@ +package com.casic.smart.town.sanxi.util + +import android.util.Base64 +import java.security.* +import java.security.spec.InvalidKeySpecException +import java.security.spec.X509EncodedKeySpec +import javax.crypto.BadPaddingException +import javax.crypto.Cipher +import javax.crypto.IllegalBlockSizeException +import javax.crypto.NoSuchPaddingException + +object RSAUtils { + //构建Cipher实例时所传入的的字符串,默认为"RSA/NONE/PKCS1Padding" + private fun processData(srcData: ByteArray, key: Key): ByteArray? { //用来保存处理结果 + var resultBytes: ByteArray? = null + try { //获取Cipher实例 + val cipher = Cipher.getInstance("RSA/None/PKCS1Padding") + //初始化Cipher,mode指定是加密还是解密,key为公钥或私钥 + cipher.init(Cipher.ENCRYPT_MODE, key) + //处理数据 + resultBytes = cipher.doFinal(srcData) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: NoSuchPaddingException) { + e.printStackTrace() + } catch (e: InvalidKeyException) { + e.printStackTrace() + } catch (e: BadPaddingException) { + e.printStackTrace() + } catch (e: IllegalBlockSizeException) { + e.printStackTrace() + } + return resultBytes + } + + fun encryptDataByPublicKey(srcData: ByteArray, publicKey: PublicKey): String { + val resultBytes = processData(srcData, publicKey) + return Base64.encodeToString(resultBytes, Base64.DEFAULT) + } + + fun keyStrToPublicKey(publicKeyStr: String?): PublicKey? { + var publicKey: PublicKey? = null + val keyBytes = Base64.decode(publicKeyStr, Base64.DEFAULT) + val keySpec = X509EncodedKeySpec(keyBytes) + try { + val keyFactory = KeyFactory.getInstance("RSA") + publicKey = keyFactory.generatePublic(keySpec) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: InvalidKeySpecException) { + e.printStackTrace() + } + return publicKey + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt index 63db0ec..50d1742 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt @@ -1,12 +1,64 @@ package com.casic.smart.town.sanxi.util.retrofit -import retrofit2.http.GET +import retrofit2.http.* interface RetrofitService { + /** + * PublicKey校验 + */ + @GET("/config/baseConfig") + suspend fun obtainPublicKey(): String + + /** + * 登录并获取Token + * + * @param sid + * @param account 用户名 + * @param secretKey 加密后的密码 + */ + @FormUrlEncoded + @POST("/user/login") + suspend fun login( + @Field("sid") sid: String, + @Field("username") account: String, + @Field("password") secretKey: String + ): String + + /** + * 退出登录 + */ + @GET("/user/logout") + suspend fun loginOut(@Header("token") token: String): String + + /** + * 获取用户信息 + */ + @GET("/user/info") + suspend fun obtainUserDetail(@Header("token") token: String): String + + /** + * 修改密码 + * + * @param oldPwd 旧密码 + * @param newPwd 新密码 + */ + @FormUrlEncoded + @POST("/mgr/changePwd") + suspend fun changePassword( + @Header("token") token: String, + @Field("oldPwd") oldPwd: String, + @Field("newPwd") newPwd: String + ): String /** * 根据布防状态统计闸井数量接口 */ @GET("/well/countByBfzt") - suspend fun countWellByState(): String + suspend fun countWellByState(@Header("token") token: String): String + + /** + * 更新APK版本 + */ + @POST("/app/checkVersion") + suspend fun obtainVersionResult(@Header("token") token: String): String } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt index 46bbf4b..410444b 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt @@ -1,5 +1,6 @@ package com.casic.smart.town.sanxi.util.retrofit +import com.casic.smart.town.sanxi.util.AuthenticationHelper import com.casic.smart.town.sanxi.util.LocaleConstant import com.pengxh.kt.lite.utils.RetrofitFactory import com.pengxh.kt.lite.utils.SaveKeyValues @@ -15,9 +16,52 @@ } /** + * 验证PublicKey + */ + suspend fun authenticate(): String { + return api.obtainPublicKey() + } + + /** + * 登录并获取Token + */ + suspend fun login(sid: String, account: String, secretKey: String): String { + return api.login(sid, account, secretKey) + } + + /** + * 退出登录 + */ + suspend fun loginOut(): String { + return api.loginOut(AuthenticationHelper.token!!) + } + + /** + * 获取用户信息 + */ + suspend fun obtainUserDetail(): String { + return api.obtainUserDetail(AuthenticationHelper.token!!) + } + + /** + * 修改密码 + */ + suspend fun changePassword(oldPwd: String, newPwd: String): String { + return api.changePassword(AuthenticationHelper.token!!, oldPwd, newPwd) + } + + /** * 根据布防状态统计闸井数量接口 */ suspend fun countWellByState(): String { - return api.countWellByState() + return api.countWellByState(AuthenticationHelper.token!!) } + + /** + * 更新APK版本 + */ + suspend fun updateVersion(): String { + return api.obtainVersionResult(AuthenticationHelper.token!!) + } + } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/AboutUsActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/AboutUsActivity.kt new file mode 100644 index 0000000..7e43c47 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/AboutUsActivity.kt @@ -0,0 +1,34 @@ +package com.casic.smart.town.sanxi.view + +import com.casic.smart.town.sanxi.BuildConfig +import com.casic.smart.town.sanxi.R +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.kt.lite.base.KotlinBaseActivity +import com.pengxh.kt.lite.extensions.convertColor +import com.pengxh.kt.lite.utils.ImmerseStatusBarUtil +import kotlinx.android.synthetic.main.activity_about_us.* +import kotlinx.android.synthetic.main.include_base_title.* + +class AboutUsActivity : KotlinBaseActivity() { + + override fun initLayoutView(): Int = R.layout.activity_about_us + + override fun setupTopBarLayout() { + ImmersionBar.with(this).statusBarDarkFont(false).init() + ImmerseStatusBarUtil.setColor(this, R.color.mainThemeColor.convertColor(this)) + titleView.text = "关于我们" + leftBackView.setOnClickListener { finish() } + } + + override fun initData() { + versionCodeView.text = String.format("版本信息:V${BuildConfig.VERSION_NAME}") + } + + override fun observeRequestState() { + + } + + override fun initEvent() { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/LoginActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/LoginActivity.kt new file mode 100644 index 0000000..b5d1d50 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/LoginActivity.kt @@ -0,0 +1,97 @@ +package com.casic.smart.town.sanxi.view + +import androidx.lifecycle.ViewModelProvider +import com.casic.smart.town.sanxi.R +import com.casic.smart.town.sanxi.util.AuthenticationHelper +import com.casic.smart.town.sanxi.util.LoadingDialogHub +import com.casic.smart.town.sanxi.util.LocaleConstant +import com.casic.smart.town.sanxi.util.RSAUtils +import com.casic.smart.town.sanxi.vm.AuthenticateViewModel +import com.casic.smart.town.sanxi.vm.LoginViewModel +import com.casic.smart.town.sanxi.vm.UserViewModel +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.kt.lite.base.KotlinBaseActivity +import com.pengxh.kt.lite.extensions.navigatePageTo +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.utils.SaveKeyValues +import kotlinx.android.synthetic.main.activity_login.* + +class LoginActivity : KotlinBaseActivity() { + + private lateinit var authenticateViewModel: AuthenticateViewModel + private lateinit var loginViewModel: LoginViewModel + private lateinit var userViewModel: UserViewModel + + override fun initLayoutView(): Int = R.layout.activity_login + + override fun setupTopBarLayout() { + ImmersionBar.with(this).statusBarDarkFont(true).init() + } + + override fun initData() { + // 设置默认账号密码 + userNameView.setText(SaveKeyValues.getValue(LocaleConstant.ACCOUNT, "") as String) + userPasswordView.setText(SaveKeyValues.getValue(LocaleConstant.PASSWORD, "") as String) + authenticateViewModel = ViewModelProvider(this)[AuthenticateViewModel::class.java] + loginViewModel = ViewModelProvider(this)[LoginViewModel::class.java] + userViewModel = ViewModelProvider(this)[UserViewModel::class.java] + + authenticateViewModel.keyModel.observe(this, { + if (it.code == 200) {//用code判断,别的判断可能有坑 + val keyString = it.data!!.publicKey!! + /** + * 修改密码需要用到key,先存着 + * */ + AuthenticationHelper.savePublicKey(keyString) + val publicKey = RSAUtils.keyStrToPublicKey(keyString) + + val account = userNameView.text.toString() + val userPassword = userPasswordView.text.toString() + val dataByPublicKey = + RSAUtils.encryptDataByPublicKey(userPassword.toByteArray(), publicKey!!) + //登录并获取Token,POST请求 + loginViewModel.enter(it.data!!.sid!!, account, dataByPublicKey) + loginViewModel.enterResultModel.observe(this, { loginResult -> + if (loginResult.code == 200) { + AuthenticationHelper.saveToken(loginResult.data!!.token!!) + /** + * 获取token之后保存用户信息 + * */ + userViewModel.obtainUserDetail() + //验证成功登录 + navigatePageTo() + finish() + } + }) + } + }) + } + + override fun observeRequestState() { + authenticateViewModel.loadState.observe(this, { + LoadingDialogHub.show(this, "登录中,请稍后") + }) + + loginViewModel.loadState.observe(this, { + LoadingDialogHub.dismiss() + }) + } + + override fun initEvent() { + loginButton.setOnClickListener { + val account = userNameView.text.toString().trim() + val userPassword = userPasswordView.text.toString().trim() + if (account.isBlank()) { + "用户名不能为空".show(this) + return@setOnClickListener + } + if (userPassword.isBlank()) { + "密码不能为空".show(this) + return@setOnClickListener + } + SaveKeyValues.putValue(LocaleConstant.ACCOUNT, account) + SaveKeyValues.putValue(LocaleConstant.PASSWORD, userPassword) + authenticateViewModel.obtainPublicKey() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt index 3bea1c9..a4fa951 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt @@ -7,6 +7,7 @@ import com.casic.smart.town.sanxi.R import com.casic.smart.town.sanxi.adapter.ViewPagerAdapter import com.casic.smart.town.sanxi.fragment.AlarmPageFragment +import com.casic.smart.town.sanxi.fragment.MinePageFragment import com.casic.smart.town.sanxi.fragment.MonitorPageFragment import com.casic.smart.town.sanxi.fragment.StatisticsPageFragment import com.gyf.immersionbar.ImmersionBar @@ -33,6 +34,7 @@ fragmentPages.add(StatisticsPageFragment()) fragmentPages.add(MonitorPageFragment()) fragmentPages.add(AlarmPageFragment()) + fragmentPages.add(MinePageFragment()) } override fun initLayoutView(): Int = R.layout.activity_main @@ -68,6 +70,9 @@ R.id.nav_alarm -> { mainViewPager.currentItem = 2 } + R.id.nav_more -> { + mainViewPager.currentItem = 3 + } } false } diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/PermissionActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/PermissionActivity.kt index c478d83..3238f3c 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/view/PermissionActivity.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/PermissionActivity.kt @@ -32,7 +32,7 @@ //先把导航隐私政策声明,后面导航会用到 MapsInitializer.updatePrivacyAgree(this, true) MapsInitializer.updatePrivacyShow(this, true, true) - this.navigatePageTo() + this.navigatePageTo() finish() } diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/SplashScreenActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/SplashScreenActivity.kt new file mode 100644 index 0000000..fa5a74d --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/SplashScreenActivity.kt @@ -0,0 +1,54 @@ +package com.casic.smart.town.sanxi.view + +import android.annotation.SuppressLint +import android.os.CountDownTimer +import androidx.lifecycle.ViewModelProvider +import com.casic.smart.town.sanxi.R +import com.casic.smart.town.sanxi.vm.UserViewModel +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.kt.lite.base.KotlinBaseActivity +import com.pengxh.kt.lite.extensions.navigatePageTo + +@SuppressLint("CustomSplashScreen") +class SplashScreenActivity : KotlinBaseActivity() { + + private lateinit var userDetailViewModel: UserViewModel + private val countDownTimer = object : CountDownTimer(1000, 500) { + override fun onFinish() { + /** + * 获取token之后保存用户信息 + * */ + userDetailViewModel.obtainUserDetail() + userDetailViewModel.userDetailModel.observe(this@SplashScreenActivity, { + if (it.code == 200) { + navigatePageTo() + } else { + navigatePageTo() + } + finish() + }) + } + + override fun onTick(millisUntilFinished: Long) { + + } + } + + override fun initLayoutView(): Int = R.layout.activity_splash + + override fun setupTopBarLayout() { + ImmersionBar.with(this).statusBarDarkFont(false).init() + } + + override fun initData() { + userDetailViewModel = ViewModelProvider(this).get(UserViewModel::class.java) + } + + override fun observeRequestState() { + + } + + override fun initEvent() { + countDownTimer.start() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/vm/AuthenticateViewModel.kt b/app/src/main/java/com/casic/smart/town/sanxi/vm/AuthenticateViewModel.kt new file mode 100644 index 0000000..87ddbf8 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/vm/AuthenticateViewModel.kt @@ -0,0 +1,35 @@ +package com.casic.smart.town.sanxi.vm + +import androidx.lifecycle.MutableLiveData +import com.casic.smart.town.sanxi.base.BaseApplication +import com.casic.smart.town.sanxi.extensions.separateResponseCode +import com.casic.smart.town.sanxi.extensions.toErrorMessage +import com.casic.smart.town.sanxi.model.PublicKeyModel +import com.casic.smart.town.sanxi.util.retrofit.RetrofitServiceManager +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.launch +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.vm.BaseViewModel +import com.pengxh.kt.lite.vm.LoadState + +class AuthenticateViewModel : BaseViewModel() { + + private val gson = Gson() + val keyModel = MutableLiveData() + + fun obtainPublicKey() = launch({ + loadState.value = LoadState.Loading + val response = RetrofitServiceManager.authenticate() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + keyModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ) + } else { + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + it.printStackTrace() + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/vm/LoginViewModel.kt b/app/src/main/java/com/casic/smart/town/sanxi/vm/LoginViewModel.kt new file mode 100644 index 0000000..c377c61 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/vm/LoginViewModel.kt @@ -0,0 +1,54 @@ +package com.casic.smart.town.sanxi.vm + +import androidx.lifecycle.MutableLiveData +import com.casic.smart.town.sanxi.base.BaseApplication +import com.casic.smart.town.sanxi.extensions.separateResponseCode +import com.casic.smart.town.sanxi.extensions.toErrorMessage +import com.casic.smart.town.sanxi.model.CommonResultModel +import com.casic.smart.town.sanxi.model.LoginResultModel +import com.casic.smart.town.sanxi.util.retrofit.RetrofitServiceManager +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.launch +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.vm.BaseViewModel +import com.pengxh.kt.lite.vm.LoadState + +class LoginViewModel : BaseViewModel() { + + private val gson = Gson() + val enterResultModel = MutableLiveData() + val outResultModel = MutableLiveData() + + fun enter(sid: String, account: String, secretKey: String) = launch({ + val response = RetrofitServiceManager.login(sid, account, secretKey) + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + loadState.value = LoadState.Success + "登录成功".show(BaseApplication.obtainInstance()) + enterResultModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ) + } else { + loadState.value = LoadState.Fail + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + loadState.value = LoadState.Fail + it.printStackTrace() + }) + + fun out() = launch({ + val response = RetrofitServiceManager.loginOut() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + outResultModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ) + } else { + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + it.printStackTrace() + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/vm/UserViewModel.kt b/app/src/main/java/com/casic/smart/town/sanxi/vm/UserViewModel.kt new file mode 100644 index 0000000..29a8756 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/vm/UserViewModel.kt @@ -0,0 +1,57 @@ +package com.casic.smart.town.sanxi.vm + +import androidx.lifecycle.MutableLiveData +import com.casic.smart.town.sanxi.base.BaseApplication +import com.casic.smart.town.sanxi.extensions.separateResponseCode +import com.casic.smart.town.sanxi.extensions.toErrorMessage +import com.casic.smart.town.sanxi.model.UserDetailModel +import com.casic.smart.town.sanxi.util.LocaleConstant +import com.casic.smart.town.sanxi.util.retrofit.RetrofitServiceManager +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.launch +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.utils.SaveKeyValues +import com.pengxh.kt.lite.vm.BaseViewModel +import com.pengxh.kt.lite.vm.LoadState + +class UserViewModel : BaseViewModel() { + + private val gson = Gson() + val userDetailModel = MutableLiveData() + + fun obtainUserDetail() = launch({ + val response = RetrofitServiceManager.obtainUserDetail() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + val userDetail = gson.fromJson( + response, object : TypeToken() {}.type + ) + userDetailModel.value = userDetail + SaveKeyValues.putValue(LocaleConstant.USER_DETAIL_MODEL, gson.toJson(userDetail.data)) + } else { + val errorModel = UserDetailModel() + errorModel.code = responseCode + userDetailModel.value = errorModel + //如果此次获取不到用户信息,那么就清空之前的用户缓存,然后让用户重新登录 + SaveKeyValues.removeKey(LocaleConstant.USER_DETAIL_MODEL) + } + }, { + it.printStackTrace() + }) + + fun changePassword(oldPwd: String, newPwd: String) = launch({ + loadState.value = LoadState.Loading + val response = RetrofitServiceManager.changePassword(oldPwd, newPwd) + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + loadState.value = LoadState.Success + } else { + loadState.value = LoadState.Fail + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + loadState.value = LoadState.Fail + it.printStackTrace() + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/vm/VersionViewModel.kt b/app/src/main/java/com/casic/smart/town/sanxi/vm/VersionViewModel.kt new file mode 100644 index 0000000..20ea457 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/vm/VersionViewModel.kt @@ -0,0 +1,38 @@ +package com.casic.smart.town.sanxi.vm + +import androidx.lifecycle.MutableLiveData +import com.casic.smart.town.sanxi.base.BaseApplication +import com.casic.smart.town.sanxi.extensions.separateResponseCode +import com.casic.smart.town.sanxi.extensions.toErrorMessage +import com.casic.smart.town.sanxi.model.VersionResultModel +import com.casic.smart.town.sanxi.util.retrofit.RetrofitServiceManager +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.launch +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.vm.BaseViewModel +import com.pengxh.kt.lite.vm.LoadState + +class VersionViewModel : BaseViewModel() { + + private val gson = Gson() + val versionResultModel = MutableLiveData() + + fun updateVersion() = launch({ + loadState.value = LoadState.Loading + val response = RetrofitServiceManager.updateVersion() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + loadState.value = LoadState.Success + versionResultModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ).data + } else { + loadState.value = LoadState.Fail + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + loadState.value = LoadState.Fail + it.printStackTrace() + }) +} \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_solid_layout_gray_radius_5.xml b/app/src/main/res/drawable/bg_solid_layout_gray_radius_5.xml new file mode 100644 index 0000000..96eb93e --- /dev/null +++ b/app/src/main/res/drawable/bg_solid_layout_gray_radius_5.xml @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/button_main_selector.xml b/app/src/main/res/drawable/button_main_selector.xml new file mode 100644 index 0000000..5875998 --- /dev/null +++ b/app/src/main/res/drawable/button_main_selector.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/button_white_selector.xml b/app/src/main/res/drawable/button_white_selector.xml new file mode 100644 index 0000000..4dd3c0b --- /dev/null +++ b/app/src/main/res/drawable/button_white_selector.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/download_progress.xml b/app/src/main/res/drawable/download_progress.xml new file mode 100644 index 0000000..493d923 --- /dev/null +++ b/app/src/main/res/drawable/download_progress.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_about.xml b/app/src/main/res/drawable/ic_about.xml new file mode 100644 index 0000000..8469319 --- /dev/null +++ b/app/src/main/res/drawable/ic_about.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_change_pwd.xml b/app/src/main/res/drawable/ic_change_pwd.xml new file mode 100644 index 0000000..75caa39 --- /dev/null +++ b/app/src/main/res/drawable/ic_change_pwd.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/ic_clean.xml b/app/src/main/res/drawable/ic_clean.xml new file mode 100644 index 0000000..027f809 --- /dev/null +++ b/app/src/main/res/drawable/ic_clean.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_left.xml b/app/src/main/res/drawable/ic_left.xml new file mode 100644 index 0000000..04ee5da --- /dev/null +++ b/app/src/main/res/drawable/ic_left.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1df917d..8d4f33b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -6,6 +6,7 @@ + @@ -32,11 +33,14 @@ + + + + + + + diff --git a/app/src/main/assets/svg/mine.svg b/app/src/main/assets/svg/mine.svg new file mode 100644 index 0000000..3fbb0a3 --- /dev/null +++ b/app/src/main/assets/svg/mine.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt b/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt index d730417..b1972c2 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt @@ -1,8 +1,10 @@ package com.casic.smart.town.sanxi.extensions import com.casic.smart.town.sanxi.model.ErrorMessageModel +import com.casic.smart.town.sanxi.util.LocaleConstant import com.google.gson.Gson import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.utils.SaveKeyValues import org.json.JSONObject fun String.separateResponseCode(): Int { @@ -17,4 +19,15 @@ this, object : TypeToken() {}.type ) return errorModel.message.toString() +} + +/** + * 下载路径为 http://xx.com/static/ 拼接downloadUrl + * */ +fun String.appendDownloadUrl(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + LocaleConstant.DEFAULT_SERVER_CONFIG, LocaleConstant.DEFAULT_SERVER + ) as String + return "$defaultValue/static/${this}" } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt b/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt new file mode 100644 index 0000000..1809dc5 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt @@ -0,0 +1,288 @@ +package com.casic.smart.town.sanxi.fragment + +import android.app.ProgressDialog +import android.content.Intent +import android.net.Uri +import android.os.Build +import android.os.CountDownTimer +import androidx.core.content.FileProvider +import androidx.lifecycle.ViewModelProvider +import com.casic.smart.town.sanxi.BuildConfig +import com.casic.smart.town.sanxi.R +import com.casic.smart.town.sanxi.extensions.appendDownloadUrl +import com.casic.smart.town.sanxi.model.UserDetailModel +import com.casic.smart.town.sanxi.util.AuthenticationHelper +import com.casic.smart.town.sanxi.util.LoadingDialogHub +import com.casic.smart.town.sanxi.util.LocaleConstant +import com.casic.smart.town.sanxi.util.RSAUtils +import com.casic.smart.town.sanxi.view.AboutUsActivity +import com.casic.smart.town.sanxi.view.LoginActivity +import com.casic.smart.town.sanxi.vm.LoginViewModel +import com.casic.smart.town.sanxi.vm.UserViewModel +import com.casic.smart.town.sanxi.vm.VersionViewModel +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.base.KotlinBaseFragment +import com.pengxh.kt.lite.callback.OnDownloadListener +import com.pengxh.kt.lite.extensions.* +import com.pengxh.kt.lite.utils.PageNavigationManager +import com.pengxh.kt.lite.utils.SaveKeyValues +import com.pengxh.kt.lite.vm.LoadState +import com.pengxh.kt.lite.widget.dialog.AlertControlDialog +import com.pengxh.kt.lite.widget.dialog.ChangePasswordDialog +import kotlinx.android.synthetic.main.fragment_mine.* +import java.io.File +import java.nio.charset.StandardCharsets + +class MinePageFragment : KotlinBaseFragment() { + + private lateinit var userData: UserDetailModel.Data + private lateinit var userViewModel: UserViewModel + private lateinit var loginViewModel: LoginViewModel + private lateinit var versionViewModel: VersionViewModel + private lateinit var progressDialog: ProgressDialog + + override fun initLayoutView(): Int = R.layout.fragment_mine + + override fun setupTopBarLayout() { + + } + + override fun initData() { + userViewModel = ViewModelProvider(this)[UserViewModel::class.java] + loginViewModel = ViewModelProvider(this)[LoginViewModel::class.java] + versionViewModel = ViewModelProvider(this)[VersionViewModel::class.java] + + //初始化下载对话框 + progressDialog = ProgressDialog(requireContext()) + progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL) + progressDialog.setProgressDrawable(resources.getDrawable(R.drawable.download_progress)) + progressDialog.setCanceledOnTouchOutside(false) + progressDialog.setCancelable(false) + + /** + * 数据监听 + * */ + dataObserve() + } + + override fun observeRequestState() { + userViewModel.loadState.observe(this, { + when (it) { + is LoadState.Loading -> LoadingDialogHub.show(requireActivity(), "修改中,请稍后") + is LoadState.Success -> { + "修改成功,请重新登录".show(requireContext()) + LoadingDialogHub.dismiss() + AuthenticationHelper.removeToken() + requireContext().navigatePageTo() + PageNavigationManager.finishAllActivity() + } + else -> LoadingDialogHub.dismiss() + } + }) + + versionViewModel.loadState.observe(this, { + when (it) { + is LoadState.Loading -> LoadingDialogHub.show(requireActivity(), "检查版本中,请稍后") + else -> LoadingDialogHub.dismiss() + } + }) + } + + override fun initEvent() { + userImageView.setOnClickListener { + "尽情期待~".show(requireContext()) + } + + updateUserButton.setOnClickListener { + LoadingDialogHub.show(requireActivity(), "同步中,请稍后...") + userViewModel.obtainUserDetail() + } + + changePwdLayout.setOnClickListener { + ChangePasswordDialog.Builder() + .setContext(requireContext()) + .setOnDialogButtonClickListener(object : + ChangePasswordDialog.OnDialogButtonClickListener { + override fun onConfirmClick(oldPwd: String, newPwd: String) { + val publicKey = + RSAUtils.keyStrToPublicKey(AuthenticationHelper.publicKey)!! + val oldPassKey = RSAUtils.encryptDataByPublicKey( + oldPwd.toByteArray(StandardCharsets.UTF_8), + publicKey + ) + val newPassKey = RSAUtils.encryptDataByPublicKey( + newPwd.toByteArray(StandardCharsets.UTF_8), + publicKey + ) + userViewModel.changePassword(oldPassKey, newPassKey) + } + }).build().show() + } + + aboutUsLayout.setOnClickListener { + requireContext().navigatePageTo() + } + + updateVersionLayout.setOnClickListener { + versionViewModel.updateVersion() + } + + clearCacheLayout.setOnClickListener { + //删除缓存之后在设置缓存大小 + LoadingDialogHub.show(requireActivity(), "清理中,请稍后") + File(requireContext().cacheDir.path).deleteFile() + object : CountDownTimer(1500, 500) { + override fun onTick(millisUntilFinished: Long) { + + } + + override fun onFinish() { + LoadingDialogHub.dismiss() + cacheSizeView.text = collectApplicationCache().formatFileSize() + } + }.start() + } + + loginOutButton.setOnClickListener { + AlertControlDialog.Builder() + .setContext(requireContext()) + .setTitle("退出登录") + .setMessage("确定要退出吗?") + .setNegativeButton("取消") + .setPositiveButton("确定") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + loginViewModel.out() + } + + override fun onCancelClick() {} + }).build().show() + } + } + + private fun dataObserve() { + userViewModel.userDetailModel.observe(this, { + if (it.code == 200) { + LoadingDialogHub.dismiss() + "同步完成".show(requireContext()) + userData = it.data + updateUserInfo() + } + }) + + versionViewModel.versionResultModel.observe(this, { + if (BuildConfig.VERSION_NAME == it.version) { + "已是最新版本,无需更新".show(requireContext()) + } else { + AlertControlDialog.Builder() + .setContext(requireContext()) + .setTitle("提示") + .setMessage("有新版本,是否更新?") + .setNegativeButton("稍后再说") + .setPositiveButton("立即下载") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + downloadApk(it.downloadUrl) + } + + override fun onCancelClick() { + + } + }).build().show() + } + }) + + loginViewModel.outResultModel.observe(this, { + if (it.code == 200) { + AuthenticationHelper.removeToken() + requireContext().navigatePageTo() + PageNavigationManager.finishAllActivity() + } + }) + } + + private fun collectApplicationCache(): Long { + return File(requireContext().cacheDir.path).calculateSize() + } + + override fun onResume() { + val userDetailJson = SaveKeyValues.getValue(LocaleConstant.USER_DETAIL_MODEL, "") as String + if (userDetailJson.isNotBlank()) { + userData = Gson().fromJson( + userDetailJson, object : TypeToken() {}.type + ) + updateUserInfo() + } + //缓存 + cacheSizeView.text = collectApplicationCache().formatFileSize() + super.onResume() + } + + private fun updateUserInfo() { + //设置头像,圆形,暂时是默认的 +// val roundDrawable = +// BitmapFactory.decodeResource(requireContext().resources, R.mipmap.login_casic) +// .createRoundDrawable( +// requireContext(), +// 3f.dp2px(requireContext()), +// R.color.mainThemeColor.convertColor(requireContext()) +// ) +// userImageView.setImageDrawable(roundDrawable) + userNameView.text = userData.name + userPhoneView.text = String.format("电话:${userData.phone}") + userDeptView.text = String.format("部门:${userData.deptName}") + } + + private fun downloadApk(url: String?) { + progressDialog.setMessage("下载新版本中...") + progressDialog.show() + if (url.toString().isBlank()) { + "抱歉,版本下载失败".show(requireContext()) + return + } + /** + * http://111.198.10.15:11304/static/apk/1.0.1.apk + * */ + val downloadPath = url!!.appendDownloadUrl() + //开始下载 + downloadPath.downloadFile(requireContext().createDownloadFileDir().toString(), + object : OnDownloadListener { + override fun onDownloadStart(totalBytes: Long) { + progressDialog.max = totalBytes.toInt() + } + + override fun onProgressChanged(currentBytes: Long) { + progressDialog.progress = currentBytes.toInt() + } + + override fun onDownloadEnd(file: File?) { + progressDialog.dismiss() + progressDialog.progress = 0 + //安装APK + installApk(file) + } + }) + } + + private fun installApk(apkPackage: File?) { + if (apkPackage == null) { + "文件异常,无法安装".show(requireContext()) + return + } + val intent = Intent(Intent.ACTION_VIEW) + val data: Uri + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { //判断版本大于等于7.0 + data = FileProvider.getUriForFile( + requireContext(), LocaleConstant.APP_AUTHORITY, apkPackage + ) + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) // 给目标应用一个临时授权 + } else { + data = Uri.fromFile(apkPackage) + } + intent.setDataAndType(data, "application/vnd.android.package-archive") + requireContext().startActivity(intent) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java new file mode 100644 index 0000000..467e33d --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java @@ -0,0 +1,43 @@ +package com.casic.smart.town.sanxi.model; + +/** + * 普通实体类,失败/成功数据结构一致 + */ +public class CommonResultModel { + private int code; + private String data; + private String message; + private boolean isSuccess; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return isSuccess; + } + + public void setSuccess(boolean success) { + isSuccess = success; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java new file mode 100644 index 0000000..9bdabef --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java @@ -0,0 +1,62 @@ +package com.casic.smart.town.sanxi.model; + +public class LoginResultModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private String kaptcha; + private String token; + + public String getKaptcha() { + return kaptcha; + } + + public void setKaptcha(String kaptcha) { + this.kaptcha = kaptcha; + } + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java new file mode 100644 index 0000000..09f8d59 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java @@ -0,0 +1,80 @@ +package com.casic.smart.town.sanxi.model; + +public class PublicKeyModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private boolean appKaptcha; + private boolean kaptcha; + private String publicKey; + private String sid; + + public boolean isAppKaptcha() { + return appKaptcha; + } + + public void setAppKaptcha(boolean appKaptcha) { + this.appKaptcha = appKaptcha; + } + + public boolean isKaptcha() { + return kaptcha; + } + + public void setKaptcha(boolean kaptcha) { + this.kaptcha = kaptcha; + } + + public String getPublicKey() { + return publicKey; + } + + public void setPublicKey(String publicKey) { + this.publicKey = publicKey; + } + + public String getSid() { + return sid; + } + + public void setSid(String sid) { + this.sid = sid; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java new file mode 100644 index 0000000..db577fb --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java @@ -0,0 +1,227 @@ +package com.casic.smart.town.sanxi.model; + + +import java.util.List; + +public class UserDetailModel { + private int code; + private Data data; + private String message; + private boolean success; + + public void setCode(int code) { + this.code = code; + } + + public int getCode() { + return code; + } + + public void setData(Data data) { + this.data = data; + } + + public Data getData() { + return data; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getMessage() { + return message; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public boolean getSuccess() { + return success; + } + + public static class Data { + + private String account; + private String attr1; + private String avatar; + private String bizData; + private List dataScope; + private String deptId; + private String deptName; + private List devices; + private String id; + private String ipAddr; + private String name; + private String phone; + private List roleList; + private List roleNames; + private List roleTips; + private String scopeType; + private String sysData; + private String targetId; + private String targetName; + private String tenantId; + + public void setAccount(String account) { + this.account = account; + } + + public String getAccount() { + return account; + } + + public void setAttr1(String attr1) { + this.attr1 = attr1; + } + + public String getAttr1() { + return attr1; + } + + public void setAvatar(String avatar) { + this.avatar = avatar; + } + + public String getAvatar() { + return avatar; + } + + public void setBizData(String bizData) { + this.bizData = bizData; + } + + public String getBizData() { + return bizData; + } + + public void setDataScope(List dataScope) { + this.dataScope = dataScope; + } + + public List getDataScope() { + return dataScope; + } + + public void setDeptId(String deptId) { + this.deptId = deptId; + } + + public String getDeptId() { + return deptId; + } + + public void setDeptName(String deptName) { + this.deptName = deptName; + } + + public String getDeptName() { + return deptName; + } + + public void setDevices(List devices) { + this.devices = devices; + } + + public List getDevices() { + return devices; + } + + public void setId(String id) { + this.id = id; + } + + public String getId() { + return id; + } + + public void setIpAddr(String ipAddr) { + this.ipAddr = ipAddr; + } + + public String getIpAddr() { + return ipAddr; + } + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setPhone(String phone) { + this.phone = phone; + } + + public String getPhone() { + return phone; + } + + public void setRoleList(List roleList) { + this.roleList = roleList; + } + + public List getRoleList() { + return roleList; + } + + public void setRoleNames(List roleNames) { + this.roleNames = roleNames; + } + + public List getRoleNames() { + return roleNames; + } + + public void setRoleTips(List roleTips) { + this.roleTips = roleTips; + } + + public List getRoleTips() { + return roleTips; + } + + public void setScopeType(String scopeType) { + this.scopeType = scopeType; + } + + public String getScopeType() { + return scopeType; + } + + public void setSysData(String sysData) { + this.sysData = sysData; + } + + public String getSysData() { + return sysData; + } + + public void setTargetId(String targetId) { + this.targetId = targetId; + } + + public String getTargetId() { + return targetId; + } + + public void setTargetName(String targetName) { + this.targetName = targetName; + } + + public String getTargetName() { + return targetName; + } + + public void setTenantId(String tenantId) { + this.tenantId = tenantId; + } + + public String getTenantId() { + return tenantId; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/VersionResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/VersionResultModel.java new file mode 100644 index 0000000..79374e0 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/VersionResultModel.java @@ -0,0 +1,61 @@ +package com.casic.smart.town.sanxi.model; + +public class VersionResultModel { + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private String downloadUrl; + private String version; + + public String getDownloadUrl() { + return downloadUrl; + } + + public void setDownloadUrl(String downloadUrl) { + this.downloadUrl = downloadUrl; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/AuthenticationHelper.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/AuthenticationHelper.kt new file mode 100644 index 0000000..f498e4e --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/AuthenticationHelper.kt @@ -0,0 +1,24 @@ +package com.casic.smart.town.sanxi.util + +import com.pengxh.kt.lite.utils.SaveKeyValues + +object AuthenticationHelper { + + fun savePublicKey(key: String) { + SaveKeyValues.putValue("keyString", key) + } + + val publicKey: String? + get() = SaveKeyValues.getValue("keyString", "") as String? + + fun saveToken(token: String?) { + SaveKeyValues.putValue("token", token!!) + } + + val token: String? + get() = SaveKeyValues.getValue("token", "") as String? + + fun removeToken() { + SaveKeyValues.removeKey("token") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt index ab1a36d..99f0d19 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt @@ -14,5 +14,9 @@ const val PAGE_LIMIT = 20 const val DEFAULT_SERVER_CONFIG = "defaultServerConfig" - const val DEFAULT_SERVER = "http://111.198.10.15:11308" + const val DEFAULT_SERVER = "http://36.133.189.112:8082" + const val APP_AUTHORITY = "com.casic.smart.town.sanxi.fileprovider" + const val ACCOUNT = "account" + const val PASSWORD = "password" + const val USER_DETAIL_MODEL = "userDetailModel" } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/RSAUtils.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/RSAUtils.kt new file mode 100644 index 0000000..b9216ba --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/RSAUtils.kt @@ -0,0 +1,55 @@ +package com.casic.smart.town.sanxi.util + +import android.util.Base64 +import java.security.* +import java.security.spec.InvalidKeySpecException +import java.security.spec.X509EncodedKeySpec +import javax.crypto.BadPaddingException +import javax.crypto.Cipher +import javax.crypto.IllegalBlockSizeException +import javax.crypto.NoSuchPaddingException + +object RSAUtils { + //构建Cipher实例时所传入的的字符串,默认为"RSA/NONE/PKCS1Padding" + private fun processData(srcData: ByteArray, key: Key): ByteArray? { //用来保存处理结果 + var resultBytes: ByteArray? = null + try { //获取Cipher实例 + val cipher = Cipher.getInstance("RSA/None/PKCS1Padding") + //初始化Cipher,mode指定是加密还是解密,key为公钥或私钥 + cipher.init(Cipher.ENCRYPT_MODE, key) + //处理数据 + resultBytes = cipher.doFinal(srcData) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: NoSuchPaddingException) { + e.printStackTrace() + } catch (e: InvalidKeyException) { + e.printStackTrace() + } catch (e: BadPaddingException) { + e.printStackTrace() + } catch (e: IllegalBlockSizeException) { + e.printStackTrace() + } + return resultBytes + } + + fun encryptDataByPublicKey(srcData: ByteArray, publicKey: PublicKey): String { + val resultBytes = processData(srcData, publicKey) + return Base64.encodeToString(resultBytes, Base64.DEFAULT) + } + + fun keyStrToPublicKey(publicKeyStr: String?): PublicKey? { + var publicKey: PublicKey? = null + val keyBytes = Base64.decode(publicKeyStr, Base64.DEFAULT) + val keySpec = X509EncodedKeySpec(keyBytes) + try { + val keyFactory = KeyFactory.getInstance("RSA") + publicKey = keyFactory.generatePublic(keySpec) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: InvalidKeySpecException) { + e.printStackTrace() + } + return publicKey + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt index 63db0ec..50d1742 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt @@ -1,12 +1,64 @@ package com.casic.smart.town.sanxi.util.retrofit -import retrofit2.http.GET +import retrofit2.http.* interface RetrofitService { + /** + * PublicKey校验 + */ + @GET("/config/baseConfig") + suspend fun obtainPublicKey(): String + + /** + * 登录并获取Token + * + * @param sid + * @param account 用户名 + * @param secretKey 加密后的密码 + */ + @FormUrlEncoded + @POST("/user/login") + suspend fun login( + @Field("sid") sid: String, + @Field("username") account: String, + @Field("password") secretKey: String + ): String + + /** + * 退出登录 + */ + @GET("/user/logout") + suspend fun loginOut(@Header("token") token: String): String + + /** + * 获取用户信息 + */ + @GET("/user/info") + suspend fun obtainUserDetail(@Header("token") token: String): String + + /** + * 修改密码 + * + * @param oldPwd 旧密码 + * @param newPwd 新密码 + */ + @FormUrlEncoded + @POST("/mgr/changePwd") + suspend fun changePassword( + @Header("token") token: String, + @Field("oldPwd") oldPwd: String, + @Field("newPwd") newPwd: String + ): String /** * 根据布防状态统计闸井数量接口 */ @GET("/well/countByBfzt") - suspend fun countWellByState(): String + suspend fun countWellByState(@Header("token") token: String): String + + /** + * 更新APK版本 + */ + @POST("/app/checkVersion") + suspend fun obtainVersionResult(@Header("token") token: String): String } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt index 46bbf4b..410444b 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt @@ -1,5 +1,6 @@ package com.casic.smart.town.sanxi.util.retrofit +import com.casic.smart.town.sanxi.util.AuthenticationHelper import com.casic.smart.town.sanxi.util.LocaleConstant import com.pengxh.kt.lite.utils.RetrofitFactory import com.pengxh.kt.lite.utils.SaveKeyValues @@ -15,9 +16,52 @@ } /** + * 验证PublicKey + */ + suspend fun authenticate(): String { + return api.obtainPublicKey() + } + + /** + * 登录并获取Token + */ + suspend fun login(sid: String, account: String, secretKey: String): String { + return api.login(sid, account, secretKey) + } + + /** + * 退出登录 + */ + suspend fun loginOut(): String { + return api.loginOut(AuthenticationHelper.token!!) + } + + /** + * 获取用户信息 + */ + suspend fun obtainUserDetail(): String { + return api.obtainUserDetail(AuthenticationHelper.token!!) + } + + /** + * 修改密码 + */ + suspend fun changePassword(oldPwd: String, newPwd: String): String { + return api.changePassword(AuthenticationHelper.token!!, oldPwd, newPwd) + } + + /** * 根据布防状态统计闸井数量接口 */ suspend fun countWellByState(): String { - return api.countWellByState() + return api.countWellByState(AuthenticationHelper.token!!) } + + /** + * 更新APK版本 + */ + suspend fun updateVersion(): String { + return api.obtainVersionResult(AuthenticationHelper.token!!) + } + } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/AboutUsActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/AboutUsActivity.kt new file mode 100644 index 0000000..7e43c47 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/AboutUsActivity.kt @@ -0,0 +1,34 @@ +package com.casic.smart.town.sanxi.view + +import com.casic.smart.town.sanxi.BuildConfig +import com.casic.smart.town.sanxi.R +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.kt.lite.base.KotlinBaseActivity +import com.pengxh.kt.lite.extensions.convertColor +import com.pengxh.kt.lite.utils.ImmerseStatusBarUtil +import kotlinx.android.synthetic.main.activity_about_us.* +import kotlinx.android.synthetic.main.include_base_title.* + +class AboutUsActivity : KotlinBaseActivity() { + + override fun initLayoutView(): Int = R.layout.activity_about_us + + override fun setupTopBarLayout() { + ImmersionBar.with(this).statusBarDarkFont(false).init() + ImmerseStatusBarUtil.setColor(this, R.color.mainThemeColor.convertColor(this)) + titleView.text = "关于我们" + leftBackView.setOnClickListener { finish() } + } + + override fun initData() { + versionCodeView.text = String.format("版本信息:V${BuildConfig.VERSION_NAME}") + } + + override fun observeRequestState() { + + } + + override fun initEvent() { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/LoginActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/LoginActivity.kt new file mode 100644 index 0000000..b5d1d50 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/LoginActivity.kt @@ -0,0 +1,97 @@ +package com.casic.smart.town.sanxi.view + +import androidx.lifecycle.ViewModelProvider +import com.casic.smart.town.sanxi.R +import com.casic.smart.town.sanxi.util.AuthenticationHelper +import com.casic.smart.town.sanxi.util.LoadingDialogHub +import com.casic.smart.town.sanxi.util.LocaleConstant +import com.casic.smart.town.sanxi.util.RSAUtils +import com.casic.smart.town.sanxi.vm.AuthenticateViewModel +import com.casic.smart.town.sanxi.vm.LoginViewModel +import com.casic.smart.town.sanxi.vm.UserViewModel +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.kt.lite.base.KotlinBaseActivity +import com.pengxh.kt.lite.extensions.navigatePageTo +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.utils.SaveKeyValues +import kotlinx.android.synthetic.main.activity_login.* + +class LoginActivity : KotlinBaseActivity() { + + private lateinit var authenticateViewModel: AuthenticateViewModel + private lateinit var loginViewModel: LoginViewModel + private lateinit var userViewModel: UserViewModel + + override fun initLayoutView(): Int = R.layout.activity_login + + override fun setupTopBarLayout() { + ImmersionBar.with(this).statusBarDarkFont(true).init() + } + + override fun initData() { + // 设置默认账号密码 + userNameView.setText(SaveKeyValues.getValue(LocaleConstant.ACCOUNT, "") as String) + userPasswordView.setText(SaveKeyValues.getValue(LocaleConstant.PASSWORD, "") as String) + authenticateViewModel = ViewModelProvider(this)[AuthenticateViewModel::class.java] + loginViewModel = ViewModelProvider(this)[LoginViewModel::class.java] + userViewModel = ViewModelProvider(this)[UserViewModel::class.java] + + authenticateViewModel.keyModel.observe(this, { + if (it.code == 200) {//用code判断,别的判断可能有坑 + val keyString = it.data!!.publicKey!! + /** + * 修改密码需要用到key,先存着 + * */ + AuthenticationHelper.savePublicKey(keyString) + val publicKey = RSAUtils.keyStrToPublicKey(keyString) + + val account = userNameView.text.toString() + val userPassword = userPasswordView.text.toString() + val dataByPublicKey = + RSAUtils.encryptDataByPublicKey(userPassword.toByteArray(), publicKey!!) + //登录并获取Token,POST请求 + loginViewModel.enter(it.data!!.sid!!, account, dataByPublicKey) + loginViewModel.enterResultModel.observe(this, { loginResult -> + if (loginResult.code == 200) { + AuthenticationHelper.saveToken(loginResult.data!!.token!!) + /** + * 获取token之后保存用户信息 + * */ + userViewModel.obtainUserDetail() + //验证成功登录 + navigatePageTo() + finish() + } + }) + } + }) + } + + override fun observeRequestState() { + authenticateViewModel.loadState.observe(this, { + LoadingDialogHub.show(this, "登录中,请稍后") + }) + + loginViewModel.loadState.observe(this, { + LoadingDialogHub.dismiss() + }) + } + + override fun initEvent() { + loginButton.setOnClickListener { + val account = userNameView.text.toString().trim() + val userPassword = userPasswordView.text.toString().trim() + if (account.isBlank()) { + "用户名不能为空".show(this) + return@setOnClickListener + } + if (userPassword.isBlank()) { + "密码不能为空".show(this) + return@setOnClickListener + } + SaveKeyValues.putValue(LocaleConstant.ACCOUNT, account) + SaveKeyValues.putValue(LocaleConstant.PASSWORD, userPassword) + authenticateViewModel.obtainPublicKey() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt index 3bea1c9..a4fa951 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt @@ -7,6 +7,7 @@ import com.casic.smart.town.sanxi.R import com.casic.smart.town.sanxi.adapter.ViewPagerAdapter import com.casic.smart.town.sanxi.fragment.AlarmPageFragment +import com.casic.smart.town.sanxi.fragment.MinePageFragment import com.casic.smart.town.sanxi.fragment.MonitorPageFragment import com.casic.smart.town.sanxi.fragment.StatisticsPageFragment import com.gyf.immersionbar.ImmersionBar @@ -33,6 +34,7 @@ fragmentPages.add(StatisticsPageFragment()) fragmentPages.add(MonitorPageFragment()) fragmentPages.add(AlarmPageFragment()) + fragmentPages.add(MinePageFragment()) } override fun initLayoutView(): Int = R.layout.activity_main @@ -68,6 +70,9 @@ R.id.nav_alarm -> { mainViewPager.currentItem = 2 } + R.id.nav_more -> { + mainViewPager.currentItem = 3 + } } false } diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/PermissionActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/PermissionActivity.kt index c478d83..3238f3c 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/view/PermissionActivity.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/PermissionActivity.kt @@ -32,7 +32,7 @@ //先把导航隐私政策声明,后面导航会用到 MapsInitializer.updatePrivacyAgree(this, true) MapsInitializer.updatePrivacyShow(this, true, true) - this.navigatePageTo() + this.navigatePageTo() finish() } diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/SplashScreenActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/SplashScreenActivity.kt new file mode 100644 index 0000000..fa5a74d --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/SplashScreenActivity.kt @@ -0,0 +1,54 @@ +package com.casic.smart.town.sanxi.view + +import android.annotation.SuppressLint +import android.os.CountDownTimer +import androidx.lifecycle.ViewModelProvider +import com.casic.smart.town.sanxi.R +import com.casic.smart.town.sanxi.vm.UserViewModel +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.kt.lite.base.KotlinBaseActivity +import com.pengxh.kt.lite.extensions.navigatePageTo + +@SuppressLint("CustomSplashScreen") +class SplashScreenActivity : KotlinBaseActivity() { + + private lateinit var userDetailViewModel: UserViewModel + private val countDownTimer = object : CountDownTimer(1000, 500) { + override fun onFinish() { + /** + * 获取token之后保存用户信息 + * */ + userDetailViewModel.obtainUserDetail() + userDetailViewModel.userDetailModel.observe(this@SplashScreenActivity, { + if (it.code == 200) { + navigatePageTo() + } else { + navigatePageTo() + } + finish() + }) + } + + override fun onTick(millisUntilFinished: Long) { + + } + } + + override fun initLayoutView(): Int = R.layout.activity_splash + + override fun setupTopBarLayout() { + ImmersionBar.with(this).statusBarDarkFont(false).init() + } + + override fun initData() { + userDetailViewModel = ViewModelProvider(this).get(UserViewModel::class.java) + } + + override fun observeRequestState() { + + } + + override fun initEvent() { + countDownTimer.start() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/vm/AuthenticateViewModel.kt b/app/src/main/java/com/casic/smart/town/sanxi/vm/AuthenticateViewModel.kt new file mode 100644 index 0000000..87ddbf8 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/vm/AuthenticateViewModel.kt @@ -0,0 +1,35 @@ +package com.casic.smart.town.sanxi.vm + +import androidx.lifecycle.MutableLiveData +import com.casic.smart.town.sanxi.base.BaseApplication +import com.casic.smart.town.sanxi.extensions.separateResponseCode +import com.casic.smart.town.sanxi.extensions.toErrorMessage +import com.casic.smart.town.sanxi.model.PublicKeyModel +import com.casic.smart.town.sanxi.util.retrofit.RetrofitServiceManager +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.launch +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.vm.BaseViewModel +import com.pengxh.kt.lite.vm.LoadState + +class AuthenticateViewModel : BaseViewModel() { + + private val gson = Gson() + val keyModel = MutableLiveData() + + fun obtainPublicKey() = launch({ + loadState.value = LoadState.Loading + val response = RetrofitServiceManager.authenticate() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + keyModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ) + } else { + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + it.printStackTrace() + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/vm/LoginViewModel.kt b/app/src/main/java/com/casic/smart/town/sanxi/vm/LoginViewModel.kt new file mode 100644 index 0000000..c377c61 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/vm/LoginViewModel.kt @@ -0,0 +1,54 @@ +package com.casic.smart.town.sanxi.vm + +import androidx.lifecycle.MutableLiveData +import com.casic.smart.town.sanxi.base.BaseApplication +import com.casic.smart.town.sanxi.extensions.separateResponseCode +import com.casic.smart.town.sanxi.extensions.toErrorMessage +import com.casic.smart.town.sanxi.model.CommonResultModel +import com.casic.smart.town.sanxi.model.LoginResultModel +import com.casic.smart.town.sanxi.util.retrofit.RetrofitServiceManager +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.launch +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.vm.BaseViewModel +import com.pengxh.kt.lite.vm.LoadState + +class LoginViewModel : BaseViewModel() { + + private val gson = Gson() + val enterResultModel = MutableLiveData() + val outResultModel = MutableLiveData() + + fun enter(sid: String, account: String, secretKey: String) = launch({ + val response = RetrofitServiceManager.login(sid, account, secretKey) + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + loadState.value = LoadState.Success + "登录成功".show(BaseApplication.obtainInstance()) + enterResultModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ) + } else { + loadState.value = LoadState.Fail + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + loadState.value = LoadState.Fail + it.printStackTrace() + }) + + fun out() = launch({ + val response = RetrofitServiceManager.loginOut() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + outResultModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ) + } else { + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + it.printStackTrace() + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/vm/UserViewModel.kt b/app/src/main/java/com/casic/smart/town/sanxi/vm/UserViewModel.kt new file mode 100644 index 0000000..29a8756 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/vm/UserViewModel.kt @@ -0,0 +1,57 @@ +package com.casic.smart.town.sanxi.vm + +import androidx.lifecycle.MutableLiveData +import com.casic.smart.town.sanxi.base.BaseApplication +import com.casic.smart.town.sanxi.extensions.separateResponseCode +import com.casic.smart.town.sanxi.extensions.toErrorMessage +import com.casic.smart.town.sanxi.model.UserDetailModel +import com.casic.smart.town.sanxi.util.LocaleConstant +import com.casic.smart.town.sanxi.util.retrofit.RetrofitServiceManager +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.launch +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.utils.SaveKeyValues +import com.pengxh.kt.lite.vm.BaseViewModel +import com.pengxh.kt.lite.vm.LoadState + +class UserViewModel : BaseViewModel() { + + private val gson = Gson() + val userDetailModel = MutableLiveData() + + fun obtainUserDetail() = launch({ + val response = RetrofitServiceManager.obtainUserDetail() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + val userDetail = gson.fromJson( + response, object : TypeToken() {}.type + ) + userDetailModel.value = userDetail + SaveKeyValues.putValue(LocaleConstant.USER_DETAIL_MODEL, gson.toJson(userDetail.data)) + } else { + val errorModel = UserDetailModel() + errorModel.code = responseCode + userDetailModel.value = errorModel + //如果此次获取不到用户信息,那么就清空之前的用户缓存,然后让用户重新登录 + SaveKeyValues.removeKey(LocaleConstant.USER_DETAIL_MODEL) + } + }, { + it.printStackTrace() + }) + + fun changePassword(oldPwd: String, newPwd: String) = launch({ + loadState.value = LoadState.Loading + val response = RetrofitServiceManager.changePassword(oldPwd, newPwd) + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + loadState.value = LoadState.Success + } else { + loadState.value = LoadState.Fail + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + loadState.value = LoadState.Fail + it.printStackTrace() + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/vm/VersionViewModel.kt b/app/src/main/java/com/casic/smart/town/sanxi/vm/VersionViewModel.kt new file mode 100644 index 0000000..20ea457 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/vm/VersionViewModel.kt @@ -0,0 +1,38 @@ +package com.casic.smart.town.sanxi.vm + +import androidx.lifecycle.MutableLiveData +import com.casic.smart.town.sanxi.base.BaseApplication +import com.casic.smart.town.sanxi.extensions.separateResponseCode +import com.casic.smart.town.sanxi.extensions.toErrorMessage +import com.casic.smart.town.sanxi.model.VersionResultModel +import com.casic.smart.town.sanxi.util.retrofit.RetrofitServiceManager +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.launch +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.vm.BaseViewModel +import com.pengxh.kt.lite.vm.LoadState + +class VersionViewModel : BaseViewModel() { + + private val gson = Gson() + val versionResultModel = MutableLiveData() + + fun updateVersion() = launch({ + loadState.value = LoadState.Loading + val response = RetrofitServiceManager.updateVersion() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + loadState.value = LoadState.Success + versionResultModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ).data + } else { + loadState.value = LoadState.Fail + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + loadState.value = LoadState.Fail + it.printStackTrace() + }) +} \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_solid_layout_gray_radius_5.xml b/app/src/main/res/drawable/bg_solid_layout_gray_radius_5.xml new file mode 100644 index 0000000..96eb93e --- /dev/null +++ b/app/src/main/res/drawable/bg_solid_layout_gray_radius_5.xml @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/button_main_selector.xml b/app/src/main/res/drawable/button_main_selector.xml new file mode 100644 index 0000000..5875998 --- /dev/null +++ b/app/src/main/res/drawable/button_main_selector.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/button_white_selector.xml b/app/src/main/res/drawable/button_white_selector.xml new file mode 100644 index 0000000..4dd3c0b --- /dev/null +++ b/app/src/main/res/drawable/button_white_selector.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/download_progress.xml b/app/src/main/res/drawable/download_progress.xml new file mode 100644 index 0000000..493d923 --- /dev/null +++ b/app/src/main/res/drawable/download_progress.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_about.xml b/app/src/main/res/drawable/ic_about.xml new file mode 100644 index 0000000..8469319 --- /dev/null +++ b/app/src/main/res/drawable/ic_about.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_change_pwd.xml b/app/src/main/res/drawable/ic_change_pwd.xml new file mode 100644 index 0000000..75caa39 --- /dev/null +++ b/app/src/main/res/drawable/ic_change_pwd.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/ic_clean.xml b/app/src/main/res/drawable/ic_clean.xml new file mode 100644 index 0000000..027f809 --- /dev/null +++ b/app/src/main/res/drawable/ic_clean.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_left.xml b/app/src/main/res/drawable/ic_left.xml new file mode 100644 index 0000000..04ee5da --- /dev/null +++ b/app/src/main/res/drawable/ic_left.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_lock.xml b/app/src/main/res/drawable/ic_lock.xml new file mode 100644 index 0000000..f0a5610 --- /dev/null +++ b/app/src/main/res/drawable/ic_lock.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1df917d..8d4f33b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -6,6 +6,7 @@ + @@ -32,11 +33,14 @@ + + + + + + + diff --git a/app/src/main/assets/svg/mine.svg b/app/src/main/assets/svg/mine.svg new file mode 100644 index 0000000..3fbb0a3 --- /dev/null +++ b/app/src/main/assets/svg/mine.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt b/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt index d730417..b1972c2 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt @@ -1,8 +1,10 @@ package com.casic.smart.town.sanxi.extensions import com.casic.smart.town.sanxi.model.ErrorMessageModel +import com.casic.smart.town.sanxi.util.LocaleConstant import com.google.gson.Gson import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.utils.SaveKeyValues import org.json.JSONObject fun String.separateResponseCode(): Int { @@ -17,4 +19,15 @@ this, object : TypeToken() {}.type ) return errorModel.message.toString() +} + +/** + * 下载路径为 http://xx.com/static/ 拼接downloadUrl + * */ +fun String.appendDownloadUrl(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + LocaleConstant.DEFAULT_SERVER_CONFIG, LocaleConstant.DEFAULT_SERVER + ) as String + return "$defaultValue/static/${this}" } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt b/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt new file mode 100644 index 0000000..1809dc5 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt @@ -0,0 +1,288 @@ +package com.casic.smart.town.sanxi.fragment + +import android.app.ProgressDialog +import android.content.Intent +import android.net.Uri +import android.os.Build +import android.os.CountDownTimer +import androidx.core.content.FileProvider +import androidx.lifecycle.ViewModelProvider +import com.casic.smart.town.sanxi.BuildConfig +import com.casic.smart.town.sanxi.R +import com.casic.smart.town.sanxi.extensions.appendDownloadUrl +import com.casic.smart.town.sanxi.model.UserDetailModel +import com.casic.smart.town.sanxi.util.AuthenticationHelper +import com.casic.smart.town.sanxi.util.LoadingDialogHub +import com.casic.smart.town.sanxi.util.LocaleConstant +import com.casic.smart.town.sanxi.util.RSAUtils +import com.casic.smart.town.sanxi.view.AboutUsActivity +import com.casic.smart.town.sanxi.view.LoginActivity +import com.casic.smart.town.sanxi.vm.LoginViewModel +import com.casic.smart.town.sanxi.vm.UserViewModel +import com.casic.smart.town.sanxi.vm.VersionViewModel +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.base.KotlinBaseFragment +import com.pengxh.kt.lite.callback.OnDownloadListener +import com.pengxh.kt.lite.extensions.* +import com.pengxh.kt.lite.utils.PageNavigationManager +import com.pengxh.kt.lite.utils.SaveKeyValues +import com.pengxh.kt.lite.vm.LoadState +import com.pengxh.kt.lite.widget.dialog.AlertControlDialog +import com.pengxh.kt.lite.widget.dialog.ChangePasswordDialog +import kotlinx.android.synthetic.main.fragment_mine.* +import java.io.File +import java.nio.charset.StandardCharsets + +class MinePageFragment : KotlinBaseFragment() { + + private lateinit var userData: UserDetailModel.Data + private lateinit var userViewModel: UserViewModel + private lateinit var loginViewModel: LoginViewModel + private lateinit var versionViewModel: VersionViewModel + private lateinit var progressDialog: ProgressDialog + + override fun initLayoutView(): Int = R.layout.fragment_mine + + override fun setupTopBarLayout() { + + } + + override fun initData() { + userViewModel = ViewModelProvider(this)[UserViewModel::class.java] + loginViewModel = ViewModelProvider(this)[LoginViewModel::class.java] + versionViewModel = ViewModelProvider(this)[VersionViewModel::class.java] + + //初始化下载对话框 + progressDialog = ProgressDialog(requireContext()) + progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL) + progressDialog.setProgressDrawable(resources.getDrawable(R.drawable.download_progress)) + progressDialog.setCanceledOnTouchOutside(false) + progressDialog.setCancelable(false) + + /** + * 数据监听 + * */ + dataObserve() + } + + override fun observeRequestState() { + userViewModel.loadState.observe(this, { + when (it) { + is LoadState.Loading -> LoadingDialogHub.show(requireActivity(), "修改中,请稍后") + is LoadState.Success -> { + "修改成功,请重新登录".show(requireContext()) + LoadingDialogHub.dismiss() + AuthenticationHelper.removeToken() + requireContext().navigatePageTo() + PageNavigationManager.finishAllActivity() + } + else -> LoadingDialogHub.dismiss() + } + }) + + versionViewModel.loadState.observe(this, { + when (it) { + is LoadState.Loading -> LoadingDialogHub.show(requireActivity(), "检查版本中,请稍后") + else -> LoadingDialogHub.dismiss() + } + }) + } + + override fun initEvent() { + userImageView.setOnClickListener { + "尽情期待~".show(requireContext()) + } + + updateUserButton.setOnClickListener { + LoadingDialogHub.show(requireActivity(), "同步中,请稍后...") + userViewModel.obtainUserDetail() + } + + changePwdLayout.setOnClickListener { + ChangePasswordDialog.Builder() + .setContext(requireContext()) + .setOnDialogButtonClickListener(object : + ChangePasswordDialog.OnDialogButtonClickListener { + override fun onConfirmClick(oldPwd: String, newPwd: String) { + val publicKey = + RSAUtils.keyStrToPublicKey(AuthenticationHelper.publicKey)!! + val oldPassKey = RSAUtils.encryptDataByPublicKey( + oldPwd.toByteArray(StandardCharsets.UTF_8), + publicKey + ) + val newPassKey = RSAUtils.encryptDataByPublicKey( + newPwd.toByteArray(StandardCharsets.UTF_8), + publicKey + ) + userViewModel.changePassword(oldPassKey, newPassKey) + } + }).build().show() + } + + aboutUsLayout.setOnClickListener { + requireContext().navigatePageTo() + } + + updateVersionLayout.setOnClickListener { + versionViewModel.updateVersion() + } + + clearCacheLayout.setOnClickListener { + //删除缓存之后在设置缓存大小 + LoadingDialogHub.show(requireActivity(), "清理中,请稍后") + File(requireContext().cacheDir.path).deleteFile() + object : CountDownTimer(1500, 500) { + override fun onTick(millisUntilFinished: Long) { + + } + + override fun onFinish() { + LoadingDialogHub.dismiss() + cacheSizeView.text = collectApplicationCache().formatFileSize() + } + }.start() + } + + loginOutButton.setOnClickListener { + AlertControlDialog.Builder() + .setContext(requireContext()) + .setTitle("退出登录") + .setMessage("确定要退出吗?") + .setNegativeButton("取消") + .setPositiveButton("确定") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + loginViewModel.out() + } + + override fun onCancelClick() {} + }).build().show() + } + } + + private fun dataObserve() { + userViewModel.userDetailModel.observe(this, { + if (it.code == 200) { + LoadingDialogHub.dismiss() + "同步完成".show(requireContext()) + userData = it.data + updateUserInfo() + } + }) + + versionViewModel.versionResultModel.observe(this, { + if (BuildConfig.VERSION_NAME == it.version) { + "已是最新版本,无需更新".show(requireContext()) + } else { + AlertControlDialog.Builder() + .setContext(requireContext()) + .setTitle("提示") + .setMessage("有新版本,是否更新?") + .setNegativeButton("稍后再说") + .setPositiveButton("立即下载") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + downloadApk(it.downloadUrl) + } + + override fun onCancelClick() { + + } + }).build().show() + } + }) + + loginViewModel.outResultModel.observe(this, { + if (it.code == 200) { + AuthenticationHelper.removeToken() + requireContext().navigatePageTo() + PageNavigationManager.finishAllActivity() + } + }) + } + + private fun collectApplicationCache(): Long { + return File(requireContext().cacheDir.path).calculateSize() + } + + override fun onResume() { + val userDetailJson = SaveKeyValues.getValue(LocaleConstant.USER_DETAIL_MODEL, "") as String + if (userDetailJson.isNotBlank()) { + userData = Gson().fromJson( + userDetailJson, object : TypeToken() {}.type + ) + updateUserInfo() + } + //缓存 + cacheSizeView.text = collectApplicationCache().formatFileSize() + super.onResume() + } + + private fun updateUserInfo() { + //设置头像,圆形,暂时是默认的 +// val roundDrawable = +// BitmapFactory.decodeResource(requireContext().resources, R.mipmap.login_casic) +// .createRoundDrawable( +// requireContext(), +// 3f.dp2px(requireContext()), +// R.color.mainThemeColor.convertColor(requireContext()) +// ) +// userImageView.setImageDrawable(roundDrawable) + userNameView.text = userData.name + userPhoneView.text = String.format("电话:${userData.phone}") + userDeptView.text = String.format("部门:${userData.deptName}") + } + + private fun downloadApk(url: String?) { + progressDialog.setMessage("下载新版本中...") + progressDialog.show() + if (url.toString().isBlank()) { + "抱歉,版本下载失败".show(requireContext()) + return + } + /** + * http://111.198.10.15:11304/static/apk/1.0.1.apk + * */ + val downloadPath = url!!.appendDownloadUrl() + //开始下载 + downloadPath.downloadFile(requireContext().createDownloadFileDir().toString(), + object : OnDownloadListener { + override fun onDownloadStart(totalBytes: Long) { + progressDialog.max = totalBytes.toInt() + } + + override fun onProgressChanged(currentBytes: Long) { + progressDialog.progress = currentBytes.toInt() + } + + override fun onDownloadEnd(file: File?) { + progressDialog.dismiss() + progressDialog.progress = 0 + //安装APK + installApk(file) + } + }) + } + + private fun installApk(apkPackage: File?) { + if (apkPackage == null) { + "文件异常,无法安装".show(requireContext()) + return + } + val intent = Intent(Intent.ACTION_VIEW) + val data: Uri + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { //判断版本大于等于7.0 + data = FileProvider.getUriForFile( + requireContext(), LocaleConstant.APP_AUTHORITY, apkPackage + ) + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) // 给目标应用一个临时授权 + } else { + data = Uri.fromFile(apkPackage) + } + intent.setDataAndType(data, "application/vnd.android.package-archive") + requireContext().startActivity(intent) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java new file mode 100644 index 0000000..467e33d --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java @@ -0,0 +1,43 @@ +package com.casic.smart.town.sanxi.model; + +/** + * 普通实体类,失败/成功数据结构一致 + */ +public class CommonResultModel { + private int code; + private String data; + private String message; + private boolean isSuccess; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return isSuccess; + } + + public void setSuccess(boolean success) { + isSuccess = success; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java new file mode 100644 index 0000000..9bdabef --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java @@ -0,0 +1,62 @@ +package com.casic.smart.town.sanxi.model; + +public class LoginResultModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private String kaptcha; + private String token; + + public String getKaptcha() { + return kaptcha; + } + + public void setKaptcha(String kaptcha) { + this.kaptcha = kaptcha; + } + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java new file mode 100644 index 0000000..09f8d59 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java @@ -0,0 +1,80 @@ +package com.casic.smart.town.sanxi.model; + +public class PublicKeyModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private boolean appKaptcha; + private boolean kaptcha; + private String publicKey; + private String sid; + + public boolean isAppKaptcha() { + return appKaptcha; + } + + public void setAppKaptcha(boolean appKaptcha) { + this.appKaptcha = appKaptcha; + } + + public boolean isKaptcha() { + return kaptcha; + } + + public void setKaptcha(boolean kaptcha) { + this.kaptcha = kaptcha; + } + + public String getPublicKey() { + return publicKey; + } + + public void setPublicKey(String publicKey) { + this.publicKey = publicKey; + } + + public String getSid() { + return sid; + } + + public void setSid(String sid) { + this.sid = sid; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java new file mode 100644 index 0000000..db577fb --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java @@ -0,0 +1,227 @@ +package com.casic.smart.town.sanxi.model; + + +import java.util.List; + +public class UserDetailModel { + private int code; + private Data data; + private String message; + private boolean success; + + public void setCode(int code) { + this.code = code; + } + + public int getCode() { + return code; + } + + public void setData(Data data) { + this.data = data; + } + + public Data getData() { + return data; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getMessage() { + return message; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public boolean getSuccess() { + return success; + } + + public static class Data { + + private String account; + private String attr1; + private String avatar; + private String bizData; + private List dataScope; + private String deptId; + private String deptName; + private List devices; + private String id; + private String ipAddr; + private String name; + private String phone; + private List roleList; + private List roleNames; + private List roleTips; + private String scopeType; + private String sysData; + private String targetId; + private String targetName; + private String tenantId; + + public void setAccount(String account) { + this.account = account; + } + + public String getAccount() { + return account; + } + + public void setAttr1(String attr1) { + this.attr1 = attr1; + } + + public String getAttr1() { + return attr1; + } + + public void setAvatar(String avatar) { + this.avatar = avatar; + } + + public String getAvatar() { + return avatar; + } + + public void setBizData(String bizData) { + this.bizData = bizData; + } + + public String getBizData() { + return bizData; + } + + public void setDataScope(List dataScope) { + this.dataScope = dataScope; + } + + public List getDataScope() { + return dataScope; + } + + public void setDeptId(String deptId) { + this.deptId = deptId; + } + + public String getDeptId() { + return deptId; + } + + public void setDeptName(String deptName) { + this.deptName = deptName; + } + + public String getDeptName() { + return deptName; + } + + public void setDevices(List devices) { + this.devices = devices; + } + + public List getDevices() { + return devices; + } + + public void setId(String id) { + this.id = id; + } + + public String getId() { + return id; + } + + public void setIpAddr(String ipAddr) { + this.ipAddr = ipAddr; + } + + public String getIpAddr() { + return ipAddr; + } + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setPhone(String phone) { + this.phone = phone; + } + + public String getPhone() { + return phone; + } + + public void setRoleList(List roleList) { + this.roleList = roleList; + } + + public List getRoleList() { + return roleList; + } + + public void setRoleNames(List roleNames) { + this.roleNames = roleNames; + } + + public List getRoleNames() { + return roleNames; + } + + public void setRoleTips(List roleTips) { + this.roleTips = roleTips; + } + + public List getRoleTips() { + return roleTips; + } + + public void setScopeType(String scopeType) { + this.scopeType = scopeType; + } + + public String getScopeType() { + return scopeType; + } + + public void setSysData(String sysData) { + this.sysData = sysData; + } + + public String getSysData() { + return sysData; + } + + public void setTargetId(String targetId) { + this.targetId = targetId; + } + + public String getTargetId() { + return targetId; + } + + public void setTargetName(String targetName) { + this.targetName = targetName; + } + + public String getTargetName() { + return targetName; + } + + public void setTenantId(String tenantId) { + this.tenantId = tenantId; + } + + public String getTenantId() { + return tenantId; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/VersionResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/VersionResultModel.java new file mode 100644 index 0000000..79374e0 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/VersionResultModel.java @@ -0,0 +1,61 @@ +package com.casic.smart.town.sanxi.model; + +public class VersionResultModel { + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private String downloadUrl; + private String version; + + public String getDownloadUrl() { + return downloadUrl; + } + + public void setDownloadUrl(String downloadUrl) { + this.downloadUrl = downloadUrl; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/AuthenticationHelper.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/AuthenticationHelper.kt new file mode 100644 index 0000000..f498e4e --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/AuthenticationHelper.kt @@ -0,0 +1,24 @@ +package com.casic.smart.town.sanxi.util + +import com.pengxh.kt.lite.utils.SaveKeyValues + +object AuthenticationHelper { + + fun savePublicKey(key: String) { + SaveKeyValues.putValue("keyString", key) + } + + val publicKey: String? + get() = SaveKeyValues.getValue("keyString", "") as String? + + fun saveToken(token: String?) { + SaveKeyValues.putValue("token", token!!) + } + + val token: String? + get() = SaveKeyValues.getValue("token", "") as String? + + fun removeToken() { + SaveKeyValues.removeKey("token") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt index ab1a36d..99f0d19 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt @@ -14,5 +14,9 @@ const val PAGE_LIMIT = 20 const val DEFAULT_SERVER_CONFIG = "defaultServerConfig" - const val DEFAULT_SERVER = "http://111.198.10.15:11308" + const val DEFAULT_SERVER = "http://36.133.189.112:8082" + const val APP_AUTHORITY = "com.casic.smart.town.sanxi.fileprovider" + const val ACCOUNT = "account" + const val PASSWORD = "password" + const val USER_DETAIL_MODEL = "userDetailModel" } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/RSAUtils.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/RSAUtils.kt new file mode 100644 index 0000000..b9216ba --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/RSAUtils.kt @@ -0,0 +1,55 @@ +package com.casic.smart.town.sanxi.util + +import android.util.Base64 +import java.security.* +import java.security.spec.InvalidKeySpecException +import java.security.spec.X509EncodedKeySpec +import javax.crypto.BadPaddingException +import javax.crypto.Cipher +import javax.crypto.IllegalBlockSizeException +import javax.crypto.NoSuchPaddingException + +object RSAUtils { + //构建Cipher实例时所传入的的字符串,默认为"RSA/NONE/PKCS1Padding" + private fun processData(srcData: ByteArray, key: Key): ByteArray? { //用来保存处理结果 + var resultBytes: ByteArray? = null + try { //获取Cipher实例 + val cipher = Cipher.getInstance("RSA/None/PKCS1Padding") + //初始化Cipher,mode指定是加密还是解密,key为公钥或私钥 + cipher.init(Cipher.ENCRYPT_MODE, key) + //处理数据 + resultBytes = cipher.doFinal(srcData) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: NoSuchPaddingException) { + e.printStackTrace() + } catch (e: InvalidKeyException) { + e.printStackTrace() + } catch (e: BadPaddingException) { + e.printStackTrace() + } catch (e: IllegalBlockSizeException) { + e.printStackTrace() + } + return resultBytes + } + + fun encryptDataByPublicKey(srcData: ByteArray, publicKey: PublicKey): String { + val resultBytes = processData(srcData, publicKey) + return Base64.encodeToString(resultBytes, Base64.DEFAULT) + } + + fun keyStrToPublicKey(publicKeyStr: String?): PublicKey? { + var publicKey: PublicKey? = null + val keyBytes = Base64.decode(publicKeyStr, Base64.DEFAULT) + val keySpec = X509EncodedKeySpec(keyBytes) + try { + val keyFactory = KeyFactory.getInstance("RSA") + publicKey = keyFactory.generatePublic(keySpec) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: InvalidKeySpecException) { + e.printStackTrace() + } + return publicKey + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt index 63db0ec..50d1742 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt @@ -1,12 +1,64 @@ package com.casic.smart.town.sanxi.util.retrofit -import retrofit2.http.GET +import retrofit2.http.* interface RetrofitService { + /** + * PublicKey校验 + */ + @GET("/config/baseConfig") + suspend fun obtainPublicKey(): String + + /** + * 登录并获取Token + * + * @param sid + * @param account 用户名 + * @param secretKey 加密后的密码 + */ + @FormUrlEncoded + @POST("/user/login") + suspend fun login( + @Field("sid") sid: String, + @Field("username") account: String, + @Field("password") secretKey: String + ): String + + /** + * 退出登录 + */ + @GET("/user/logout") + suspend fun loginOut(@Header("token") token: String): String + + /** + * 获取用户信息 + */ + @GET("/user/info") + suspend fun obtainUserDetail(@Header("token") token: String): String + + /** + * 修改密码 + * + * @param oldPwd 旧密码 + * @param newPwd 新密码 + */ + @FormUrlEncoded + @POST("/mgr/changePwd") + suspend fun changePassword( + @Header("token") token: String, + @Field("oldPwd") oldPwd: String, + @Field("newPwd") newPwd: String + ): String /** * 根据布防状态统计闸井数量接口 */ @GET("/well/countByBfzt") - suspend fun countWellByState(): String + suspend fun countWellByState(@Header("token") token: String): String + + /** + * 更新APK版本 + */ + @POST("/app/checkVersion") + suspend fun obtainVersionResult(@Header("token") token: String): String } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt index 46bbf4b..410444b 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt @@ -1,5 +1,6 @@ package com.casic.smart.town.sanxi.util.retrofit +import com.casic.smart.town.sanxi.util.AuthenticationHelper import com.casic.smart.town.sanxi.util.LocaleConstant import com.pengxh.kt.lite.utils.RetrofitFactory import com.pengxh.kt.lite.utils.SaveKeyValues @@ -15,9 +16,52 @@ } /** + * 验证PublicKey + */ + suspend fun authenticate(): String { + return api.obtainPublicKey() + } + + /** + * 登录并获取Token + */ + suspend fun login(sid: String, account: String, secretKey: String): String { + return api.login(sid, account, secretKey) + } + + /** + * 退出登录 + */ + suspend fun loginOut(): String { + return api.loginOut(AuthenticationHelper.token!!) + } + + /** + * 获取用户信息 + */ + suspend fun obtainUserDetail(): String { + return api.obtainUserDetail(AuthenticationHelper.token!!) + } + + /** + * 修改密码 + */ + suspend fun changePassword(oldPwd: String, newPwd: String): String { + return api.changePassword(AuthenticationHelper.token!!, oldPwd, newPwd) + } + + /** * 根据布防状态统计闸井数量接口 */ suspend fun countWellByState(): String { - return api.countWellByState() + return api.countWellByState(AuthenticationHelper.token!!) } + + /** + * 更新APK版本 + */ + suspend fun updateVersion(): String { + return api.obtainVersionResult(AuthenticationHelper.token!!) + } + } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/AboutUsActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/AboutUsActivity.kt new file mode 100644 index 0000000..7e43c47 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/AboutUsActivity.kt @@ -0,0 +1,34 @@ +package com.casic.smart.town.sanxi.view + +import com.casic.smart.town.sanxi.BuildConfig +import com.casic.smart.town.sanxi.R +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.kt.lite.base.KotlinBaseActivity +import com.pengxh.kt.lite.extensions.convertColor +import com.pengxh.kt.lite.utils.ImmerseStatusBarUtil +import kotlinx.android.synthetic.main.activity_about_us.* +import kotlinx.android.synthetic.main.include_base_title.* + +class AboutUsActivity : KotlinBaseActivity() { + + override fun initLayoutView(): Int = R.layout.activity_about_us + + override fun setupTopBarLayout() { + ImmersionBar.with(this).statusBarDarkFont(false).init() + ImmerseStatusBarUtil.setColor(this, R.color.mainThemeColor.convertColor(this)) + titleView.text = "关于我们" + leftBackView.setOnClickListener { finish() } + } + + override fun initData() { + versionCodeView.text = String.format("版本信息:V${BuildConfig.VERSION_NAME}") + } + + override fun observeRequestState() { + + } + + override fun initEvent() { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/LoginActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/LoginActivity.kt new file mode 100644 index 0000000..b5d1d50 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/LoginActivity.kt @@ -0,0 +1,97 @@ +package com.casic.smart.town.sanxi.view + +import androidx.lifecycle.ViewModelProvider +import com.casic.smart.town.sanxi.R +import com.casic.smart.town.sanxi.util.AuthenticationHelper +import com.casic.smart.town.sanxi.util.LoadingDialogHub +import com.casic.smart.town.sanxi.util.LocaleConstant +import com.casic.smart.town.sanxi.util.RSAUtils +import com.casic.smart.town.sanxi.vm.AuthenticateViewModel +import com.casic.smart.town.sanxi.vm.LoginViewModel +import com.casic.smart.town.sanxi.vm.UserViewModel +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.kt.lite.base.KotlinBaseActivity +import com.pengxh.kt.lite.extensions.navigatePageTo +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.utils.SaveKeyValues +import kotlinx.android.synthetic.main.activity_login.* + +class LoginActivity : KotlinBaseActivity() { + + private lateinit var authenticateViewModel: AuthenticateViewModel + private lateinit var loginViewModel: LoginViewModel + private lateinit var userViewModel: UserViewModel + + override fun initLayoutView(): Int = R.layout.activity_login + + override fun setupTopBarLayout() { + ImmersionBar.with(this).statusBarDarkFont(true).init() + } + + override fun initData() { + // 设置默认账号密码 + userNameView.setText(SaveKeyValues.getValue(LocaleConstant.ACCOUNT, "") as String) + userPasswordView.setText(SaveKeyValues.getValue(LocaleConstant.PASSWORD, "") as String) + authenticateViewModel = ViewModelProvider(this)[AuthenticateViewModel::class.java] + loginViewModel = ViewModelProvider(this)[LoginViewModel::class.java] + userViewModel = ViewModelProvider(this)[UserViewModel::class.java] + + authenticateViewModel.keyModel.observe(this, { + if (it.code == 200) {//用code判断,别的判断可能有坑 + val keyString = it.data!!.publicKey!! + /** + * 修改密码需要用到key,先存着 + * */ + AuthenticationHelper.savePublicKey(keyString) + val publicKey = RSAUtils.keyStrToPublicKey(keyString) + + val account = userNameView.text.toString() + val userPassword = userPasswordView.text.toString() + val dataByPublicKey = + RSAUtils.encryptDataByPublicKey(userPassword.toByteArray(), publicKey!!) + //登录并获取Token,POST请求 + loginViewModel.enter(it.data!!.sid!!, account, dataByPublicKey) + loginViewModel.enterResultModel.observe(this, { loginResult -> + if (loginResult.code == 200) { + AuthenticationHelper.saveToken(loginResult.data!!.token!!) + /** + * 获取token之后保存用户信息 + * */ + userViewModel.obtainUserDetail() + //验证成功登录 + navigatePageTo() + finish() + } + }) + } + }) + } + + override fun observeRequestState() { + authenticateViewModel.loadState.observe(this, { + LoadingDialogHub.show(this, "登录中,请稍后") + }) + + loginViewModel.loadState.observe(this, { + LoadingDialogHub.dismiss() + }) + } + + override fun initEvent() { + loginButton.setOnClickListener { + val account = userNameView.text.toString().trim() + val userPassword = userPasswordView.text.toString().trim() + if (account.isBlank()) { + "用户名不能为空".show(this) + return@setOnClickListener + } + if (userPassword.isBlank()) { + "密码不能为空".show(this) + return@setOnClickListener + } + SaveKeyValues.putValue(LocaleConstant.ACCOUNT, account) + SaveKeyValues.putValue(LocaleConstant.PASSWORD, userPassword) + authenticateViewModel.obtainPublicKey() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt index 3bea1c9..a4fa951 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt @@ -7,6 +7,7 @@ import com.casic.smart.town.sanxi.R import com.casic.smart.town.sanxi.adapter.ViewPagerAdapter import com.casic.smart.town.sanxi.fragment.AlarmPageFragment +import com.casic.smart.town.sanxi.fragment.MinePageFragment import com.casic.smart.town.sanxi.fragment.MonitorPageFragment import com.casic.smart.town.sanxi.fragment.StatisticsPageFragment import com.gyf.immersionbar.ImmersionBar @@ -33,6 +34,7 @@ fragmentPages.add(StatisticsPageFragment()) fragmentPages.add(MonitorPageFragment()) fragmentPages.add(AlarmPageFragment()) + fragmentPages.add(MinePageFragment()) } override fun initLayoutView(): Int = R.layout.activity_main @@ -68,6 +70,9 @@ R.id.nav_alarm -> { mainViewPager.currentItem = 2 } + R.id.nav_more -> { + mainViewPager.currentItem = 3 + } } false } diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/PermissionActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/PermissionActivity.kt index c478d83..3238f3c 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/view/PermissionActivity.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/PermissionActivity.kt @@ -32,7 +32,7 @@ //先把导航隐私政策声明,后面导航会用到 MapsInitializer.updatePrivacyAgree(this, true) MapsInitializer.updatePrivacyShow(this, true, true) - this.navigatePageTo() + this.navigatePageTo() finish() } diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/SplashScreenActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/SplashScreenActivity.kt new file mode 100644 index 0000000..fa5a74d --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/SplashScreenActivity.kt @@ -0,0 +1,54 @@ +package com.casic.smart.town.sanxi.view + +import android.annotation.SuppressLint +import android.os.CountDownTimer +import androidx.lifecycle.ViewModelProvider +import com.casic.smart.town.sanxi.R +import com.casic.smart.town.sanxi.vm.UserViewModel +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.kt.lite.base.KotlinBaseActivity +import com.pengxh.kt.lite.extensions.navigatePageTo + +@SuppressLint("CustomSplashScreen") +class SplashScreenActivity : KotlinBaseActivity() { + + private lateinit var userDetailViewModel: UserViewModel + private val countDownTimer = object : CountDownTimer(1000, 500) { + override fun onFinish() { + /** + * 获取token之后保存用户信息 + * */ + userDetailViewModel.obtainUserDetail() + userDetailViewModel.userDetailModel.observe(this@SplashScreenActivity, { + if (it.code == 200) { + navigatePageTo() + } else { + navigatePageTo() + } + finish() + }) + } + + override fun onTick(millisUntilFinished: Long) { + + } + } + + override fun initLayoutView(): Int = R.layout.activity_splash + + override fun setupTopBarLayout() { + ImmersionBar.with(this).statusBarDarkFont(false).init() + } + + override fun initData() { + userDetailViewModel = ViewModelProvider(this).get(UserViewModel::class.java) + } + + override fun observeRequestState() { + + } + + override fun initEvent() { + countDownTimer.start() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/vm/AuthenticateViewModel.kt b/app/src/main/java/com/casic/smart/town/sanxi/vm/AuthenticateViewModel.kt new file mode 100644 index 0000000..87ddbf8 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/vm/AuthenticateViewModel.kt @@ -0,0 +1,35 @@ +package com.casic.smart.town.sanxi.vm + +import androidx.lifecycle.MutableLiveData +import com.casic.smart.town.sanxi.base.BaseApplication +import com.casic.smart.town.sanxi.extensions.separateResponseCode +import com.casic.smart.town.sanxi.extensions.toErrorMessage +import com.casic.smart.town.sanxi.model.PublicKeyModel +import com.casic.smart.town.sanxi.util.retrofit.RetrofitServiceManager +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.launch +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.vm.BaseViewModel +import com.pengxh.kt.lite.vm.LoadState + +class AuthenticateViewModel : BaseViewModel() { + + private val gson = Gson() + val keyModel = MutableLiveData() + + fun obtainPublicKey() = launch({ + loadState.value = LoadState.Loading + val response = RetrofitServiceManager.authenticate() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + keyModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ) + } else { + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + it.printStackTrace() + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/vm/LoginViewModel.kt b/app/src/main/java/com/casic/smart/town/sanxi/vm/LoginViewModel.kt new file mode 100644 index 0000000..c377c61 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/vm/LoginViewModel.kt @@ -0,0 +1,54 @@ +package com.casic.smart.town.sanxi.vm + +import androidx.lifecycle.MutableLiveData +import com.casic.smart.town.sanxi.base.BaseApplication +import com.casic.smart.town.sanxi.extensions.separateResponseCode +import com.casic.smart.town.sanxi.extensions.toErrorMessage +import com.casic.smart.town.sanxi.model.CommonResultModel +import com.casic.smart.town.sanxi.model.LoginResultModel +import com.casic.smart.town.sanxi.util.retrofit.RetrofitServiceManager +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.launch +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.vm.BaseViewModel +import com.pengxh.kt.lite.vm.LoadState + +class LoginViewModel : BaseViewModel() { + + private val gson = Gson() + val enterResultModel = MutableLiveData() + val outResultModel = MutableLiveData() + + fun enter(sid: String, account: String, secretKey: String) = launch({ + val response = RetrofitServiceManager.login(sid, account, secretKey) + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + loadState.value = LoadState.Success + "登录成功".show(BaseApplication.obtainInstance()) + enterResultModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ) + } else { + loadState.value = LoadState.Fail + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + loadState.value = LoadState.Fail + it.printStackTrace() + }) + + fun out() = launch({ + val response = RetrofitServiceManager.loginOut() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + outResultModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ) + } else { + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + it.printStackTrace() + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/vm/UserViewModel.kt b/app/src/main/java/com/casic/smart/town/sanxi/vm/UserViewModel.kt new file mode 100644 index 0000000..29a8756 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/vm/UserViewModel.kt @@ -0,0 +1,57 @@ +package com.casic.smart.town.sanxi.vm + +import androidx.lifecycle.MutableLiveData +import com.casic.smart.town.sanxi.base.BaseApplication +import com.casic.smart.town.sanxi.extensions.separateResponseCode +import com.casic.smart.town.sanxi.extensions.toErrorMessage +import com.casic.smart.town.sanxi.model.UserDetailModel +import com.casic.smart.town.sanxi.util.LocaleConstant +import com.casic.smart.town.sanxi.util.retrofit.RetrofitServiceManager +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.launch +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.utils.SaveKeyValues +import com.pengxh.kt.lite.vm.BaseViewModel +import com.pengxh.kt.lite.vm.LoadState + +class UserViewModel : BaseViewModel() { + + private val gson = Gson() + val userDetailModel = MutableLiveData() + + fun obtainUserDetail() = launch({ + val response = RetrofitServiceManager.obtainUserDetail() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + val userDetail = gson.fromJson( + response, object : TypeToken() {}.type + ) + userDetailModel.value = userDetail + SaveKeyValues.putValue(LocaleConstant.USER_DETAIL_MODEL, gson.toJson(userDetail.data)) + } else { + val errorModel = UserDetailModel() + errorModel.code = responseCode + userDetailModel.value = errorModel + //如果此次获取不到用户信息,那么就清空之前的用户缓存,然后让用户重新登录 + SaveKeyValues.removeKey(LocaleConstant.USER_DETAIL_MODEL) + } + }, { + it.printStackTrace() + }) + + fun changePassword(oldPwd: String, newPwd: String) = launch({ + loadState.value = LoadState.Loading + val response = RetrofitServiceManager.changePassword(oldPwd, newPwd) + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + loadState.value = LoadState.Success + } else { + loadState.value = LoadState.Fail + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + loadState.value = LoadState.Fail + it.printStackTrace() + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/vm/VersionViewModel.kt b/app/src/main/java/com/casic/smart/town/sanxi/vm/VersionViewModel.kt new file mode 100644 index 0000000..20ea457 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/vm/VersionViewModel.kt @@ -0,0 +1,38 @@ +package com.casic.smart.town.sanxi.vm + +import androidx.lifecycle.MutableLiveData +import com.casic.smart.town.sanxi.base.BaseApplication +import com.casic.smart.town.sanxi.extensions.separateResponseCode +import com.casic.smart.town.sanxi.extensions.toErrorMessage +import com.casic.smart.town.sanxi.model.VersionResultModel +import com.casic.smart.town.sanxi.util.retrofit.RetrofitServiceManager +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.launch +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.vm.BaseViewModel +import com.pengxh.kt.lite.vm.LoadState + +class VersionViewModel : BaseViewModel() { + + private val gson = Gson() + val versionResultModel = MutableLiveData() + + fun updateVersion() = launch({ + loadState.value = LoadState.Loading + val response = RetrofitServiceManager.updateVersion() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + loadState.value = LoadState.Success + versionResultModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ).data + } else { + loadState.value = LoadState.Fail + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + loadState.value = LoadState.Fail + it.printStackTrace() + }) +} \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_solid_layout_gray_radius_5.xml b/app/src/main/res/drawable/bg_solid_layout_gray_radius_5.xml new file mode 100644 index 0000000..96eb93e --- /dev/null +++ b/app/src/main/res/drawable/bg_solid_layout_gray_radius_5.xml @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/button_main_selector.xml b/app/src/main/res/drawable/button_main_selector.xml new file mode 100644 index 0000000..5875998 --- /dev/null +++ b/app/src/main/res/drawable/button_main_selector.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/button_white_selector.xml b/app/src/main/res/drawable/button_white_selector.xml new file mode 100644 index 0000000..4dd3c0b --- /dev/null +++ b/app/src/main/res/drawable/button_white_selector.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/download_progress.xml b/app/src/main/res/drawable/download_progress.xml new file mode 100644 index 0000000..493d923 --- /dev/null +++ b/app/src/main/res/drawable/download_progress.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_about.xml b/app/src/main/res/drawable/ic_about.xml new file mode 100644 index 0000000..8469319 --- /dev/null +++ b/app/src/main/res/drawable/ic_about.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_change_pwd.xml b/app/src/main/res/drawable/ic_change_pwd.xml new file mode 100644 index 0000000..75caa39 --- /dev/null +++ b/app/src/main/res/drawable/ic_change_pwd.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/ic_clean.xml b/app/src/main/res/drawable/ic_clean.xml new file mode 100644 index 0000000..027f809 --- /dev/null +++ b/app/src/main/res/drawable/ic_clean.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_left.xml b/app/src/main/res/drawable/ic_left.xml new file mode 100644 index 0000000..04ee5da --- /dev/null +++ b/app/src/main/res/drawable/ic_left.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_lock.xml b/app/src/main/res/drawable/ic_lock.xml new file mode 100644 index 0000000..f0a5610 --- /dev/null +++ b/app/src/main/res/drawable/ic_lock.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/ic_more.xml b/app/src/main/res/drawable/ic_more.xml new file mode 100644 index 0000000..0794eaa --- /dev/null +++ b/app/src/main/res/drawable/ic_more.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1df917d..8d4f33b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -6,6 +6,7 @@ + @@ -32,11 +33,14 @@ + + + + + + + diff --git a/app/src/main/assets/svg/mine.svg b/app/src/main/assets/svg/mine.svg new file mode 100644 index 0000000..3fbb0a3 --- /dev/null +++ b/app/src/main/assets/svg/mine.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt b/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt index d730417..b1972c2 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt @@ -1,8 +1,10 @@ package com.casic.smart.town.sanxi.extensions import com.casic.smart.town.sanxi.model.ErrorMessageModel +import com.casic.smart.town.sanxi.util.LocaleConstant import com.google.gson.Gson import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.utils.SaveKeyValues import org.json.JSONObject fun String.separateResponseCode(): Int { @@ -17,4 +19,15 @@ this, object : TypeToken() {}.type ) return errorModel.message.toString() +} + +/** + * 下载路径为 http://xx.com/static/ 拼接downloadUrl + * */ +fun String.appendDownloadUrl(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + LocaleConstant.DEFAULT_SERVER_CONFIG, LocaleConstant.DEFAULT_SERVER + ) as String + return "$defaultValue/static/${this}" } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt b/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt new file mode 100644 index 0000000..1809dc5 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt @@ -0,0 +1,288 @@ +package com.casic.smart.town.sanxi.fragment + +import android.app.ProgressDialog +import android.content.Intent +import android.net.Uri +import android.os.Build +import android.os.CountDownTimer +import androidx.core.content.FileProvider +import androidx.lifecycle.ViewModelProvider +import com.casic.smart.town.sanxi.BuildConfig +import com.casic.smart.town.sanxi.R +import com.casic.smart.town.sanxi.extensions.appendDownloadUrl +import com.casic.smart.town.sanxi.model.UserDetailModel +import com.casic.smart.town.sanxi.util.AuthenticationHelper +import com.casic.smart.town.sanxi.util.LoadingDialogHub +import com.casic.smart.town.sanxi.util.LocaleConstant +import com.casic.smart.town.sanxi.util.RSAUtils +import com.casic.smart.town.sanxi.view.AboutUsActivity +import com.casic.smart.town.sanxi.view.LoginActivity +import com.casic.smart.town.sanxi.vm.LoginViewModel +import com.casic.smart.town.sanxi.vm.UserViewModel +import com.casic.smart.town.sanxi.vm.VersionViewModel +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.base.KotlinBaseFragment +import com.pengxh.kt.lite.callback.OnDownloadListener +import com.pengxh.kt.lite.extensions.* +import com.pengxh.kt.lite.utils.PageNavigationManager +import com.pengxh.kt.lite.utils.SaveKeyValues +import com.pengxh.kt.lite.vm.LoadState +import com.pengxh.kt.lite.widget.dialog.AlertControlDialog +import com.pengxh.kt.lite.widget.dialog.ChangePasswordDialog +import kotlinx.android.synthetic.main.fragment_mine.* +import java.io.File +import java.nio.charset.StandardCharsets + +class MinePageFragment : KotlinBaseFragment() { + + private lateinit var userData: UserDetailModel.Data + private lateinit var userViewModel: UserViewModel + private lateinit var loginViewModel: LoginViewModel + private lateinit var versionViewModel: VersionViewModel + private lateinit var progressDialog: ProgressDialog + + override fun initLayoutView(): Int = R.layout.fragment_mine + + override fun setupTopBarLayout() { + + } + + override fun initData() { + userViewModel = ViewModelProvider(this)[UserViewModel::class.java] + loginViewModel = ViewModelProvider(this)[LoginViewModel::class.java] + versionViewModel = ViewModelProvider(this)[VersionViewModel::class.java] + + //初始化下载对话框 + progressDialog = ProgressDialog(requireContext()) + progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL) + progressDialog.setProgressDrawable(resources.getDrawable(R.drawable.download_progress)) + progressDialog.setCanceledOnTouchOutside(false) + progressDialog.setCancelable(false) + + /** + * 数据监听 + * */ + dataObserve() + } + + override fun observeRequestState() { + userViewModel.loadState.observe(this, { + when (it) { + is LoadState.Loading -> LoadingDialogHub.show(requireActivity(), "修改中,请稍后") + is LoadState.Success -> { + "修改成功,请重新登录".show(requireContext()) + LoadingDialogHub.dismiss() + AuthenticationHelper.removeToken() + requireContext().navigatePageTo() + PageNavigationManager.finishAllActivity() + } + else -> LoadingDialogHub.dismiss() + } + }) + + versionViewModel.loadState.observe(this, { + when (it) { + is LoadState.Loading -> LoadingDialogHub.show(requireActivity(), "检查版本中,请稍后") + else -> LoadingDialogHub.dismiss() + } + }) + } + + override fun initEvent() { + userImageView.setOnClickListener { + "尽情期待~".show(requireContext()) + } + + updateUserButton.setOnClickListener { + LoadingDialogHub.show(requireActivity(), "同步中,请稍后...") + userViewModel.obtainUserDetail() + } + + changePwdLayout.setOnClickListener { + ChangePasswordDialog.Builder() + .setContext(requireContext()) + .setOnDialogButtonClickListener(object : + ChangePasswordDialog.OnDialogButtonClickListener { + override fun onConfirmClick(oldPwd: String, newPwd: String) { + val publicKey = + RSAUtils.keyStrToPublicKey(AuthenticationHelper.publicKey)!! + val oldPassKey = RSAUtils.encryptDataByPublicKey( + oldPwd.toByteArray(StandardCharsets.UTF_8), + publicKey + ) + val newPassKey = RSAUtils.encryptDataByPublicKey( + newPwd.toByteArray(StandardCharsets.UTF_8), + publicKey + ) + userViewModel.changePassword(oldPassKey, newPassKey) + } + }).build().show() + } + + aboutUsLayout.setOnClickListener { + requireContext().navigatePageTo() + } + + updateVersionLayout.setOnClickListener { + versionViewModel.updateVersion() + } + + clearCacheLayout.setOnClickListener { + //删除缓存之后在设置缓存大小 + LoadingDialogHub.show(requireActivity(), "清理中,请稍后") + File(requireContext().cacheDir.path).deleteFile() + object : CountDownTimer(1500, 500) { + override fun onTick(millisUntilFinished: Long) { + + } + + override fun onFinish() { + LoadingDialogHub.dismiss() + cacheSizeView.text = collectApplicationCache().formatFileSize() + } + }.start() + } + + loginOutButton.setOnClickListener { + AlertControlDialog.Builder() + .setContext(requireContext()) + .setTitle("退出登录") + .setMessage("确定要退出吗?") + .setNegativeButton("取消") + .setPositiveButton("确定") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + loginViewModel.out() + } + + override fun onCancelClick() {} + }).build().show() + } + } + + private fun dataObserve() { + userViewModel.userDetailModel.observe(this, { + if (it.code == 200) { + LoadingDialogHub.dismiss() + "同步完成".show(requireContext()) + userData = it.data + updateUserInfo() + } + }) + + versionViewModel.versionResultModel.observe(this, { + if (BuildConfig.VERSION_NAME == it.version) { + "已是最新版本,无需更新".show(requireContext()) + } else { + AlertControlDialog.Builder() + .setContext(requireContext()) + .setTitle("提示") + .setMessage("有新版本,是否更新?") + .setNegativeButton("稍后再说") + .setPositiveButton("立即下载") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + downloadApk(it.downloadUrl) + } + + override fun onCancelClick() { + + } + }).build().show() + } + }) + + loginViewModel.outResultModel.observe(this, { + if (it.code == 200) { + AuthenticationHelper.removeToken() + requireContext().navigatePageTo() + PageNavigationManager.finishAllActivity() + } + }) + } + + private fun collectApplicationCache(): Long { + return File(requireContext().cacheDir.path).calculateSize() + } + + override fun onResume() { + val userDetailJson = SaveKeyValues.getValue(LocaleConstant.USER_DETAIL_MODEL, "") as String + if (userDetailJson.isNotBlank()) { + userData = Gson().fromJson( + userDetailJson, object : TypeToken() {}.type + ) + updateUserInfo() + } + //缓存 + cacheSizeView.text = collectApplicationCache().formatFileSize() + super.onResume() + } + + private fun updateUserInfo() { + //设置头像,圆形,暂时是默认的 +// val roundDrawable = +// BitmapFactory.decodeResource(requireContext().resources, R.mipmap.login_casic) +// .createRoundDrawable( +// requireContext(), +// 3f.dp2px(requireContext()), +// R.color.mainThemeColor.convertColor(requireContext()) +// ) +// userImageView.setImageDrawable(roundDrawable) + userNameView.text = userData.name + userPhoneView.text = String.format("电话:${userData.phone}") + userDeptView.text = String.format("部门:${userData.deptName}") + } + + private fun downloadApk(url: String?) { + progressDialog.setMessage("下载新版本中...") + progressDialog.show() + if (url.toString().isBlank()) { + "抱歉,版本下载失败".show(requireContext()) + return + } + /** + * http://111.198.10.15:11304/static/apk/1.0.1.apk + * */ + val downloadPath = url!!.appendDownloadUrl() + //开始下载 + downloadPath.downloadFile(requireContext().createDownloadFileDir().toString(), + object : OnDownloadListener { + override fun onDownloadStart(totalBytes: Long) { + progressDialog.max = totalBytes.toInt() + } + + override fun onProgressChanged(currentBytes: Long) { + progressDialog.progress = currentBytes.toInt() + } + + override fun onDownloadEnd(file: File?) { + progressDialog.dismiss() + progressDialog.progress = 0 + //安装APK + installApk(file) + } + }) + } + + private fun installApk(apkPackage: File?) { + if (apkPackage == null) { + "文件异常,无法安装".show(requireContext()) + return + } + val intent = Intent(Intent.ACTION_VIEW) + val data: Uri + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { //判断版本大于等于7.0 + data = FileProvider.getUriForFile( + requireContext(), LocaleConstant.APP_AUTHORITY, apkPackage + ) + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) // 给目标应用一个临时授权 + } else { + data = Uri.fromFile(apkPackage) + } + intent.setDataAndType(data, "application/vnd.android.package-archive") + requireContext().startActivity(intent) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java new file mode 100644 index 0000000..467e33d --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java @@ -0,0 +1,43 @@ +package com.casic.smart.town.sanxi.model; + +/** + * 普通实体类,失败/成功数据结构一致 + */ +public class CommonResultModel { + private int code; + private String data; + private String message; + private boolean isSuccess; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return isSuccess; + } + + public void setSuccess(boolean success) { + isSuccess = success; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java new file mode 100644 index 0000000..9bdabef --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java @@ -0,0 +1,62 @@ +package com.casic.smart.town.sanxi.model; + +public class LoginResultModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private String kaptcha; + private String token; + + public String getKaptcha() { + return kaptcha; + } + + public void setKaptcha(String kaptcha) { + this.kaptcha = kaptcha; + } + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java new file mode 100644 index 0000000..09f8d59 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java @@ -0,0 +1,80 @@ +package com.casic.smart.town.sanxi.model; + +public class PublicKeyModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private boolean appKaptcha; + private boolean kaptcha; + private String publicKey; + private String sid; + + public boolean isAppKaptcha() { + return appKaptcha; + } + + public void setAppKaptcha(boolean appKaptcha) { + this.appKaptcha = appKaptcha; + } + + public boolean isKaptcha() { + return kaptcha; + } + + public void setKaptcha(boolean kaptcha) { + this.kaptcha = kaptcha; + } + + public String getPublicKey() { + return publicKey; + } + + public void setPublicKey(String publicKey) { + this.publicKey = publicKey; + } + + public String getSid() { + return sid; + } + + public void setSid(String sid) { + this.sid = sid; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java new file mode 100644 index 0000000..db577fb --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java @@ -0,0 +1,227 @@ +package com.casic.smart.town.sanxi.model; + + +import java.util.List; + +public class UserDetailModel { + private int code; + private Data data; + private String message; + private boolean success; + + public void setCode(int code) { + this.code = code; + } + + public int getCode() { + return code; + } + + public void setData(Data data) { + this.data = data; + } + + public Data getData() { + return data; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getMessage() { + return message; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public boolean getSuccess() { + return success; + } + + public static class Data { + + private String account; + private String attr1; + private String avatar; + private String bizData; + private List dataScope; + private String deptId; + private String deptName; + private List devices; + private String id; + private String ipAddr; + private String name; + private String phone; + private List roleList; + private List roleNames; + private List roleTips; + private String scopeType; + private String sysData; + private String targetId; + private String targetName; + private String tenantId; + + public void setAccount(String account) { + this.account = account; + } + + public String getAccount() { + return account; + } + + public void setAttr1(String attr1) { + this.attr1 = attr1; + } + + public String getAttr1() { + return attr1; + } + + public void setAvatar(String avatar) { + this.avatar = avatar; + } + + public String getAvatar() { + return avatar; + } + + public void setBizData(String bizData) { + this.bizData = bizData; + } + + public String getBizData() { + return bizData; + } + + public void setDataScope(List dataScope) { + this.dataScope = dataScope; + } + + public List getDataScope() { + return dataScope; + } + + public void setDeptId(String deptId) { + this.deptId = deptId; + } + + public String getDeptId() { + return deptId; + } + + public void setDeptName(String deptName) { + this.deptName = deptName; + } + + public String getDeptName() { + return deptName; + } + + public void setDevices(List devices) { + this.devices = devices; + } + + public List getDevices() { + return devices; + } + + public void setId(String id) { + this.id = id; + } + + public String getId() { + return id; + } + + public void setIpAddr(String ipAddr) { + this.ipAddr = ipAddr; + } + + public String getIpAddr() { + return ipAddr; + } + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setPhone(String phone) { + this.phone = phone; + } + + public String getPhone() { + return phone; + } + + public void setRoleList(List roleList) { + this.roleList = roleList; + } + + public List getRoleList() { + return roleList; + } + + public void setRoleNames(List roleNames) { + this.roleNames = roleNames; + } + + public List getRoleNames() { + return roleNames; + } + + public void setRoleTips(List roleTips) { + this.roleTips = roleTips; + } + + public List getRoleTips() { + return roleTips; + } + + public void setScopeType(String scopeType) { + this.scopeType = scopeType; + } + + public String getScopeType() { + return scopeType; + } + + public void setSysData(String sysData) { + this.sysData = sysData; + } + + public String getSysData() { + return sysData; + } + + public void setTargetId(String targetId) { + this.targetId = targetId; + } + + public String getTargetId() { + return targetId; + } + + public void setTargetName(String targetName) { + this.targetName = targetName; + } + + public String getTargetName() { + return targetName; + } + + public void setTenantId(String tenantId) { + this.tenantId = tenantId; + } + + public String getTenantId() { + return tenantId; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/VersionResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/VersionResultModel.java new file mode 100644 index 0000000..79374e0 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/VersionResultModel.java @@ -0,0 +1,61 @@ +package com.casic.smart.town.sanxi.model; + +public class VersionResultModel { + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private String downloadUrl; + private String version; + + public String getDownloadUrl() { + return downloadUrl; + } + + public void setDownloadUrl(String downloadUrl) { + this.downloadUrl = downloadUrl; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/AuthenticationHelper.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/AuthenticationHelper.kt new file mode 100644 index 0000000..f498e4e --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/AuthenticationHelper.kt @@ -0,0 +1,24 @@ +package com.casic.smart.town.sanxi.util + +import com.pengxh.kt.lite.utils.SaveKeyValues + +object AuthenticationHelper { + + fun savePublicKey(key: String) { + SaveKeyValues.putValue("keyString", key) + } + + val publicKey: String? + get() = SaveKeyValues.getValue("keyString", "") as String? + + fun saveToken(token: String?) { + SaveKeyValues.putValue("token", token!!) + } + + val token: String? + get() = SaveKeyValues.getValue("token", "") as String? + + fun removeToken() { + SaveKeyValues.removeKey("token") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt index ab1a36d..99f0d19 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt @@ -14,5 +14,9 @@ const val PAGE_LIMIT = 20 const val DEFAULT_SERVER_CONFIG = "defaultServerConfig" - const val DEFAULT_SERVER = "http://111.198.10.15:11308" + const val DEFAULT_SERVER = "http://36.133.189.112:8082" + const val APP_AUTHORITY = "com.casic.smart.town.sanxi.fileprovider" + const val ACCOUNT = "account" + const val PASSWORD = "password" + const val USER_DETAIL_MODEL = "userDetailModel" } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/RSAUtils.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/RSAUtils.kt new file mode 100644 index 0000000..b9216ba --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/RSAUtils.kt @@ -0,0 +1,55 @@ +package com.casic.smart.town.sanxi.util + +import android.util.Base64 +import java.security.* +import java.security.spec.InvalidKeySpecException +import java.security.spec.X509EncodedKeySpec +import javax.crypto.BadPaddingException +import javax.crypto.Cipher +import javax.crypto.IllegalBlockSizeException +import javax.crypto.NoSuchPaddingException + +object RSAUtils { + //构建Cipher实例时所传入的的字符串,默认为"RSA/NONE/PKCS1Padding" + private fun processData(srcData: ByteArray, key: Key): ByteArray? { //用来保存处理结果 + var resultBytes: ByteArray? = null + try { //获取Cipher实例 + val cipher = Cipher.getInstance("RSA/None/PKCS1Padding") + //初始化Cipher,mode指定是加密还是解密,key为公钥或私钥 + cipher.init(Cipher.ENCRYPT_MODE, key) + //处理数据 + resultBytes = cipher.doFinal(srcData) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: NoSuchPaddingException) { + e.printStackTrace() + } catch (e: InvalidKeyException) { + e.printStackTrace() + } catch (e: BadPaddingException) { + e.printStackTrace() + } catch (e: IllegalBlockSizeException) { + e.printStackTrace() + } + return resultBytes + } + + fun encryptDataByPublicKey(srcData: ByteArray, publicKey: PublicKey): String { + val resultBytes = processData(srcData, publicKey) + return Base64.encodeToString(resultBytes, Base64.DEFAULT) + } + + fun keyStrToPublicKey(publicKeyStr: String?): PublicKey? { + var publicKey: PublicKey? = null + val keyBytes = Base64.decode(publicKeyStr, Base64.DEFAULT) + val keySpec = X509EncodedKeySpec(keyBytes) + try { + val keyFactory = KeyFactory.getInstance("RSA") + publicKey = keyFactory.generatePublic(keySpec) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: InvalidKeySpecException) { + e.printStackTrace() + } + return publicKey + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt index 63db0ec..50d1742 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt @@ -1,12 +1,64 @@ package com.casic.smart.town.sanxi.util.retrofit -import retrofit2.http.GET +import retrofit2.http.* interface RetrofitService { + /** + * PublicKey校验 + */ + @GET("/config/baseConfig") + suspend fun obtainPublicKey(): String + + /** + * 登录并获取Token + * + * @param sid + * @param account 用户名 + * @param secretKey 加密后的密码 + */ + @FormUrlEncoded + @POST("/user/login") + suspend fun login( + @Field("sid") sid: String, + @Field("username") account: String, + @Field("password") secretKey: String + ): String + + /** + * 退出登录 + */ + @GET("/user/logout") + suspend fun loginOut(@Header("token") token: String): String + + /** + * 获取用户信息 + */ + @GET("/user/info") + suspend fun obtainUserDetail(@Header("token") token: String): String + + /** + * 修改密码 + * + * @param oldPwd 旧密码 + * @param newPwd 新密码 + */ + @FormUrlEncoded + @POST("/mgr/changePwd") + suspend fun changePassword( + @Header("token") token: String, + @Field("oldPwd") oldPwd: String, + @Field("newPwd") newPwd: String + ): String /** * 根据布防状态统计闸井数量接口 */ @GET("/well/countByBfzt") - suspend fun countWellByState(): String + suspend fun countWellByState(@Header("token") token: String): String + + /** + * 更新APK版本 + */ + @POST("/app/checkVersion") + suspend fun obtainVersionResult(@Header("token") token: String): String } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt index 46bbf4b..410444b 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt @@ -1,5 +1,6 @@ package com.casic.smart.town.sanxi.util.retrofit +import com.casic.smart.town.sanxi.util.AuthenticationHelper import com.casic.smart.town.sanxi.util.LocaleConstant import com.pengxh.kt.lite.utils.RetrofitFactory import com.pengxh.kt.lite.utils.SaveKeyValues @@ -15,9 +16,52 @@ } /** + * 验证PublicKey + */ + suspend fun authenticate(): String { + return api.obtainPublicKey() + } + + /** + * 登录并获取Token + */ + suspend fun login(sid: String, account: String, secretKey: String): String { + return api.login(sid, account, secretKey) + } + + /** + * 退出登录 + */ + suspend fun loginOut(): String { + return api.loginOut(AuthenticationHelper.token!!) + } + + /** + * 获取用户信息 + */ + suspend fun obtainUserDetail(): String { + return api.obtainUserDetail(AuthenticationHelper.token!!) + } + + /** + * 修改密码 + */ + suspend fun changePassword(oldPwd: String, newPwd: String): String { + return api.changePassword(AuthenticationHelper.token!!, oldPwd, newPwd) + } + + /** * 根据布防状态统计闸井数量接口 */ suspend fun countWellByState(): String { - return api.countWellByState() + return api.countWellByState(AuthenticationHelper.token!!) } + + /** + * 更新APK版本 + */ + suspend fun updateVersion(): String { + return api.obtainVersionResult(AuthenticationHelper.token!!) + } + } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/AboutUsActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/AboutUsActivity.kt new file mode 100644 index 0000000..7e43c47 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/AboutUsActivity.kt @@ -0,0 +1,34 @@ +package com.casic.smart.town.sanxi.view + +import com.casic.smart.town.sanxi.BuildConfig +import com.casic.smart.town.sanxi.R +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.kt.lite.base.KotlinBaseActivity +import com.pengxh.kt.lite.extensions.convertColor +import com.pengxh.kt.lite.utils.ImmerseStatusBarUtil +import kotlinx.android.synthetic.main.activity_about_us.* +import kotlinx.android.synthetic.main.include_base_title.* + +class AboutUsActivity : KotlinBaseActivity() { + + override fun initLayoutView(): Int = R.layout.activity_about_us + + override fun setupTopBarLayout() { + ImmersionBar.with(this).statusBarDarkFont(false).init() + ImmerseStatusBarUtil.setColor(this, R.color.mainThemeColor.convertColor(this)) + titleView.text = "关于我们" + leftBackView.setOnClickListener { finish() } + } + + override fun initData() { + versionCodeView.text = String.format("版本信息:V${BuildConfig.VERSION_NAME}") + } + + override fun observeRequestState() { + + } + + override fun initEvent() { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/LoginActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/LoginActivity.kt new file mode 100644 index 0000000..b5d1d50 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/LoginActivity.kt @@ -0,0 +1,97 @@ +package com.casic.smart.town.sanxi.view + +import androidx.lifecycle.ViewModelProvider +import com.casic.smart.town.sanxi.R +import com.casic.smart.town.sanxi.util.AuthenticationHelper +import com.casic.smart.town.sanxi.util.LoadingDialogHub +import com.casic.smart.town.sanxi.util.LocaleConstant +import com.casic.smart.town.sanxi.util.RSAUtils +import com.casic.smart.town.sanxi.vm.AuthenticateViewModel +import com.casic.smart.town.sanxi.vm.LoginViewModel +import com.casic.smart.town.sanxi.vm.UserViewModel +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.kt.lite.base.KotlinBaseActivity +import com.pengxh.kt.lite.extensions.navigatePageTo +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.utils.SaveKeyValues +import kotlinx.android.synthetic.main.activity_login.* + +class LoginActivity : KotlinBaseActivity() { + + private lateinit var authenticateViewModel: AuthenticateViewModel + private lateinit var loginViewModel: LoginViewModel + private lateinit var userViewModel: UserViewModel + + override fun initLayoutView(): Int = R.layout.activity_login + + override fun setupTopBarLayout() { + ImmersionBar.with(this).statusBarDarkFont(true).init() + } + + override fun initData() { + // 设置默认账号密码 + userNameView.setText(SaveKeyValues.getValue(LocaleConstant.ACCOUNT, "") as String) + userPasswordView.setText(SaveKeyValues.getValue(LocaleConstant.PASSWORD, "") as String) + authenticateViewModel = ViewModelProvider(this)[AuthenticateViewModel::class.java] + loginViewModel = ViewModelProvider(this)[LoginViewModel::class.java] + userViewModel = ViewModelProvider(this)[UserViewModel::class.java] + + authenticateViewModel.keyModel.observe(this, { + if (it.code == 200) {//用code判断,别的判断可能有坑 + val keyString = it.data!!.publicKey!! + /** + * 修改密码需要用到key,先存着 + * */ + AuthenticationHelper.savePublicKey(keyString) + val publicKey = RSAUtils.keyStrToPublicKey(keyString) + + val account = userNameView.text.toString() + val userPassword = userPasswordView.text.toString() + val dataByPublicKey = + RSAUtils.encryptDataByPublicKey(userPassword.toByteArray(), publicKey!!) + //登录并获取Token,POST请求 + loginViewModel.enter(it.data!!.sid!!, account, dataByPublicKey) + loginViewModel.enterResultModel.observe(this, { loginResult -> + if (loginResult.code == 200) { + AuthenticationHelper.saveToken(loginResult.data!!.token!!) + /** + * 获取token之后保存用户信息 + * */ + userViewModel.obtainUserDetail() + //验证成功登录 + navigatePageTo() + finish() + } + }) + } + }) + } + + override fun observeRequestState() { + authenticateViewModel.loadState.observe(this, { + LoadingDialogHub.show(this, "登录中,请稍后") + }) + + loginViewModel.loadState.observe(this, { + LoadingDialogHub.dismiss() + }) + } + + override fun initEvent() { + loginButton.setOnClickListener { + val account = userNameView.text.toString().trim() + val userPassword = userPasswordView.text.toString().trim() + if (account.isBlank()) { + "用户名不能为空".show(this) + return@setOnClickListener + } + if (userPassword.isBlank()) { + "密码不能为空".show(this) + return@setOnClickListener + } + SaveKeyValues.putValue(LocaleConstant.ACCOUNT, account) + SaveKeyValues.putValue(LocaleConstant.PASSWORD, userPassword) + authenticateViewModel.obtainPublicKey() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt index 3bea1c9..a4fa951 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt @@ -7,6 +7,7 @@ import com.casic.smart.town.sanxi.R import com.casic.smart.town.sanxi.adapter.ViewPagerAdapter import com.casic.smart.town.sanxi.fragment.AlarmPageFragment +import com.casic.smart.town.sanxi.fragment.MinePageFragment import com.casic.smart.town.sanxi.fragment.MonitorPageFragment import com.casic.smart.town.sanxi.fragment.StatisticsPageFragment import com.gyf.immersionbar.ImmersionBar @@ -33,6 +34,7 @@ fragmentPages.add(StatisticsPageFragment()) fragmentPages.add(MonitorPageFragment()) fragmentPages.add(AlarmPageFragment()) + fragmentPages.add(MinePageFragment()) } override fun initLayoutView(): Int = R.layout.activity_main @@ -68,6 +70,9 @@ R.id.nav_alarm -> { mainViewPager.currentItem = 2 } + R.id.nav_more -> { + mainViewPager.currentItem = 3 + } } false } diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/PermissionActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/PermissionActivity.kt index c478d83..3238f3c 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/view/PermissionActivity.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/PermissionActivity.kt @@ -32,7 +32,7 @@ //先把导航隐私政策声明,后面导航会用到 MapsInitializer.updatePrivacyAgree(this, true) MapsInitializer.updatePrivacyShow(this, true, true) - this.navigatePageTo() + this.navigatePageTo() finish() } diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/SplashScreenActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/SplashScreenActivity.kt new file mode 100644 index 0000000..fa5a74d --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/SplashScreenActivity.kt @@ -0,0 +1,54 @@ +package com.casic.smart.town.sanxi.view + +import android.annotation.SuppressLint +import android.os.CountDownTimer +import androidx.lifecycle.ViewModelProvider +import com.casic.smart.town.sanxi.R +import com.casic.smart.town.sanxi.vm.UserViewModel +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.kt.lite.base.KotlinBaseActivity +import com.pengxh.kt.lite.extensions.navigatePageTo + +@SuppressLint("CustomSplashScreen") +class SplashScreenActivity : KotlinBaseActivity() { + + private lateinit var userDetailViewModel: UserViewModel + private val countDownTimer = object : CountDownTimer(1000, 500) { + override fun onFinish() { + /** + * 获取token之后保存用户信息 + * */ + userDetailViewModel.obtainUserDetail() + userDetailViewModel.userDetailModel.observe(this@SplashScreenActivity, { + if (it.code == 200) { + navigatePageTo() + } else { + navigatePageTo() + } + finish() + }) + } + + override fun onTick(millisUntilFinished: Long) { + + } + } + + override fun initLayoutView(): Int = R.layout.activity_splash + + override fun setupTopBarLayout() { + ImmersionBar.with(this).statusBarDarkFont(false).init() + } + + override fun initData() { + userDetailViewModel = ViewModelProvider(this).get(UserViewModel::class.java) + } + + override fun observeRequestState() { + + } + + override fun initEvent() { + countDownTimer.start() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/vm/AuthenticateViewModel.kt b/app/src/main/java/com/casic/smart/town/sanxi/vm/AuthenticateViewModel.kt new file mode 100644 index 0000000..87ddbf8 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/vm/AuthenticateViewModel.kt @@ -0,0 +1,35 @@ +package com.casic.smart.town.sanxi.vm + +import androidx.lifecycle.MutableLiveData +import com.casic.smart.town.sanxi.base.BaseApplication +import com.casic.smart.town.sanxi.extensions.separateResponseCode +import com.casic.smart.town.sanxi.extensions.toErrorMessage +import com.casic.smart.town.sanxi.model.PublicKeyModel +import com.casic.smart.town.sanxi.util.retrofit.RetrofitServiceManager +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.launch +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.vm.BaseViewModel +import com.pengxh.kt.lite.vm.LoadState + +class AuthenticateViewModel : BaseViewModel() { + + private val gson = Gson() + val keyModel = MutableLiveData() + + fun obtainPublicKey() = launch({ + loadState.value = LoadState.Loading + val response = RetrofitServiceManager.authenticate() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + keyModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ) + } else { + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + it.printStackTrace() + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/vm/LoginViewModel.kt b/app/src/main/java/com/casic/smart/town/sanxi/vm/LoginViewModel.kt new file mode 100644 index 0000000..c377c61 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/vm/LoginViewModel.kt @@ -0,0 +1,54 @@ +package com.casic.smart.town.sanxi.vm + +import androidx.lifecycle.MutableLiveData +import com.casic.smart.town.sanxi.base.BaseApplication +import com.casic.smart.town.sanxi.extensions.separateResponseCode +import com.casic.smart.town.sanxi.extensions.toErrorMessage +import com.casic.smart.town.sanxi.model.CommonResultModel +import com.casic.smart.town.sanxi.model.LoginResultModel +import com.casic.smart.town.sanxi.util.retrofit.RetrofitServiceManager +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.launch +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.vm.BaseViewModel +import com.pengxh.kt.lite.vm.LoadState + +class LoginViewModel : BaseViewModel() { + + private val gson = Gson() + val enterResultModel = MutableLiveData() + val outResultModel = MutableLiveData() + + fun enter(sid: String, account: String, secretKey: String) = launch({ + val response = RetrofitServiceManager.login(sid, account, secretKey) + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + loadState.value = LoadState.Success + "登录成功".show(BaseApplication.obtainInstance()) + enterResultModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ) + } else { + loadState.value = LoadState.Fail + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + loadState.value = LoadState.Fail + it.printStackTrace() + }) + + fun out() = launch({ + val response = RetrofitServiceManager.loginOut() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + outResultModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ) + } else { + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + it.printStackTrace() + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/vm/UserViewModel.kt b/app/src/main/java/com/casic/smart/town/sanxi/vm/UserViewModel.kt new file mode 100644 index 0000000..29a8756 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/vm/UserViewModel.kt @@ -0,0 +1,57 @@ +package com.casic.smart.town.sanxi.vm + +import androidx.lifecycle.MutableLiveData +import com.casic.smart.town.sanxi.base.BaseApplication +import com.casic.smart.town.sanxi.extensions.separateResponseCode +import com.casic.smart.town.sanxi.extensions.toErrorMessage +import com.casic.smart.town.sanxi.model.UserDetailModel +import com.casic.smart.town.sanxi.util.LocaleConstant +import com.casic.smart.town.sanxi.util.retrofit.RetrofitServiceManager +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.launch +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.utils.SaveKeyValues +import com.pengxh.kt.lite.vm.BaseViewModel +import com.pengxh.kt.lite.vm.LoadState + +class UserViewModel : BaseViewModel() { + + private val gson = Gson() + val userDetailModel = MutableLiveData() + + fun obtainUserDetail() = launch({ + val response = RetrofitServiceManager.obtainUserDetail() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + val userDetail = gson.fromJson( + response, object : TypeToken() {}.type + ) + userDetailModel.value = userDetail + SaveKeyValues.putValue(LocaleConstant.USER_DETAIL_MODEL, gson.toJson(userDetail.data)) + } else { + val errorModel = UserDetailModel() + errorModel.code = responseCode + userDetailModel.value = errorModel + //如果此次获取不到用户信息,那么就清空之前的用户缓存,然后让用户重新登录 + SaveKeyValues.removeKey(LocaleConstant.USER_DETAIL_MODEL) + } + }, { + it.printStackTrace() + }) + + fun changePassword(oldPwd: String, newPwd: String) = launch({ + loadState.value = LoadState.Loading + val response = RetrofitServiceManager.changePassword(oldPwd, newPwd) + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + loadState.value = LoadState.Success + } else { + loadState.value = LoadState.Fail + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + loadState.value = LoadState.Fail + it.printStackTrace() + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/vm/VersionViewModel.kt b/app/src/main/java/com/casic/smart/town/sanxi/vm/VersionViewModel.kt new file mode 100644 index 0000000..20ea457 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/vm/VersionViewModel.kt @@ -0,0 +1,38 @@ +package com.casic.smart.town.sanxi.vm + +import androidx.lifecycle.MutableLiveData +import com.casic.smart.town.sanxi.base.BaseApplication +import com.casic.smart.town.sanxi.extensions.separateResponseCode +import com.casic.smart.town.sanxi.extensions.toErrorMessage +import com.casic.smart.town.sanxi.model.VersionResultModel +import com.casic.smart.town.sanxi.util.retrofit.RetrofitServiceManager +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.launch +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.vm.BaseViewModel +import com.pengxh.kt.lite.vm.LoadState + +class VersionViewModel : BaseViewModel() { + + private val gson = Gson() + val versionResultModel = MutableLiveData() + + fun updateVersion() = launch({ + loadState.value = LoadState.Loading + val response = RetrofitServiceManager.updateVersion() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + loadState.value = LoadState.Success + versionResultModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ).data + } else { + loadState.value = LoadState.Fail + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + loadState.value = LoadState.Fail + it.printStackTrace() + }) +} \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_solid_layout_gray_radius_5.xml b/app/src/main/res/drawable/bg_solid_layout_gray_radius_5.xml new file mode 100644 index 0000000..96eb93e --- /dev/null +++ b/app/src/main/res/drawable/bg_solid_layout_gray_radius_5.xml @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/button_main_selector.xml b/app/src/main/res/drawable/button_main_selector.xml new file mode 100644 index 0000000..5875998 --- /dev/null +++ b/app/src/main/res/drawable/button_main_selector.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/button_white_selector.xml b/app/src/main/res/drawable/button_white_selector.xml new file mode 100644 index 0000000..4dd3c0b --- /dev/null +++ b/app/src/main/res/drawable/button_white_selector.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/download_progress.xml b/app/src/main/res/drawable/download_progress.xml new file mode 100644 index 0000000..493d923 --- /dev/null +++ b/app/src/main/res/drawable/download_progress.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_about.xml b/app/src/main/res/drawable/ic_about.xml new file mode 100644 index 0000000..8469319 --- /dev/null +++ b/app/src/main/res/drawable/ic_about.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_change_pwd.xml b/app/src/main/res/drawable/ic_change_pwd.xml new file mode 100644 index 0000000..75caa39 --- /dev/null +++ b/app/src/main/res/drawable/ic_change_pwd.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/ic_clean.xml b/app/src/main/res/drawable/ic_clean.xml new file mode 100644 index 0000000..027f809 --- /dev/null +++ b/app/src/main/res/drawable/ic_clean.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_left.xml b/app/src/main/res/drawable/ic_left.xml new file mode 100644 index 0000000..04ee5da --- /dev/null +++ b/app/src/main/res/drawable/ic_left.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_lock.xml b/app/src/main/res/drawable/ic_lock.xml new file mode 100644 index 0000000..f0a5610 --- /dev/null +++ b/app/src/main/res/drawable/ic_lock.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/ic_more.xml b/app/src/main/res/drawable/ic_more.xml new file mode 100644 index 0000000..0794eaa --- /dev/null +++ b/app/src/main/res/drawable/ic_more.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_more_selected.xml b/app/src/main/res/drawable/ic_more_selected.xml new file mode 100644 index 0000000..9b6d3df --- /dev/null +++ b/app/src/main/res/drawable/ic_more_selected.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1df917d..8d4f33b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -6,6 +6,7 @@ + @@ -32,11 +33,14 @@ + + + + + + + diff --git a/app/src/main/assets/svg/mine.svg b/app/src/main/assets/svg/mine.svg new file mode 100644 index 0000000..3fbb0a3 --- /dev/null +++ b/app/src/main/assets/svg/mine.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt b/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt index d730417..b1972c2 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt @@ -1,8 +1,10 @@ package com.casic.smart.town.sanxi.extensions import com.casic.smart.town.sanxi.model.ErrorMessageModel +import com.casic.smart.town.sanxi.util.LocaleConstant import com.google.gson.Gson import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.utils.SaveKeyValues import org.json.JSONObject fun String.separateResponseCode(): Int { @@ -17,4 +19,15 @@ this, object : TypeToken() {}.type ) return errorModel.message.toString() +} + +/** + * 下载路径为 http://xx.com/static/ 拼接downloadUrl + * */ +fun String.appendDownloadUrl(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + LocaleConstant.DEFAULT_SERVER_CONFIG, LocaleConstant.DEFAULT_SERVER + ) as String + return "$defaultValue/static/${this}" } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt b/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt new file mode 100644 index 0000000..1809dc5 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt @@ -0,0 +1,288 @@ +package com.casic.smart.town.sanxi.fragment + +import android.app.ProgressDialog +import android.content.Intent +import android.net.Uri +import android.os.Build +import android.os.CountDownTimer +import androidx.core.content.FileProvider +import androidx.lifecycle.ViewModelProvider +import com.casic.smart.town.sanxi.BuildConfig +import com.casic.smart.town.sanxi.R +import com.casic.smart.town.sanxi.extensions.appendDownloadUrl +import com.casic.smart.town.sanxi.model.UserDetailModel +import com.casic.smart.town.sanxi.util.AuthenticationHelper +import com.casic.smart.town.sanxi.util.LoadingDialogHub +import com.casic.smart.town.sanxi.util.LocaleConstant +import com.casic.smart.town.sanxi.util.RSAUtils +import com.casic.smart.town.sanxi.view.AboutUsActivity +import com.casic.smart.town.sanxi.view.LoginActivity +import com.casic.smart.town.sanxi.vm.LoginViewModel +import com.casic.smart.town.sanxi.vm.UserViewModel +import com.casic.smart.town.sanxi.vm.VersionViewModel +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.base.KotlinBaseFragment +import com.pengxh.kt.lite.callback.OnDownloadListener +import com.pengxh.kt.lite.extensions.* +import com.pengxh.kt.lite.utils.PageNavigationManager +import com.pengxh.kt.lite.utils.SaveKeyValues +import com.pengxh.kt.lite.vm.LoadState +import com.pengxh.kt.lite.widget.dialog.AlertControlDialog +import com.pengxh.kt.lite.widget.dialog.ChangePasswordDialog +import kotlinx.android.synthetic.main.fragment_mine.* +import java.io.File +import java.nio.charset.StandardCharsets + +class MinePageFragment : KotlinBaseFragment() { + + private lateinit var userData: UserDetailModel.Data + private lateinit var userViewModel: UserViewModel + private lateinit var loginViewModel: LoginViewModel + private lateinit var versionViewModel: VersionViewModel + private lateinit var progressDialog: ProgressDialog + + override fun initLayoutView(): Int = R.layout.fragment_mine + + override fun setupTopBarLayout() { + + } + + override fun initData() { + userViewModel = ViewModelProvider(this)[UserViewModel::class.java] + loginViewModel = ViewModelProvider(this)[LoginViewModel::class.java] + versionViewModel = ViewModelProvider(this)[VersionViewModel::class.java] + + //初始化下载对话框 + progressDialog = ProgressDialog(requireContext()) + progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL) + progressDialog.setProgressDrawable(resources.getDrawable(R.drawable.download_progress)) + progressDialog.setCanceledOnTouchOutside(false) + progressDialog.setCancelable(false) + + /** + * 数据监听 + * */ + dataObserve() + } + + override fun observeRequestState() { + userViewModel.loadState.observe(this, { + when (it) { + is LoadState.Loading -> LoadingDialogHub.show(requireActivity(), "修改中,请稍后") + is LoadState.Success -> { + "修改成功,请重新登录".show(requireContext()) + LoadingDialogHub.dismiss() + AuthenticationHelper.removeToken() + requireContext().navigatePageTo() + PageNavigationManager.finishAllActivity() + } + else -> LoadingDialogHub.dismiss() + } + }) + + versionViewModel.loadState.observe(this, { + when (it) { + is LoadState.Loading -> LoadingDialogHub.show(requireActivity(), "检查版本中,请稍后") + else -> LoadingDialogHub.dismiss() + } + }) + } + + override fun initEvent() { + userImageView.setOnClickListener { + "尽情期待~".show(requireContext()) + } + + updateUserButton.setOnClickListener { + LoadingDialogHub.show(requireActivity(), "同步中,请稍后...") + userViewModel.obtainUserDetail() + } + + changePwdLayout.setOnClickListener { + ChangePasswordDialog.Builder() + .setContext(requireContext()) + .setOnDialogButtonClickListener(object : + ChangePasswordDialog.OnDialogButtonClickListener { + override fun onConfirmClick(oldPwd: String, newPwd: String) { + val publicKey = + RSAUtils.keyStrToPublicKey(AuthenticationHelper.publicKey)!! + val oldPassKey = RSAUtils.encryptDataByPublicKey( + oldPwd.toByteArray(StandardCharsets.UTF_8), + publicKey + ) + val newPassKey = RSAUtils.encryptDataByPublicKey( + newPwd.toByteArray(StandardCharsets.UTF_8), + publicKey + ) + userViewModel.changePassword(oldPassKey, newPassKey) + } + }).build().show() + } + + aboutUsLayout.setOnClickListener { + requireContext().navigatePageTo() + } + + updateVersionLayout.setOnClickListener { + versionViewModel.updateVersion() + } + + clearCacheLayout.setOnClickListener { + //删除缓存之后在设置缓存大小 + LoadingDialogHub.show(requireActivity(), "清理中,请稍后") + File(requireContext().cacheDir.path).deleteFile() + object : CountDownTimer(1500, 500) { + override fun onTick(millisUntilFinished: Long) { + + } + + override fun onFinish() { + LoadingDialogHub.dismiss() + cacheSizeView.text = collectApplicationCache().formatFileSize() + } + }.start() + } + + loginOutButton.setOnClickListener { + AlertControlDialog.Builder() + .setContext(requireContext()) + .setTitle("退出登录") + .setMessage("确定要退出吗?") + .setNegativeButton("取消") + .setPositiveButton("确定") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + loginViewModel.out() + } + + override fun onCancelClick() {} + }).build().show() + } + } + + private fun dataObserve() { + userViewModel.userDetailModel.observe(this, { + if (it.code == 200) { + LoadingDialogHub.dismiss() + "同步完成".show(requireContext()) + userData = it.data + updateUserInfo() + } + }) + + versionViewModel.versionResultModel.observe(this, { + if (BuildConfig.VERSION_NAME == it.version) { + "已是最新版本,无需更新".show(requireContext()) + } else { + AlertControlDialog.Builder() + .setContext(requireContext()) + .setTitle("提示") + .setMessage("有新版本,是否更新?") + .setNegativeButton("稍后再说") + .setPositiveButton("立即下载") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + downloadApk(it.downloadUrl) + } + + override fun onCancelClick() { + + } + }).build().show() + } + }) + + loginViewModel.outResultModel.observe(this, { + if (it.code == 200) { + AuthenticationHelper.removeToken() + requireContext().navigatePageTo() + PageNavigationManager.finishAllActivity() + } + }) + } + + private fun collectApplicationCache(): Long { + return File(requireContext().cacheDir.path).calculateSize() + } + + override fun onResume() { + val userDetailJson = SaveKeyValues.getValue(LocaleConstant.USER_DETAIL_MODEL, "") as String + if (userDetailJson.isNotBlank()) { + userData = Gson().fromJson( + userDetailJson, object : TypeToken() {}.type + ) + updateUserInfo() + } + //缓存 + cacheSizeView.text = collectApplicationCache().formatFileSize() + super.onResume() + } + + private fun updateUserInfo() { + //设置头像,圆形,暂时是默认的 +// val roundDrawable = +// BitmapFactory.decodeResource(requireContext().resources, R.mipmap.login_casic) +// .createRoundDrawable( +// requireContext(), +// 3f.dp2px(requireContext()), +// R.color.mainThemeColor.convertColor(requireContext()) +// ) +// userImageView.setImageDrawable(roundDrawable) + userNameView.text = userData.name + userPhoneView.text = String.format("电话:${userData.phone}") + userDeptView.text = String.format("部门:${userData.deptName}") + } + + private fun downloadApk(url: String?) { + progressDialog.setMessage("下载新版本中...") + progressDialog.show() + if (url.toString().isBlank()) { + "抱歉,版本下载失败".show(requireContext()) + return + } + /** + * http://111.198.10.15:11304/static/apk/1.0.1.apk + * */ + val downloadPath = url!!.appendDownloadUrl() + //开始下载 + downloadPath.downloadFile(requireContext().createDownloadFileDir().toString(), + object : OnDownloadListener { + override fun onDownloadStart(totalBytes: Long) { + progressDialog.max = totalBytes.toInt() + } + + override fun onProgressChanged(currentBytes: Long) { + progressDialog.progress = currentBytes.toInt() + } + + override fun onDownloadEnd(file: File?) { + progressDialog.dismiss() + progressDialog.progress = 0 + //安装APK + installApk(file) + } + }) + } + + private fun installApk(apkPackage: File?) { + if (apkPackage == null) { + "文件异常,无法安装".show(requireContext()) + return + } + val intent = Intent(Intent.ACTION_VIEW) + val data: Uri + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { //判断版本大于等于7.0 + data = FileProvider.getUriForFile( + requireContext(), LocaleConstant.APP_AUTHORITY, apkPackage + ) + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) // 给目标应用一个临时授权 + } else { + data = Uri.fromFile(apkPackage) + } + intent.setDataAndType(data, "application/vnd.android.package-archive") + requireContext().startActivity(intent) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java new file mode 100644 index 0000000..467e33d --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java @@ -0,0 +1,43 @@ +package com.casic.smart.town.sanxi.model; + +/** + * 普通实体类,失败/成功数据结构一致 + */ +public class CommonResultModel { + private int code; + private String data; + private String message; + private boolean isSuccess; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return isSuccess; + } + + public void setSuccess(boolean success) { + isSuccess = success; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java new file mode 100644 index 0000000..9bdabef --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java @@ -0,0 +1,62 @@ +package com.casic.smart.town.sanxi.model; + +public class LoginResultModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private String kaptcha; + private String token; + + public String getKaptcha() { + return kaptcha; + } + + public void setKaptcha(String kaptcha) { + this.kaptcha = kaptcha; + } + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java new file mode 100644 index 0000000..09f8d59 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java @@ -0,0 +1,80 @@ +package com.casic.smart.town.sanxi.model; + +public class PublicKeyModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private boolean appKaptcha; + private boolean kaptcha; + private String publicKey; + private String sid; + + public boolean isAppKaptcha() { + return appKaptcha; + } + + public void setAppKaptcha(boolean appKaptcha) { + this.appKaptcha = appKaptcha; + } + + public boolean isKaptcha() { + return kaptcha; + } + + public void setKaptcha(boolean kaptcha) { + this.kaptcha = kaptcha; + } + + public String getPublicKey() { + return publicKey; + } + + public void setPublicKey(String publicKey) { + this.publicKey = publicKey; + } + + public String getSid() { + return sid; + } + + public void setSid(String sid) { + this.sid = sid; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java new file mode 100644 index 0000000..db577fb --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java @@ -0,0 +1,227 @@ +package com.casic.smart.town.sanxi.model; + + +import java.util.List; + +public class UserDetailModel { + private int code; + private Data data; + private String message; + private boolean success; + + public void setCode(int code) { + this.code = code; + } + + public int getCode() { + return code; + } + + public void setData(Data data) { + this.data = data; + } + + public Data getData() { + return data; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getMessage() { + return message; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public boolean getSuccess() { + return success; + } + + public static class Data { + + private String account; + private String attr1; + private String avatar; + private String bizData; + private List dataScope; + private String deptId; + private String deptName; + private List devices; + private String id; + private String ipAddr; + private String name; + private String phone; + private List roleList; + private List roleNames; + private List roleTips; + private String scopeType; + private String sysData; + private String targetId; + private String targetName; + private String tenantId; + + public void setAccount(String account) { + this.account = account; + } + + public String getAccount() { + return account; + } + + public void setAttr1(String attr1) { + this.attr1 = attr1; + } + + public String getAttr1() { + return attr1; + } + + public void setAvatar(String avatar) { + this.avatar = avatar; + } + + public String getAvatar() { + return avatar; + } + + public void setBizData(String bizData) { + this.bizData = bizData; + } + + public String getBizData() { + return bizData; + } + + public void setDataScope(List dataScope) { + this.dataScope = dataScope; + } + + public List getDataScope() { + return dataScope; + } + + public void setDeptId(String deptId) { + this.deptId = deptId; + } + + public String getDeptId() { + return deptId; + } + + public void setDeptName(String deptName) { + this.deptName = deptName; + } + + public String getDeptName() { + return deptName; + } + + public void setDevices(List devices) { + this.devices = devices; + } + + public List getDevices() { + return devices; + } + + public void setId(String id) { + this.id = id; + } + + public String getId() { + return id; + } + + public void setIpAddr(String ipAddr) { + this.ipAddr = ipAddr; + } + + public String getIpAddr() { + return ipAddr; + } + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setPhone(String phone) { + this.phone = phone; + } + + public String getPhone() { + return phone; + } + + public void setRoleList(List roleList) { + this.roleList = roleList; + } + + public List getRoleList() { + return roleList; + } + + public void setRoleNames(List roleNames) { + this.roleNames = roleNames; + } + + public List getRoleNames() { + return roleNames; + } + + public void setRoleTips(List roleTips) { + this.roleTips = roleTips; + } + + public List getRoleTips() { + return roleTips; + } + + public void setScopeType(String scopeType) { + this.scopeType = scopeType; + } + + public String getScopeType() { + return scopeType; + } + + public void setSysData(String sysData) { + this.sysData = sysData; + } + + public String getSysData() { + return sysData; + } + + public void setTargetId(String targetId) { + this.targetId = targetId; + } + + public String getTargetId() { + return targetId; + } + + public void setTargetName(String targetName) { + this.targetName = targetName; + } + + public String getTargetName() { + return targetName; + } + + public void setTenantId(String tenantId) { + this.tenantId = tenantId; + } + + public String getTenantId() { + return tenantId; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/VersionResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/VersionResultModel.java new file mode 100644 index 0000000..79374e0 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/VersionResultModel.java @@ -0,0 +1,61 @@ +package com.casic.smart.town.sanxi.model; + +public class VersionResultModel { + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private String downloadUrl; + private String version; + + public String getDownloadUrl() { + return downloadUrl; + } + + public void setDownloadUrl(String downloadUrl) { + this.downloadUrl = downloadUrl; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/AuthenticationHelper.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/AuthenticationHelper.kt new file mode 100644 index 0000000..f498e4e --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/AuthenticationHelper.kt @@ -0,0 +1,24 @@ +package com.casic.smart.town.sanxi.util + +import com.pengxh.kt.lite.utils.SaveKeyValues + +object AuthenticationHelper { + + fun savePublicKey(key: String) { + SaveKeyValues.putValue("keyString", key) + } + + val publicKey: String? + get() = SaveKeyValues.getValue("keyString", "") as String? + + fun saveToken(token: String?) { + SaveKeyValues.putValue("token", token!!) + } + + val token: String? + get() = SaveKeyValues.getValue("token", "") as String? + + fun removeToken() { + SaveKeyValues.removeKey("token") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt index ab1a36d..99f0d19 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt @@ -14,5 +14,9 @@ const val PAGE_LIMIT = 20 const val DEFAULT_SERVER_CONFIG = "defaultServerConfig" - const val DEFAULT_SERVER = "http://111.198.10.15:11308" + const val DEFAULT_SERVER = "http://36.133.189.112:8082" + const val APP_AUTHORITY = "com.casic.smart.town.sanxi.fileprovider" + const val ACCOUNT = "account" + const val PASSWORD = "password" + const val USER_DETAIL_MODEL = "userDetailModel" } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/RSAUtils.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/RSAUtils.kt new file mode 100644 index 0000000..b9216ba --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/RSAUtils.kt @@ -0,0 +1,55 @@ +package com.casic.smart.town.sanxi.util + +import android.util.Base64 +import java.security.* +import java.security.spec.InvalidKeySpecException +import java.security.spec.X509EncodedKeySpec +import javax.crypto.BadPaddingException +import javax.crypto.Cipher +import javax.crypto.IllegalBlockSizeException +import javax.crypto.NoSuchPaddingException + +object RSAUtils { + //构建Cipher实例时所传入的的字符串,默认为"RSA/NONE/PKCS1Padding" + private fun processData(srcData: ByteArray, key: Key): ByteArray? { //用来保存处理结果 + var resultBytes: ByteArray? = null + try { //获取Cipher实例 + val cipher = Cipher.getInstance("RSA/None/PKCS1Padding") + //初始化Cipher,mode指定是加密还是解密,key为公钥或私钥 + cipher.init(Cipher.ENCRYPT_MODE, key) + //处理数据 + resultBytes = cipher.doFinal(srcData) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: NoSuchPaddingException) { + e.printStackTrace() + } catch (e: InvalidKeyException) { + e.printStackTrace() + } catch (e: BadPaddingException) { + e.printStackTrace() + } catch (e: IllegalBlockSizeException) { + e.printStackTrace() + } + return resultBytes + } + + fun encryptDataByPublicKey(srcData: ByteArray, publicKey: PublicKey): String { + val resultBytes = processData(srcData, publicKey) + return Base64.encodeToString(resultBytes, Base64.DEFAULT) + } + + fun keyStrToPublicKey(publicKeyStr: String?): PublicKey? { + var publicKey: PublicKey? = null + val keyBytes = Base64.decode(publicKeyStr, Base64.DEFAULT) + val keySpec = X509EncodedKeySpec(keyBytes) + try { + val keyFactory = KeyFactory.getInstance("RSA") + publicKey = keyFactory.generatePublic(keySpec) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: InvalidKeySpecException) { + e.printStackTrace() + } + return publicKey + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt index 63db0ec..50d1742 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt @@ -1,12 +1,64 @@ package com.casic.smart.town.sanxi.util.retrofit -import retrofit2.http.GET +import retrofit2.http.* interface RetrofitService { + /** + * PublicKey校验 + */ + @GET("/config/baseConfig") + suspend fun obtainPublicKey(): String + + /** + * 登录并获取Token + * + * @param sid + * @param account 用户名 + * @param secretKey 加密后的密码 + */ + @FormUrlEncoded + @POST("/user/login") + suspend fun login( + @Field("sid") sid: String, + @Field("username") account: String, + @Field("password") secretKey: String + ): String + + /** + * 退出登录 + */ + @GET("/user/logout") + suspend fun loginOut(@Header("token") token: String): String + + /** + * 获取用户信息 + */ + @GET("/user/info") + suspend fun obtainUserDetail(@Header("token") token: String): String + + /** + * 修改密码 + * + * @param oldPwd 旧密码 + * @param newPwd 新密码 + */ + @FormUrlEncoded + @POST("/mgr/changePwd") + suspend fun changePassword( + @Header("token") token: String, + @Field("oldPwd") oldPwd: String, + @Field("newPwd") newPwd: String + ): String /** * 根据布防状态统计闸井数量接口 */ @GET("/well/countByBfzt") - suspend fun countWellByState(): String + suspend fun countWellByState(@Header("token") token: String): String + + /** + * 更新APK版本 + */ + @POST("/app/checkVersion") + suspend fun obtainVersionResult(@Header("token") token: String): String } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt index 46bbf4b..410444b 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt @@ -1,5 +1,6 @@ package com.casic.smart.town.sanxi.util.retrofit +import com.casic.smart.town.sanxi.util.AuthenticationHelper import com.casic.smart.town.sanxi.util.LocaleConstant import com.pengxh.kt.lite.utils.RetrofitFactory import com.pengxh.kt.lite.utils.SaveKeyValues @@ -15,9 +16,52 @@ } /** + * 验证PublicKey + */ + suspend fun authenticate(): String { + return api.obtainPublicKey() + } + + /** + * 登录并获取Token + */ + suspend fun login(sid: String, account: String, secretKey: String): String { + return api.login(sid, account, secretKey) + } + + /** + * 退出登录 + */ + suspend fun loginOut(): String { + return api.loginOut(AuthenticationHelper.token!!) + } + + /** + * 获取用户信息 + */ + suspend fun obtainUserDetail(): String { + return api.obtainUserDetail(AuthenticationHelper.token!!) + } + + /** + * 修改密码 + */ + suspend fun changePassword(oldPwd: String, newPwd: String): String { + return api.changePassword(AuthenticationHelper.token!!, oldPwd, newPwd) + } + + /** * 根据布防状态统计闸井数量接口 */ suspend fun countWellByState(): String { - return api.countWellByState() + return api.countWellByState(AuthenticationHelper.token!!) } + + /** + * 更新APK版本 + */ + suspend fun updateVersion(): String { + return api.obtainVersionResult(AuthenticationHelper.token!!) + } + } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/AboutUsActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/AboutUsActivity.kt new file mode 100644 index 0000000..7e43c47 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/AboutUsActivity.kt @@ -0,0 +1,34 @@ +package com.casic.smart.town.sanxi.view + +import com.casic.smart.town.sanxi.BuildConfig +import com.casic.smart.town.sanxi.R +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.kt.lite.base.KotlinBaseActivity +import com.pengxh.kt.lite.extensions.convertColor +import com.pengxh.kt.lite.utils.ImmerseStatusBarUtil +import kotlinx.android.synthetic.main.activity_about_us.* +import kotlinx.android.synthetic.main.include_base_title.* + +class AboutUsActivity : KotlinBaseActivity() { + + override fun initLayoutView(): Int = R.layout.activity_about_us + + override fun setupTopBarLayout() { + ImmersionBar.with(this).statusBarDarkFont(false).init() + ImmerseStatusBarUtil.setColor(this, R.color.mainThemeColor.convertColor(this)) + titleView.text = "关于我们" + leftBackView.setOnClickListener { finish() } + } + + override fun initData() { + versionCodeView.text = String.format("版本信息:V${BuildConfig.VERSION_NAME}") + } + + override fun observeRequestState() { + + } + + override fun initEvent() { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/LoginActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/LoginActivity.kt new file mode 100644 index 0000000..b5d1d50 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/LoginActivity.kt @@ -0,0 +1,97 @@ +package com.casic.smart.town.sanxi.view + +import androidx.lifecycle.ViewModelProvider +import com.casic.smart.town.sanxi.R +import com.casic.smart.town.sanxi.util.AuthenticationHelper +import com.casic.smart.town.sanxi.util.LoadingDialogHub +import com.casic.smart.town.sanxi.util.LocaleConstant +import com.casic.smart.town.sanxi.util.RSAUtils +import com.casic.smart.town.sanxi.vm.AuthenticateViewModel +import com.casic.smart.town.sanxi.vm.LoginViewModel +import com.casic.smart.town.sanxi.vm.UserViewModel +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.kt.lite.base.KotlinBaseActivity +import com.pengxh.kt.lite.extensions.navigatePageTo +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.utils.SaveKeyValues +import kotlinx.android.synthetic.main.activity_login.* + +class LoginActivity : KotlinBaseActivity() { + + private lateinit var authenticateViewModel: AuthenticateViewModel + private lateinit var loginViewModel: LoginViewModel + private lateinit var userViewModel: UserViewModel + + override fun initLayoutView(): Int = R.layout.activity_login + + override fun setupTopBarLayout() { + ImmersionBar.with(this).statusBarDarkFont(true).init() + } + + override fun initData() { + // 设置默认账号密码 + userNameView.setText(SaveKeyValues.getValue(LocaleConstant.ACCOUNT, "") as String) + userPasswordView.setText(SaveKeyValues.getValue(LocaleConstant.PASSWORD, "") as String) + authenticateViewModel = ViewModelProvider(this)[AuthenticateViewModel::class.java] + loginViewModel = ViewModelProvider(this)[LoginViewModel::class.java] + userViewModel = ViewModelProvider(this)[UserViewModel::class.java] + + authenticateViewModel.keyModel.observe(this, { + if (it.code == 200) {//用code判断,别的判断可能有坑 + val keyString = it.data!!.publicKey!! + /** + * 修改密码需要用到key,先存着 + * */ + AuthenticationHelper.savePublicKey(keyString) + val publicKey = RSAUtils.keyStrToPublicKey(keyString) + + val account = userNameView.text.toString() + val userPassword = userPasswordView.text.toString() + val dataByPublicKey = + RSAUtils.encryptDataByPublicKey(userPassword.toByteArray(), publicKey!!) + //登录并获取Token,POST请求 + loginViewModel.enter(it.data!!.sid!!, account, dataByPublicKey) + loginViewModel.enterResultModel.observe(this, { loginResult -> + if (loginResult.code == 200) { + AuthenticationHelper.saveToken(loginResult.data!!.token!!) + /** + * 获取token之后保存用户信息 + * */ + userViewModel.obtainUserDetail() + //验证成功登录 + navigatePageTo() + finish() + } + }) + } + }) + } + + override fun observeRequestState() { + authenticateViewModel.loadState.observe(this, { + LoadingDialogHub.show(this, "登录中,请稍后") + }) + + loginViewModel.loadState.observe(this, { + LoadingDialogHub.dismiss() + }) + } + + override fun initEvent() { + loginButton.setOnClickListener { + val account = userNameView.text.toString().trim() + val userPassword = userPasswordView.text.toString().trim() + if (account.isBlank()) { + "用户名不能为空".show(this) + return@setOnClickListener + } + if (userPassword.isBlank()) { + "密码不能为空".show(this) + return@setOnClickListener + } + SaveKeyValues.putValue(LocaleConstant.ACCOUNT, account) + SaveKeyValues.putValue(LocaleConstant.PASSWORD, userPassword) + authenticateViewModel.obtainPublicKey() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt index 3bea1c9..a4fa951 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt @@ -7,6 +7,7 @@ import com.casic.smart.town.sanxi.R import com.casic.smart.town.sanxi.adapter.ViewPagerAdapter import com.casic.smart.town.sanxi.fragment.AlarmPageFragment +import com.casic.smart.town.sanxi.fragment.MinePageFragment import com.casic.smart.town.sanxi.fragment.MonitorPageFragment import com.casic.smart.town.sanxi.fragment.StatisticsPageFragment import com.gyf.immersionbar.ImmersionBar @@ -33,6 +34,7 @@ fragmentPages.add(StatisticsPageFragment()) fragmentPages.add(MonitorPageFragment()) fragmentPages.add(AlarmPageFragment()) + fragmentPages.add(MinePageFragment()) } override fun initLayoutView(): Int = R.layout.activity_main @@ -68,6 +70,9 @@ R.id.nav_alarm -> { mainViewPager.currentItem = 2 } + R.id.nav_more -> { + mainViewPager.currentItem = 3 + } } false } diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/PermissionActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/PermissionActivity.kt index c478d83..3238f3c 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/view/PermissionActivity.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/PermissionActivity.kt @@ -32,7 +32,7 @@ //先把导航隐私政策声明,后面导航会用到 MapsInitializer.updatePrivacyAgree(this, true) MapsInitializer.updatePrivacyShow(this, true, true) - this.navigatePageTo() + this.navigatePageTo() finish() } diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/SplashScreenActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/SplashScreenActivity.kt new file mode 100644 index 0000000..fa5a74d --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/SplashScreenActivity.kt @@ -0,0 +1,54 @@ +package com.casic.smart.town.sanxi.view + +import android.annotation.SuppressLint +import android.os.CountDownTimer +import androidx.lifecycle.ViewModelProvider +import com.casic.smart.town.sanxi.R +import com.casic.smart.town.sanxi.vm.UserViewModel +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.kt.lite.base.KotlinBaseActivity +import com.pengxh.kt.lite.extensions.navigatePageTo + +@SuppressLint("CustomSplashScreen") +class SplashScreenActivity : KotlinBaseActivity() { + + private lateinit var userDetailViewModel: UserViewModel + private val countDownTimer = object : CountDownTimer(1000, 500) { + override fun onFinish() { + /** + * 获取token之后保存用户信息 + * */ + userDetailViewModel.obtainUserDetail() + userDetailViewModel.userDetailModel.observe(this@SplashScreenActivity, { + if (it.code == 200) { + navigatePageTo() + } else { + navigatePageTo() + } + finish() + }) + } + + override fun onTick(millisUntilFinished: Long) { + + } + } + + override fun initLayoutView(): Int = R.layout.activity_splash + + override fun setupTopBarLayout() { + ImmersionBar.with(this).statusBarDarkFont(false).init() + } + + override fun initData() { + userDetailViewModel = ViewModelProvider(this).get(UserViewModel::class.java) + } + + override fun observeRequestState() { + + } + + override fun initEvent() { + countDownTimer.start() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/vm/AuthenticateViewModel.kt b/app/src/main/java/com/casic/smart/town/sanxi/vm/AuthenticateViewModel.kt new file mode 100644 index 0000000..87ddbf8 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/vm/AuthenticateViewModel.kt @@ -0,0 +1,35 @@ +package com.casic.smart.town.sanxi.vm + +import androidx.lifecycle.MutableLiveData +import com.casic.smart.town.sanxi.base.BaseApplication +import com.casic.smart.town.sanxi.extensions.separateResponseCode +import com.casic.smart.town.sanxi.extensions.toErrorMessage +import com.casic.smart.town.sanxi.model.PublicKeyModel +import com.casic.smart.town.sanxi.util.retrofit.RetrofitServiceManager +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.launch +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.vm.BaseViewModel +import com.pengxh.kt.lite.vm.LoadState + +class AuthenticateViewModel : BaseViewModel() { + + private val gson = Gson() + val keyModel = MutableLiveData() + + fun obtainPublicKey() = launch({ + loadState.value = LoadState.Loading + val response = RetrofitServiceManager.authenticate() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + keyModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ) + } else { + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + it.printStackTrace() + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/vm/LoginViewModel.kt b/app/src/main/java/com/casic/smart/town/sanxi/vm/LoginViewModel.kt new file mode 100644 index 0000000..c377c61 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/vm/LoginViewModel.kt @@ -0,0 +1,54 @@ +package com.casic.smart.town.sanxi.vm + +import androidx.lifecycle.MutableLiveData +import com.casic.smart.town.sanxi.base.BaseApplication +import com.casic.smart.town.sanxi.extensions.separateResponseCode +import com.casic.smart.town.sanxi.extensions.toErrorMessage +import com.casic.smart.town.sanxi.model.CommonResultModel +import com.casic.smart.town.sanxi.model.LoginResultModel +import com.casic.smart.town.sanxi.util.retrofit.RetrofitServiceManager +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.launch +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.vm.BaseViewModel +import com.pengxh.kt.lite.vm.LoadState + +class LoginViewModel : BaseViewModel() { + + private val gson = Gson() + val enterResultModel = MutableLiveData() + val outResultModel = MutableLiveData() + + fun enter(sid: String, account: String, secretKey: String) = launch({ + val response = RetrofitServiceManager.login(sid, account, secretKey) + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + loadState.value = LoadState.Success + "登录成功".show(BaseApplication.obtainInstance()) + enterResultModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ) + } else { + loadState.value = LoadState.Fail + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + loadState.value = LoadState.Fail + it.printStackTrace() + }) + + fun out() = launch({ + val response = RetrofitServiceManager.loginOut() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + outResultModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ) + } else { + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + it.printStackTrace() + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/vm/UserViewModel.kt b/app/src/main/java/com/casic/smart/town/sanxi/vm/UserViewModel.kt new file mode 100644 index 0000000..29a8756 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/vm/UserViewModel.kt @@ -0,0 +1,57 @@ +package com.casic.smart.town.sanxi.vm + +import androidx.lifecycle.MutableLiveData +import com.casic.smart.town.sanxi.base.BaseApplication +import com.casic.smart.town.sanxi.extensions.separateResponseCode +import com.casic.smart.town.sanxi.extensions.toErrorMessage +import com.casic.smart.town.sanxi.model.UserDetailModel +import com.casic.smart.town.sanxi.util.LocaleConstant +import com.casic.smart.town.sanxi.util.retrofit.RetrofitServiceManager +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.launch +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.utils.SaveKeyValues +import com.pengxh.kt.lite.vm.BaseViewModel +import com.pengxh.kt.lite.vm.LoadState + +class UserViewModel : BaseViewModel() { + + private val gson = Gson() + val userDetailModel = MutableLiveData() + + fun obtainUserDetail() = launch({ + val response = RetrofitServiceManager.obtainUserDetail() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + val userDetail = gson.fromJson( + response, object : TypeToken() {}.type + ) + userDetailModel.value = userDetail + SaveKeyValues.putValue(LocaleConstant.USER_DETAIL_MODEL, gson.toJson(userDetail.data)) + } else { + val errorModel = UserDetailModel() + errorModel.code = responseCode + userDetailModel.value = errorModel + //如果此次获取不到用户信息,那么就清空之前的用户缓存,然后让用户重新登录 + SaveKeyValues.removeKey(LocaleConstant.USER_DETAIL_MODEL) + } + }, { + it.printStackTrace() + }) + + fun changePassword(oldPwd: String, newPwd: String) = launch({ + loadState.value = LoadState.Loading + val response = RetrofitServiceManager.changePassword(oldPwd, newPwd) + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + loadState.value = LoadState.Success + } else { + loadState.value = LoadState.Fail + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + loadState.value = LoadState.Fail + it.printStackTrace() + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/vm/VersionViewModel.kt b/app/src/main/java/com/casic/smart/town/sanxi/vm/VersionViewModel.kt new file mode 100644 index 0000000..20ea457 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/vm/VersionViewModel.kt @@ -0,0 +1,38 @@ +package com.casic.smart.town.sanxi.vm + +import androidx.lifecycle.MutableLiveData +import com.casic.smart.town.sanxi.base.BaseApplication +import com.casic.smart.town.sanxi.extensions.separateResponseCode +import com.casic.smart.town.sanxi.extensions.toErrorMessage +import com.casic.smart.town.sanxi.model.VersionResultModel +import com.casic.smart.town.sanxi.util.retrofit.RetrofitServiceManager +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.launch +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.vm.BaseViewModel +import com.pengxh.kt.lite.vm.LoadState + +class VersionViewModel : BaseViewModel() { + + private val gson = Gson() + val versionResultModel = MutableLiveData() + + fun updateVersion() = launch({ + loadState.value = LoadState.Loading + val response = RetrofitServiceManager.updateVersion() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + loadState.value = LoadState.Success + versionResultModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ).data + } else { + loadState.value = LoadState.Fail + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + loadState.value = LoadState.Fail + it.printStackTrace() + }) +} \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_solid_layout_gray_radius_5.xml b/app/src/main/res/drawable/bg_solid_layout_gray_radius_5.xml new file mode 100644 index 0000000..96eb93e --- /dev/null +++ b/app/src/main/res/drawable/bg_solid_layout_gray_radius_5.xml @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/button_main_selector.xml b/app/src/main/res/drawable/button_main_selector.xml new file mode 100644 index 0000000..5875998 --- /dev/null +++ b/app/src/main/res/drawable/button_main_selector.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/button_white_selector.xml b/app/src/main/res/drawable/button_white_selector.xml new file mode 100644 index 0000000..4dd3c0b --- /dev/null +++ b/app/src/main/res/drawable/button_white_selector.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/download_progress.xml b/app/src/main/res/drawable/download_progress.xml new file mode 100644 index 0000000..493d923 --- /dev/null +++ b/app/src/main/res/drawable/download_progress.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_about.xml b/app/src/main/res/drawable/ic_about.xml new file mode 100644 index 0000000..8469319 --- /dev/null +++ b/app/src/main/res/drawable/ic_about.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_change_pwd.xml b/app/src/main/res/drawable/ic_change_pwd.xml new file mode 100644 index 0000000..75caa39 --- /dev/null +++ b/app/src/main/res/drawable/ic_change_pwd.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/ic_clean.xml b/app/src/main/res/drawable/ic_clean.xml new file mode 100644 index 0000000..027f809 --- /dev/null +++ b/app/src/main/res/drawable/ic_clean.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_left.xml b/app/src/main/res/drawable/ic_left.xml new file mode 100644 index 0000000..04ee5da --- /dev/null +++ b/app/src/main/res/drawable/ic_left.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_lock.xml b/app/src/main/res/drawable/ic_lock.xml new file mode 100644 index 0000000..f0a5610 --- /dev/null +++ b/app/src/main/res/drawable/ic_lock.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/ic_more.xml b/app/src/main/res/drawable/ic_more.xml new file mode 100644 index 0000000..0794eaa --- /dev/null +++ b/app/src/main/res/drawable/ic_more.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_more_selected.xml b/app/src/main/res/drawable/ic_more_selected.xml new file mode 100644 index 0000000..9b6d3df --- /dev/null +++ b/app/src/main/res/drawable/ic_more_selected.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_phone.xml b/app/src/main/res/drawable/ic_phone.xml new file mode 100644 index 0000000..253f843 --- /dev/null +++ b/app/src/main/res/drawable/ic_phone.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1df917d..8d4f33b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -6,6 +6,7 @@ + @@ -32,11 +33,14 @@ + + + + + + + diff --git a/app/src/main/assets/svg/mine.svg b/app/src/main/assets/svg/mine.svg new file mode 100644 index 0000000..3fbb0a3 --- /dev/null +++ b/app/src/main/assets/svg/mine.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt b/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt index d730417..b1972c2 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt @@ -1,8 +1,10 @@ package com.casic.smart.town.sanxi.extensions import com.casic.smart.town.sanxi.model.ErrorMessageModel +import com.casic.smart.town.sanxi.util.LocaleConstant import com.google.gson.Gson import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.utils.SaveKeyValues import org.json.JSONObject fun String.separateResponseCode(): Int { @@ -17,4 +19,15 @@ this, object : TypeToken() {}.type ) return errorModel.message.toString() +} + +/** + * 下载路径为 http://xx.com/static/ 拼接downloadUrl + * */ +fun String.appendDownloadUrl(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + LocaleConstant.DEFAULT_SERVER_CONFIG, LocaleConstant.DEFAULT_SERVER + ) as String + return "$defaultValue/static/${this}" } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt b/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt new file mode 100644 index 0000000..1809dc5 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt @@ -0,0 +1,288 @@ +package com.casic.smart.town.sanxi.fragment + +import android.app.ProgressDialog +import android.content.Intent +import android.net.Uri +import android.os.Build +import android.os.CountDownTimer +import androidx.core.content.FileProvider +import androidx.lifecycle.ViewModelProvider +import com.casic.smart.town.sanxi.BuildConfig +import com.casic.smart.town.sanxi.R +import com.casic.smart.town.sanxi.extensions.appendDownloadUrl +import com.casic.smart.town.sanxi.model.UserDetailModel +import com.casic.smart.town.sanxi.util.AuthenticationHelper +import com.casic.smart.town.sanxi.util.LoadingDialogHub +import com.casic.smart.town.sanxi.util.LocaleConstant +import com.casic.smart.town.sanxi.util.RSAUtils +import com.casic.smart.town.sanxi.view.AboutUsActivity +import com.casic.smart.town.sanxi.view.LoginActivity +import com.casic.smart.town.sanxi.vm.LoginViewModel +import com.casic.smart.town.sanxi.vm.UserViewModel +import com.casic.smart.town.sanxi.vm.VersionViewModel +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.base.KotlinBaseFragment +import com.pengxh.kt.lite.callback.OnDownloadListener +import com.pengxh.kt.lite.extensions.* +import com.pengxh.kt.lite.utils.PageNavigationManager +import com.pengxh.kt.lite.utils.SaveKeyValues +import com.pengxh.kt.lite.vm.LoadState +import com.pengxh.kt.lite.widget.dialog.AlertControlDialog +import com.pengxh.kt.lite.widget.dialog.ChangePasswordDialog +import kotlinx.android.synthetic.main.fragment_mine.* +import java.io.File +import java.nio.charset.StandardCharsets + +class MinePageFragment : KotlinBaseFragment() { + + private lateinit var userData: UserDetailModel.Data + private lateinit var userViewModel: UserViewModel + private lateinit var loginViewModel: LoginViewModel + private lateinit var versionViewModel: VersionViewModel + private lateinit var progressDialog: ProgressDialog + + override fun initLayoutView(): Int = R.layout.fragment_mine + + override fun setupTopBarLayout() { + + } + + override fun initData() { + userViewModel = ViewModelProvider(this)[UserViewModel::class.java] + loginViewModel = ViewModelProvider(this)[LoginViewModel::class.java] + versionViewModel = ViewModelProvider(this)[VersionViewModel::class.java] + + //初始化下载对话框 + progressDialog = ProgressDialog(requireContext()) + progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL) + progressDialog.setProgressDrawable(resources.getDrawable(R.drawable.download_progress)) + progressDialog.setCanceledOnTouchOutside(false) + progressDialog.setCancelable(false) + + /** + * 数据监听 + * */ + dataObserve() + } + + override fun observeRequestState() { + userViewModel.loadState.observe(this, { + when (it) { + is LoadState.Loading -> LoadingDialogHub.show(requireActivity(), "修改中,请稍后") + is LoadState.Success -> { + "修改成功,请重新登录".show(requireContext()) + LoadingDialogHub.dismiss() + AuthenticationHelper.removeToken() + requireContext().navigatePageTo() + PageNavigationManager.finishAllActivity() + } + else -> LoadingDialogHub.dismiss() + } + }) + + versionViewModel.loadState.observe(this, { + when (it) { + is LoadState.Loading -> LoadingDialogHub.show(requireActivity(), "检查版本中,请稍后") + else -> LoadingDialogHub.dismiss() + } + }) + } + + override fun initEvent() { + userImageView.setOnClickListener { + "尽情期待~".show(requireContext()) + } + + updateUserButton.setOnClickListener { + LoadingDialogHub.show(requireActivity(), "同步中,请稍后...") + userViewModel.obtainUserDetail() + } + + changePwdLayout.setOnClickListener { + ChangePasswordDialog.Builder() + .setContext(requireContext()) + .setOnDialogButtonClickListener(object : + ChangePasswordDialog.OnDialogButtonClickListener { + override fun onConfirmClick(oldPwd: String, newPwd: String) { + val publicKey = + RSAUtils.keyStrToPublicKey(AuthenticationHelper.publicKey)!! + val oldPassKey = RSAUtils.encryptDataByPublicKey( + oldPwd.toByteArray(StandardCharsets.UTF_8), + publicKey + ) + val newPassKey = RSAUtils.encryptDataByPublicKey( + newPwd.toByteArray(StandardCharsets.UTF_8), + publicKey + ) + userViewModel.changePassword(oldPassKey, newPassKey) + } + }).build().show() + } + + aboutUsLayout.setOnClickListener { + requireContext().navigatePageTo() + } + + updateVersionLayout.setOnClickListener { + versionViewModel.updateVersion() + } + + clearCacheLayout.setOnClickListener { + //删除缓存之后在设置缓存大小 + LoadingDialogHub.show(requireActivity(), "清理中,请稍后") + File(requireContext().cacheDir.path).deleteFile() + object : CountDownTimer(1500, 500) { + override fun onTick(millisUntilFinished: Long) { + + } + + override fun onFinish() { + LoadingDialogHub.dismiss() + cacheSizeView.text = collectApplicationCache().formatFileSize() + } + }.start() + } + + loginOutButton.setOnClickListener { + AlertControlDialog.Builder() + .setContext(requireContext()) + .setTitle("退出登录") + .setMessage("确定要退出吗?") + .setNegativeButton("取消") + .setPositiveButton("确定") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + loginViewModel.out() + } + + override fun onCancelClick() {} + }).build().show() + } + } + + private fun dataObserve() { + userViewModel.userDetailModel.observe(this, { + if (it.code == 200) { + LoadingDialogHub.dismiss() + "同步完成".show(requireContext()) + userData = it.data + updateUserInfo() + } + }) + + versionViewModel.versionResultModel.observe(this, { + if (BuildConfig.VERSION_NAME == it.version) { + "已是最新版本,无需更新".show(requireContext()) + } else { + AlertControlDialog.Builder() + .setContext(requireContext()) + .setTitle("提示") + .setMessage("有新版本,是否更新?") + .setNegativeButton("稍后再说") + .setPositiveButton("立即下载") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + downloadApk(it.downloadUrl) + } + + override fun onCancelClick() { + + } + }).build().show() + } + }) + + loginViewModel.outResultModel.observe(this, { + if (it.code == 200) { + AuthenticationHelper.removeToken() + requireContext().navigatePageTo() + PageNavigationManager.finishAllActivity() + } + }) + } + + private fun collectApplicationCache(): Long { + return File(requireContext().cacheDir.path).calculateSize() + } + + override fun onResume() { + val userDetailJson = SaveKeyValues.getValue(LocaleConstant.USER_DETAIL_MODEL, "") as String + if (userDetailJson.isNotBlank()) { + userData = Gson().fromJson( + userDetailJson, object : TypeToken() {}.type + ) + updateUserInfo() + } + //缓存 + cacheSizeView.text = collectApplicationCache().formatFileSize() + super.onResume() + } + + private fun updateUserInfo() { + //设置头像,圆形,暂时是默认的 +// val roundDrawable = +// BitmapFactory.decodeResource(requireContext().resources, R.mipmap.login_casic) +// .createRoundDrawable( +// requireContext(), +// 3f.dp2px(requireContext()), +// R.color.mainThemeColor.convertColor(requireContext()) +// ) +// userImageView.setImageDrawable(roundDrawable) + userNameView.text = userData.name + userPhoneView.text = String.format("电话:${userData.phone}") + userDeptView.text = String.format("部门:${userData.deptName}") + } + + private fun downloadApk(url: String?) { + progressDialog.setMessage("下载新版本中...") + progressDialog.show() + if (url.toString().isBlank()) { + "抱歉,版本下载失败".show(requireContext()) + return + } + /** + * http://111.198.10.15:11304/static/apk/1.0.1.apk + * */ + val downloadPath = url!!.appendDownloadUrl() + //开始下载 + downloadPath.downloadFile(requireContext().createDownloadFileDir().toString(), + object : OnDownloadListener { + override fun onDownloadStart(totalBytes: Long) { + progressDialog.max = totalBytes.toInt() + } + + override fun onProgressChanged(currentBytes: Long) { + progressDialog.progress = currentBytes.toInt() + } + + override fun onDownloadEnd(file: File?) { + progressDialog.dismiss() + progressDialog.progress = 0 + //安装APK + installApk(file) + } + }) + } + + private fun installApk(apkPackage: File?) { + if (apkPackage == null) { + "文件异常,无法安装".show(requireContext()) + return + } + val intent = Intent(Intent.ACTION_VIEW) + val data: Uri + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { //判断版本大于等于7.0 + data = FileProvider.getUriForFile( + requireContext(), LocaleConstant.APP_AUTHORITY, apkPackage + ) + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) // 给目标应用一个临时授权 + } else { + data = Uri.fromFile(apkPackage) + } + intent.setDataAndType(data, "application/vnd.android.package-archive") + requireContext().startActivity(intent) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java new file mode 100644 index 0000000..467e33d --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java @@ -0,0 +1,43 @@ +package com.casic.smart.town.sanxi.model; + +/** + * 普通实体类,失败/成功数据结构一致 + */ +public class CommonResultModel { + private int code; + private String data; + private String message; + private boolean isSuccess; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return isSuccess; + } + + public void setSuccess(boolean success) { + isSuccess = success; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java new file mode 100644 index 0000000..9bdabef --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java @@ -0,0 +1,62 @@ +package com.casic.smart.town.sanxi.model; + +public class LoginResultModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private String kaptcha; + private String token; + + public String getKaptcha() { + return kaptcha; + } + + public void setKaptcha(String kaptcha) { + this.kaptcha = kaptcha; + } + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java new file mode 100644 index 0000000..09f8d59 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java @@ -0,0 +1,80 @@ +package com.casic.smart.town.sanxi.model; + +public class PublicKeyModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private boolean appKaptcha; + private boolean kaptcha; + private String publicKey; + private String sid; + + public boolean isAppKaptcha() { + return appKaptcha; + } + + public void setAppKaptcha(boolean appKaptcha) { + this.appKaptcha = appKaptcha; + } + + public boolean isKaptcha() { + return kaptcha; + } + + public void setKaptcha(boolean kaptcha) { + this.kaptcha = kaptcha; + } + + public String getPublicKey() { + return publicKey; + } + + public void setPublicKey(String publicKey) { + this.publicKey = publicKey; + } + + public String getSid() { + return sid; + } + + public void setSid(String sid) { + this.sid = sid; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java new file mode 100644 index 0000000..db577fb --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java @@ -0,0 +1,227 @@ +package com.casic.smart.town.sanxi.model; + + +import java.util.List; + +public class UserDetailModel { + private int code; + private Data data; + private String message; + private boolean success; + + public void setCode(int code) { + this.code = code; + } + + public int getCode() { + return code; + } + + public void setData(Data data) { + this.data = data; + } + + public Data getData() { + return data; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getMessage() { + return message; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public boolean getSuccess() { + return success; + } + + public static class Data { + + private String account; + private String attr1; + private String avatar; + private String bizData; + private List dataScope; + private String deptId; + private String deptName; + private List devices; + private String id; + private String ipAddr; + private String name; + private String phone; + private List roleList; + private List roleNames; + private List roleTips; + private String scopeType; + private String sysData; + private String targetId; + private String targetName; + private String tenantId; + + public void setAccount(String account) { + this.account = account; + } + + public String getAccount() { + return account; + } + + public void setAttr1(String attr1) { + this.attr1 = attr1; + } + + public String getAttr1() { + return attr1; + } + + public void setAvatar(String avatar) { + this.avatar = avatar; + } + + public String getAvatar() { + return avatar; + } + + public void setBizData(String bizData) { + this.bizData = bizData; + } + + public String getBizData() { + return bizData; + } + + public void setDataScope(List dataScope) { + this.dataScope = dataScope; + } + + public List getDataScope() { + return dataScope; + } + + public void setDeptId(String deptId) { + this.deptId = deptId; + } + + public String getDeptId() { + return deptId; + } + + public void setDeptName(String deptName) { + this.deptName = deptName; + } + + public String getDeptName() { + return deptName; + } + + public void setDevices(List devices) { + this.devices = devices; + } + + public List getDevices() { + return devices; + } + + public void setId(String id) { + this.id = id; + } + + public String getId() { + return id; + } + + public void setIpAddr(String ipAddr) { + this.ipAddr = ipAddr; + } + + public String getIpAddr() { + return ipAddr; + } + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setPhone(String phone) { + this.phone = phone; + } + + public String getPhone() { + return phone; + } + + public void setRoleList(List roleList) { + this.roleList = roleList; + } + + public List getRoleList() { + return roleList; + } + + public void setRoleNames(List roleNames) { + this.roleNames = roleNames; + } + + public List getRoleNames() { + return roleNames; + } + + public void setRoleTips(List roleTips) { + this.roleTips = roleTips; + } + + public List getRoleTips() { + return roleTips; + } + + public void setScopeType(String scopeType) { + this.scopeType = scopeType; + } + + public String getScopeType() { + return scopeType; + } + + public void setSysData(String sysData) { + this.sysData = sysData; + } + + public String getSysData() { + return sysData; + } + + public void setTargetId(String targetId) { + this.targetId = targetId; + } + + public String getTargetId() { + return targetId; + } + + public void setTargetName(String targetName) { + this.targetName = targetName; + } + + public String getTargetName() { + return targetName; + } + + public void setTenantId(String tenantId) { + this.tenantId = tenantId; + } + + public String getTenantId() { + return tenantId; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/VersionResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/VersionResultModel.java new file mode 100644 index 0000000..79374e0 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/VersionResultModel.java @@ -0,0 +1,61 @@ +package com.casic.smart.town.sanxi.model; + +public class VersionResultModel { + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private String downloadUrl; + private String version; + + public String getDownloadUrl() { + return downloadUrl; + } + + public void setDownloadUrl(String downloadUrl) { + this.downloadUrl = downloadUrl; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/AuthenticationHelper.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/AuthenticationHelper.kt new file mode 100644 index 0000000..f498e4e --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/AuthenticationHelper.kt @@ -0,0 +1,24 @@ +package com.casic.smart.town.sanxi.util + +import com.pengxh.kt.lite.utils.SaveKeyValues + +object AuthenticationHelper { + + fun savePublicKey(key: String) { + SaveKeyValues.putValue("keyString", key) + } + + val publicKey: String? + get() = SaveKeyValues.getValue("keyString", "") as String? + + fun saveToken(token: String?) { + SaveKeyValues.putValue("token", token!!) + } + + val token: String? + get() = SaveKeyValues.getValue("token", "") as String? + + fun removeToken() { + SaveKeyValues.removeKey("token") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt index ab1a36d..99f0d19 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt @@ -14,5 +14,9 @@ const val PAGE_LIMIT = 20 const val DEFAULT_SERVER_CONFIG = "defaultServerConfig" - const val DEFAULT_SERVER = "http://111.198.10.15:11308" + const val DEFAULT_SERVER = "http://36.133.189.112:8082" + const val APP_AUTHORITY = "com.casic.smart.town.sanxi.fileprovider" + const val ACCOUNT = "account" + const val PASSWORD = "password" + const val USER_DETAIL_MODEL = "userDetailModel" } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/RSAUtils.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/RSAUtils.kt new file mode 100644 index 0000000..b9216ba --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/RSAUtils.kt @@ -0,0 +1,55 @@ +package com.casic.smart.town.sanxi.util + +import android.util.Base64 +import java.security.* +import java.security.spec.InvalidKeySpecException +import java.security.spec.X509EncodedKeySpec +import javax.crypto.BadPaddingException +import javax.crypto.Cipher +import javax.crypto.IllegalBlockSizeException +import javax.crypto.NoSuchPaddingException + +object RSAUtils { + //构建Cipher实例时所传入的的字符串,默认为"RSA/NONE/PKCS1Padding" + private fun processData(srcData: ByteArray, key: Key): ByteArray? { //用来保存处理结果 + var resultBytes: ByteArray? = null + try { //获取Cipher实例 + val cipher = Cipher.getInstance("RSA/None/PKCS1Padding") + //初始化Cipher,mode指定是加密还是解密,key为公钥或私钥 + cipher.init(Cipher.ENCRYPT_MODE, key) + //处理数据 + resultBytes = cipher.doFinal(srcData) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: NoSuchPaddingException) { + e.printStackTrace() + } catch (e: InvalidKeyException) { + e.printStackTrace() + } catch (e: BadPaddingException) { + e.printStackTrace() + } catch (e: IllegalBlockSizeException) { + e.printStackTrace() + } + return resultBytes + } + + fun encryptDataByPublicKey(srcData: ByteArray, publicKey: PublicKey): String { + val resultBytes = processData(srcData, publicKey) + return Base64.encodeToString(resultBytes, Base64.DEFAULT) + } + + fun keyStrToPublicKey(publicKeyStr: String?): PublicKey? { + var publicKey: PublicKey? = null + val keyBytes = Base64.decode(publicKeyStr, Base64.DEFAULT) + val keySpec = X509EncodedKeySpec(keyBytes) + try { + val keyFactory = KeyFactory.getInstance("RSA") + publicKey = keyFactory.generatePublic(keySpec) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: InvalidKeySpecException) { + e.printStackTrace() + } + return publicKey + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt index 63db0ec..50d1742 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt @@ -1,12 +1,64 @@ package com.casic.smart.town.sanxi.util.retrofit -import retrofit2.http.GET +import retrofit2.http.* interface RetrofitService { + /** + * PublicKey校验 + */ + @GET("/config/baseConfig") + suspend fun obtainPublicKey(): String + + /** + * 登录并获取Token + * + * @param sid + * @param account 用户名 + * @param secretKey 加密后的密码 + */ + @FormUrlEncoded + @POST("/user/login") + suspend fun login( + @Field("sid") sid: String, + @Field("username") account: String, + @Field("password") secretKey: String + ): String + + /** + * 退出登录 + */ + @GET("/user/logout") + suspend fun loginOut(@Header("token") token: String): String + + /** + * 获取用户信息 + */ + @GET("/user/info") + suspend fun obtainUserDetail(@Header("token") token: String): String + + /** + * 修改密码 + * + * @param oldPwd 旧密码 + * @param newPwd 新密码 + */ + @FormUrlEncoded + @POST("/mgr/changePwd") + suspend fun changePassword( + @Header("token") token: String, + @Field("oldPwd") oldPwd: String, + @Field("newPwd") newPwd: String + ): String /** * 根据布防状态统计闸井数量接口 */ @GET("/well/countByBfzt") - suspend fun countWellByState(): String + suspend fun countWellByState(@Header("token") token: String): String + + /** + * 更新APK版本 + */ + @POST("/app/checkVersion") + suspend fun obtainVersionResult(@Header("token") token: String): String } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt index 46bbf4b..410444b 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt @@ -1,5 +1,6 @@ package com.casic.smart.town.sanxi.util.retrofit +import com.casic.smart.town.sanxi.util.AuthenticationHelper import com.casic.smart.town.sanxi.util.LocaleConstant import com.pengxh.kt.lite.utils.RetrofitFactory import com.pengxh.kt.lite.utils.SaveKeyValues @@ -15,9 +16,52 @@ } /** + * 验证PublicKey + */ + suspend fun authenticate(): String { + return api.obtainPublicKey() + } + + /** + * 登录并获取Token + */ + suspend fun login(sid: String, account: String, secretKey: String): String { + return api.login(sid, account, secretKey) + } + + /** + * 退出登录 + */ + suspend fun loginOut(): String { + return api.loginOut(AuthenticationHelper.token!!) + } + + /** + * 获取用户信息 + */ + suspend fun obtainUserDetail(): String { + return api.obtainUserDetail(AuthenticationHelper.token!!) + } + + /** + * 修改密码 + */ + suspend fun changePassword(oldPwd: String, newPwd: String): String { + return api.changePassword(AuthenticationHelper.token!!, oldPwd, newPwd) + } + + /** * 根据布防状态统计闸井数量接口 */ suspend fun countWellByState(): String { - return api.countWellByState() + return api.countWellByState(AuthenticationHelper.token!!) } + + /** + * 更新APK版本 + */ + suspend fun updateVersion(): String { + return api.obtainVersionResult(AuthenticationHelper.token!!) + } + } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/AboutUsActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/AboutUsActivity.kt new file mode 100644 index 0000000..7e43c47 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/AboutUsActivity.kt @@ -0,0 +1,34 @@ +package com.casic.smart.town.sanxi.view + +import com.casic.smart.town.sanxi.BuildConfig +import com.casic.smart.town.sanxi.R +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.kt.lite.base.KotlinBaseActivity +import com.pengxh.kt.lite.extensions.convertColor +import com.pengxh.kt.lite.utils.ImmerseStatusBarUtil +import kotlinx.android.synthetic.main.activity_about_us.* +import kotlinx.android.synthetic.main.include_base_title.* + +class AboutUsActivity : KotlinBaseActivity() { + + override fun initLayoutView(): Int = R.layout.activity_about_us + + override fun setupTopBarLayout() { + ImmersionBar.with(this).statusBarDarkFont(false).init() + ImmerseStatusBarUtil.setColor(this, R.color.mainThemeColor.convertColor(this)) + titleView.text = "关于我们" + leftBackView.setOnClickListener { finish() } + } + + override fun initData() { + versionCodeView.text = String.format("版本信息:V${BuildConfig.VERSION_NAME}") + } + + override fun observeRequestState() { + + } + + override fun initEvent() { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/LoginActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/LoginActivity.kt new file mode 100644 index 0000000..b5d1d50 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/LoginActivity.kt @@ -0,0 +1,97 @@ +package com.casic.smart.town.sanxi.view + +import androidx.lifecycle.ViewModelProvider +import com.casic.smart.town.sanxi.R +import com.casic.smart.town.sanxi.util.AuthenticationHelper +import com.casic.smart.town.sanxi.util.LoadingDialogHub +import com.casic.smart.town.sanxi.util.LocaleConstant +import com.casic.smart.town.sanxi.util.RSAUtils +import com.casic.smart.town.sanxi.vm.AuthenticateViewModel +import com.casic.smart.town.sanxi.vm.LoginViewModel +import com.casic.smart.town.sanxi.vm.UserViewModel +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.kt.lite.base.KotlinBaseActivity +import com.pengxh.kt.lite.extensions.navigatePageTo +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.utils.SaveKeyValues +import kotlinx.android.synthetic.main.activity_login.* + +class LoginActivity : KotlinBaseActivity() { + + private lateinit var authenticateViewModel: AuthenticateViewModel + private lateinit var loginViewModel: LoginViewModel + private lateinit var userViewModel: UserViewModel + + override fun initLayoutView(): Int = R.layout.activity_login + + override fun setupTopBarLayout() { + ImmersionBar.with(this).statusBarDarkFont(true).init() + } + + override fun initData() { + // 设置默认账号密码 + userNameView.setText(SaveKeyValues.getValue(LocaleConstant.ACCOUNT, "") as String) + userPasswordView.setText(SaveKeyValues.getValue(LocaleConstant.PASSWORD, "") as String) + authenticateViewModel = ViewModelProvider(this)[AuthenticateViewModel::class.java] + loginViewModel = ViewModelProvider(this)[LoginViewModel::class.java] + userViewModel = ViewModelProvider(this)[UserViewModel::class.java] + + authenticateViewModel.keyModel.observe(this, { + if (it.code == 200) {//用code判断,别的判断可能有坑 + val keyString = it.data!!.publicKey!! + /** + * 修改密码需要用到key,先存着 + * */ + AuthenticationHelper.savePublicKey(keyString) + val publicKey = RSAUtils.keyStrToPublicKey(keyString) + + val account = userNameView.text.toString() + val userPassword = userPasswordView.text.toString() + val dataByPublicKey = + RSAUtils.encryptDataByPublicKey(userPassword.toByteArray(), publicKey!!) + //登录并获取Token,POST请求 + loginViewModel.enter(it.data!!.sid!!, account, dataByPublicKey) + loginViewModel.enterResultModel.observe(this, { loginResult -> + if (loginResult.code == 200) { + AuthenticationHelper.saveToken(loginResult.data!!.token!!) + /** + * 获取token之后保存用户信息 + * */ + userViewModel.obtainUserDetail() + //验证成功登录 + navigatePageTo() + finish() + } + }) + } + }) + } + + override fun observeRequestState() { + authenticateViewModel.loadState.observe(this, { + LoadingDialogHub.show(this, "登录中,请稍后") + }) + + loginViewModel.loadState.observe(this, { + LoadingDialogHub.dismiss() + }) + } + + override fun initEvent() { + loginButton.setOnClickListener { + val account = userNameView.text.toString().trim() + val userPassword = userPasswordView.text.toString().trim() + if (account.isBlank()) { + "用户名不能为空".show(this) + return@setOnClickListener + } + if (userPassword.isBlank()) { + "密码不能为空".show(this) + return@setOnClickListener + } + SaveKeyValues.putValue(LocaleConstant.ACCOUNT, account) + SaveKeyValues.putValue(LocaleConstant.PASSWORD, userPassword) + authenticateViewModel.obtainPublicKey() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt index 3bea1c9..a4fa951 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt @@ -7,6 +7,7 @@ import com.casic.smart.town.sanxi.R import com.casic.smart.town.sanxi.adapter.ViewPagerAdapter import com.casic.smart.town.sanxi.fragment.AlarmPageFragment +import com.casic.smart.town.sanxi.fragment.MinePageFragment import com.casic.smart.town.sanxi.fragment.MonitorPageFragment import com.casic.smart.town.sanxi.fragment.StatisticsPageFragment import com.gyf.immersionbar.ImmersionBar @@ -33,6 +34,7 @@ fragmentPages.add(StatisticsPageFragment()) fragmentPages.add(MonitorPageFragment()) fragmentPages.add(AlarmPageFragment()) + fragmentPages.add(MinePageFragment()) } override fun initLayoutView(): Int = R.layout.activity_main @@ -68,6 +70,9 @@ R.id.nav_alarm -> { mainViewPager.currentItem = 2 } + R.id.nav_more -> { + mainViewPager.currentItem = 3 + } } false } diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/PermissionActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/PermissionActivity.kt index c478d83..3238f3c 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/view/PermissionActivity.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/PermissionActivity.kt @@ -32,7 +32,7 @@ //先把导航隐私政策声明,后面导航会用到 MapsInitializer.updatePrivacyAgree(this, true) MapsInitializer.updatePrivacyShow(this, true, true) - this.navigatePageTo() + this.navigatePageTo() finish() } diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/SplashScreenActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/SplashScreenActivity.kt new file mode 100644 index 0000000..fa5a74d --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/SplashScreenActivity.kt @@ -0,0 +1,54 @@ +package com.casic.smart.town.sanxi.view + +import android.annotation.SuppressLint +import android.os.CountDownTimer +import androidx.lifecycle.ViewModelProvider +import com.casic.smart.town.sanxi.R +import com.casic.smart.town.sanxi.vm.UserViewModel +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.kt.lite.base.KotlinBaseActivity +import com.pengxh.kt.lite.extensions.navigatePageTo + +@SuppressLint("CustomSplashScreen") +class SplashScreenActivity : KotlinBaseActivity() { + + private lateinit var userDetailViewModel: UserViewModel + private val countDownTimer = object : CountDownTimer(1000, 500) { + override fun onFinish() { + /** + * 获取token之后保存用户信息 + * */ + userDetailViewModel.obtainUserDetail() + userDetailViewModel.userDetailModel.observe(this@SplashScreenActivity, { + if (it.code == 200) { + navigatePageTo() + } else { + navigatePageTo() + } + finish() + }) + } + + override fun onTick(millisUntilFinished: Long) { + + } + } + + override fun initLayoutView(): Int = R.layout.activity_splash + + override fun setupTopBarLayout() { + ImmersionBar.with(this).statusBarDarkFont(false).init() + } + + override fun initData() { + userDetailViewModel = ViewModelProvider(this).get(UserViewModel::class.java) + } + + override fun observeRequestState() { + + } + + override fun initEvent() { + countDownTimer.start() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/vm/AuthenticateViewModel.kt b/app/src/main/java/com/casic/smart/town/sanxi/vm/AuthenticateViewModel.kt new file mode 100644 index 0000000..87ddbf8 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/vm/AuthenticateViewModel.kt @@ -0,0 +1,35 @@ +package com.casic.smart.town.sanxi.vm + +import androidx.lifecycle.MutableLiveData +import com.casic.smart.town.sanxi.base.BaseApplication +import com.casic.smart.town.sanxi.extensions.separateResponseCode +import com.casic.smart.town.sanxi.extensions.toErrorMessage +import com.casic.smart.town.sanxi.model.PublicKeyModel +import com.casic.smart.town.sanxi.util.retrofit.RetrofitServiceManager +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.launch +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.vm.BaseViewModel +import com.pengxh.kt.lite.vm.LoadState + +class AuthenticateViewModel : BaseViewModel() { + + private val gson = Gson() + val keyModel = MutableLiveData() + + fun obtainPublicKey() = launch({ + loadState.value = LoadState.Loading + val response = RetrofitServiceManager.authenticate() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + keyModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ) + } else { + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + it.printStackTrace() + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/vm/LoginViewModel.kt b/app/src/main/java/com/casic/smart/town/sanxi/vm/LoginViewModel.kt new file mode 100644 index 0000000..c377c61 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/vm/LoginViewModel.kt @@ -0,0 +1,54 @@ +package com.casic.smart.town.sanxi.vm + +import androidx.lifecycle.MutableLiveData +import com.casic.smart.town.sanxi.base.BaseApplication +import com.casic.smart.town.sanxi.extensions.separateResponseCode +import com.casic.smart.town.sanxi.extensions.toErrorMessage +import com.casic.smart.town.sanxi.model.CommonResultModel +import com.casic.smart.town.sanxi.model.LoginResultModel +import com.casic.smart.town.sanxi.util.retrofit.RetrofitServiceManager +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.launch +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.vm.BaseViewModel +import com.pengxh.kt.lite.vm.LoadState + +class LoginViewModel : BaseViewModel() { + + private val gson = Gson() + val enterResultModel = MutableLiveData() + val outResultModel = MutableLiveData() + + fun enter(sid: String, account: String, secretKey: String) = launch({ + val response = RetrofitServiceManager.login(sid, account, secretKey) + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + loadState.value = LoadState.Success + "登录成功".show(BaseApplication.obtainInstance()) + enterResultModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ) + } else { + loadState.value = LoadState.Fail + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + loadState.value = LoadState.Fail + it.printStackTrace() + }) + + fun out() = launch({ + val response = RetrofitServiceManager.loginOut() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + outResultModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ) + } else { + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + it.printStackTrace() + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/vm/UserViewModel.kt b/app/src/main/java/com/casic/smart/town/sanxi/vm/UserViewModel.kt new file mode 100644 index 0000000..29a8756 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/vm/UserViewModel.kt @@ -0,0 +1,57 @@ +package com.casic.smart.town.sanxi.vm + +import androidx.lifecycle.MutableLiveData +import com.casic.smart.town.sanxi.base.BaseApplication +import com.casic.smart.town.sanxi.extensions.separateResponseCode +import com.casic.smart.town.sanxi.extensions.toErrorMessage +import com.casic.smart.town.sanxi.model.UserDetailModel +import com.casic.smart.town.sanxi.util.LocaleConstant +import com.casic.smart.town.sanxi.util.retrofit.RetrofitServiceManager +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.launch +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.utils.SaveKeyValues +import com.pengxh.kt.lite.vm.BaseViewModel +import com.pengxh.kt.lite.vm.LoadState + +class UserViewModel : BaseViewModel() { + + private val gson = Gson() + val userDetailModel = MutableLiveData() + + fun obtainUserDetail() = launch({ + val response = RetrofitServiceManager.obtainUserDetail() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + val userDetail = gson.fromJson( + response, object : TypeToken() {}.type + ) + userDetailModel.value = userDetail + SaveKeyValues.putValue(LocaleConstant.USER_DETAIL_MODEL, gson.toJson(userDetail.data)) + } else { + val errorModel = UserDetailModel() + errorModel.code = responseCode + userDetailModel.value = errorModel + //如果此次获取不到用户信息,那么就清空之前的用户缓存,然后让用户重新登录 + SaveKeyValues.removeKey(LocaleConstant.USER_DETAIL_MODEL) + } + }, { + it.printStackTrace() + }) + + fun changePassword(oldPwd: String, newPwd: String) = launch({ + loadState.value = LoadState.Loading + val response = RetrofitServiceManager.changePassword(oldPwd, newPwd) + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + loadState.value = LoadState.Success + } else { + loadState.value = LoadState.Fail + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + loadState.value = LoadState.Fail + it.printStackTrace() + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/vm/VersionViewModel.kt b/app/src/main/java/com/casic/smart/town/sanxi/vm/VersionViewModel.kt new file mode 100644 index 0000000..20ea457 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/vm/VersionViewModel.kt @@ -0,0 +1,38 @@ +package com.casic.smart.town.sanxi.vm + +import androidx.lifecycle.MutableLiveData +import com.casic.smart.town.sanxi.base.BaseApplication +import com.casic.smart.town.sanxi.extensions.separateResponseCode +import com.casic.smart.town.sanxi.extensions.toErrorMessage +import com.casic.smart.town.sanxi.model.VersionResultModel +import com.casic.smart.town.sanxi.util.retrofit.RetrofitServiceManager +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.launch +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.vm.BaseViewModel +import com.pengxh.kt.lite.vm.LoadState + +class VersionViewModel : BaseViewModel() { + + private val gson = Gson() + val versionResultModel = MutableLiveData() + + fun updateVersion() = launch({ + loadState.value = LoadState.Loading + val response = RetrofitServiceManager.updateVersion() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + loadState.value = LoadState.Success + versionResultModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ).data + } else { + loadState.value = LoadState.Fail + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + loadState.value = LoadState.Fail + it.printStackTrace() + }) +} \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_solid_layout_gray_radius_5.xml b/app/src/main/res/drawable/bg_solid_layout_gray_radius_5.xml new file mode 100644 index 0000000..96eb93e --- /dev/null +++ b/app/src/main/res/drawable/bg_solid_layout_gray_radius_5.xml @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/button_main_selector.xml b/app/src/main/res/drawable/button_main_selector.xml new file mode 100644 index 0000000..5875998 --- /dev/null +++ b/app/src/main/res/drawable/button_main_selector.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/button_white_selector.xml b/app/src/main/res/drawable/button_white_selector.xml new file mode 100644 index 0000000..4dd3c0b --- /dev/null +++ b/app/src/main/res/drawable/button_white_selector.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/download_progress.xml b/app/src/main/res/drawable/download_progress.xml new file mode 100644 index 0000000..493d923 --- /dev/null +++ b/app/src/main/res/drawable/download_progress.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_about.xml b/app/src/main/res/drawable/ic_about.xml new file mode 100644 index 0000000..8469319 --- /dev/null +++ b/app/src/main/res/drawable/ic_about.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_change_pwd.xml b/app/src/main/res/drawable/ic_change_pwd.xml new file mode 100644 index 0000000..75caa39 --- /dev/null +++ b/app/src/main/res/drawable/ic_change_pwd.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/ic_clean.xml b/app/src/main/res/drawable/ic_clean.xml new file mode 100644 index 0000000..027f809 --- /dev/null +++ b/app/src/main/res/drawable/ic_clean.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_left.xml b/app/src/main/res/drawable/ic_left.xml new file mode 100644 index 0000000..04ee5da --- /dev/null +++ b/app/src/main/res/drawable/ic_left.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_lock.xml b/app/src/main/res/drawable/ic_lock.xml new file mode 100644 index 0000000..f0a5610 --- /dev/null +++ b/app/src/main/res/drawable/ic_lock.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/ic_more.xml b/app/src/main/res/drawable/ic_more.xml new file mode 100644 index 0000000..0794eaa --- /dev/null +++ b/app/src/main/res/drawable/ic_more.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_more_selected.xml b/app/src/main/res/drawable/ic_more_selected.xml new file mode 100644 index 0000000..9b6d3df --- /dev/null +++ b/app/src/main/res/drawable/ic_more_selected.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_phone.xml b/app/src/main/res/drawable/ic_phone.xml new file mode 100644 index 0000000..253f843 --- /dev/null +++ b/app/src/main/res/drawable/ic_phone.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_right.xml b/app/src/main/res/drawable/ic_right.xml new file mode 100644 index 0000000..6020f09 --- /dev/null +++ b/app/src/main/res/drawable/ic_right.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1df917d..8d4f33b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -6,6 +6,7 @@ + @@ -32,11 +33,14 @@ + + + + + + + diff --git a/app/src/main/assets/svg/mine.svg b/app/src/main/assets/svg/mine.svg new file mode 100644 index 0000000..3fbb0a3 --- /dev/null +++ b/app/src/main/assets/svg/mine.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt b/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt index d730417..b1972c2 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt @@ -1,8 +1,10 @@ package com.casic.smart.town.sanxi.extensions import com.casic.smart.town.sanxi.model.ErrorMessageModel +import com.casic.smart.town.sanxi.util.LocaleConstant import com.google.gson.Gson import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.utils.SaveKeyValues import org.json.JSONObject fun String.separateResponseCode(): Int { @@ -17,4 +19,15 @@ this, object : TypeToken() {}.type ) return errorModel.message.toString() +} + +/** + * 下载路径为 http://xx.com/static/ 拼接downloadUrl + * */ +fun String.appendDownloadUrl(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + LocaleConstant.DEFAULT_SERVER_CONFIG, LocaleConstant.DEFAULT_SERVER + ) as String + return "$defaultValue/static/${this}" } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt b/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt new file mode 100644 index 0000000..1809dc5 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt @@ -0,0 +1,288 @@ +package com.casic.smart.town.sanxi.fragment + +import android.app.ProgressDialog +import android.content.Intent +import android.net.Uri +import android.os.Build +import android.os.CountDownTimer +import androidx.core.content.FileProvider +import androidx.lifecycle.ViewModelProvider +import com.casic.smart.town.sanxi.BuildConfig +import com.casic.smart.town.sanxi.R +import com.casic.smart.town.sanxi.extensions.appendDownloadUrl +import com.casic.smart.town.sanxi.model.UserDetailModel +import com.casic.smart.town.sanxi.util.AuthenticationHelper +import com.casic.smart.town.sanxi.util.LoadingDialogHub +import com.casic.smart.town.sanxi.util.LocaleConstant +import com.casic.smart.town.sanxi.util.RSAUtils +import com.casic.smart.town.sanxi.view.AboutUsActivity +import com.casic.smart.town.sanxi.view.LoginActivity +import com.casic.smart.town.sanxi.vm.LoginViewModel +import com.casic.smart.town.sanxi.vm.UserViewModel +import com.casic.smart.town.sanxi.vm.VersionViewModel +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.base.KotlinBaseFragment +import com.pengxh.kt.lite.callback.OnDownloadListener +import com.pengxh.kt.lite.extensions.* +import com.pengxh.kt.lite.utils.PageNavigationManager +import com.pengxh.kt.lite.utils.SaveKeyValues +import com.pengxh.kt.lite.vm.LoadState +import com.pengxh.kt.lite.widget.dialog.AlertControlDialog +import com.pengxh.kt.lite.widget.dialog.ChangePasswordDialog +import kotlinx.android.synthetic.main.fragment_mine.* +import java.io.File +import java.nio.charset.StandardCharsets + +class MinePageFragment : KotlinBaseFragment() { + + private lateinit var userData: UserDetailModel.Data + private lateinit var userViewModel: UserViewModel + private lateinit var loginViewModel: LoginViewModel + private lateinit var versionViewModel: VersionViewModel + private lateinit var progressDialog: ProgressDialog + + override fun initLayoutView(): Int = R.layout.fragment_mine + + override fun setupTopBarLayout() { + + } + + override fun initData() { + userViewModel = ViewModelProvider(this)[UserViewModel::class.java] + loginViewModel = ViewModelProvider(this)[LoginViewModel::class.java] + versionViewModel = ViewModelProvider(this)[VersionViewModel::class.java] + + //初始化下载对话框 + progressDialog = ProgressDialog(requireContext()) + progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL) + progressDialog.setProgressDrawable(resources.getDrawable(R.drawable.download_progress)) + progressDialog.setCanceledOnTouchOutside(false) + progressDialog.setCancelable(false) + + /** + * 数据监听 + * */ + dataObserve() + } + + override fun observeRequestState() { + userViewModel.loadState.observe(this, { + when (it) { + is LoadState.Loading -> LoadingDialogHub.show(requireActivity(), "修改中,请稍后") + is LoadState.Success -> { + "修改成功,请重新登录".show(requireContext()) + LoadingDialogHub.dismiss() + AuthenticationHelper.removeToken() + requireContext().navigatePageTo() + PageNavigationManager.finishAllActivity() + } + else -> LoadingDialogHub.dismiss() + } + }) + + versionViewModel.loadState.observe(this, { + when (it) { + is LoadState.Loading -> LoadingDialogHub.show(requireActivity(), "检查版本中,请稍后") + else -> LoadingDialogHub.dismiss() + } + }) + } + + override fun initEvent() { + userImageView.setOnClickListener { + "尽情期待~".show(requireContext()) + } + + updateUserButton.setOnClickListener { + LoadingDialogHub.show(requireActivity(), "同步中,请稍后...") + userViewModel.obtainUserDetail() + } + + changePwdLayout.setOnClickListener { + ChangePasswordDialog.Builder() + .setContext(requireContext()) + .setOnDialogButtonClickListener(object : + ChangePasswordDialog.OnDialogButtonClickListener { + override fun onConfirmClick(oldPwd: String, newPwd: String) { + val publicKey = + RSAUtils.keyStrToPublicKey(AuthenticationHelper.publicKey)!! + val oldPassKey = RSAUtils.encryptDataByPublicKey( + oldPwd.toByteArray(StandardCharsets.UTF_8), + publicKey + ) + val newPassKey = RSAUtils.encryptDataByPublicKey( + newPwd.toByteArray(StandardCharsets.UTF_8), + publicKey + ) + userViewModel.changePassword(oldPassKey, newPassKey) + } + }).build().show() + } + + aboutUsLayout.setOnClickListener { + requireContext().navigatePageTo() + } + + updateVersionLayout.setOnClickListener { + versionViewModel.updateVersion() + } + + clearCacheLayout.setOnClickListener { + //删除缓存之后在设置缓存大小 + LoadingDialogHub.show(requireActivity(), "清理中,请稍后") + File(requireContext().cacheDir.path).deleteFile() + object : CountDownTimer(1500, 500) { + override fun onTick(millisUntilFinished: Long) { + + } + + override fun onFinish() { + LoadingDialogHub.dismiss() + cacheSizeView.text = collectApplicationCache().formatFileSize() + } + }.start() + } + + loginOutButton.setOnClickListener { + AlertControlDialog.Builder() + .setContext(requireContext()) + .setTitle("退出登录") + .setMessage("确定要退出吗?") + .setNegativeButton("取消") + .setPositiveButton("确定") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + loginViewModel.out() + } + + override fun onCancelClick() {} + }).build().show() + } + } + + private fun dataObserve() { + userViewModel.userDetailModel.observe(this, { + if (it.code == 200) { + LoadingDialogHub.dismiss() + "同步完成".show(requireContext()) + userData = it.data + updateUserInfo() + } + }) + + versionViewModel.versionResultModel.observe(this, { + if (BuildConfig.VERSION_NAME == it.version) { + "已是最新版本,无需更新".show(requireContext()) + } else { + AlertControlDialog.Builder() + .setContext(requireContext()) + .setTitle("提示") + .setMessage("有新版本,是否更新?") + .setNegativeButton("稍后再说") + .setPositiveButton("立即下载") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + downloadApk(it.downloadUrl) + } + + override fun onCancelClick() { + + } + }).build().show() + } + }) + + loginViewModel.outResultModel.observe(this, { + if (it.code == 200) { + AuthenticationHelper.removeToken() + requireContext().navigatePageTo() + PageNavigationManager.finishAllActivity() + } + }) + } + + private fun collectApplicationCache(): Long { + return File(requireContext().cacheDir.path).calculateSize() + } + + override fun onResume() { + val userDetailJson = SaveKeyValues.getValue(LocaleConstant.USER_DETAIL_MODEL, "") as String + if (userDetailJson.isNotBlank()) { + userData = Gson().fromJson( + userDetailJson, object : TypeToken() {}.type + ) + updateUserInfo() + } + //缓存 + cacheSizeView.text = collectApplicationCache().formatFileSize() + super.onResume() + } + + private fun updateUserInfo() { + //设置头像,圆形,暂时是默认的 +// val roundDrawable = +// BitmapFactory.decodeResource(requireContext().resources, R.mipmap.login_casic) +// .createRoundDrawable( +// requireContext(), +// 3f.dp2px(requireContext()), +// R.color.mainThemeColor.convertColor(requireContext()) +// ) +// userImageView.setImageDrawable(roundDrawable) + userNameView.text = userData.name + userPhoneView.text = String.format("电话:${userData.phone}") + userDeptView.text = String.format("部门:${userData.deptName}") + } + + private fun downloadApk(url: String?) { + progressDialog.setMessage("下载新版本中...") + progressDialog.show() + if (url.toString().isBlank()) { + "抱歉,版本下载失败".show(requireContext()) + return + } + /** + * http://111.198.10.15:11304/static/apk/1.0.1.apk + * */ + val downloadPath = url!!.appendDownloadUrl() + //开始下载 + downloadPath.downloadFile(requireContext().createDownloadFileDir().toString(), + object : OnDownloadListener { + override fun onDownloadStart(totalBytes: Long) { + progressDialog.max = totalBytes.toInt() + } + + override fun onProgressChanged(currentBytes: Long) { + progressDialog.progress = currentBytes.toInt() + } + + override fun onDownloadEnd(file: File?) { + progressDialog.dismiss() + progressDialog.progress = 0 + //安装APK + installApk(file) + } + }) + } + + private fun installApk(apkPackage: File?) { + if (apkPackage == null) { + "文件异常,无法安装".show(requireContext()) + return + } + val intent = Intent(Intent.ACTION_VIEW) + val data: Uri + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { //判断版本大于等于7.0 + data = FileProvider.getUriForFile( + requireContext(), LocaleConstant.APP_AUTHORITY, apkPackage + ) + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) // 给目标应用一个临时授权 + } else { + data = Uri.fromFile(apkPackage) + } + intent.setDataAndType(data, "application/vnd.android.package-archive") + requireContext().startActivity(intent) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java new file mode 100644 index 0000000..467e33d --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java @@ -0,0 +1,43 @@ +package com.casic.smart.town.sanxi.model; + +/** + * 普通实体类,失败/成功数据结构一致 + */ +public class CommonResultModel { + private int code; + private String data; + private String message; + private boolean isSuccess; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return isSuccess; + } + + public void setSuccess(boolean success) { + isSuccess = success; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java new file mode 100644 index 0000000..9bdabef --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java @@ -0,0 +1,62 @@ +package com.casic.smart.town.sanxi.model; + +public class LoginResultModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private String kaptcha; + private String token; + + public String getKaptcha() { + return kaptcha; + } + + public void setKaptcha(String kaptcha) { + this.kaptcha = kaptcha; + } + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java new file mode 100644 index 0000000..09f8d59 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java @@ -0,0 +1,80 @@ +package com.casic.smart.town.sanxi.model; + +public class PublicKeyModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private boolean appKaptcha; + private boolean kaptcha; + private String publicKey; + private String sid; + + public boolean isAppKaptcha() { + return appKaptcha; + } + + public void setAppKaptcha(boolean appKaptcha) { + this.appKaptcha = appKaptcha; + } + + public boolean isKaptcha() { + return kaptcha; + } + + public void setKaptcha(boolean kaptcha) { + this.kaptcha = kaptcha; + } + + public String getPublicKey() { + return publicKey; + } + + public void setPublicKey(String publicKey) { + this.publicKey = publicKey; + } + + public String getSid() { + return sid; + } + + public void setSid(String sid) { + this.sid = sid; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java new file mode 100644 index 0000000..db577fb --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java @@ -0,0 +1,227 @@ +package com.casic.smart.town.sanxi.model; + + +import java.util.List; + +public class UserDetailModel { + private int code; + private Data data; + private String message; + private boolean success; + + public void setCode(int code) { + this.code = code; + } + + public int getCode() { + return code; + } + + public void setData(Data data) { + this.data = data; + } + + public Data getData() { + return data; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getMessage() { + return message; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public boolean getSuccess() { + return success; + } + + public static class Data { + + private String account; + private String attr1; + private String avatar; + private String bizData; + private List dataScope; + private String deptId; + private String deptName; + private List devices; + private String id; + private String ipAddr; + private String name; + private String phone; + private List roleList; + private List roleNames; + private List roleTips; + private String scopeType; + private String sysData; + private String targetId; + private String targetName; + private String tenantId; + + public void setAccount(String account) { + this.account = account; + } + + public String getAccount() { + return account; + } + + public void setAttr1(String attr1) { + this.attr1 = attr1; + } + + public String getAttr1() { + return attr1; + } + + public void setAvatar(String avatar) { + this.avatar = avatar; + } + + public String getAvatar() { + return avatar; + } + + public void setBizData(String bizData) { + this.bizData = bizData; + } + + public String getBizData() { + return bizData; + } + + public void setDataScope(List dataScope) { + this.dataScope = dataScope; + } + + public List getDataScope() { + return dataScope; + } + + public void setDeptId(String deptId) { + this.deptId = deptId; + } + + public String getDeptId() { + return deptId; + } + + public void setDeptName(String deptName) { + this.deptName = deptName; + } + + public String getDeptName() { + return deptName; + } + + public void setDevices(List devices) { + this.devices = devices; + } + + public List getDevices() { + return devices; + } + + public void setId(String id) { + this.id = id; + } + + public String getId() { + return id; + } + + public void setIpAddr(String ipAddr) { + this.ipAddr = ipAddr; + } + + public String getIpAddr() { + return ipAddr; + } + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setPhone(String phone) { + this.phone = phone; + } + + public String getPhone() { + return phone; + } + + public void setRoleList(List roleList) { + this.roleList = roleList; + } + + public List getRoleList() { + return roleList; + } + + public void setRoleNames(List roleNames) { + this.roleNames = roleNames; + } + + public List getRoleNames() { + return roleNames; + } + + public void setRoleTips(List roleTips) { + this.roleTips = roleTips; + } + + public List getRoleTips() { + return roleTips; + } + + public void setScopeType(String scopeType) { + this.scopeType = scopeType; + } + + public String getScopeType() { + return scopeType; + } + + public void setSysData(String sysData) { + this.sysData = sysData; + } + + public String getSysData() { + return sysData; + } + + public void setTargetId(String targetId) { + this.targetId = targetId; + } + + public String getTargetId() { + return targetId; + } + + public void setTargetName(String targetName) { + this.targetName = targetName; + } + + public String getTargetName() { + return targetName; + } + + public void setTenantId(String tenantId) { + this.tenantId = tenantId; + } + + public String getTenantId() { + return tenantId; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/VersionResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/VersionResultModel.java new file mode 100644 index 0000000..79374e0 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/VersionResultModel.java @@ -0,0 +1,61 @@ +package com.casic.smart.town.sanxi.model; + +public class VersionResultModel { + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private String downloadUrl; + private String version; + + public String getDownloadUrl() { + return downloadUrl; + } + + public void setDownloadUrl(String downloadUrl) { + this.downloadUrl = downloadUrl; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/AuthenticationHelper.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/AuthenticationHelper.kt new file mode 100644 index 0000000..f498e4e --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/AuthenticationHelper.kt @@ -0,0 +1,24 @@ +package com.casic.smart.town.sanxi.util + +import com.pengxh.kt.lite.utils.SaveKeyValues + +object AuthenticationHelper { + + fun savePublicKey(key: String) { + SaveKeyValues.putValue("keyString", key) + } + + val publicKey: String? + get() = SaveKeyValues.getValue("keyString", "") as String? + + fun saveToken(token: String?) { + SaveKeyValues.putValue("token", token!!) + } + + val token: String? + get() = SaveKeyValues.getValue("token", "") as String? + + fun removeToken() { + SaveKeyValues.removeKey("token") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt index ab1a36d..99f0d19 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt @@ -14,5 +14,9 @@ const val PAGE_LIMIT = 20 const val DEFAULT_SERVER_CONFIG = "defaultServerConfig" - const val DEFAULT_SERVER = "http://111.198.10.15:11308" + const val DEFAULT_SERVER = "http://36.133.189.112:8082" + const val APP_AUTHORITY = "com.casic.smart.town.sanxi.fileprovider" + const val ACCOUNT = "account" + const val PASSWORD = "password" + const val USER_DETAIL_MODEL = "userDetailModel" } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/RSAUtils.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/RSAUtils.kt new file mode 100644 index 0000000..b9216ba --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/RSAUtils.kt @@ -0,0 +1,55 @@ +package com.casic.smart.town.sanxi.util + +import android.util.Base64 +import java.security.* +import java.security.spec.InvalidKeySpecException +import java.security.spec.X509EncodedKeySpec +import javax.crypto.BadPaddingException +import javax.crypto.Cipher +import javax.crypto.IllegalBlockSizeException +import javax.crypto.NoSuchPaddingException + +object RSAUtils { + //构建Cipher实例时所传入的的字符串,默认为"RSA/NONE/PKCS1Padding" + private fun processData(srcData: ByteArray, key: Key): ByteArray? { //用来保存处理结果 + var resultBytes: ByteArray? = null + try { //获取Cipher实例 + val cipher = Cipher.getInstance("RSA/None/PKCS1Padding") + //初始化Cipher,mode指定是加密还是解密,key为公钥或私钥 + cipher.init(Cipher.ENCRYPT_MODE, key) + //处理数据 + resultBytes = cipher.doFinal(srcData) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: NoSuchPaddingException) { + e.printStackTrace() + } catch (e: InvalidKeyException) { + e.printStackTrace() + } catch (e: BadPaddingException) { + e.printStackTrace() + } catch (e: IllegalBlockSizeException) { + e.printStackTrace() + } + return resultBytes + } + + fun encryptDataByPublicKey(srcData: ByteArray, publicKey: PublicKey): String { + val resultBytes = processData(srcData, publicKey) + return Base64.encodeToString(resultBytes, Base64.DEFAULT) + } + + fun keyStrToPublicKey(publicKeyStr: String?): PublicKey? { + var publicKey: PublicKey? = null + val keyBytes = Base64.decode(publicKeyStr, Base64.DEFAULT) + val keySpec = X509EncodedKeySpec(keyBytes) + try { + val keyFactory = KeyFactory.getInstance("RSA") + publicKey = keyFactory.generatePublic(keySpec) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: InvalidKeySpecException) { + e.printStackTrace() + } + return publicKey + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt index 63db0ec..50d1742 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt @@ -1,12 +1,64 @@ package com.casic.smart.town.sanxi.util.retrofit -import retrofit2.http.GET +import retrofit2.http.* interface RetrofitService { + /** + * PublicKey校验 + */ + @GET("/config/baseConfig") + suspend fun obtainPublicKey(): String + + /** + * 登录并获取Token + * + * @param sid + * @param account 用户名 + * @param secretKey 加密后的密码 + */ + @FormUrlEncoded + @POST("/user/login") + suspend fun login( + @Field("sid") sid: String, + @Field("username") account: String, + @Field("password") secretKey: String + ): String + + /** + * 退出登录 + */ + @GET("/user/logout") + suspend fun loginOut(@Header("token") token: String): String + + /** + * 获取用户信息 + */ + @GET("/user/info") + suspend fun obtainUserDetail(@Header("token") token: String): String + + /** + * 修改密码 + * + * @param oldPwd 旧密码 + * @param newPwd 新密码 + */ + @FormUrlEncoded + @POST("/mgr/changePwd") + suspend fun changePassword( + @Header("token") token: String, + @Field("oldPwd") oldPwd: String, + @Field("newPwd") newPwd: String + ): String /** * 根据布防状态统计闸井数量接口 */ @GET("/well/countByBfzt") - suspend fun countWellByState(): String + suspend fun countWellByState(@Header("token") token: String): String + + /** + * 更新APK版本 + */ + @POST("/app/checkVersion") + suspend fun obtainVersionResult(@Header("token") token: String): String } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt index 46bbf4b..410444b 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt @@ -1,5 +1,6 @@ package com.casic.smart.town.sanxi.util.retrofit +import com.casic.smart.town.sanxi.util.AuthenticationHelper import com.casic.smart.town.sanxi.util.LocaleConstant import com.pengxh.kt.lite.utils.RetrofitFactory import com.pengxh.kt.lite.utils.SaveKeyValues @@ -15,9 +16,52 @@ } /** + * 验证PublicKey + */ + suspend fun authenticate(): String { + return api.obtainPublicKey() + } + + /** + * 登录并获取Token + */ + suspend fun login(sid: String, account: String, secretKey: String): String { + return api.login(sid, account, secretKey) + } + + /** + * 退出登录 + */ + suspend fun loginOut(): String { + return api.loginOut(AuthenticationHelper.token!!) + } + + /** + * 获取用户信息 + */ + suspend fun obtainUserDetail(): String { + return api.obtainUserDetail(AuthenticationHelper.token!!) + } + + /** + * 修改密码 + */ + suspend fun changePassword(oldPwd: String, newPwd: String): String { + return api.changePassword(AuthenticationHelper.token!!, oldPwd, newPwd) + } + + /** * 根据布防状态统计闸井数量接口 */ suspend fun countWellByState(): String { - return api.countWellByState() + return api.countWellByState(AuthenticationHelper.token!!) } + + /** + * 更新APK版本 + */ + suspend fun updateVersion(): String { + return api.obtainVersionResult(AuthenticationHelper.token!!) + } + } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/AboutUsActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/AboutUsActivity.kt new file mode 100644 index 0000000..7e43c47 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/AboutUsActivity.kt @@ -0,0 +1,34 @@ +package com.casic.smart.town.sanxi.view + +import com.casic.smart.town.sanxi.BuildConfig +import com.casic.smart.town.sanxi.R +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.kt.lite.base.KotlinBaseActivity +import com.pengxh.kt.lite.extensions.convertColor +import com.pengxh.kt.lite.utils.ImmerseStatusBarUtil +import kotlinx.android.synthetic.main.activity_about_us.* +import kotlinx.android.synthetic.main.include_base_title.* + +class AboutUsActivity : KotlinBaseActivity() { + + override fun initLayoutView(): Int = R.layout.activity_about_us + + override fun setupTopBarLayout() { + ImmersionBar.with(this).statusBarDarkFont(false).init() + ImmerseStatusBarUtil.setColor(this, R.color.mainThemeColor.convertColor(this)) + titleView.text = "关于我们" + leftBackView.setOnClickListener { finish() } + } + + override fun initData() { + versionCodeView.text = String.format("版本信息:V${BuildConfig.VERSION_NAME}") + } + + override fun observeRequestState() { + + } + + override fun initEvent() { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/LoginActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/LoginActivity.kt new file mode 100644 index 0000000..b5d1d50 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/LoginActivity.kt @@ -0,0 +1,97 @@ +package com.casic.smart.town.sanxi.view + +import androidx.lifecycle.ViewModelProvider +import com.casic.smart.town.sanxi.R +import com.casic.smart.town.sanxi.util.AuthenticationHelper +import com.casic.smart.town.sanxi.util.LoadingDialogHub +import com.casic.smart.town.sanxi.util.LocaleConstant +import com.casic.smart.town.sanxi.util.RSAUtils +import com.casic.smart.town.sanxi.vm.AuthenticateViewModel +import com.casic.smart.town.sanxi.vm.LoginViewModel +import com.casic.smart.town.sanxi.vm.UserViewModel +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.kt.lite.base.KotlinBaseActivity +import com.pengxh.kt.lite.extensions.navigatePageTo +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.utils.SaveKeyValues +import kotlinx.android.synthetic.main.activity_login.* + +class LoginActivity : KotlinBaseActivity() { + + private lateinit var authenticateViewModel: AuthenticateViewModel + private lateinit var loginViewModel: LoginViewModel + private lateinit var userViewModel: UserViewModel + + override fun initLayoutView(): Int = R.layout.activity_login + + override fun setupTopBarLayout() { + ImmersionBar.with(this).statusBarDarkFont(true).init() + } + + override fun initData() { + // 设置默认账号密码 + userNameView.setText(SaveKeyValues.getValue(LocaleConstant.ACCOUNT, "") as String) + userPasswordView.setText(SaveKeyValues.getValue(LocaleConstant.PASSWORD, "") as String) + authenticateViewModel = ViewModelProvider(this)[AuthenticateViewModel::class.java] + loginViewModel = ViewModelProvider(this)[LoginViewModel::class.java] + userViewModel = ViewModelProvider(this)[UserViewModel::class.java] + + authenticateViewModel.keyModel.observe(this, { + if (it.code == 200) {//用code判断,别的判断可能有坑 + val keyString = it.data!!.publicKey!! + /** + * 修改密码需要用到key,先存着 + * */ + AuthenticationHelper.savePublicKey(keyString) + val publicKey = RSAUtils.keyStrToPublicKey(keyString) + + val account = userNameView.text.toString() + val userPassword = userPasswordView.text.toString() + val dataByPublicKey = + RSAUtils.encryptDataByPublicKey(userPassword.toByteArray(), publicKey!!) + //登录并获取Token,POST请求 + loginViewModel.enter(it.data!!.sid!!, account, dataByPublicKey) + loginViewModel.enterResultModel.observe(this, { loginResult -> + if (loginResult.code == 200) { + AuthenticationHelper.saveToken(loginResult.data!!.token!!) + /** + * 获取token之后保存用户信息 + * */ + userViewModel.obtainUserDetail() + //验证成功登录 + navigatePageTo() + finish() + } + }) + } + }) + } + + override fun observeRequestState() { + authenticateViewModel.loadState.observe(this, { + LoadingDialogHub.show(this, "登录中,请稍后") + }) + + loginViewModel.loadState.observe(this, { + LoadingDialogHub.dismiss() + }) + } + + override fun initEvent() { + loginButton.setOnClickListener { + val account = userNameView.text.toString().trim() + val userPassword = userPasswordView.text.toString().trim() + if (account.isBlank()) { + "用户名不能为空".show(this) + return@setOnClickListener + } + if (userPassword.isBlank()) { + "密码不能为空".show(this) + return@setOnClickListener + } + SaveKeyValues.putValue(LocaleConstant.ACCOUNT, account) + SaveKeyValues.putValue(LocaleConstant.PASSWORD, userPassword) + authenticateViewModel.obtainPublicKey() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt index 3bea1c9..a4fa951 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt @@ -7,6 +7,7 @@ import com.casic.smart.town.sanxi.R import com.casic.smart.town.sanxi.adapter.ViewPagerAdapter import com.casic.smart.town.sanxi.fragment.AlarmPageFragment +import com.casic.smart.town.sanxi.fragment.MinePageFragment import com.casic.smart.town.sanxi.fragment.MonitorPageFragment import com.casic.smart.town.sanxi.fragment.StatisticsPageFragment import com.gyf.immersionbar.ImmersionBar @@ -33,6 +34,7 @@ fragmentPages.add(StatisticsPageFragment()) fragmentPages.add(MonitorPageFragment()) fragmentPages.add(AlarmPageFragment()) + fragmentPages.add(MinePageFragment()) } override fun initLayoutView(): Int = R.layout.activity_main @@ -68,6 +70,9 @@ R.id.nav_alarm -> { mainViewPager.currentItem = 2 } + R.id.nav_more -> { + mainViewPager.currentItem = 3 + } } false } diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/PermissionActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/PermissionActivity.kt index c478d83..3238f3c 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/view/PermissionActivity.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/PermissionActivity.kt @@ -32,7 +32,7 @@ //先把导航隐私政策声明,后面导航会用到 MapsInitializer.updatePrivacyAgree(this, true) MapsInitializer.updatePrivacyShow(this, true, true) - this.navigatePageTo() + this.navigatePageTo() finish() } diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/SplashScreenActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/SplashScreenActivity.kt new file mode 100644 index 0000000..fa5a74d --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/SplashScreenActivity.kt @@ -0,0 +1,54 @@ +package com.casic.smart.town.sanxi.view + +import android.annotation.SuppressLint +import android.os.CountDownTimer +import androidx.lifecycle.ViewModelProvider +import com.casic.smart.town.sanxi.R +import com.casic.smart.town.sanxi.vm.UserViewModel +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.kt.lite.base.KotlinBaseActivity +import com.pengxh.kt.lite.extensions.navigatePageTo + +@SuppressLint("CustomSplashScreen") +class SplashScreenActivity : KotlinBaseActivity() { + + private lateinit var userDetailViewModel: UserViewModel + private val countDownTimer = object : CountDownTimer(1000, 500) { + override fun onFinish() { + /** + * 获取token之后保存用户信息 + * */ + userDetailViewModel.obtainUserDetail() + userDetailViewModel.userDetailModel.observe(this@SplashScreenActivity, { + if (it.code == 200) { + navigatePageTo() + } else { + navigatePageTo() + } + finish() + }) + } + + override fun onTick(millisUntilFinished: Long) { + + } + } + + override fun initLayoutView(): Int = R.layout.activity_splash + + override fun setupTopBarLayout() { + ImmersionBar.with(this).statusBarDarkFont(false).init() + } + + override fun initData() { + userDetailViewModel = ViewModelProvider(this).get(UserViewModel::class.java) + } + + override fun observeRequestState() { + + } + + override fun initEvent() { + countDownTimer.start() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/vm/AuthenticateViewModel.kt b/app/src/main/java/com/casic/smart/town/sanxi/vm/AuthenticateViewModel.kt new file mode 100644 index 0000000..87ddbf8 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/vm/AuthenticateViewModel.kt @@ -0,0 +1,35 @@ +package com.casic.smart.town.sanxi.vm + +import androidx.lifecycle.MutableLiveData +import com.casic.smart.town.sanxi.base.BaseApplication +import com.casic.smart.town.sanxi.extensions.separateResponseCode +import com.casic.smart.town.sanxi.extensions.toErrorMessage +import com.casic.smart.town.sanxi.model.PublicKeyModel +import com.casic.smart.town.sanxi.util.retrofit.RetrofitServiceManager +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.launch +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.vm.BaseViewModel +import com.pengxh.kt.lite.vm.LoadState + +class AuthenticateViewModel : BaseViewModel() { + + private val gson = Gson() + val keyModel = MutableLiveData() + + fun obtainPublicKey() = launch({ + loadState.value = LoadState.Loading + val response = RetrofitServiceManager.authenticate() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + keyModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ) + } else { + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + it.printStackTrace() + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/vm/LoginViewModel.kt b/app/src/main/java/com/casic/smart/town/sanxi/vm/LoginViewModel.kt new file mode 100644 index 0000000..c377c61 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/vm/LoginViewModel.kt @@ -0,0 +1,54 @@ +package com.casic.smart.town.sanxi.vm + +import androidx.lifecycle.MutableLiveData +import com.casic.smart.town.sanxi.base.BaseApplication +import com.casic.smart.town.sanxi.extensions.separateResponseCode +import com.casic.smart.town.sanxi.extensions.toErrorMessage +import com.casic.smart.town.sanxi.model.CommonResultModel +import com.casic.smart.town.sanxi.model.LoginResultModel +import com.casic.smart.town.sanxi.util.retrofit.RetrofitServiceManager +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.launch +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.vm.BaseViewModel +import com.pengxh.kt.lite.vm.LoadState + +class LoginViewModel : BaseViewModel() { + + private val gson = Gson() + val enterResultModel = MutableLiveData() + val outResultModel = MutableLiveData() + + fun enter(sid: String, account: String, secretKey: String) = launch({ + val response = RetrofitServiceManager.login(sid, account, secretKey) + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + loadState.value = LoadState.Success + "登录成功".show(BaseApplication.obtainInstance()) + enterResultModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ) + } else { + loadState.value = LoadState.Fail + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + loadState.value = LoadState.Fail + it.printStackTrace() + }) + + fun out() = launch({ + val response = RetrofitServiceManager.loginOut() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + outResultModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ) + } else { + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + it.printStackTrace() + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/vm/UserViewModel.kt b/app/src/main/java/com/casic/smart/town/sanxi/vm/UserViewModel.kt new file mode 100644 index 0000000..29a8756 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/vm/UserViewModel.kt @@ -0,0 +1,57 @@ +package com.casic.smart.town.sanxi.vm + +import androidx.lifecycle.MutableLiveData +import com.casic.smart.town.sanxi.base.BaseApplication +import com.casic.smart.town.sanxi.extensions.separateResponseCode +import com.casic.smart.town.sanxi.extensions.toErrorMessage +import com.casic.smart.town.sanxi.model.UserDetailModel +import com.casic.smart.town.sanxi.util.LocaleConstant +import com.casic.smart.town.sanxi.util.retrofit.RetrofitServiceManager +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.launch +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.utils.SaveKeyValues +import com.pengxh.kt.lite.vm.BaseViewModel +import com.pengxh.kt.lite.vm.LoadState + +class UserViewModel : BaseViewModel() { + + private val gson = Gson() + val userDetailModel = MutableLiveData() + + fun obtainUserDetail() = launch({ + val response = RetrofitServiceManager.obtainUserDetail() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + val userDetail = gson.fromJson( + response, object : TypeToken() {}.type + ) + userDetailModel.value = userDetail + SaveKeyValues.putValue(LocaleConstant.USER_DETAIL_MODEL, gson.toJson(userDetail.data)) + } else { + val errorModel = UserDetailModel() + errorModel.code = responseCode + userDetailModel.value = errorModel + //如果此次获取不到用户信息,那么就清空之前的用户缓存,然后让用户重新登录 + SaveKeyValues.removeKey(LocaleConstant.USER_DETAIL_MODEL) + } + }, { + it.printStackTrace() + }) + + fun changePassword(oldPwd: String, newPwd: String) = launch({ + loadState.value = LoadState.Loading + val response = RetrofitServiceManager.changePassword(oldPwd, newPwd) + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + loadState.value = LoadState.Success + } else { + loadState.value = LoadState.Fail + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + loadState.value = LoadState.Fail + it.printStackTrace() + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/vm/VersionViewModel.kt b/app/src/main/java/com/casic/smart/town/sanxi/vm/VersionViewModel.kt new file mode 100644 index 0000000..20ea457 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/vm/VersionViewModel.kt @@ -0,0 +1,38 @@ +package com.casic.smart.town.sanxi.vm + +import androidx.lifecycle.MutableLiveData +import com.casic.smart.town.sanxi.base.BaseApplication +import com.casic.smart.town.sanxi.extensions.separateResponseCode +import com.casic.smart.town.sanxi.extensions.toErrorMessage +import com.casic.smart.town.sanxi.model.VersionResultModel +import com.casic.smart.town.sanxi.util.retrofit.RetrofitServiceManager +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.launch +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.vm.BaseViewModel +import com.pengxh.kt.lite.vm.LoadState + +class VersionViewModel : BaseViewModel() { + + private val gson = Gson() + val versionResultModel = MutableLiveData() + + fun updateVersion() = launch({ + loadState.value = LoadState.Loading + val response = RetrofitServiceManager.updateVersion() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + loadState.value = LoadState.Success + versionResultModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ).data + } else { + loadState.value = LoadState.Fail + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + loadState.value = LoadState.Fail + it.printStackTrace() + }) +} \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_solid_layout_gray_radius_5.xml b/app/src/main/res/drawable/bg_solid_layout_gray_radius_5.xml new file mode 100644 index 0000000..96eb93e --- /dev/null +++ b/app/src/main/res/drawable/bg_solid_layout_gray_radius_5.xml @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/button_main_selector.xml b/app/src/main/res/drawable/button_main_selector.xml new file mode 100644 index 0000000..5875998 --- /dev/null +++ b/app/src/main/res/drawable/button_main_selector.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/button_white_selector.xml b/app/src/main/res/drawable/button_white_selector.xml new file mode 100644 index 0000000..4dd3c0b --- /dev/null +++ b/app/src/main/res/drawable/button_white_selector.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/download_progress.xml b/app/src/main/res/drawable/download_progress.xml new file mode 100644 index 0000000..493d923 --- /dev/null +++ b/app/src/main/res/drawable/download_progress.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_about.xml b/app/src/main/res/drawable/ic_about.xml new file mode 100644 index 0000000..8469319 --- /dev/null +++ b/app/src/main/res/drawable/ic_about.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_change_pwd.xml b/app/src/main/res/drawable/ic_change_pwd.xml new file mode 100644 index 0000000..75caa39 --- /dev/null +++ b/app/src/main/res/drawable/ic_change_pwd.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/ic_clean.xml b/app/src/main/res/drawable/ic_clean.xml new file mode 100644 index 0000000..027f809 --- /dev/null +++ b/app/src/main/res/drawable/ic_clean.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_left.xml b/app/src/main/res/drawable/ic_left.xml new file mode 100644 index 0000000..04ee5da --- /dev/null +++ b/app/src/main/res/drawable/ic_left.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_lock.xml b/app/src/main/res/drawable/ic_lock.xml new file mode 100644 index 0000000..f0a5610 --- /dev/null +++ b/app/src/main/res/drawable/ic_lock.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/ic_more.xml b/app/src/main/res/drawable/ic_more.xml new file mode 100644 index 0000000..0794eaa --- /dev/null +++ b/app/src/main/res/drawable/ic_more.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_more_selected.xml b/app/src/main/res/drawable/ic_more_selected.xml new file mode 100644 index 0000000..9b6d3df --- /dev/null +++ b/app/src/main/res/drawable/ic_more_selected.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_phone.xml b/app/src/main/res/drawable/ic_phone.xml new file mode 100644 index 0000000..253f843 --- /dev/null +++ b/app/src/main/res/drawable/ic_phone.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_right.xml b/app/src/main/res/drawable/ic_right.xml new file mode 100644 index 0000000..6020f09 --- /dev/null +++ b/app/src/main/res/drawable/ic_right.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_update_version.xml b/app/src/main/res/drawable/ic_update_version.xml new file mode 100644 index 0000000..3a62d63 --- /dev/null +++ b/app/src/main/res/drawable/ic_update_version.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1df917d..8d4f33b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -6,6 +6,7 @@ + @@ -32,11 +33,14 @@ + + + + + + + diff --git a/app/src/main/assets/svg/mine.svg b/app/src/main/assets/svg/mine.svg new file mode 100644 index 0000000..3fbb0a3 --- /dev/null +++ b/app/src/main/assets/svg/mine.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt b/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt index d730417..b1972c2 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt @@ -1,8 +1,10 @@ package com.casic.smart.town.sanxi.extensions import com.casic.smart.town.sanxi.model.ErrorMessageModel +import com.casic.smart.town.sanxi.util.LocaleConstant import com.google.gson.Gson import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.utils.SaveKeyValues import org.json.JSONObject fun String.separateResponseCode(): Int { @@ -17,4 +19,15 @@ this, object : TypeToken() {}.type ) return errorModel.message.toString() +} + +/** + * 下载路径为 http://xx.com/static/ 拼接downloadUrl + * */ +fun String.appendDownloadUrl(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + LocaleConstant.DEFAULT_SERVER_CONFIG, LocaleConstant.DEFAULT_SERVER + ) as String + return "$defaultValue/static/${this}" } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt b/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt new file mode 100644 index 0000000..1809dc5 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt @@ -0,0 +1,288 @@ +package com.casic.smart.town.sanxi.fragment + +import android.app.ProgressDialog +import android.content.Intent +import android.net.Uri +import android.os.Build +import android.os.CountDownTimer +import androidx.core.content.FileProvider +import androidx.lifecycle.ViewModelProvider +import com.casic.smart.town.sanxi.BuildConfig +import com.casic.smart.town.sanxi.R +import com.casic.smart.town.sanxi.extensions.appendDownloadUrl +import com.casic.smart.town.sanxi.model.UserDetailModel +import com.casic.smart.town.sanxi.util.AuthenticationHelper +import com.casic.smart.town.sanxi.util.LoadingDialogHub +import com.casic.smart.town.sanxi.util.LocaleConstant +import com.casic.smart.town.sanxi.util.RSAUtils +import com.casic.smart.town.sanxi.view.AboutUsActivity +import com.casic.smart.town.sanxi.view.LoginActivity +import com.casic.smart.town.sanxi.vm.LoginViewModel +import com.casic.smart.town.sanxi.vm.UserViewModel +import com.casic.smart.town.sanxi.vm.VersionViewModel +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.base.KotlinBaseFragment +import com.pengxh.kt.lite.callback.OnDownloadListener +import com.pengxh.kt.lite.extensions.* +import com.pengxh.kt.lite.utils.PageNavigationManager +import com.pengxh.kt.lite.utils.SaveKeyValues +import com.pengxh.kt.lite.vm.LoadState +import com.pengxh.kt.lite.widget.dialog.AlertControlDialog +import com.pengxh.kt.lite.widget.dialog.ChangePasswordDialog +import kotlinx.android.synthetic.main.fragment_mine.* +import java.io.File +import java.nio.charset.StandardCharsets + +class MinePageFragment : KotlinBaseFragment() { + + private lateinit var userData: UserDetailModel.Data + private lateinit var userViewModel: UserViewModel + private lateinit var loginViewModel: LoginViewModel + private lateinit var versionViewModel: VersionViewModel + private lateinit var progressDialog: ProgressDialog + + override fun initLayoutView(): Int = R.layout.fragment_mine + + override fun setupTopBarLayout() { + + } + + override fun initData() { + userViewModel = ViewModelProvider(this)[UserViewModel::class.java] + loginViewModel = ViewModelProvider(this)[LoginViewModel::class.java] + versionViewModel = ViewModelProvider(this)[VersionViewModel::class.java] + + //初始化下载对话框 + progressDialog = ProgressDialog(requireContext()) + progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL) + progressDialog.setProgressDrawable(resources.getDrawable(R.drawable.download_progress)) + progressDialog.setCanceledOnTouchOutside(false) + progressDialog.setCancelable(false) + + /** + * 数据监听 + * */ + dataObserve() + } + + override fun observeRequestState() { + userViewModel.loadState.observe(this, { + when (it) { + is LoadState.Loading -> LoadingDialogHub.show(requireActivity(), "修改中,请稍后") + is LoadState.Success -> { + "修改成功,请重新登录".show(requireContext()) + LoadingDialogHub.dismiss() + AuthenticationHelper.removeToken() + requireContext().navigatePageTo() + PageNavigationManager.finishAllActivity() + } + else -> LoadingDialogHub.dismiss() + } + }) + + versionViewModel.loadState.observe(this, { + when (it) { + is LoadState.Loading -> LoadingDialogHub.show(requireActivity(), "检查版本中,请稍后") + else -> LoadingDialogHub.dismiss() + } + }) + } + + override fun initEvent() { + userImageView.setOnClickListener { + "尽情期待~".show(requireContext()) + } + + updateUserButton.setOnClickListener { + LoadingDialogHub.show(requireActivity(), "同步中,请稍后...") + userViewModel.obtainUserDetail() + } + + changePwdLayout.setOnClickListener { + ChangePasswordDialog.Builder() + .setContext(requireContext()) + .setOnDialogButtonClickListener(object : + ChangePasswordDialog.OnDialogButtonClickListener { + override fun onConfirmClick(oldPwd: String, newPwd: String) { + val publicKey = + RSAUtils.keyStrToPublicKey(AuthenticationHelper.publicKey)!! + val oldPassKey = RSAUtils.encryptDataByPublicKey( + oldPwd.toByteArray(StandardCharsets.UTF_8), + publicKey + ) + val newPassKey = RSAUtils.encryptDataByPublicKey( + newPwd.toByteArray(StandardCharsets.UTF_8), + publicKey + ) + userViewModel.changePassword(oldPassKey, newPassKey) + } + }).build().show() + } + + aboutUsLayout.setOnClickListener { + requireContext().navigatePageTo() + } + + updateVersionLayout.setOnClickListener { + versionViewModel.updateVersion() + } + + clearCacheLayout.setOnClickListener { + //删除缓存之后在设置缓存大小 + LoadingDialogHub.show(requireActivity(), "清理中,请稍后") + File(requireContext().cacheDir.path).deleteFile() + object : CountDownTimer(1500, 500) { + override fun onTick(millisUntilFinished: Long) { + + } + + override fun onFinish() { + LoadingDialogHub.dismiss() + cacheSizeView.text = collectApplicationCache().formatFileSize() + } + }.start() + } + + loginOutButton.setOnClickListener { + AlertControlDialog.Builder() + .setContext(requireContext()) + .setTitle("退出登录") + .setMessage("确定要退出吗?") + .setNegativeButton("取消") + .setPositiveButton("确定") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + loginViewModel.out() + } + + override fun onCancelClick() {} + }).build().show() + } + } + + private fun dataObserve() { + userViewModel.userDetailModel.observe(this, { + if (it.code == 200) { + LoadingDialogHub.dismiss() + "同步完成".show(requireContext()) + userData = it.data + updateUserInfo() + } + }) + + versionViewModel.versionResultModel.observe(this, { + if (BuildConfig.VERSION_NAME == it.version) { + "已是最新版本,无需更新".show(requireContext()) + } else { + AlertControlDialog.Builder() + .setContext(requireContext()) + .setTitle("提示") + .setMessage("有新版本,是否更新?") + .setNegativeButton("稍后再说") + .setPositiveButton("立即下载") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + downloadApk(it.downloadUrl) + } + + override fun onCancelClick() { + + } + }).build().show() + } + }) + + loginViewModel.outResultModel.observe(this, { + if (it.code == 200) { + AuthenticationHelper.removeToken() + requireContext().navigatePageTo() + PageNavigationManager.finishAllActivity() + } + }) + } + + private fun collectApplicationCache(): Long { + return File(requireContext().cacheDir.path).calculateSize() + } + + override fun onResume() { + val userDetailJson = SaveKeyValues.getValue(LocaleConstant.USER_DETAIL_MODEL, "") as String + if (userDetailJson.isNotBlank()) { + userData = Gson().fromJson( + userDetailJson, object : TypeToken() {}.type + ) + updateUserInfo() + } + //缓存 + cacheSizeView.text = collectApplicationCache().formatFileSize() + super.onResume() + } + + private fun updateUserInfo() { + //设置头像,圆形,暂时是默认的 +// val roundDrawable = +// BitmapFactory.decodeResource(requireContext().resources, R.mipmap.login_casic) +// .createRoundDrawable( +// requireContext(), +// 3f.dp2px(requireContext()), +// R.color.mainThemeColor.convertColor(requireContext()) +// ) +// userImageView.setImageDrawable(roundDrawable) + userNameView.text = userData.name + userPhoneView.text = String.format("电话:${userData.phone}") + userDeptView.text = String.format("部门:${userData.deptName}") + } + + private fun downloadApk(url: String?) { + progressDialog.setMessage("下载新版本中...") + progressDialog.show() + if (url.toString().isBlank()) { + "抱歉,版本下载失败".show(requireContext()) + return + } + /** + * http://111.198.10.15:11304/static/apk/1.0.1.apk + * */ + val downloadPath = url!!.appendDownloadUrl() + //开始下载 + downloadPath.downloadFile(requireContext().createDownloadFileDir().toString(), + object : OnDownloadListener { + override fun onDownloadStart(totalBytes: Long) { + progressDialog.max = totalBytes.toInt() + } + + override fun onProgressChanged(currentBytes: Long) { + progressDialog.progress = currentBytes.toInt() + } + + override fun onDownloadEnd(file: File?) { + progressDialog.dismiss() + progressDialog.progress = 0 + //安装APK + installApk(file) + } + }) + } + + private fun installApk(apkPackage: File?) { + if (apkPackage == null) { + "文件异常,无法安装".show(requireContext()) + return + } + val intent = Intent(Intent.ACTION_VIEW) + val data: Uri + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { //判断版本大于等于7.0 + data = FileProvider.getUriForFile( + requireContext(), LocaleConstant.APP_AUTHORITY, apkPackage + ) + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) // 给目标应用一个临时授权 + } else { + data = Uri.fromFile(apkPackage) + } + intent.setDataAndType(data, "application/vnd.android.package-archive") + requireContext().startActivity(intent) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java new file mode 100644 index 0000000..467e33d --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java @@ -0,0 +1,43 @@ +package com.casic.smart.town.sanxi.model; + +/** + * 普通实体类,失败/成功数据结构一致 + */ +public class CommonResultModel { + private int code; + private String data; + private String message; + private boolean isSuccess; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return isSuccess; + } + + public void setSuccess(boolean success) { + isSuccess = success; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java new file mode 100644 index 0000000..9bdabef --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java @@ -0,0 +1,62 @@ +package com.casic.smart.town.sanxi.model; + +public class LoginResultModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private String kaptcha; + private String token; + + public String getKaptcha() { + return kaptcha; + } + + public void setKaptcha(String kaptcha) { + this.kaptcha = kaptcha; + } + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java new file mode 100644 index 0000000..09f8d59 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java @@ -0,0 +1,80 @@ +package com.casic.smart.town.sanxi.model; + +public class PublicKeyModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private boolean appKaptcha; + private boolean kaptcha; + private String publicKey; + private String sid; + + public boolean isAppKaptcha() { + return appKaptcha; + } + + public void setAppKaptcha(boolean appKaptcha) { + this.appKaptcha = appKaptcha; + } + + public boolean isKaptcha() { + return kaptcha; + } + + public void setKaptcha(boolean kaptcha) { + this.kaptcha = kaptcha; + } + + public String getPublicKey() { + return publicKey; + } + + public void setPublicKey(String publicKey) { + this.publicKey = publicKey; + } + + public String getSid() { + return sid; + } + + public void setSid(String sid) { + this.sid = sid; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java new file mode 100644 index 0000000..db577fb --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java @@ -0,0 +1,227 @@ +package com.casic.smart.town.sanxi.model; + + +import java.util.List; + +public class UserDetailModel { + private int code; + private Data data; + private String message; + private boolean success; + + public void setCode(int code) { + this.code = code; + } + + public int getCode() { + return code; + } + + public void setData(Data data) { + this.data = data; + } + + public Data getData() { + return data; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getMessage() { + return message; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public boolean getSuccess() { + return success; + } + + public static class Data { + + private String account; + private String attr1; + private String avatar; + private String bizData; + private List dataScope; + private String deptId; + private String deptName; + private List devices; + private String id; + private String ipAddr; + private String name; + private String phone; + private List roleList; + private List roleNames; + private List roleTips; + private String scopeType; + private String sysData; + private String targetId; + private String targetName; + private String tenantId; + + public void setAccount(String account) { + this.account = account; + } + + public String getAccount() { + return account; + } + + public void setAttr1(String attr1) { + this.attr1 = attr1; + } + + public String getAttr1() { + return attr1; + } + + public void setAvatar(String avatar) { + this.avatar = avatar; + } + + public String getAvatar() { + return avatar; + } + + public void setBizData(String bizData) { + this.bizData = bizData; + } + + public String getBizData() { + return bizData; + } + + public void setDataScope(List dataScope) { + this.dataScope = dataScope; + } + + public List getDataScope() { + return dataScope; + } + + public void setDeptId(String deptId) { + this.deptId = deptId; + } + + public String getDeptId() { + return deptId; + } + + public void setDeptName(String deptName) { + this.deptName = deptName; + } + + public String getDeptName() { + return deptName; + } + + public void setDevices(List devices) { + this.devices = devices; + } + + public List getDevices() { + return devices; + } + + public void setId(String id) { + this.id = id; + } + + public String getId() { + return id; + } + + public void setIpAddr(String ipAddr) { + this.ipAddr = ipAddr; + } + + public String getIpAddr() { + return ipAddr; + } + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setPhone(String phone) { + this.phone = phone; + } + + public String getPhone() { + return phone; + } + + public void setRoleList(List roleList) { + this.roleList = roleList; + } + + public List getRoleList() { + return roleList; + } + + public void setRoleNames(List roleNames) { + this.roleNames = roleNames; + } + + public List getRoleNames() { + return roleNames; + } + + public void setRoleTips(List roleTips) { + this.roleTips = roleTips; + } + + public List getRoleTips() { + return roleTips; + } + + public void setScopeType(String scopeType) { + this.scopeType = scopeType; + } + + public String getScopeType() { + return scopeType; + } + + public void setSysData(String sysData) { + this.sysData = sysData; + } + + public String getSysData() { + return sysData; + } + + public void setTargetId(String targetId) { + this.targetId = targetId; + } + + public String getTargetId() { + return targetId; + } + + public void setTargetName(String targetName) { + this.targetName = targetName; + } + + public String getTargetName() { + return targetName; + } + + public void setTenantId(String tenantId) { + this.tenantId = tenantId; + } + + public String getTenantId() { + return tenantId; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/VersionResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/VersionResultModel.java new file mode 100644 index 0000000..79374e0 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/VersionResultModel.java @@ -0,0 +1,61 @@ +package com.casic.smart.town.sanxi.model; + +public class VersionResultModel { + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private String downloadUrl; + private String version; + + public String getDownloadUrl() { + return downloadUrl; + } + + public void setDownloadUrl(String downloadUrl) { + this.downloadUrl = downloadUrl; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/AuthenticationHelper.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/AuthenticationHelper.kt new file mode 100644 index 0000000..f498e4e --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/AuthenticationHelper.kt @@ -0,0 +1,24 @@ +package com.casic.smart.town.sanxi.util + +import com.pengxh.kt.lite.utils.SaveKeyValues + +object AuthenticationHelper { + + fun savePublicKey(key: String) { + SaveKeyValues.putValue("keyString", key) + } + + val publicKey: String? + get() = SaveKeyValues.getValue("keyString", "") as String? + + fun saveToken(token: String?) { + SaveKeyValues.putValue("token", token!!) + } + + val token: String? + get() = SaveKeyValues.getValue("token", "") as String? + + fun removeToken() { + SaveKeyValues.removeKey("token") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt index ab1a36d..99f0d19 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt @@ -14,5 +14,9 @@ const val PAGE_LIMIT = 20 const val DEFAULT_SERVER_CONFIG = "defaultServerConfig" - const val DEFAULT_SERVER = "http://111.198.10.15:11308" + const val DEFAULT_SERVER = "http://36.133.189.112:8082" + const val APP_AUTHORITY = "com.casic.smart.town.sanxi.fileprovider" + const val ACCOUNT = "account" + const val PASSWORD = "password" + const val USER_DETAIL_MODEL = "userDetailModel" } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/RSAUtils.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/RSAUtils.kt new file mode 100644 index 0000000..b9216ba --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/RSAUtils.kt @@ -0,0 +1,55 @@ +package com.casic.smart.town.sanxi.util + +import android.util.Base64 +import java.security.* +import java.security.spec.InvalidKeySpecException +import java.security.spec.X509EncodedKeySpec +import javax.crypto.BadPaddingException +import javax.crypto.Cipher +import javax.crypto.IllegalBlockSizeException +import javax.crypto.NoSuchPaddingException + +object RSAUtils { + //构建Cipher实例时所传入的的字符串,默认为"RSA/NONE/PKCS1Padding" + private fun processData(srcData: ByteArray, key: Key): ByteArray? { //用来保存处理结果 + var resultBytes: ByteArray? = null + try { //获取Cipher实例 + val cipher = Cipher.getInstance("RSA/None/PKCS1Padding") + //初始化Cipher,mode指定是加密还是解密,key为公钥或私钥 + cipher.init(Cipher.ENCRYPT_MODE, key) + //处理数据 + resultBytes = cipher.doFinal(srcData) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: NoSuchPaddingException) { + e.printStackTrace() + } catch (e: InvalidKeyException) { + e.printStackTrace() + } catch (e: BadPaddingException) { + e.printStackTrace() + } catch (e: IllegalBlockSizeException) { + e.printStackTrace() + } + return resultBytes + } + + fun encryptDataByPublicKey(srcData: ByteArray, publicKey: PublicKey): String { + val resultBytes = processData(srcData, publicKey) + return Base64.encodeToString(resultBytes, Base64.DEFAULT) + } + + fun keyStrToPublicKey(publicKeyStr: String?): PublicKey? { + var publicKey: PublicKey? = null + val keyBytes = Base64.decode(publicKeyStr, Base64.DEFAULT) + val keySpec = X509EncodedKeySpec(keyBytes) + try { + val keyFactory = KeyFactory.getInstance("RSA") + publicKey = keyFactory.generatePublic(keySpec) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: InvalidKeySpecException) { + e.printStackTrace() + } + return publicKey + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt index 63db0ec..50d1742 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt @@ -1,12 +1,64 @@ package com.casic.smart.town.sanxi.util.retrofit -import retrofit2.http.GET +import retrofit2.http.* interface RetrofitService { + /** + * PublicKey校验 + */ + @GET("/config/baseConfig") + suspend fun obtainPublicKey(): String + + /** + * 登录并获取Token + * + * @param sid + * @param account 用户名 + * @param secretKey 加密后的密码 + */ + @FormUrlEncoded + @POST("/user/login") + suspend fun login( + @Field("sid") sid: String, + @Field("username") account: String, + @Field("password") secretKey: String + ): String + + /** + * 退出登录 + */ + @GET("/user/logout") + suspend fun loginOut(@Header("token") token: String): String + + /** + * 获取用户信息 + */ + @GET("/user/info") + suspend fun obtainUserDetail(@Header("token") token: String): String + + /** + * 修改密码 + * + * @param oldPwd 旧密码 + * @param newPwd 新密码 + */ + @FormUrlEncoded + @POST("/mgr/changePwd") + suspend fun changePassword( + @Header("token") token: String, + @Field("oldPwd") oldPwd: String, + @Field("newPwd") newPwd: String + ): String /** * 根据布防状态统计闸井数量接口 */ @GET("/well/countByBfzt") - suspend fun countWellByState(): String + suspend fun countWellByState(@Header("token") token: String): String + + /** + * 更新APK版本 + */ + @POST("/app/checkVersion") + suspend fun obtainVersionResult(@Header("token") token: String): String } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt index 46bbf4b..410444b 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt @@ -1,5 +1,6 @@ package com.casic.smart.town.sanxi.util.retrofit +import com.casic.smart.town.sanxi.util.AuthenticationHelper import com.casic.smart.town.sanxi.util.LocaleConstant import com.pengxh.kt.lite.utils.RetrofitFactory import com.pengxh.kt.lite.utils.SaveKeyValues @@ -15,9 +16,52 @@ } /** + * 验证PublicKey + */ + suspend fun authenticate(): String { + return api.obtainPublicKey() + } + + /** + * 登录并获取Token + */ + suspend fun login(sid: String, account: String, secretKey: String): String { + return api.login(sid, account, secretKey) + } + + /** + * 退出登录 + */ + suspend fun loginOut(): String { + return api.loginOut(AuthenticationHelper.token!!) + } + + /** + * 获取用户信息 + */ + suspend fun obtainUserDetail(): String { + return api.obtainUserDetail(AuthenticationHelper.token!!) + } + + /** + * 修改密码 + */ + suspend fun changePassword(oldPwd: String, newPwd: String): String { + return api.changePassword(AuthenticationHelper.token!!, oldPwd, newPwd) + } + + /** * 根据布防状态统计闸井数量接口 */ suspend fun countWellByState(): String { - return api.countWellByState() + return api.countWellByState(AuthenticationHelper.token!!) } + + /** + * 更新APK版本 + */ + suspend fun updateVersion(): String { + return api.obtainVersionResult(AuthenticationHelper.token!!) + } + } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/AboutUsActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/AboutUsActivity.kt new file mode 100644 index 0000000..7e43c47 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/AboutUsActivity.kt @@ -0,0 +1,34 @@ +package com.casic.smart.town.sanxi.view + +import com.casic.smart.town.sanxi.BuildConfig +import com.casic.smart.town.sanxi.R +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.kt.lite.base.KotlinBaseActivity +import com.pengxh.kt.lite.extensions.convertColor +import com.pengxh.kt.lite.utils.ImmerseStatusBarUtil +import kotlinx.android.synthetic.main.activity_about_us.* +import kotlinx.android.synthetic.main.include_base_title.* + +class AboutUsActivity : KotlinBaseActivity() { + + override fun initLayoutView(): Int = R.layout.activity_about_us + + override fun setupTopBarLayout() { + ImmersionBar.with(this).statusBarDarkFont(false).init() + ImmerseStatusBarUtil.setColor(this, R.color.mainThemeColor.convertColor(this)) + titleView.text = "关于我们" + leftBackView.setOnClickListener { finish() } + } + + override fun initData() { + versionCodeView.text = String.format("版本信息:V${BuildConfig.VERSION_NAME}") + } + + override fun observeRequestState() { + + } + + override fun initEvent() { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/LoginActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/LoginActivity.kt new file mode 100644 index 0000000..b5d1d50 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/LoginActivity.kt @@ -0,0 +1,97 @@ +package com.casic.smart.town.sanxi.view + +import androidx.lifecycle.ViewModelProvider +import com.casic.smart.town.sanxi.R +import com.casic.smart.town.sanxi.util.AuthenticationHelper +import com.casic.smart.town.sanxi.util.LoadingDialogHub +import com.casic.smart.town.sanxi.util.LocaleConstant +import com.casic.smart.town.sanxi.util.RSAUtils +import com.casic.smart.town.sanxi.vm.AuthenticateViewModel +import com.casic.smart.town.sanxi.vm.LoginViewModel +import com.casic.smart.town.sanxi.vm.UserViewModel +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.kt.lite.base.KotlinBaseActivity +import com.pengxh.kt.lite.extensions.navigatePageTo +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.utils.SaveKeyValues +import kotlinx.android.synthetic.main.activity_login.* + +class LoginActivity : KotlinBaseActivity() { + + private lateinit var authenticateViewModel: AuthenticateViewModel + private lateinit var loginViewModel: LoginViewModel + private lateinit var userViewModel: UserViewModel + + override fun initLayoutView(): Int = R.layout.activity_login + + override fun setupTopBarLayout() { + ImmersionBar.with(this).statusBarDarkFont(true).init() + } + + override fun initData() { + // 设置默认账号密码 + userNameView.setText(SaveKeyValues.getValue(LocaleConstant.ACCOUNT, "") as String) + userPasswordView.setText(SaveKeyValues.getValue(LocaleConstant.PASSWORD, "") as String) + authenticateViewModel = ViewModelProvider(this)[AuthenticateViewModel::class.java] + loginViewModel = ViewModelProvider(this)[LoginViewModel::class.java] + userViewModel = ViewModelProvider(this)[UserViewModel::class.java] + + authenticateViewModel.keyModel.observe(this, { + if (it.code == 200) {//用code判断,别的判断可能有坑 + val keyString = it.data!!.publicKey!! + /** + * 修改密码需要用到key,先存着 + * */ + AuthenticationHelper.savePublicKey(keyString) + val publicKey = RSAUtils.keyStrToPublicKey(keyString) + + val account = userNameView.text.toString() + val userPassword = userPasswordView.text.toString() + val dataByPublicKey = + RSAUtils.encryptDataByPublicKey(userPassword.toByteArray(), publicKey!!) + //登录并获取Token,POST请求 + loginViewModel.enter(it.data!!.sid!!, account, dataByPublicKey) + loginViewModel.enterResultModel.observe(this, { loginResult -> + if (loginResult.code == 200) { + AuthenticationHelper.saveToken(loginResult.data!!.token!!) + /** + * 获取token之后保存用户信息 + * */ + userViewModel.obtainUserDetail() + //验证成功登录 + navigatePageTo() + finish() + } + }) + } + }) + } + + override fun observeRequestState() { + authenticateViewModel.loadState.observe(this, { + LoadingDialogHub.show(this, "登录中,请稍后") + }) + + loginViewModel.loadState.observe(this, { + LoadingDialogHub.dismiss() + }) + } + + override fun initEvent() { + loginButton.setOnClickListener { + val account = userNameView.text.toString().trim() + val userPassword = userPasswordView.text.toString().trim() + if (account.isBlank()) { + "用户名不能为空".show(this) + return@setOnClickListener + } + if (userPassword.isBlank()) { + "密码不能为空".show(this) + return@setOnClickListener + } + SaveKeyValues.putValue(LocaleConstant.ACCOUNT, account) + SaveKeyValues.putValue(LocaleConstant.PASSWORD, userPassword) + authenticateViewModel.obtainPublicKey() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt index 3bea1c9..a4fa951 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt @@ -7,6 +7,7 @@ import com.casic.smart.town.sanxi.R import com.casic.smart.town.sanxi.adapter.ViewPagerAdapter import com.casic.smart.town.sanxi.fragment.AlarmPageFragment +import com.casic.smart.town.sanxi.fragment.MinePageFragment import com.casic.smart.town.sanxi.fragment.MonitorPageFragment import com.casic.smart.town.sanxi.fragment.StatisticsPageFragment import com.gyf.immersionbar.ImmersionBar @@ -33,6 +34,7 @@ fragmentPages.add(StatisticsPageFragment()) fragmentPages.add(MonitorPageFragment()) fragmentPages.add(AlarmPageFragment()) + fragmentPages.add(MinePageFragment()) } override fun initLayoutView(): Int = R.layout.activity_main @@ -68,6 +70,9 @@ R.id.nav_alarm -> { mainViewPager.currentItem = 2 } + R.id.nav_more -> { + mainViewPager.currentItem = 3 + } } false } diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/PermissionActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/PermissionActivity.kt index c478d83..3238f3c 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/view/PermissionActivity.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/PermissionActivity.kt @@ -32,7 +32,7 @@ //先把导航隐私政策声明,后面导航会用到 MapsInitializer.updatePrivacyAgree(this, true) MapsInitializer.updatePrivacyShow(this, true, true) - this.navigatePageTo() + this.navigatePageTo() finish() } diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/SplashScreenActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/SplashScreenActivity.kt new file mode 100644 index 0000000..fa5a74d --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/SplashScreenActivity.kt @@ -0,0 +1,54 @@ +package com.casic.smart.town.sanxi.view + +import android.annotation.SuppressLint +import android.os.CountDownTimer +import androidx.lifecycle.ViewModelProvider +import com.casic.smart.town.sanxi.R +import com.casic.smart.town.sanxi.vm.UserViewModel +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.kt.lite.base.KotlinBaseActivity +import com.pengxh.kt.lite.extensions.navigatePageTo + +@SuppressLint("CustomSplashScreen") +class SplashScreenActivity : KotlinBaseActivity() { + + private lateinit var userDetailViewModel: UserViewModel + private val countDownTimer = object : CountDownTimer(1000, 500) { + override fun onFinish() { + /** + * 获取token之后保存用户信息 + * */ + userDetailViewModel.obtainUserDetail() + userDetailViewModel.userDetailModel.observe(this@SplashScreenActivity, { + if (it.code == 200) { + navigatePageTo() + } else { + navigatePageTo() + } + finish() + }) + } + + override fun onTick(millisUntilFinished: Long) { + + } + } + + override fun initLayoutView(): Int = R.layout.activity_splash + + override fun setupTopBarLayout() { + ImmersionBar.with(this).statusBarDarkFont(false).init() + } + + override fun initData() { + userDetailViewModel = ViewModelProvider(this).get(UserViewModel::class.java) + } + + override fun observeRequestState() { + + } + + override fun initEvent() { + countDownTimer.start() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/vm/AuthenticateViewModel.kt b/app/src/main/java/com/casic/smart/town/sanxi/vm/AuthenticateViewModel.kt new file mode 100644 index 0000000..87ddbf8 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/vm/AuthenticateViewModel.kt @@ -0,0 +1,35 @@ +package com.casic.smart.town.sanxi.vm + +import androidx.lifecycle.MutableLiveData +import com.casic.smart.town.sanxi.base.BaseApplication +import com.casic.smart.town.sanxi.extensions.separateResponseCode +import com.casic.smart.town.sanxi.extensions.toErrorMessage +import com.casic.smart.town.sanxi.model.PublicKeyModel +import com.casic.smart.town.sanxi.util.retrofit.RetrofitServiceManager +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.launch +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.vm.BaseViewModel +import com.pengxh.kt.lite.vm.LoadState + +class AuthenticateViewModel : BaseViewModel() { + + private val gson = Gson() + val keyModel = MutableLiveData() + + fun obtainPublicKey() = launch({ + loadState.value = LoadState.Loading + val response = RetrofitServiceManager.authenticate() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + keyModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ) + } else { + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + it.printStackTrace() + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/vm/LoginViewModel.kt b/app/src/main/java/com/casic/smart/town/sanxi/vm/LoginViewModel.kt new file mode 100644 index 0000000..c377c61 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/vm/LoginViewModel.kt @@ -0,0 +1,54 @@ +package com.casic.smart.town.sanxi.vm + +import androidx.lifecycle.MutableLiveData +import com.casic.smart.town.sanxi.base.BaseApplication +import com.casic.smart.town.sanxi.extensions.separateResponseCode +import com.casic.smart.town.sanxi.extensions.toErrorMessage +import com.casic.smart.town.sanxi.model.CommonResultModel +import com.casic.smart.town.sanxi.model.LoginResultModel +import com.casic.smart.town.sanxi.util.retrofit.RetrofitServiceManager +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.launch +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.vm.BaseViewModel +import com.pengxh.kt.lite.vm.LoadState + +class LoginViewModel : BaseViewModel() { + + private val gson = Gson() + val enterResultModel = MutableLiveData() + val outResultModel = MutableLiveData() + + fun enter(sid: String, account: String, secretKey: String) = launch({ + val response = RetrofitServiceManager.login(sid, account, secretKey) + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + loadState.value = LoadState.Success + "登录成功".show(BaseApplication.obtainInstance()) + enterResultModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ) + } else { + loadState.value = LoadState.Fail + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + loadState.value = LoadState.Fail + it.printStackTrace() + }) + + fun out() = launch({ + val response = RetrofitServiceManager.loginOut() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + outResultModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ) + } else { + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + it.printStackTrace() + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/vm/UserViewModel.kt b/app/src/main/java/com/casic/smart/town/sanxi/vm/UserViewModel.kt new file mode 100644 index 0000000..29a8756 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/vm/UserViewModel.kt @@ -0,0 +1,57 @@ +package com.casic.smart.town.sanxi.vm + +import androidx.lifecycle.MutableLiveData +import com.casic.smart.town.sanxi.base.BaseApplication +import com.casic.smart.town.sanxi.extensions.separateResponseCode +import com.casic.smart.town.sanxi.extensions.toErrorMessage +import com.casic.smart.town.sanxi.model.UserDetailModel +import com.casic.smart.town.sanxi.util.LocaleConstant +import com.casic.smart.town.sanxi.util.retrofit.RetrofitServiceManager +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.launch +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.utils.SaveKeyValues +import com.pengxh.kt.lite.vm.BaseViewModel +import com.pengxh.kt.lite.vm.LoadState + +class UserViewModel : BaseViewModel() { + + private val gson = Gson() + val userDetailModel = MutableLiveData() + + fun obtainUserDetail() = launch({ + val response = RetrofitServiceManager.obtainUserDetail() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + val userDetail = gson.fromJson( + response, object : TypeToken() {}.type + ) + userDetailModel.value = userDetail + SaveKeyValues.putValue(LocaleConstant.USER_DETAIL_MODEL, gson.toJson(userDetail.data)) + } else { + val errorModel = UserDetailModel() + errorModel.code = responseCode + userDetailModel.value = errorModel + //如果此次获取不到用户信息,那么就清空之前的用户缓存,然后让用户重新登录 + SaveKeyValues.removeKey(LocaleConstant.USER_DETAIL_MODEL) + } + }, { + it.printStackTrace() + }) + + fun changePassword(oldPwd: String, newPwd: String) = launch({ + loadState.value = LoadState.Loading + val response = RetrofitServiceManager.changePassword(oldPwd, newPwd) + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + loadState.value = LoadState.Success + } else { + loadState.value = LoadState.Fail + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + loadState.value = LoadState.Fail + it.printStackTrace() + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/vm/VersionViewModel.kt b/app/src/main/java/com/casic/smart/town/sanxi/vm/VersionViewModel.kt new file mode 100644 index 0000000..20ea457 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/vm/VersionViewModel.kt @@ -0,0 +1,38 @@ +package com.casic.smart.town.sanxi.vm + +import androidx.lifecycle.MutableLiveData +import com.casic.smart.town.sanxi.base.BaseApplication +import com.casic.smart.town.sanxi.extensions.separateResponseCode +import com.casic.smart.town.sanxi.extensions.toErrorMessage +import com.casic.smart.town.sanxi.model.VersionResultModel +import com.casic.smart.town.sanxi.util.retrofit.RetrofitServiceManager +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.launch +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.vm.BaseViewModel +import com.pengxh.kt.lite.vm.LoadState + +class VersionViewModel : BaseViewModel() { + + private val gson = Gson() + val versionResultModel = MutableLiveData() + + fun updateVersion() = launch({ + loadState.value = LoadState.Loading + val response = RetrofitServiceManager.updateVersion() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + loadState.value = LoadState.Success + versionResultModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ).data + } else { + loadState.value = LoadState.Fail + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + loadState.value = LoadState.Fail + it.printStackTrace() + }) +} \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_solid_layout_gray_radius_5.xml b/app/src/main/res/drawable/bg_solid_layout_gray_radius_5.xml new file mode 100644 index 0000000..96eb93e --- /dev/null +++ b/app/src/main/res/drawable/bg_solid_layout_gray_radius_5.xml @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/button_main_selector.xml b/app/src/main/res/drawable/button_main_selector.xml new file mode 100644 index 0000000..5875998 --- /dev/null +++ b/app/src/main/res/drawable/button_main_selector.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/button_white_selector.xml b/app/src/main/res/drawable/button_white_selector.xml new file mode 100644 index 0000000..4dd3c0b --- /dev/null +++ b/app/src/main/res/drawable/button_white_selector.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/download_progress.xml b/app/src/main/res/drawable/download_progress.xml new file mode 100644 index 0000000..493d923 --- /dev/null +++ b/app/src/main/res/drawable/download_progress.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_about.xml b/app/src/main/res/drawable/ic_about.xml new file mode 100644 index 0000000..8469319 --- /dev/null +++ b/app/src/main/res/drawable/ic_about.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_change_pwd.xml b/app/src/main/res/drawable/ic_change_pwd.xml new file mode 100644 index 0000000..75caa39 --- /dev/null +++ b/app/src/main/res/drawable/ic_change_pwd.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/ic_clean.xml b/app/src/main/res/drawable/ic_clean.xml new file mode 100644 index 0000000..027f809 --- /dev/null +++ b/app/src/main/res/drawable/ic_clean.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_left.xml b/app/src/main/res/drawable/ic_left.xml new file mode 100644 index 0000000..04ee5da --- /dev/null +++ b/app/src/main/res/drawable/ic_left.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_lock.xml b/app/src/main/res/drawable/ic_lock.xml new file mode 100644 index 0000000..f0a5610 --- /dev/null +++ b/app/src/main/res/drawable/ic_lock.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/ic_more.xml b/app/src/main/res/drawable/ic_more.xml new file mode 100644 index 0000000..0794eaa --- /dev/null +++ b/app/src/main/res/drawable/ic_more.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_more_selected.xml b/app/src/main/res/drawable/ic_more_selected.xml new file mode 100644 index 0000000..9b6d3df --- /dev/null +++ b/app/src/main/res/drawable/ic_more_selected.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_phone.xml b/app/src/main/res/drawable/ic_phone.xml new file mode 100644 index 0000000..253f843 --- /dev/null +++ b/app/src/main/res/drawable/ic_phone.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_right.xml b/app/src/main/res/drawable/ic_right.xml new file mode 100644 index 0000000..6020f09 --- /dev/null +++ b/app/src/main/res/drawable/ic_right.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_update_version.xml b/app/src/main/res/drawable/ic_update_version.xml new file mode 100644 index 0000000..3a62d63 --- /dev/null +++ b/app/src/main/res/drawable/ic_update_version.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_user.xml b/app/src/main/res/drawable/ic_user.xml new file mode 100644 index 0000000..8c4a29d --- /dev/null +++ b/app/src/main/res/drawable/ic_user.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1df917d..8d4f33b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -6,6 +6,7 @@ + @@ -32,11 +33,14 @@ + + + + + + + diff --git a/app/src/main/assets/svg/mine.svg b/app/src/main/assets/svg/mine.svg new file mode 100644 index 0000000..3fbb0a3 --- /dev/null +++ b/app/src/main/assets/svg/mine.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt b/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt index d730417..b1972c2 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt @@ -1,8 +1,10 @@ package com.casic.smart.town.sanxi.extensions import com.casic.smart.town.sanxi.model.ErrorMessageModel +import com.casic.smart.town.sanxi.util.LocaleConstant import com.google.gson.Gson import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.utils.SaveKeyValues import org.json.JSONObject fun String.separateResponseCode(): Int { @@ -17,4 +19,15 @@ this, object : TypeToken() {}.type ) return errorModel.message.toString() +} + +/** + * 下载路径为 http://xx.com/static/ 拼接downloadUrl + * */ +fun String.appendDownloadUrl(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + LocaleConstant.DEFAULT_SERVER_CONFIG, LocaleConstant.DEFAULT_SERVER + ) as String + return "$defaultValue/static/${this}" } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt b/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt new file mode 100644 index 0000000..1809dc5 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt @@ -0,0 +1,288 @@ +package com.casic.smart.town.sanxi.fragment + +import android.app.ProgressDialog +import android.content.Intent +import android.net.Uri +import android.os.Build +import android.os.CountDownTimer +import androidx.core.content.FileProvider +import androidx.lifecycle.ViewModelProvider +import com.casic.smart.town.sanxi.BuildConfig +import com.casic.smart.town.sanxi.R +import com.casic.smart.town.sanxi.extensions.appendDownloadUrl +import com.casic.smart.town.sanxi.model.UserDetailModel +import com.casic.smart.town.sanxi.util.AuthenticationHelper +import com.casic.smart.town.sanxi.util.LoadingDialogHub +import com.casic.smart.town.sanxi.util.LocaleConstant +import com.casic.smart.town.sanxi.util.RSAUtils +import com.casic.smart.town.sanxi.view.AboutUsActivity +import com.casic.smart.town.sanxi.view.LoginActivity +import com.casic.smart.town.sanxi.vm.LoginViewModel +import com.casic.smart.town.sanxi.vm.UserViewModel +import com.casic.smart.town.sanxi.vm.VersionViewModel +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.base.KotlinBaseFragment +import com.pengxh.kt.lite.callback.OnDownloadListener +import com.pengxh.kt.lite.extensions.* +import com.pengxh.kt.lite.utils.PageNavigationManager +import com.pengxh.kt.lite.utils.SaveKeyValues +import com.pengxh.kt.lite.vm.LoadState +import com.pengxh.kt.lite.widget.dialog.AlertControlDialog +import com.pengxh.kt.lite.widget.dialog.ChangePasswordDialog +import kotlinx.android.synthetic.main.fragment_mine.* +import java.io.File +import java.nio.charset.StandardCharsets + +class MinePageFragment : KotlinBaseFragment() { + + private lateinit var userData: UserDetailModel.Data + private lateinit var userViewModel: UserViewModel + private lateinit var loginViewModel: LoginViewModel + private lateinit var versionViewModel: VersionViewModel + private lateinit var progressDialog: ProgressDialog + + override fun initLayoutView(): Int = R.layout.fragment_mine + + override fun setupTopBarLayout() { + + } + + override fun initData() { + userViewModel = ViewModelProvider(this)[UserViewModel::class.java] + loginViewModel = ViewModelProvider(this)[LoginViewModel::class.java] + versionViewModel = ViewModelProvider(this)[VersionViewModel::class.java] + + //初始化下载对话框 + progressDialog = ProgressDialog(requireContext()) + progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL) + progressDialog.setProgressDrawable(resources.getDrawable(R.drawable.download_progress)) + progressDialog.setCanceledOnTouchOutside(false) + progressDialog.setCancelable(false) + + /** + * 数据监听 + * */ + dataObserve() + } + + override fun observeRequestState() { + userViewModel.loadState.observe(this, { + when (it) { + is LoadState.Loading -> LoadingDialogHub.show(requireActivity(), "修改中,请稍后") + is LoadState.Success -> { + "修改成功,请重新登录".show(requireContext()) + LoadingDialogHub.dismiss() + AuthenticationHelper.removeToken() + requireContext().navigatePageTo() + PageNavigationManager.finishAllActivity() + } + else -> LoadingDialogHub.dismiss() + } + }) + + versionViewModel.loadState.observe(this, { + when (it) { + is LoadState.Loading -> LoadingDialogHub.show(requireActivity(), "检查版本中,请稍后") + else -> LoadingDialogHub.dismiss() + } + }) + } + + override fun initEvent() { + userImageView.setOnClickListener { + "尽情期待~".show(requireContext()) + } + + updateUserButton.setOnClickListener { + LoadingDialogHub.show(requireActivity(), "同步中,请稍后...") + userViewModel.obtainUserDetail() + } + + changePwdLayout.setOnClickListener { + ChangePasswordDialog.Builder() + .setContext(requireContext()) + .setOnDialogButtonClickListener(object : + ChangePasswordDialog.OnDialogButtonClickListener { + override fun onConfirmClick(oldPwd: String, newPwd: String) { + val publicKey = + RSAUtils.keyStrToPublicKey(AuthenticationHelper.publicKey)!! + val oldPassKey = RSAUtils.encryptDataByPublicKey( + oldPwd.toByteArray(StandardCharsets.UTF_8), + publicKey + ) + val newPassKey = RSAUtils.encryptDataByPublicKey( + newPwd.toByteArray(StandardCharsets.UTF_8), + publicKey + ) + userViewModel.changePassword(oldPassKey, newPassKey) + } + }).build().show() + } + + aboutUsLayout.setOnClickListener { + requireContext().navigatePageTo() + } + + updateVersionLayout.setOnClickListener { + versionViewModel.updateVersion() + } + + clearCacheLayout.setOnClickListener { + //删除缓存之后在设置缓存大小 + LoadingDialogHub.show(requireActivity(), "清理中,请稍后") + File(requireContext().cacheDir.path).deleteFile() + object : CountDownTimer(1500, 500) { + override fun onTick(millisUntilFinished: Long) { + + } + + override fun onFinish() { + LoadingDialogHub.dismiss() + cacheSizeView.text = collectApplicationCache().formatFileSize() + } + }.start() + } + + loginOutButton.setOnClickListener { + AlertControlDialog.Builder() + .setContext(requireContext()) + .setTitle("退出登录") + .setMessage("确定要退出吗?") + .setNegativeButton("取消") + .setPositiveButton("确定") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + loginViewModel.out() + } + + override fun onCancelClick() {} + }).build().show() + } + } + + private fun dataObserve() { + userViewModel.userDetailModel.observe(this, { + if (it.code == 200) { + LoadingDialogHub.dismiss() + "同步完成".show(requireContext()) + userData = it.data + updateUserInfo() + } + }) + + versionViewModel.versionResultModel.observe(this, { + if (BuildConfig.VERSION_NAME == it.version) { + "已是最新版本,无需更新".show(requireContext()) + } else { + AlertControlDialog.Builder() + .setContext(requireContext()) + .setTitle("提示") + .setMessage("有新版本,是否更新?") + .setNegativeButton("稍后再说") + .setPositiveButton("立即下载") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + downloadApk(it.downloadUrl) + } + + override fun onCancelClick() { + + } + }).build().show() + } + }) + + loginViewModel.outResultModel.observe(this, { + if (it.code == 200) { + AuthenticationHelper.removeToken() + requireContext().navigatePageTo() + PageNavigationManager.finishAllActivity() + } + }) + } + + private fun collectApplicationCache(): Long { + return File(requireContext().cacheDir.path).calculateSize() + } + + override fun onResume() { + val userDetailJson = SaveKeyValues.getValue(LocaleConstant.USER_DETAIL_MODEL, "") as String + if (userDetailJson.isNotBlank()) { + userData = Gson().fromJson( + userDetailJson, object : TypeToken() {}.type + ) + updateUserInfo() + } + //缓存 + cacheSizeView.text = collectApplicationCache().formatFileSize() + super.onResume() + } + + private fun updateUserInfo() { + //设置头像,圆形,暂时是默认的 +// val roundDrawable = +// BitmapFactory.decodeResource(requireContext().resources, R.mipmap.login_casic) +// .createRoundDrawable( +// requireContext(), +// 3f.dp2px(requireContext()), +// R.color.mainThemeColor.convertColor(requireContext()) +// ) +// userImageView.setImageDrawable(roundDrawable) + userNameView.text = userData.name + userPhoneView.text = String.format("电话:${userData.phone}") + userDeptView.text = String.format("部门:${userData.deptName}") + } + + private fun downloadApk(url: String?) { + progressDialog.setMessage("下载新版本中...") + progressDialog.show() + if (url.toString().isBlank()) { + "抱歉,版本下载失败".show(requireContext()) + return + } + /** + * http://111.198.10.15:11304/static/apk/1.0.1.apk + * */ + val downloadPath = url!!.appendDownloadUrl() + //开始下载 + downloadPath.downloadFile(requireContext().createDownloadFileDir().toString(), + object : OnDownloadListener { + override fun onDownloadStart(totalBytes: Long) { + progressDialog.max = totalBytes.toInt() + } + + override fun onProgressChanged(currentBytes: Long) { + progressDialog.progress = currentBytes.toInt() + } + + override fun onDownloadEnd(file: File?) { + progressDialog.dismiss() + progressDialog.progress = 0 + //安装APK + installApk(file) + } + }) + } + + private fun installApk(apkPackage: File?) { + if (apkPackage == null) { + "文件异常,无法安装".show(requireContext()) + return + } + val intent = Intent(Intent.ACTION_VIEW) + val data: Uri + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { //判断版本大于等于7.0 + data = FileProvider.getUriForFile( + requireContext(), LocaleConstant.APP_AUTHORITY, apkPackage + ) + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) // 给目标应用一个临时授权 + } else { + data = Uri.fromFile(apkPackage) + } + intent.setDataAndType(data, "application/vnd.android.package-archive") + requireContext().startActivity(intent) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java new file mode 100644 index 0000000..467e33d --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java @@ -0,0 +1,43 @@ +package com.casic.smart.town.sanxi.model; + +/** + * 普通实体类,失败/成功数据结构一致 + */ +public class CommonResultModel { + private int code; + private String data; + private String message; + private boolean isSuccess; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return isSuccess; + } + + public void setSuccess(boolean success) { + isSuccess = success; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java new file mode 100644 index 0000000..9bdabef --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java @@ -0,0 +1,62 @@ +package com.casic.smart.town.sanxi.model; + +public class LoginResultModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private String kaptcha; + private String token; + + public String getKaptcha() { + return kaptcha; + } + + public void setKaptcha(String kaptcha) { + this.kaptcha = kaptcha; + } + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java new file mode 100644 index 0000000..09f8d59 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java @@ -0,0 +1,80 @@ +package com.casic.smart.town.sanxi.model; + +public class PublicKeyModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private boolean appKaptcha; + private boolean kaptcha; + private String publicKey; + private String sid; + + public boolean isAppKaptcha() { + return appKaptcha; + } + + public void setAppKaptcha(boolean appKaptcha) { + this.appKaptcha = appKaptcha; + } + + public boolean isKaptcha() { + return kaptcha; + } + + public void setKaptcha(boolean kaptcha) { + this.kaptcha = kaptcha; + } + + public String getPublicKey() { + return publicKey; + } + + public void setPublicKey(String publicKey) { + this.publicKey = publicKey; + } + + public String getSid() { + return sid; + } + + public void setSid(String sid) { + this.sid = sid; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java new file mode 100644 index 0000000..db577fb --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java @@ -0,0 +1,227 @@ +package com.casic.smart.town.sanxi.model; + + +import java.util.List; + +public class UserDetailModel { + private int code; + private Data data; + private String message; + private boolean success; + + public void setCode(int code) { + this.code = code; + } + + public int getCode() { + return code; + } + + public void setData(Data data) { + this.data = data; + } + + public Data getData() { + return data; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getMessage() { + return message; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public boolean getSuccess() { + return success; + } + + public static class Data { + + private String account; + private String attr1; + private String avatar; + private String bizData; + private List dataScope; + private String deptId; + private String deptName; + private List devices; + private String id; + private String ipAddr; + private String name; + private String phone; + private List roleList; + private List roleNames; + private List roleTips; + private String scopeType; + private String sysData; + private String targetId; + private String targetName; + private String tenantId; + + public void setAccount(String account) { + this.account = account; + } + + public String getAccount() { + return account; + } + + public void setAttr1(String attr1) { + this.attr1 = attr1; + } + + public String getAttr1() { + return attr1; + } + + public void setAvatar(String avatar) { + this.avatar = avatar; + } + + public String getAvatar() { + return avatar; + } + + public void setBizData(String bizData) { + this.bizData = bizData; + } + + public String getBizData() { + return bizData; + } + + public void setDataScope(List dataScope) { + this.dataScope = dataScope; + } + + public List getDataScope() { + return dataScope; + } + + public void setDeptId(String deptId) { + this.deptId = deptId; + } + + public String getDeptId() { + return deptId; + } + + public void setDeptName(String deptName) { + this.deptName = deptName; + } + + public String getDeptName() { + return deptName; + } + + public void setDevices(List devices) { + this.devices = devices; + } + + public List getDevices() { + return devices; + } + + public void setId(String id) { + this.id = id; + } + + public String getId() { + return id; + } + + public void setIpAddr(String ipAddr) { + this.ipAddr = ipAddr; + } + + public String getIpAddr() { + return ipAddr; + } + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setPhone(String phone) { + this.phone = phone; + } + + public String getPhone() { + return phone; + } + + public void setRoleList(List roleList) { + this.roleList = roleList; + } + + public List getRoleList() { + return roleList; + } + + public void setRoleNames(List roleNames) { + this.roleNames = roleNames; + } + + public List getRoleNames() { + return roleNames; + } + + public void setRoleTips(List roleTips) { + this.roleTips = roleTips; + } + + public List getRoleTips() { + return roleTips; + } + + public void setScopeType(String scopeType) { + this.scopeType = scopeType; + } + + public String getScopeType() { + return scopeType; + } + + public void setSysData(String sysData) { + this.sysData = sysData; + } + + public String getSysData() { + return sysData; + } + + public void setTargetId(String targetId) { + this.targetId = targetId; + } + + public String getTargetId() { + return targetId; + } + + public void setTargetName(String targetName) { + this.targetName = targetName; + } + + public String getTargetName() { + return targetName; + } + + public void setTenantId(String tenantId) { + this.tenantId = tenantId; + } + + public String getTenantId() { + return tenantId; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/VersionResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/VersionResultModel.java new file mode 100644 index 0000000..79374e0 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/VersionResultModel.java @@ -0,0 +1,61 @@ +package com.casic.smart.town.sanxi.model; + +public class VersionResultModel { + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private String downloadUrl; + private String version; + + public String getDownloadUrl() { + return downloadUrl; + } + + public void setDownloadUrl(String downloadUrl) { + this.downloadUrl = downloadUrl; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/AuthenticationHelper.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/AuthenticationHelper.kt new file mode 100644 index 0000000..f498e4e --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/AuthenticationHelper.kt @@ -0,0 +1,24 @@ +package com.casic.smart.town.sanxi.util + +import com.pengxh.kt.lite.utils.SaveKeyValues + +object AuthenticationHelper { + + fun savePublicKey(key: String) { + SaveKeyValues.putValue("keyString", key) + } + + val publicKey: String? + get() = SaveKeyValues.getValue("keyString", "") as String? + + fun saveToken(token: String?) { + SaveKeyValues.putValue("token", token!!) + } + + val token: String? + get() = SaveKeyValues.getValue("token", "") as String? + + fun removeToken() { + SaveKeyValues.removeKey("token") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt index ab1a36d..99f0d19 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt @@ -14,5 +14,9 @@ const val PAGE_LIMIT = 20 const val DEFAULT_SERVER_CONFIG = "defaultServerConfig" - const val DEFAULT_SERVER = "http://111.198.10.15:11308" + const val DEFAULT_SERVER = "http://36.133.189.112:8082" + const val APP_AUTHORITY = "com.casic.smart.town.sanxi.fileprovider" + const val ACCOUNT = "account" + const val PASSWORD = "password" + const val USER_DETAIL_MODEL = "userDetailModel" } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/RSAUtils.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/RSAUtils.kt new file mode 100644 index 0000000..b9216ba --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/RSAUtils.kt @@ -0,0 +1,55 @@ +package com.casic.smart.town.sanxi.util + +import android.util.Base64 +import java.security.* +import java.security.spec.InvalidKeySpecException +import java.security.spec.X509EncodedKeySpec +import javax.crypto.BadPaddingException +import javax.crypto.Cipher +import javax.crypto.IllegalBlockSizeException +import javax.crypto.NoSuchPaddingException + +object RSAUtils { + //构建Cipher实例时所传入的的字符串,默认为"RSA/NONE/PKCS1Padding" + private fun processData(srcData: ByteArray, key: Key): ByteArray? { //用来保存处理结果 + var resultBytes: ByteArray? = null + try { //获取Cipher实例 + val cipher = Cipher.getInstance("RSA/None/PKCS1Padding") + //初始化Cipher,mode指定是加密还是解密,key为公钥或私钥 + cipher.init(Cipher.ENCRYPT_MODE, key) + //处理数据 + resultBytes = cipher.doFinal(srcData) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: NoSuchPaddingException) { + e.printStackTrace() + } catch (e: InvalidKeyException) { + e.printStackTrace() + } catch (e: BadPaddingException) { + e.printStackTrace() + } catch (e: IllegalBlockSizeException) { + e.printStackTrace() + } + return resultBytes + } + + fun encryptDataByPublicKey(srcData: ByteArray, publicKey: PublicKey): String { + val resultBytes = processData(srcData, publicKey) + return Base64.encodeToString(resultBytes, Base64.DEFAULT) + } + + fun keyStrToPublicKey(publicKeyStr: String?): PublicKey? { + var publicKey: PublicKey? = null + val keyBytes = Base64.decode(publicKeyStr, Base64.DEFAULT) + val keySpec = X509EncodedKeySpec(keyBytes) + try { + val keyFactory = KeyFactory.getInstance("RSA") + publicKey = keyFactory.generatePublic(keySpec) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: InvalidKeySpecException) { + e.printStackTrace() + } + return publicKey + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt index 63db0ec..50d1742 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt @@ -1,12 +1,64 @@ package com.casic.smart.town.sanxi.util.retrofit -import retrofit2.http.GET +import retrofit2.http.* interface RetrofitService { + /** + * PublicKey校验 + */ + @GET("/config/baseConfig") + suspend fun obtainPublicKey(): String + + /** + * 登录并获取Token + * + * @param sid + * @param account 用户名 + * @param secretKey 加密后的密码 + */ + @FormUrlEncoded + @POST("/user/login") + suspend fun login( + @Field("sid") sid: String, + @Field("username") account: String, + @Field("password") secretKey: String + ): String + + /** + * 退出登录 + */ + @GET("/user/logout") + suspend fun loginOut(@Header("token") token: String): String + + /** + * 获取用户信息 + */ + @GET("/user/info") + suspend fun obtainUserDetail(@Header("token") token: String): String + + /** + * 修改密码 + * + * @param oldPwd 旧密码 + * @param newPwd 新密码 + */ + @FormUrlEncoded + @POST("/mgr/changePwd") + suspend fun changePassword( + @Header("token") token: String, + @Field("oldPwd") oldPwd: String, + @Field("newPwd") newPwd: String + ): String /** * 根据布防状态统计闸井数量接口 */ @GET("/well/countByBfzt") - suspend fun countWellByState(): String + suspend fun countWellByState(@Header("token") token: String): String + + /** + * 更新APK版本 + */ + @POST("/app/checkVersion") + suspend fun obtainVersionResult(@Header("token") token: String): String } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt index 46bbf4b..410444b 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt @@ -1,5 +1,6 @@ package com.casic.smart.town.sanxi.util.retrofit +import com.casic.smart.town.sanxi.util.AuthenticationHelper import com.casic.smart.town.sanxi.util.LocaleConstant import com.pengxh.kt.lite.utils.RetrofitFactory import com.pengxh.kt.lite.utils.SaveKeyValues @@ -15,9 +16,52 @@ } /** + * 验证PublicKey + */ + suspend fun authenticate(): String { + return api.obtainPublicKey() + } + + /** + * 登录并获取Token + */ + suspend fun login(sid: String, account: String, secretKey: String): String { + return api.login(sid, account, secretKey) + } + + /** + * 退出登录 + */ + suspend fun loginOut(): String { + return api.loginOut(AuthenticationHelper.token!!) + } + + /** + * 获取用户信息 + */ + suspend fun obtainUserDetail(): String { + return api.obtainUserDetail(AuthenticationHelper.token!!) + } + + /** + * 修改密码 + */ + suspend fun changePassword(oldPwd: String, newPwd: String): String { + return api.changePassword(AuthenticationHelper.token!!, oldPwd, newPwd) + } + + /** * 根据布防状态统计闸井数量接口 */ suspend fun countWellByState(): String { - return api.countWellByState() + return api.countWellByState(AuthenticationHelper.token!!) } + + /** + * 更新APK版本 + */ + suspend fun updateVersion(): String { + return api.obtainVersionResult(AuthenticationHelper.token!!) + } + } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/AboutUsActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/AboutUsActivity.kt new file mode 100644 index 0000000..7e43c47 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/AboutUsActivity.kt @@ -0,0 +1,34 @@ +package com.casic.smart.town.sanxi.view + +import com.casic.smart.town.sanxi.BuildConfig +import com.casic.smart.town.sanxi.R +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.kt.lite.base.KotlinBaseActivity +import com.pengxh.kt.lite.extensions.convertColor +import com.pengxh.kt.lite.utils.ImmerseStatusBarUtil +import kotlinx.android.synthetic.main.activity_about_us.* +import kotlinx.android.synthetic.main.include_base_title.* + +class AboutUsActivity : KotlinBaseActivity() { + + override fun initLayoutView(): Int = R.layout.activity_about_us + + override fun setupTopBarLayout() { + ImmersionBar.with(this).statusBarDarkFont(false).init() + ImmerseStatusBarUtil.setColor(this, R.color.mainThemeColor.convertColor(this)) + titleView.text = "关于我们" + leftBackView.setOnClickListener { finish() } + } + + override fun initData() { + versionCodeView.text = String.format("版本信息:V${BuildConfig.VERSION_NAME}") + } + + override fun observeRequestState() { + + } + + override fun initEvent() { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/LoginActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/LoginActivity.kt new file mode 100644 index 0000000..b5d1d50 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/LoginActivity.kt @@ -0,0 +1,97 @@ +package com.casic.smart.town.sanxi.view + +import androidx.lifecycle.ViewModelProvider +import com.casic.smart.town.sanxi.R +import com.casic.smart.town.sanxi.util.AuthenticationHelper +import com.casic.smart.town.sanxi.util.LoadingDialogHub +import com.casic.smart.town.sanxi.util.LocaleConstant +import com.casic.smart.town.sanxi.util.RSAUtils +import com.casic.smart.town.sanxi.vm.AuthenticateViewModel +import com.casic.smart.town.sanxi.vm.LoginViewModel +import com.casic.smart.town.sanxi.vm.UserViewModel +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.kt.lite.base.KotlinBaseActivity +import com.pengxh.kt.lite.extensions.navigatePageTo +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.utils.SaveKeyValues +import kotlinx.android.synthetic.main.activity_login.* + +class LoginActivity : KotlinBaseActivity() { + + private lateinit var authenticateViewModel: AuthenticateViewModel + private lateinit var loginViewModel: LoginViewModel + private lateinit var userViewModel: UserViewModel + + override fun initLayoutView(): Int = R.layout.activity_login + + override fun setupTopBarLayout() { + ImmersionBar.with(this).statusBarDarkFont(true).init() + } + + override fun initData() { + // 设置默认账号密码 + userNameView.setText(SaveKeyValues.getValue(LocaleConstant.ACCOUNT, "") as String) + userPasswordView.setText(SaveKeyValues.getValue(LocaleConstant.PASSWORD, "") as String) + authenticateViewModel = ViewModelProvider(this)[AuthenticateViewModel::class.java] + loginViewModel = ViewModelProvider(this)[LoginViewModel::class.java] + userViewModel = ViewModelProvider(this)[UserViewModel::class.java] + + authenticateViewModel.keyModel.observe(this, { + if (it.code == 200) {//用code判断,别的判断可能有坑 + val keyString = it.data!!.publicKey!! + /** + * 修改密码需要用到key,先存着 + * */ + AuthenticationHelper.savePublicKey(keyString) + val publicKey = RSAUtils.keyStrToPublicKey(keyString) + + val account = userNameView.text.toString() + val userPassword = userPasswordView.text.toString() + val dataByPublicKey = + RSAUtils.encryptDataByPublicKey(userPassword.toByteArray(), publicKey!!) + //登录并获取Token,POST请求 + loginViewModel.enter(it.data!!.sid!!, account, dataByPublicKey) + loginViewModel.enterResultModel.observe(this, { loginResult -> + if (loginResult.code == 200) { + AuthenticationHelper.saveToken(loginResult.data!!.token!!) + /** + * 获取token之后保存用户信息 + * */ + userViewModel.obtainUserDetail() + //验证成功登录 + navigatePageTo() + finish() + } + }) + } + }) + } + + override fun observeRequestState() { + authenticateViewModel.loadState.observe(this, { + LoadingDialogHub.show(this, "登录中,请稍后") + }) + + loginViewModel.loadState.observe(this, { + LoadingDialogHub.dismiss() + }) + } + + override fun initEvent() { + loginButton.setOnClickListener { + val account = userNameView.text.toString().trim() + val userPassword = userPasswordView.text.toString().trim() + if (account.isBlank()) { + "用户名不能为空".show(this) + return@setOnClickListener + } + if (userPassword.isBlank()) { + "密码不能为空".show(this) + return@setOnClickListener + } + SaveKeyValues.putValue(LocaleConstant.ACCOUNT, account) + SaveKeyValues.putValue(LocaleConstant.PASSWORD, userPassword) + authenticateViewModel.obtainPublicKey() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt index 3bea1c9..a4fa951 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt @@ -7,6 +7,7 @@ import com.casic.smart.town.sanxi.R import com.casic.smart.town.sanxi.adapter.ViewPagerAdapter import com.casic.smart.town.sanxi.fragment.AlarmPageFragment +import com.casic.smart.town.sanxi.fragment.MinePageFragment import com.casic.smart.town.sanxi.fragment.MonitorPageFragment import com.casic.smart.town.sanxi.fragment.StatisticsPageFragment import com.gyf.immersionbar.ImmersionBar @@ -33,6 +34,7 @@ fragmentPages.add(StatisticsPageFragment()) fragmentPages.add(MonitorPageFragment()) fragmentPages.add(AlarmPageFragment()) + fragmentPages.add(MinePageFragment()) } override fun initLayoutView(): Int = R.layout.activity_main @@ -68,6 +70,9 @@ R.id.nav_alarm -> { mainViewPager.currentItem = 2 } + R.id.nav_more -> { + mainViewPager.currentItem = 3 + } } false } diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/PermissionActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/PermissionActivity.kt index c478d83..3238f3c 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/view/PermissionActivity.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/PermissionActivity.kt @@ -32,7 +32,7 @@ //先把导航隐私政策声明,后面导航会用到 MapsInitializer.updatePrivacyAgree(this, true) MapsInitializer.updatePrivacyShow(this, true, true) - this.navigatePageTo() + this.navigatePageTo() finish() } diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/SplashScreenActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/SplashScreenActivity.kt new file mode 100644 index 0000000..fa5a74d --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/SplashScreenActivity.kt @@ -0,0 +1,54 @@ +package com.casic.smart.town.sanxi.view + +import android.annotation.SuppressLint +import android.os.CountDownTimer +import androidx.lifecycle.ViewModelProvider +import com.casic.smart.town.sanxi.R +import com.casic.smart.town.sanxi.vm.UserViewModel +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.kt.lite.base.KotlinBaseActivity +import com.pengxh.kt.lite.extensions.navigatePageTo + +@SuppressLint("CustomSplashScreen") +class SplashScreenActivity : KotlinBaseActivity() { + + private lateinit var userDetailViewModel: UserViewModel + private val countDownTimer = object : CountDownTimer(1000, 500) { + override fun onFinish() { + /** + * 获取token之后保存用户信息 + * */ + userDetailViewModel.obtainUserDetail() + userDetailViewModel.userDetailModel.observe(this@SplashScreenActivity, { + if (it.code == 200) { + navigatePageTo() + } else { + navigatePageTo() + } + finish() + }) + } + + override fun onTick(millisUntilFinished: Long) { + + } + } + + override fun initLayoutView(): Int = R.layout.activity_splash + + override fun setupTopBarLayout() { + ImmersionBar.with(this).statusBarDarkFont(false).init() + } + + override fun initData() { + userDetailViewModel = ViewModelProvider(this).get(UserViewModel::class.java) + } + + override fun observeRequestState() { + + } + + override fun initEvent() { + countDownTimer.start() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/vm/AuthenticateViewModel.kt b/app/src/main/java/com/casic/smart/town/sanxi/vm/AuthenticateViewModel.kt new file mode 100644 index 0000000..87ddbf8 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/vm/AuthenticateViewModel.kt @@ -0,0 +1,35 @@ +package com.casic.smart.town.sanxi.vm + +import androidx.lifecycle.MutableLiveData +import com.casic.smart.town.sanxi.base.BaseApplication +import com.casic.smart.town.sanxi.extensions.separateResponseCode +import com.casic.smart.town.sanxi.extensions.toErrorMessage +import com.casic.smart.town.sanxi.model.PublicKeyModel +import com.casic.smart.town.sanxi.util.retrofit.RetrofitServiceManager +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.launch +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.vm.BaseViewModel +import com.pengxh.kt.lite.vm.LoadState + +class AuthenticateViewModel : BaseViewModel() { + + private val gson = Gson() + val keyModel = MutableLiveData() + + fun obtainPublicKey() = launch({ + loadState.value = LoadState.Loading + val response = RetrofitServiceManager.authenticate() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + keyModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ) + } else { + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + it.printStackTrace() + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/vm/LoginViewModel.kt b/app/src/main/java/com/casic/smart/town/sanxi/vm/LoginViewModel.kt new file mode 100644 index 0000000..c377c61 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/vm/LoginViewModel.kt @@ -0,0 +1,54 @@ +package com.casic.smart.town.sanxi.vm + +import androidx.lifecycle.MutableLiveData +import com.casic.smart.town.sanxi.base.BaseApplication +import com.casic.smart.town.sanxi.extensions.separateResponseCode +import com.casic.smart.town.sanxi.extensions.toErrorMessage +import com.casic.smart.town.sanxi.model.CommonResultModel +import com.casic.smart.town.sanxi.model.LoginResultModel +import com.casic.smart.town.sanxi.util.retrofit.RetrofitServiceManager +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.launch +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.vm.BaseViewModel +import com.pengxh.kt.lite.vm.LoadState + +class LoginViewModel : BaseViewModel() { + + private val gson = Gson() + val enterResultModel = MutableLiveData() + val outResultModel = MutableLiveData() + + fun enter(sid: String, account: String, secretKey: String) = launch({ + val response = RetrofitServiceManager.login(sid, account, secretKey) + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + loadState.value = LoadState.Success + "登录成功".show(BaseApplication.obtainInstance()) + enterResultModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ) + } else { + loadState.value = LoadState.Fail + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + loadState.value = LoadState.Fail + it.printStackTrace() + }) + + fun out() = launch({ + val response = RetrofitServiceManager.loginOut() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + outResultModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ) + } else { + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + it.printStackTrace() + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/vm/UserViewModel.kt b/app/src/main/java/com/casic/smart/town/sanxi/vm/UserViewModel.kt new file mode 100644 index 0000000..29a8756 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/vm/UserViewModel.kt @@ -0,0 +1,57 @@ +package com.casic.smart.town.sanxi.vm + +import androidx.lifecycle.MutableLiveData +import com.casic.smart.town.sanxi.base.BaseApplication +import com.casic.smart.town.sanxi.extensions.separateResponseCode +import com.casic.smart.town.sanxi.extensions.toErrorMessage +import com.casic.smart.town.sanxi.model.UserDetailModel +import com.casic.smart.town.sanxi.util.LocaleConstant +import com.casic.smart.town.sanxi.util.retrofit.RetrofitServiceManager +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.launch +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.utils.SaveKeyValues +import com.pengxh.kt.lite.vm.BaseViewModel +import com.pengxh.kt.lite.vm.LoadState + +class UserViewModel : BaseViewModel() { + + private val gson = Gson() + val userDetailModel = MutableLiveData() + + fun obtainUserDetail() = launch({ + val response = RetrofitServiceManager.obtainUserDetail() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + val userDetail = gson.fromJson( + response, object : TypeToken() {}.type + ) + userDetailModel.value = userDetail + SaveKeyValues.putValue(LocaleConstant.USER_DETAIL_MODEL, gson.toJson(userDetail.data)) + } else { + val errorModel = UserDetailModel() + errorModel.code = responseCode + userDetailModel.value = errorModel + //如果此次获取不到用户信息,那么就清空之前的用户缓存,然后让用户重新登录 + SaveKeyValues.removeKey(LocaleConstant.USER_DETAIL_MODEL) + } + }, { + it.printStackTrace() + }) + + fun changePassword(oldPwd: String, newPwd: String) = launch({ + loadState.value = LoadState.Loading + val response = RetrofitServiceManager.changePassword(oldPwd, newPwd) + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + loadState.value = LoadState.Success + } else { + loadState.value = LoadState.Fail + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + loadState.value = LoadState.Fail + it.printStackTrace() + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/vm/VersionViewModel.kt b/app/src/main/java/com/casic/smart/town/sanxi/vm/VersionViewModel.kt new file mode 100644 index 0000000..20ea457 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/vm/VersionViewModel.kt @@ -0,0 +1,38 @@ +package com.casic.smart.town.sanxi.vm + +import androidx.lifecycle.MutableLiveData +import com.casic.smart.town.sanxi.base.BaseApplication +import com.casic.smart.town.sanxi.extensions.separateResponseCode +import com.casic.smart.town.sanxi.extensions.toErrorMessage +import com.casic.smart.town.sanxi.model.VersionResultModel +import com.casic.smart.town.sanxi.util.retrofit.RetrofitServiceManager +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.launch +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.vm.BaseViewModel +import com.pengxh.kt.lite.vm.LoadState + +class VersionViewModel : BaseViewModel() { + + private val gson = Gson() + val versionResultModel = MutableLiveData() + + fun updateVersion() = launch({ + loadState.value = LoadState.Loading + val response = RetrofitServiceManager.updateVersion() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + loadState.value = LoadState.Success + versionResultModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ).data + } else { + loadState.value = LoadState.Fail + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + loadState.value = LoadState.Fail + it.printStackTrace() + }) +} \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_solid_layout_gray_radius_5.xml b/app/src/main/res/drawable/bg_solid_layout_gray_radius_5.xml new file mode 100644 index 0000000..96eb93e --- /dev/null +++ b/app/src/main/res/drawable/bg_solid_layout_gray_radius_5.xml @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/button_main_selector.xml b/app/src/main/res/drawable/button_main_selector.xml new file mode 100644 index 0000000..5875998 --- /dev/null +++ b/app/src/main/res/drawable/button_main_selector.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/button_white_selector.xml b/app/src/main/res/drawable/button_white_selector.xml new file mode 100644 index 0000000..4dd3c0b --- /dev/null +++ b/app/src/main/res/drawable/button_white_selector.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/download_progress.xml b/app/src/main/res/drawable/download_progress.xml new file mode 100644 index 0000000..493d923 --- /dev/null +++ b/app/src/main/res/drawable/download_progress.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_about.xml b/app/src/main/res/drawable/ic_about.xml new file mode 100644 index 0000000..8469319 --- /dev/null +++ b/app/src/main/res/drawable/ic_about.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_change_pwd.xml b/app/src/main/res/drawable/ic_change_pwd.xml new file mode 100644 index 0000000..75caa39 --- /dev/null +++ b/app/src/main/res/drawable/ic_change_pwd.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/ic_clean.xml b/app/src/main/res/drawable/ic_clean.xml new file mode 100644 index 0000000..027f809 --- /dev/null +++ b/app/src/main/res/drawable/ic_clean.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_left.xml b/app/src/main/res/drawable/ic_left.xml new file mode 100644 index 0000000..04ee5da --- /dev/null +++ b/app/src/main/res/drawable/ic_left.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_lock.xml b/app/src/main/res/drawable/ic_lock.xml new file mode 100644 index 0000000..f0a5610 --- /dev/null +++ b/app/src/main/res/drawable/ic_lock.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/ic_more.xml b/app/src/main/res/drawable/ic_more.xml new file mode 100644 index 0000000..0794eaa --- /dev/null +++ b/app/src/main/res/drawable/ic_more.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_more_selected.xml b/app/src/main/res/drawable/ic_more_selected.xml new file mode 100644 index 0000000..9b6d3df --- /dev/null +++ b/app/src/main/res/drawable/ic_more_selected.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_phone.xml b/app/src/main/res/drawable/ic_phone.xml new file mode 100644 index 0000000..253f843 --- /dev/null +++ b/app/src/main/res/drawable/ic_phone.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_right.xml b/app/src/main/res/drawable/ic_right.xml new file mode 100644 index 0000000..6020f09 --- /dev/null +++ b/app/src/main/res/drawable/ic_right.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_update_version.xml b/app/src/main/res/drawable/ic_update_version.xml new file mode 100644 index 0000000..3a62d63 --- /dev/null +++ b/app/src/main/res/drawable/ic_update_version.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_user.xml b/app/src/main/res/drawable/ic_user.xml new file mode 100644 index 0000000..8c4a29d --- /dev/null +++ b/app/src/main/res/drawable/ic_user.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/drawable/more_selector.xml b/app/src/main/res/drawable/more_selector.xml new file mode 100644 index 0000000..a150a48 --- /dev/null +++ b/app/src/main/res/drawable/more_selector.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1df917d..8d4f33b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -6,6 +6,7 @@ + @@ -32,11 +33,14 @@ + + + + + + + diff --git a/app/src/main/assets/svg/mine.svg b/app/src/main/assets/svg/mine.svg new file mode 100644 index 0000000..3fbb0a3 --- /dev/null +++ b/app/src/main/assets/svg/mine.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt b/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt index d730417..b1972c2 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt @@ -1,8 +1,10 @@ package com.casic.smart.town.sanxi.extensions import com.casic.smart.town.sanxi.model.ErrorMessageModel +import com.casic.smart.town.sanxi.util.LocaleConstant import com.google.gson.Gson import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.utils.SaveKeyValues import org.json.JSONObject fun String.separateResponseCode(): Int { @@ -17,4 +19,15 @@ this, object : TypeToken() {}.type ) return errorModel.message.toString() +} + +/** + * 下载路径为 http://xx.com/static/ 拼接downloadUrl + * */ +fun String.appendDownloadUrl(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + LocaleConstant.DEFAULT_SERVER_CONFIG, LocaleConstant.DEFAULT_SERVER + ) as String + return "$defaultValue/static/${this}" } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt b/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt new file mode 100644 index 0000000..1809dc5 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt @@ -0,0 +1,288 @@ +package com.casic.smart.town.sanxi.fragment + +import android.app.ProgressDialog +import android.content.Intent +import android.net.Uri +import android.os.Build +import android.os.CountDownTimer +import androidx.core.content.FileProvider +import androidx.lifecycle.ViewModelProvider +import com.casic.smart.town.sanxi.BuildConfig +import com.casic.smart.town.sanxi.R +import com.casic.smart.town.sanxi.extensions.appendDownloadUrl +import com.casic.smart.town.sanxi.model.UserDetailModel +import com.casic.smart.town.sanxi.util.AuthenticationHelper +import com.casic.smart.town.sanxi.util.LoadingDialogHub +import com.casic.smart.town.sanxi.util.LocaleConstant +import com.casic.smart.town.sanxi.util.RSAUtils +import com.casic.smart.town.sanxi.view.AboutUsActivity +import com.casic.smart.town.sanxi.view.LoginActivity +import com.casic.smart.town.sanxi.vm.LoginViewModel +import com.casic.smart.town.sanxi.vm.UserViewModel +import com.casic.smart.town.sanxi.vm.VersionViewModel +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.base.KotlinBaseFragment +import com.pengxh.kt.lite.callback.OnDownloadListener +import com.pengxh.kt.lite.extensions.* +import com.pengxh.kt.lite.utils.PageNavigationManager +import com.pengxh.kt.lite.utils.SaveKeyValues +import com.pengxh.kt.lite.vm.LoadState +import com.pengxh.kt.lite.widget.dialog.AlertControlDialog +import com.pengxh.kt.lite.widget.dialog.ChangePasswordDialog +import kotlinx.android.synthetic.main.fragment_mine.* +import java.io.File +import java.nio.charset.StandardCharsets + +class MinePageFragment : KotlinBaseFragment() { + + private lateinit var userData: UserDetailModel.Data + private lateinit var userViewModel: UserViewModel + private lateinit var loginViewModel: LoginViewModel + private lateinit var versionViewModel: VersionViewModel + private lateinit var progressDialog: ProgressDialog + + override fun initLayoutView(): Int = R.layout.fragment_mine + + override fun setupTopBarLayout() { + + } + + override fun initData() { + userViewModel = ViewModelProvider(this)[UserViewModel::class.java] + loginViewModel = ViewModelProvider(this)[LoginViewModel::class.java] + versionViewModel = ViewModelProvider(this)[VersionViewModel::class.java] + + //初始化下载对话框 + progressDialog = ProgressDialog(requireContext()) + progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL) + progressDialog.setProgressDrawable(resources.getDrawable(R.drawable.download_progress)) + progressDialog.setCanceledOnTouchOutside(false) + progressDialog.setCancelable(false) + + /** + * 数据监听 + * */ + dataObserve() + } + + override fun observeRequestState() { + userViewModel.loadState.observe(this, { + when (it) { + is LoadState.Loading -> LoadingDialogHub.show(requireActivity(), "修改中,请稍后") + is LoadState.Success -> { + "修改成功,请重新登录".show(requireContext()) + LoadingDialogHub.dismiss() + AuthenticationHelper.removeToken() + requireContext().navigatePageTo() + PageNavigationManager.finishAllActivity() + } + else -> LoadingDialogHub.dismiss() + } + }) + + versionViewModel.loadState.observe(this, { + when (it) { + is LoadState.Loading -> LoadingDialogHub.show(requireActivity(), "检查版本中,请稍后") + else -> LoadingDialogHub.dismiss() + } + }) + } + + override fun initEvent() { + userImageView.setOnClickListener { + "尽情期待~".show(requireContext()) + } + + updateUserButton.setOnClickListener { + LoadingDialogHub.show(requireActivity(), "同步中,请稍后...") + userViewModel.obtainUserDetail() + } + + changePwdLayout.setOnClickListener { + ChangePasswordDialog.Builder() + .setContext(requireContext()) + .setOnDialogButtonClickListener(object : + ChangePasswordDialog.OnDialogButtonClickListener { + override fun onConfirmClick(oldPwd: String, newPwd: String) { + val publicKey = + RSAUtils.keyStrToPublicKey(AuthenticationHelper.publicKey)!! + val oldPassKey = RSAUtils.encryptDataByPublicKey( + oldPwd.toByteArray(StandardCharsets.UTF_8), + publicKey + ) + val newPassKey = RSAUtils.encryptDataByPublicKey( + newPwd.toByteArray(StandardCharsets.UTF_8), + publicKey + ) + userViewModel.changePassword(oldPassKey, newPassKey) + } + }).build().show() + } + + aboutUsLayout.setOnClickListener { + requireContext().navigatePageTo() + } + + updateVersionLayout.setOnClickListener { + versionViewModel.updateVersion() + } + + clearCacheLayout.setOnClickListener { + //删除缓存之后在设置缓存大小 + LoadingDialogHub.show(requireActivity(), "清理中,请稍后") + File(requireContext().cacheDir.path).deleteFile() + object : CountDownTimer(1500, 500) { + override fun onTick(millisUntilFinished: Long) { + + } + + override fun onFinish() { + LoadingDialogHub.dismiss() + cacheSizeView.text = collectApplicationCache().formatFileSize() + } + }.start() + } + + loginOutButton.setOnClickListener { + AlertControlDialog.Builder() + .setContext(requireContext()) + .setTitle("退出登录") + .setMessage("确定要退出吗?") + .setNegativeButton("取消") + .setPositiveButton("确定") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + loginViewModel.out() + } + + override fun onCancelClick() {} + }).build().show() + } + } + + private fun dataObserve() { + userViewModel.userDetailModel.observe(this, { + if (it.code == 200) { + LoadingDialogHub.dismiss() + "同步完成".show(requireContext()) + userData = it.data + updateUserInfo() + } + }) + + versionViewModel.versionResultModel.observe(this, { + if (BuildConfig.VERSION_NAME == it.version) { + "已是最新版本,无需更新".show(requireContext()) + } else { + AlertControlDialog.Builder() + .setContext(requireContext()) + .setTitle("提示") + .setMessage("有新版本,是否更新?") + .setNegativeButton("稍后再说") + .setPositiveButton("立即下载") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + downloadApk(it.downloadUrl) + } + + override fun onCancelClick() { + + } + }).build().show() + } + }) + + loginViewModel.outResultModel.observe(this, { + if (it.code == 200) { + AuthenticationHelper.removeToken() + requireContext().navigatePageTo() + PageNavigationManager.finishAllActivity() + } + }) + } + + private fun collectApplicationCache(): Long { + return File(requireContext().cacheDir.path).calculateSize() + } + + override fun onResume() { + val userDetailJson = SaveKeyValues.getValue(LocaleConstant.USER_DETAIL_MODEL, "") as String + if (userDetailJson.isNotBlank()) { + userData = Gson().fromJson( + userDetailJson, object : TypeToken() {}.type + ) + updateUserInfo() + } + //缓存 + cacheSizeView.text = collectApplicationCache().formatFileSize() + super.onResume() + } + + private fun updateUserInfo() { + //设置头像,圆形,暂时是默认的 +// val roundDrawable = +// BitmapFactory.decodeResource(requireContext().resources, R.mipmap.login_casic) +// .createRoundDrawable( +// requireContext(), +// 3f.dp2px(requireContext()), +// R.color.mainThemeColor.convertColor(requireContext()) +// ) +// userImageView.setImageDrawable(roundDrawable) + userNameView.text = userData.name + userPhoneView.text = String.format("电话:${userData.phone}") + userDeptView.text = String.format("部门:${userData.deptName}") + } + + private fun downloadApk(url: String?) { + progressDialog.setMessage("下载新版本中...") + progressDialog.show() + if (url.toString().isBlank()) { + "抱歉,版本下载失败".show(requireContext()) + return + } + /** + * http://111.198.10.15:11304/static/apk/1.0.1.apk + * */ + val downloadPath = url!!.appendDownloadUrl() + //开始下载 + downloadPath.downloadFile(requireContext().createDownloadFileDir().toString(), + object : OnDownloadListener { + override fun onDownloadStart(totalBytes: Long) { + progressDialog.max = totalBytes.toInt() + } + + override fun onProgressChanged(currentBytes: Long) { + progressDialog.progress = currentBytes.toInt() + } + + override fun onDownloadEnd(file: File?) { + progressDialog.dismiss() + progressDialog.progress = 0 + //安装APK + installApk(file) + } + }) + } + + private fun installApk(apkPackage: File?) { + if (apkPackage == null) { + "文件异常,无法安装".show(requireContext()) + return + } + val intent = Intent(Intent.ACTION_VIEW) + val data: Uri + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { //判断版本大于等于7.0 + data = FileProvider.getUriForFile( + requireContext(), LocaleConstant.APP_AUTHORITY, apkPackage + ) + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) // 给目标应用一个临时授权 + } else { + data = Uri.fromFile(apkPackage) + } + intent.setDataAndType(data, "application/vnd.android.package-archive") + requireContext().startActivity(intent) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java new file mode 100644 index 0000000..467e33d --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java @@ -0,0 +1,43 @@ +package com.casic.smart.town.sanxi.model; + +/** + * 普通实体类,失败/成功数据结构一致 + */ +public class CommonResultModel { + private int code; + private String data; + private String message; + private boolean isSuccess; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return isSuccess; + } + + public void setSuccess(boolean success) { + isSuccess = success; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java new file mode 100644 index 0000000..9bdabef --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java @@ -0,0 +1,62 @@ +package com.casic.smart.town.sanxi.model; + +public class LoginResultModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private String kaptcha; + private String token; + + public String getKaptcha() { + return kaptcha; + } + + public void setKaptcha(String kaptcha) { + this.kaptcha = kaptcha; + } + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java new file mode 100644 index 0000000..09f8d59 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java @@ -0,0 +1,80 @@ +package com.casic.smart.town.sanxi.model; + +public class PublicKeyModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private boolean appKaptcha; + private boolean kaptcha; + private String publicKey; + private String sid; + + public boolean isAppKaptcha() { + return appKaptcha; + } + + public void setAppKaptcha(boolean appKaptcha) { + this.appKaptcha = appKaptcha; + } + + public boolean isKaptcha() { + return kaptcha; + } + + public void setKaptcha(boolean kaptcha) { + this.kaptcha = kaptcha; + } + + public String getPublicKey() { + return publicKey; + } + + public void setPublicKey(String publicKey) { + this.publicKey = publicKey; + } + + public String getSid() { + return sid; + } + + public void setSid(String sid) { + this.sid = sid; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java new file mode 100644 index 0000000..db577fb --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java @@ -0,0 +1,227 @@ +package com.casic.smart.town.sanxi.model; + + +import java.util.List; + +public class UserDetailModel { + private int code; + private Data data; + private String message; + private boolean success; + + public void setCode(int code) { + this.code = code; + } + + public int getCode() { + return code; + } + + public void setData(Data data) { + this.data = data; + } + + public Data getData() { + return data; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getMessage() { + return message; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public boolean getSuccess() { + return success; + } + + public static class Data { + + private String account; + private String attr1; + private String avatar; + private String bizData; + private List dataScope; + private String deptId; + private String deptName; + private List devices; + private String id; + private String ipAddr; + private String name; + private String phone; + private List roleList; + private List roleNames; + private List roleTips; + private String scopeType; + private String sysData; + private String targetId; + private String targetName; + private String tenantId; + + public void setAccount(String account) { + this.account = account; + } + + public String getAccount() { + return account; + } + + public void setAttr1(String attr1) { + this.attr1 = attr1; + } + + public String getAttr1() { + return attr1; + } + + public void setAvatar(String avatar) { + this.avatar = avatar; + } + + public String getAvatar() { + return avatar; + } + + public void setBizData(String bizData) { + this.bizData = bizData; + } + + public String getBizData() { + return bizData; + } + + public void setDataScope(List dataScope) { + this.dataScope = dataScope; + } + + public List getDataScope() { + return dataScope; + } + + public void setDeptId(String deptId) { + this.deptId = deptId; + } + + public String getDeptId() { + return deptId; + } + + public void setDeptName(String deptName) { + this.deptName = deptName; + } + + public String getDeptName() { + return deptName; + } + + public void setDevices(List devices) { + this.devices = devices; + } + + public List getDevices() { + return devices; + } + + public void setId(String id) { + this.id = id; + } + + public String getId() { + return id; + } + + public void setIpAddr(String ipAddr) { + this.ipAddr = ipAddr; + } + + public String getIpAddr() { + return ipAddr; + } + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setPhone(String phone) { + this.phone = phone; + } + + public String getPhone() { + return phone; + } + + public void setRoleList(List roleList) { + this.roleList = roleList; + } + + public List getRoleList() { + return roleList; + } + + public void setRoleNames(List roleNames) { + this.roleNames = roleNames; + } + + public List getRoleNames() { + return roleNames; + } + + public void setRoleTips(List roleTips) { + this.roleTips = roleTips; + } + + public List getRoleTips() { + return roleTips; + } + + public void setScopeType(String scopeType) { + this.scopeType = scopeType; + } + + public String getScopeType() { + return scopeType; + } + + public void setSysData(String sysData) { + this.sysData = sysData; + } + + public String getSysData() { + return sysData; + } + + public void setTargetId(String targetId) { + this.targetId = targetId; + } + + public String getTargetId() { + return targetId; + } + + public void setTargetName(String targetName) { + this.targetName = targetName; + } + + public String getTargetName() { + return targetName; + } + + public void setTenantId(String tenantId) { + this.tenantId = tenantId; + } + + public String getTenantId() { + return tenantId; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/VersionResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/VersionResultModel.java new file mode 100644 index 0000000..79374e0 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/VersionResultModel.java @@ -0,0 +1,61 @@ +package com.casic.smart.town.sanxi.model; + +public class VersionResultModel { + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private String downloadUrl; + private String version; + + public String getDownloadUrl() { + return downloadUrl; + } + + public void setDownloadUrl(String downloadUrl) { + this.downloadUrl = downloadUrl; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/AuthenticationHelper.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/AuthenticationHelper.kt new file mode 100644 index 0000000..f498e4e --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/AuthenticationHelper.kt @@ -0,0 +1,24 @@ +package com.casic.smart.town.sanxi.util + +import com.pengxh.kt.lite.utils.SaveKeyValues + +object AuthenticationHelper { + + fun savePublicKey(key: String) { + SaveKeyValues.putValue("keyString", key) + } + + val publicKey: String? + get() = SaveKeyValues.getValue("keyString", "") as String? + + fun saveToken(token: String?) { + SaveKeyValues.putValue("token", token!!) + } + + val token: String? + get() = SaveKeyValues.getValue("token", "") as String? + + fun removeToken() { + SaveKeyValues.removeKey("token") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt index ab1a36d..99f0d19 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt @@ -14,5 +14,9 @@ const val PAGE_LIMIT = 20 const val DEFAULT_SERVER_CONFIG = "defaultServerConfig" - const val DEFAULT_SERVER = "http://111.198.10.15:11308" + const val DEFAULT_SERVER = "http://36.133.189.112:8082" + const val APP_AUTHORITY = "com.casic.smart.town.sanxi.fileprovider" + const val ACCOUNT = "account" + const val PASSWORD = "password" + const val USER_DETAIL_MODEL = "userDetailModel" } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/RSAUtils.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/RSAUtils.kt new file mode 100644 index 0000000..b9216ba --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/RSAUtils.kt @@ -0,0 +1,55 @@ +package com.casic.smart.town.sanxi.util + +import android.util.Base64 +import java.security.* +import java.security.spec.InvalidKeySpecException +import java.security.spec.X509EncodedKeySpec +import javax.crypto.BadPaddingException +import javax.crypto.Cipher +import javax.crypto.IllegalBlockSizeException +import javax.crypto.NoSuchPaddingException + +object RSAUtils { + //构建Cipher实例时所传入的的字符串,默认为"RSA/NONE/PKCS1Padding" + private fun processData(srcData: ByteArray, key: Key): ByteArray? { //用来保存处理结果 + var resultBytes: ByteArray? = null + try { //获取Cipher实例 + val cipher = Cipher.getInstance("RSA/None/PKCS1Padding") + //初始化Cipher,mode指定是加密还是解密,key为公钥或私钥 + cipher.init(Cipher.ENCRYPT_MODE, key) + //处理数据 + resultBytes = cipher.doFinal(srcData) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: NoSuchPaddingException) { + e.printStackTrace() + } catch (e: InvalidKeyException) { + e.printStackTrace() + } catch (e: BadPaddingException) { + e.printStackTrace() + } catch (e: IllegalBlockSizeException) { + e.printStackTrace() + } + return resultBytes + } + + fun encryptDataByPublicKey(srcData: ByteArray, publicKey: PublicKey): String { + val resultBytes = processData(srcData, publicKey) + return Base64.encodeToString(resultBytes, Base64.DEFAULT) + } + + fun keyStrToPublicKey(publicKeyStr: String?): PublicKey? { + var publicKey: PublicKey? = null + val keyBytes = Base64.decode(publicKeyStr, Base64.DEFAULT) + val keySpec = X509EncodedKeySpec(keyBytes) + try { + val keyFactory = KeyFactory.getInstance("RSA") + publicKey = keyFactory.generatePublic(keySpec) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: InvalidKeySpecException) { + e.printStackTrace() + } + return publicKey + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt index 63db0ec..50d1742 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt @@ -1,12 +1,64 @@ package com.casic.smart.town.sanxi.util.retrofit -import retrofit2.http.GET +import retrofit2.http.* interface RetrofitService { + /** + * PublicKey校验 + */ + @GET("/config/baseConfig") + suspend fun obtainPublicKey(): String + + /** + * 登录并获取Token + * + * @param sid + * @param account 用户名 + * @param secretKey 加密后的密码 + */ + @FormUrlEncoded + @POST("/user/login") + suspend fun login( + @Field("sid") sid: String, + @Field("username") account: String, + @Field("password") secretKey: String + ): String + + /** + * 退出登录 + */ + @GET("/user/logout") + suspend fun loginOut(@Header("token") token: String): String + + /** + * 获取用户信息 + */ + @GET("/user/info") + suspend fun obtainUserDetail(@Header("token") token: String): String + + /** + * 修改密码 + * + * @param oldPwd 旧密码 + * @param newPwd 新密码 + */ + @FormUrlEncoded + @POST("/mgr/changePwd") + suspend fun changePassword( + @Header("token") token: String, + @Field("oldPwd") oldPwd: String, + @Field("newPwd") newPwd: String + ): String /** * 根据布防状态统计闸井数量接口 */ @GET("/well/countByBfzt") - suspend fun countWellByState(): String + suspend fun countWellByState(@Header("token") token: String): String + + /** + * 更新APK版本 + */ + @POST("/app/checkVersion") + suspend fun obtainVersionResult(@Header("token") token: String): String } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt index 46bbf4b..410444b 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt @@ -1,5 +1,6 @@ package com.casic.smart.town.sanxi.util.retrofit +import com.casic.smart.town.sanxi.util.AuthenticationHelper import com.casic.smart.town.sanxi.util.LocaleConstant import com.pengxh.kt.lite.utils.RetrofitFactory import com.pengxh.kt.lite.utils.SaveKeyValues @@ -15,9 +16,52 @@ } /** + * 验证PublicKey + */ + suspend fun authenticate(): String { + return api.obtainPublicKey() + } + + /** + * 登录并获取Token + */ + suspend fun login(sid: String, account: String, secretKey: String): String { + return api.login(sid, account, secretKey) + } + + /** + * 退出登录 + */ + suspend fun loginOut(): String { + return api.loginOut(AuthenticationHelper.token!!) + } + + /** + * 获取用户信息 + */ + suspend fun obtainUserDetail(): String { + return api.obtainUserDetail(AuthenticationHelper.token!!) + } + + /** + * 修改密码 + */ + suspend fun changePassword(oldPwd: String, newPwd: String): String { + return api.changePassword(AuthenticationHelper.token!!, oldPwd, newPwd) + } + + /** * 根据布防状态统计闸井数量接口 */ suspend fun countWellByState(): String { - return api.countWellByState() + return api.countWellByState(AuthenticationHelper.token!!) } + + /** + * 更新APK版本 + */ + suspend fun updateVersion(): String { + return api.obtainVersionResult(AuthenticationHelper.token!!) + } + } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/AboutUsActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/AboutUsActivity.kt new file mode 100644 index 0000000..7e43c47 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/AboutUsActivity.kt @@ -0,0 +1,34 @@ +package com.casic.smart.town.sanxi.view + +import com.casic.smart.town.sanxi.BuildConfig +import com.casic.smart.town.sanxi.R +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.kt.lite.base.KotlinBaseActivity +import com.pengxh.kt.lite.extensions.convertColor +import com.pengxh.kt.lite.utils.ImmerseStatusBarUtil +import kotlinx.android.synthetic.main.activity_about_us.* +import kotlinx.android.synthetic.main.include_base_title.* + +class AboutUsActivity : KotlinBaseActivity() { + + override fun initLayoutView(): Int = R.layout.activity_about_us + + override fun setupTopBarLayout() { + ImmersionBar.with(this).statusBarDarkFont(false).init() + ImmerseStatusBarUtil.setColor(this, R.color.mainThemeColor.convertColor(this)) + titleView.text = "关于我们" + leftBackView.setOnClickListener { finish() } + } + + override fun initData() { + versionCodeView.text = String.format("版本信息:V${BuildConfig.VERSION_NAME}") + } + + override fun observeRequestState() { + + } + + override fun initEvent() { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/LoginActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/LoginActivity.kt new file mode 100644 index 0000000..b5d1d50 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/LoginActivity.kt @@ -0,0 +1,97 @@ +package com.casic.smart.town.sanxi.view + +import androidx.lifecycle.ViewModelProvider +import com.casic.smart.town.sanxi.R +import com.casic.smart.town.sanxi.util.AuthenticationHelper +import com.casic.smart.town.sanxi.util.LoadingDialogHub +import com.casic.smart.town.sanxi.util.LocaleConstant +import com.casic.smart.town.sanxi.util.RSAUtils +import com.casic.smart.town.sanxi.vm.AuthenticateViewModel +import com.casic.smart.town.sanxi.vm.LoginViewModel +import com.casic.smart.town.sanxi.vm.UserViewModel +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.kt.lite.base.KotlinBaseActivity +import com.pengxh.kt.lite.extensions.navigatePageTo +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.utils.SaveKeyValues +import kotlinx.android.synthetic.main.activity_login.* + +class LoginActivity : KotlinBaseActivity() { + + private lateinit var authenticateViewModel: AuthenticateViewModel + private lateinit var loginViewModel: LoginViewModel + private lateinit var userViewModel: UserViewModel + + override fun initLayoutView(): Int = R.layout.activity_login + + override fun setupTopBarLayout() { + ImmersionBar.with(this).statusBarDarkFont(true).init() + } + + override fun initData() { + // 设置默认账号密码 + userNameView.setText(SaveKeyValues.getValue(LocaleConstant.ACCOUNT, "") as String) + userPasswordView.setText(SaveKeyValues.getValue(LocaleConstant.PASSWORD, "") as String) + authenticateViewModel = ViewModelProvider(this)[AuthenticateViewModel::class.java] + loginViewModel = ViewModelProvider(this)[LoginViewModel::class.java] + userViewModel = ViewModelProvider(this)[UserViewModel::class.java] + + authenticateViewModel.keyModel.observe(this, { + if (it.code == 200) {//用code判断,别的判断可能有坑 + val keyString = it.data!!.publicKey!! + /** + * 修改密码需要用到key,先存着 + * */ + AuthenticationHelper.savePublicKey(keyString) + val publicKey = RSAUtils.keyStrToPublicKey(keyString) + + val account = userNameView.text.toString() + val userPassword = userPasswordView.text.toString() + val dataByPublicKey = + RSAUtils.encryptDataByPublicKey(userPassword.toByteArray(), publicKey!!) + //登录并获取Token,POST请求 + loginViewModel.enter(it.data!!.sid!!, account, dataByPublicKey) + loginViewModel.enterResultModel.observe(this, { loginResult -> + if (loginResult.code == 200) { + AuthenticationHelper.saveToken(loginResult.data!!.token!!) + /** + * 获取token之后保存用户信息 + * */ + userViewModel.obtainUserDetail() + //验证成功登录 + navigatePageTo() + finish() + } + }) + } + }) + } + + override fun observeRequestState() { + authenticateViewModel.loadState.observe(this, { + LoadingDialogHub.show(this, "登录中,请稍后") + }) + + loginViewModel.loadState.observe(this, { + LoadingDialogHub.dismiss() + }) + } + + override fun initEvent() { + loginButton.setOnClickListener { + val account = userNameView.text.toString().trim() + val userPassword = userPasswordView.text.toString().trim() + if (account.isBlank()) { + "用户名不能为空".show(this) + return@setOnClickListener + } + if (userPassword.isBlank()) { + "密码不能为空".show(this) + return@setOnClickListener + } + SaveKeyValues.putValue(LocaleConstant.ACCOUNT, account) + SaveKeyValues.putValue(LocaleConstant.PASSWORD, userPassword) + authenticateViewModel.obtainPublicKey() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt index 3bea1c9..a4fa951 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt @@ -7,6 +7,7 @@ import com.casic.smart.town.sanxi.R import com.casic.smart.town.sanxi.adapter.ViewPagerAdapter import com.casic.smart.town.sanxi.fragment.AlarmPageFragment +import com.casic.smart.town.sanxi.fragment.MinePageFragment import com.casic.smart.town.sanxi.fragment.MonitorPageFragment import com.casic.smart.town.sanxi.fragment.StatisticsPageFragment import com.gyf.immersionbar.ImmersionBar @@ -33,6 +34,7 @@ fragmentPages.add(StatisticsPageFragment()) fragmentPages.add(MonitorPageFragment()) fragmentPages.add(AlarmPageFragment()) + fragmentPages.add(MinePageFragment()) } override fun initLayoutView(): Int = R.layout.activity_main @@ -68,6 +70,9 @@ R.id.nav_alarm -> { mainViewPager.currentItem = 2 } + R.id.nav_more -> { + mainViewPager.currentItem = 3 + } } false } diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/PermissionActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/PermissionActivity.kt index c478d83..3238f3c 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/view/PermissionActivity.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/PermissionActivity.kt @@ -32,7 +32,7 @@ //先把导航隐私政策声明,后面导航会用到 MapsInitializer.updatePrivacyAgree(this, true) MapsInitializer.updatePrivacyShow(this, true, true) - this.navigatePageTo() + this.navigatePageTo() finish() } diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/SplashScreenActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/SplashScreenActivity.kt new file mode 100644 index 0000000..fa5a74d --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/SplashScreenActivity.kt @@ -0,0 +1,54 @@ +package com.casic.smart.town.sanxi.view + +import android.annotation.SuppressLint +import android.os.CountDownTimer +import androidx.lifecycle.ViewModelProvider +import com.casic.smart.town.sanxi.R +import com.casic.smart.town.sanxi.vm.UserViewModel +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.kt.lite.base.KotlinBaseActivity +import com.pengxh.kt.lite.extensions.navigatePageTo + +@SuppressLint("CustomSplashScreen") +class SplashScreenActivity : KotlinBaseActivity() { + + private lateinit var userDetailViewModel: UserViewModel + private val countDownTimer = object : CountDownTimer(1000, 500) { + override fun onFinish() { + /** + * 获取token之后保存用户信息 + * */ + userDetailViewModel.obtainUserDetail() + userDetailViewModel.userDetailModel.observe(this@SplashScreenActivity, { + if (it.code == 200) { + navigatePageTo() + } else { + navigatePageTo() + } + finish() + }) + } + + override fun onTick(millisUntilFinished: Long) { + + } + } + + override fun initLayoutView(): Int = R.layout.activity_splash + + override fun setupTopBarLayout() { + ImmersionBar.with(this).statusBarDarkFont(false).init() + } + + override fun initData() { + userDetailViewModel = ViewModelProvider(this).get(UserViewModel::class.java) + } + + override fun observeRequestState() { + + } + + override fun initEvent() { + countDownTimer.start() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/vm/AuthenticateViewModel.kt b/app/src/main/java/com/casic/smart/town/sanxi/vm/AuthenticateViewModel.kt new file mode 100644 index 0000000..87ddbf8 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/vm/AuthenticateViewModel.kt @@ -0,0 +1,35 @@ +package com.casic.smart.town.sanxi.vm + +import androidx.lifecycle.MutableLiveData +import com.casic.smart.town.sanxi.base.BaseApplication +import com.casic.smart.town.sanxi.extensions.separateResponseCode +import com.casic.smart.town.sanxi.extensions.toErrorMessage +import com.casic.smart.town.sanxi.model.PublicKeyModel +import com.casic.smart.town.sanxi.util.retrofit.RetrofitServiceManager +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.launch +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.vm.BaseViewModel +import com.pengxh.kt.lite.vm.LoadState + +class AuthenticateViewModel : BaseViewModel() { + + private val gson = Gson() + val keyModel = MutableLiveData() + + fun obtainPublicKey() = launch({ + loadState.value = LoadState.Loading + val response = RetrofitServiceManager.authenticate() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + keyModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ) + } else { + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + it.printStackTrace() + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/vm/LoginViewModel.kt b/app/src/main/java/com/casic/smart/town/sanxi/vm/LoginViewModel.kt new file mode 100644 index 0000000..c377c61 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/vm/LoginViewModel.kt @@ -0,0 +1,54 @@ +package com.casic.smart.town.sanxi.vm + +import androidx.lifecycle.MutableLiveData +import com.casic.smart.town.sanxi.base.BaseApplication +import com.casic.smart.town.sanxi.extensions.separateResponseCode +import com.casic.smart.town.sanxi.extensions.toErrorMessage +import com.casic.smart.town.sanxi.model.CommonResultModel +import com.casic.smart.town.sanxi.model.LoginResultModel +import com.casic.smart.town.sanxi.util.retrofit.RetrofitServiceManager +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.launch +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.vm.BaseViewModel +import com.pengxh.kt.lite.vm.LoadState + +class LoginViewModel : BaseViewModel() { + + private val gson = Gson() + val enterResultModel = MutableLiveData() + val outResultModel = MutableLiveData() + + fun enter(sid: String, account: String, secretKey: String) = launch({ + val response = RetrofitServiceManager.login(sid, account, secretKey) + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + loadState.value = LoadState.Success + "登录成功".show(BaseApplication.obtainInstance()) + enterResultModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ) + } else { + loadState.value = LoadState.Fail + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + loadState.value = LoadState.Fail + it.printStackTrace() + }) + + fun out() = launch({ + val response = RetrofitServiceManager.loginOut() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + outResultModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ) + } else { + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + it.printStackTrace() + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/vm/UserViewModel.kt b/app/src/main/java/com/casic/smart/town/sanxi/vm/UserViewModel.kt new file mode 100644 index 0000000..29a8756 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/vm/UserViewModel.kt @@ -0,0 +1,57 @@ +package com.casic.smart.town.sanxi.vm + +import androidx.lifecycle.MutableLiveData +import com.casic.smart.town.sanxi.base.BaseApplication +import com.casic.smart.town.sanxi.extensions.separateResponseCode +import com.casic.smart.town.sanxi.extensions.toErrorMessage +import com.casic.smart.town.sanxi.model.UserDetailModel +import com.casic.smart.town.sanxi.util.LocaleConstant +import com.casic.smart.town.sanxi.util.retrofit.RetrofitServiceManager +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.launch +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.utils.SaveKeyValues +import com.pengxh.kt.lite.vm.BaseViewModel +import com.pengxh.kt.lite.vm.LoadState + +class UserViewModel : BaseViewModel() { + + private val gson = Gson() + val userDetailModel = MutableLiveData() + + fun obtainUserDetail() = launch({ + val response = RetrofitServiceManager.obtainUserDetail() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + val userDetail = gson.fromJson( + response, object : TypeToken() {}.type + ) + userDetailModel.value = userDetail + SaveKeyValues.putValue(LocaleConstant.USER_DETAIL_MODEL, gson.toJson(userDetail.data)) + } else { + val errorModel = UserDetailModel() + errorModel.code = responseCode + userDetailModel.value = errorModel + //如果此次获取不到用户信息,那么就清空之前的用户缓存,然后让用户重新登录 + SaveKeyValues.removeKey(LocaleConstant.USER_DETAIL_MODEL) + } + }, { + it.printStackTrace() + }) + + fun changePassword(oldPwd: String, newPwd: String) = launch({ + loadState.value = LoadState.Loading + val response = RetrofitServiceManager.changePassword(oldPwd, newPwd) + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + loadState.value = LoadState.Success + } else { + loadState.value = LoadState.Fail + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + loadState.value = LoadState.Fail + it.printStackTrace() + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/vm/VersionViewModel.kt b/app/src/main/java/com/casic/smart/town/sanxi/vm/VersionViewModel.kt new file mode 100644 index 0000000..20ea457 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/vm/VersionViewModel.kt @@ -0,0 +1,38 @@ +package com.casic.smart.town.sanxi.vm + +import androidx.lifecycle.MutableLiveData +import com.casic.smart.town.sanxi.base.BaseApplication +import com.casic.smart.town.sanxi.extensions.separateResponseCode +import com.casic.smart.town.sanxi.extensions.toErrorMessage +import com.casic.smart.town.sanxi.model.VersionResultModel +import com.casic.smart.town.sanxi.util.retrofit.RetrofitServiceManager +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.launch +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.vm.BaseViewModel +import com.pengxh.kt.lite.vm.LoadState + +class VersionViewModel : BaseViewModel() { + + private val gson = Gson() + val versionResultModel = MutableLiveData() + + fun updateVersion() = launch({ + loadState.value = LoadState.Loading + val response = RetrofitServiceManager.updateVersion() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + loadState.value = LoadState.Success + versionResultModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ).data + } else { + loadState.value = LoadState.Fail + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + loadState.value = LoadState.Fail + it.printStackTrace() + }) +} \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_solid_layout_gray_radius_5.xml b/app/src/main/res/drawable/bg_solid_layout_gray_radius_5.xml new file mode 100644 index 0000000..96eb93e --- /dev/null +++ b/app/src/main/res/drawable/bg_solid_layout_gray_radius_5.xml @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/button_main_selector.xml b/app/src/main/res/drawable/button_main_selector.xml new file mode 100644 index 0000000..5875998 --- /dev/null +++ b/app/src/main/res/drawable/button_main_selector.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/button_white_selector.xml b/app/src/main/res/drawable/button_white_selector.xml new file mode 100644 index 0000000..4dd3c0b --- /dev/null +++ b/app/src/main/res/drawable/button_white_selector.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/download_progress.xml b/app/src/main/res/drawable/download_progress.xml new file mode 100644 index 0000000..493d923 --- /dev/null +++ b/app/src/main/res/drawable/download_progress.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_about.xml b/app/src/main/res/drawable/ic_about.xml new file mode 100644 index 0000000..8469319 --- /dev/null +++ b/app/src/main/res/drawable/ic_about.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_change_pwd.xml b/app/src/main/res/drawable/ic_change_pwd.xml new file mode 100644 index 0000000..75caa39 --- /dev/null +++ b/app/src/main/res/drawable/ic_change_pwd.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/ic_clean.xml b/app/src/main/res/drawable/ic_clean.xml new file mode 100644 index 0000000..027f809 --- /dev/null +++ b/app/src/main/res/drawable/ic_clean.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_left.xml b/app/src/main/res/drawable/ic_left.xml new file mode 100644 index 0000000..04ee5da --- /dev/null +++ b/app/src/main/res/drawable/ic_left.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_lock.xml b/app/src/main/res/drawable/ic_lock.xml new file mode 100644 index 0000000..f0a5610 --- /dev/null +++ b/app/src/main/res/drawable/ic_lock.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/ic_more.xml b/app/src/main/res/drawable/ic_more.xml new file mode 100644 index 0000000..0794eaa --- /dev/null +++ b/app/src/main/res/drawable/ic_more.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_more_selected.xml b/app/src/main/res/drawable/ic_more_selected.xml new file mode 100644 index 0000000..9b6d3df --- /dev/null +++ b/app/src/main/res/drawable/ic_more_selected.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_phone.xml b/app/src/main/res/drawable/ic_phone.xml new file mode 100644 index 0000000..253f843 --- /dev/null +++ b/app/src/main/res/drawable/ic_phone.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_right.xml b/app/src/main/res/drawable/ic_right.xml new file mode 100644 index 0000000..6020f09 --- /dev/null +++ b/app/src/main/res/drawable/ic_right.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_update_version.xml b/app/src/main/res/drawable/ic_update_version.xml new file mode 100644 index 0000000..3a62d63 --- /dev/null +++ b/app/src/main/res/drawable/ic_update_version.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_user.xml b/app/src/main/res/drawable/ic_user.xml new file mode 100644 index 0000000..8c4a29d --- /dev/null +++ b/app/src/main/res/drawable/ic_user.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/drawable/more_selector.xml b/app/src/main/res/drawable/more_selector.xml new file mode 100644 index 0000000..a150a48 --- /dev/null +++ b/app/src/main/res/drawable/more_selector.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_about_us.xml b/app/src/main/res/layout/activity_about_us.xml new file mode 100644 index 0000000..9b2d9e5 --- /dev/null +++ b/app/src/main/res/layout/activity_about_us.xml @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1df917d..8d4f33b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -6,6 +6,7 @@ + @@ -32,11 +33,14 @@ + + + + + + + diff --git a/app/src/main/assets/svg/mine.svg b/app/src/main/assets/svg/mine.svg new file mode 100644 index 0000000..3fbb0a3 --- /dev/null +++ b/app/src/main/assets/svg/mine.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt b/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt index d730417..b1972c2 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/extensions/String.kt @@ -1,8 +1,10 @@ package com.casic.smart.town.sanxi.extensions import com.casic.smart.town.sanxi.model.ErrorMessageModel +import com.casic.smart.town.sanxi.util.LocaleConstant import com.google.gson.Gson import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.utils.SaveKeyValues import org.json.JSONObject fun String.separateResponseCode(): Int { @@ -17,4 +19,15 @@ this, object : TypeToken() {}.type ) return errorModel.message.toString() +} + +/** + * 下载路径为 http://xx.com/static/ 拼接downloadUrl + * */ +fun String.appendDownloadUrl(): String { + if (this.isEmpty()) return this + val defaultValue = SaveKeyValues.getValue( + LocaleConstant.DEFAULT_SERVER_CONFIG, LocaleConstant.DEFAULT_SERVER + ) as String + return "$defaultValue/static/${this}" } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt b/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt new file mode 100644 index 0000000..1809dc5 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/fragment/MinePageFragment.kt @@ -0,0 +1,288 @@ +package com.casic.smart.town.sanxi.fragment + +import android.app.ProgressDialog +import android.content.Intent +import android.net.Uri +import android.os.Build +import android.os.CountDownTimer +import androidx.core.content.FileProvider +import androidx.lifecycle.ViewModelProvider +import com.casic.smart.town.sanxi.BuildConfig +import com.casic.smart.town.sanxi.R +import com.casic.smart.town.sanxi.extensions.appendDownloadUrl +import com.casic.smart.town.sanxi.model.UserDetailModel +import com.casic.smart.town.sanxi.util.AuthenticationHelper +import com.casic.smart.town.sanxi.util.LoadingDialogHub +import com.casic.smart.town.sanxi.util.LocaleConstant +import com.casic.smart.town.sanxi.util.RSAUtils +import com.casic.smart.town.sanxi.view.AboutUsActivity +import com.casic.smart.town.sanxi.view.LoginActivity +import com.casic.smart.town.sanxi.vm.LoginViewModel +import com.casic.smart.town.sanxi.vm.UserViewModel +import com.casic.smart.town.sanxi.vm.VersionViewModel +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.base.KotlinBaseFragment +import com.pengxh.kt.lite.callback.OnDownloadListener +import com.pengxh.kt.lite.extensions.* +import com.pengxh.kt.lite.utils.PageNavigationManager +import com.pengxh.kt.lite.utils.SaveKeyValues +import com.pengxh.kt.lite.vm.LoadState +import com.pengxh.kt.lite.widget.dialog.AlertControlDialog +import com.pengxh.kt.lite.widget.dialog.ChangePasswordDialog +import kotlinx.android.synthetic.main.fragment_mine.* +import java.io.File +import java.nio.charset.StandardCharsets + +class MinePageFragment : KotlinBaseFragment() { + + private lateinit var userData: UserDetailModel.Data + private lateinit var userViewModel: UserViewModel + private lateinit var loginViewModel: LoginViewModel + private lateinit var versionViewModel: VersionViewModel + private lateinit var progressDialog: ProgressDialog + + override fun initLayoutView(): Int = R.layout.fragment_mine + + override fun setupTopBarLayout() { + + } + + override fun initData() { + userViewModel = ViewModelProvider(this)[UserViewModel::class.java] + loginViewModel = ViewModelProvider(this)[LoginViewModel::class.java] + versionViewModel = ViewModelProvider(this)[VersionViewModel::class.java] + + //初始化下载对话框 + progressDialog = ProgressDialog(requireContext()) + progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL) + progressDialog.setProgressDrawable(resources.getDrawable(R.drawable.download_progress)) + progressDialog.setCanceledOnTouchOutside(false) + progressDialog.setCancelable(false) + + /** + * 数据监听 + * */ + dataObserve() + } + + override fun observeRequestState() { + userViewModel.loadState.observe(this, { + when (it) { + is LoadState.Loading -> LoadingDialogHub.show(requireActivity(), "修改中,请稍后") + is LoadState.Success -> { + "修改成功,请重新登录".show(requireContext()) + LoadingDialogHub.dismiss() + AuthenticationHelper.removeToken() + requireContext().navigatePageTo() + PageNavigationManager.finishAllActivity() + } + else -> LoadingDialogHub.dismiss() + } + }) + + versionViewModel.loadState.observe(this, { + when (it) { + is LoadState.Loading -> LoadingDialogHub.show(requireActivity(), "检查版本中,请稍后") + else -> LoadingDialogHub.dismiss() + } + }) + } + + override fun initEvent() { + userImageView.setOnClickListener { + "尽情期待~".show(requireContext()) + } + + updateUserButton.setOnClickListener { + LoadingDialogHub.show(requireActivity(), "同步中,请稍后...") + userViewModel.obtainUserDetail() + } + + changePwdLayout.setOnClickListener { + ChangePasswordDialog.Builder() + .setContext(requireContext()) + .setOnDialogButtonClickListener(object : + ChangePasswordDialog.OnDialogButtonClickListener { + override fun onConfirmClick(oldPwd: String, newPwd: String) { + val publicKey = + RSAUtils.keyStrToPublicKey(AuthenticationHelper.publicKey)!! + val oldPassKey = RSAUtils.encryptDataByPublicKey( + oldPwd.toByteArray(StandardCharsets.UTF_8), + publicKey + ) + val newPassKey = RSAUtils.encryptDataByPublicKey( + newPwd.toByteArray(StandardCharsets.UTF_8), + publicKey + ) + userViewModel.changePassword(oldPassKey, newPassKey) + } + }).build().show() + } + + aboutUsLayout.setOnClickListener { + requireContext().navigatePageTo() + } + + updateVersionLayout.setOnClickListener { + versionViewModel.updateVersion() + } + + clearCacheLayout.setOnClickListener { + //删除缓存之后在设置缓存大小 + LoadingDialogHub.show(requireActivity(), "清理中,请稍后") + File(requireContext().cacheDir.path).deleteFile() + object : CountDownTimer(1500, 500) { + override fun onTick(millisUntilFinished: Long) { + + } + + override fun onFinish() { + LoadingDialogHub.dismiss() + cacheSizeView.text = collectApplicationCache().formatFileSize() + } + }.start() + } + + loginOutButton.setOnClickListener { + AlertControlDialog.Builder() + .setContext(requireContext()) + .setTitle("退出登录") + .setMessage("确定要退出吗?") + .setNegativeButton("取消") + .setPositiveButton("确定") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + loginViewModel.out() + } + + override fun onCancelClick() {} + }).build().show() + } + } + + private fun dataObserve() { + userViewModel.userDetailModel.observe(this, { + if (it.code == 200) { + LoadingDialogHub.dismiss() + "同步完成".show(requireContext()) + userData = it.data + updateUserInfo() + } + }) + + versionViewModel.versionResultModel.observe(this, { + if (BuildConfig.VERSION_NAME == it.version) { + "已是最新版本,无需更新".show(requireContext()) + } else { + AlertControlDialog.Builder() + .setContext(requireContext()) + .setTitle("提示") + .setMessage("有新版本,是否更新?") + .setNegativeButton("稍后再说") + .setPositiveButton("立即下载") + .setOnDialogButtonClickListener(object : + AlertControlDialog.OnDialogButtonClickListener { + override fun onConfirmClick() { + downloadApk(it.downloadUrl) + } + + override fun onCancelClick() { + + } + }).build().show() + } + }) + + loginViewModel.outResultModel.observe(this, { + if (it.code == 200) { + AuthenticationHelper.removeToken() + requireContext().navigatePageTo() + PageNavigationManager.finishAllActivity() + } + }) + } + + private fun collectApplicationCache(): Long { + return File(requireContext().cacheDir.path).calculateSize() + } + + override fun onResume() { + val userDetailJson = SaveKeyValues.getValue(LocaleConstant.USER_DETAIL_MODEL, "") as String + if (userDetailJson.isNotBlank()) { + userData = Gson().fromJson( + userDetailJson, object : TypeToken() {}.type + ) + updateUserInfo() + } + //缓存 + cacheSizeView.text = collectApplicationCache().formatFileSize() + super.onResume() + } + + private fun updateUserInfo() { + //设置头像,圆形,暂时是默认的 +// val roundDrawable = +// BitmapFactory.decodeResource(requireContext().resources, R.mipmap.login_casic) +// .createRoundDrawable( +// requireContext(), +// 3f.dp2px(requireContext()), +// R.color.mainThemeColor.convertColor(requireContext()) +// ) +// userImageView.setImageDrawable(roundDrawable) + userNameView.text = userData.name + userPhoneView.text = String.format("电话:${userData.phone}") + userDeptView.text = String.format("部门:${userData.deptName}") + } + + private fun downloadApk(url: String?) { + progressDialog.setMessage("下载新版本中...") + progressDialog.show() + if (url.toString().isBlank()) { + "抱歉,版本下载失败".show(requireContext()) + return + } + /** + * http://111.198.10.15:11304/static/apk/1.0.1.apk + * */ + val downloadPath = url!!.appendDownloadUrl() + //开始下载 + downloadPath.downloadFile(requireContext().createDownloadFileDir().toString(), + object : OnDownloadListener { + override fun onDownloadStart(totalBytes: Long) { + progressDialog.max = totalBytes.toInt() + } + + override fun onProgressChanged(currentBytes: Long) { + progressDialog.progress = currentBytes.toInt() + } + + override fun onDownloadEnd(file: File?) { + progressDialog.dismiss() + progressDialog.progress = 0 + //安装APK + installApk(file) + } + }) + } + + private fun installApk(apkPackage: File?) { + if (apkPackage == null) { + "文件异常,无法安装".show(requireContext()) + return + } + val intent = Intent(Intent.ACTION_VIEW) + val data: Uri + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { //判断版本大于等于7.0 + data = FileProvider.getUriForFile( + requireContext(), LocaleConstant.APP_AUTHORITY, apkPackage + ) + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) // 给目标应用一个临时授权 + } else { + data = Uri.fromFile(apkPackage) + } + intent.setDataAndType(data, "application/vnd.android.package-archive") + requireContext().startActivity(intent) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java new file mode 100644 index 0000000..467e33d --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/CommonResultModel.java @@ -0,0 +1,43 @@ +package com.casic.smart.town.sanxi.model; + +/** + * 普通实体类,失败/成功数据结构一致 + */ +public class CommonResultModel { + private int code; + private String data; + private String message; + private boolean isSuccess; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return isSuccess; + } + + public void setSuccess(boolean success) { + isSuccess = success; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java new file mode 100644 index 0000000..9bdabef --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/LoginResultModel.java @@ -0,0 +1,62 @@ +package com.casic.smart.town.sanxi.model; + +public class LoginResultModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private String kaptcha; + private String token; + + public String getKaptcha() { + return kaptcha; + } + + public void setKaptcha(String kaptcha) { + this.kaptcha = kaptcha; + } + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java new file mode 100644 index 0000000..09f8d59 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/PublicKeyModel.java @@ -0,0 +1,80 @@ +package com.casic.smart.town.sanxi.model; + +public class PublicKeyModel { + + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private boolean appKaptcha; + private boolean kaptcha; + private String publicKey; + private String sid; + + public boolean isAppKaptcha() { + return appKaptcha; + } + + public void setAppKaptcha(boolean appKaptcha) { + this.appKaptcha = appKaptcha; + } + + public boolean isKaptcha() { + return kaptcha; + } + + public void setKaptcha(boolean kaptcha) { + this.kaptcha = kaptcha; + } + + public String getPublicKey() { + return publicKey; + } + + public void setPublicKey(String publicKey) { + this.publicKey = publicKey; + } + + public String getSid() { + return sid; + } + + public void setSid(String sid) { + this.sid = sid; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java new file mode 100644 index 0000000..db577fb --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/UserDetailModel.java @@ -0,0 +1,227 @@ +package com.casic.smart.town.sanxi.model; + + +import java.util.List; + +public class UserDetailModel { + private int code; + private Data data; + private String message; + private boolean success; + + public void setCode(int code) { + this.code = code; + } + + public int getCode() { + return code; + } + + public void setData(Data data) { + this.data = data; + } + + public Data getData() { + return data; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getMessage() { + return message; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public boolean getSuccess() { + return success; + } + + public static class Data { + + private String account; + private String attr1; + private String avatar; + private String bizData; + private List dataScope; + private String deptId; + private String deptName; + private List devices; + private String id; + private String ipAddr; + private String name; + private String phone; + private List roleList; + private List roleNames; + private List roleTips; + private String scopeType; + private String sysData; + private String targetId; + private String targetName; + private String tenantId; + + public void setAccount(String account) { + this.account = account; + } + + public String getAccount() { + return account; + } + + public void setAttr1(String attr1) { + this.attr1 = attr1; + } + + public String getAttr1() { + return attr1; + } + + public void setAvatar(String avatar) { + this.avatar = avatar; + } + + public String getAvatar() { + return avatar; + } + + public void setBizData(String bizData) { + this.bizData = bizData; + } + + public String getBizData() { + return bizData; + } + + public void setDataScope(List dataScope) { + this.dataScope = dataScope; + } + + public List getDataScope() { + return dataScope; + } + + public void setDeptId(String deptId) { + this.deptId = deptId; + } + + public String getDeptId() { + return deptId; + } + + public void setDeptName(String deptName) { + this.deptName = deptName; + } + + public String getDeptName() { + return deptName; + } + + public void setDevices(List devices) { + this.devices = devices; + } + + public List getDevices() { + return devices; + } + + public void setId(String id) { + this.id = id; + } + + public String getId() { + return id; + } + + public void setIpAddr(String ipAddr) { + this.ipAddr = ipAddr; + } + + public String getIpAddr() { + return ipAddr; + } + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setPhone(String phone) { + this.phone = phone; + } + + public String getPhone() { + return phone; + } + + public void setRoleList(List roleList) { + this.roleList = roleList; + } + + public List getRoleList() { + return roleList; + } + + public void setRoleNames(List roleNames) { + this.roleNames = roleNames; + } + + public List getRoleNames() { + return roleNames; + } + + public void setRoleTips(List roleTips) { + this.roleTips = roleTips; + } + + public List getRoleTips() { + return roleTips; + } + + public void setScopeType(String scopeType) { + this.scopeType = scopeType; + } + + public String getScopeType() { + return scopeType; + } + + public void setSysData(String sysData) { + this.sysData = sysData; + } + + public String getSysData() { + return sysData; + } + + public void setTargetId(String targetId) { + this.targetId = targetId; + } + + public String getTargetId() { + return targetId; + } + + public void setTargetName(String targetName) { + this.targetName = targetName; + } + + public String getTargetName() { + return targetName; + } + + public void setTenantId(String tenantId) { + this.tenantId = tenantId; + } + + public String getTenantId() { + return tenantId; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/model/VersionResultModel.java b/app/src/main/java/com/casic/smart/town/sanxi/model/VersionResultModel.java new file mode 100644 index 0000000..79374e0 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/model/VersionResultModel.java @@ -0,0 +1,61 @@ +package com.casic.smart.town.sanxi.model; + +public class VersionResultModel { + private int code; + private DataBean data; + private String message; + private boolean success; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public DataBean getData() { + return data; + } + + public void setData(DataBean data) { + this.data = data; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public static class DataBean { + private String downloadUrl; + private String version; + + public String getDownloadUrl() { + return downloadUrl; + } + + public void setDownloadUrl(String downloadUrl) { + this.downloadUrl = downloadUrl; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + } +} diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/AuthenticationHelper.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/AuthenticationHelper.kt new file mode 100644 index 0000000..f498e4e --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/AuthenticationHelper.kt @@ -0,0 +1,24 @@ +package com.casic.smart.town.sanxi.util + +import com.pengxh.kt.lite.utils.SaveKeyValues + +object AuthenticationHelper { + + fun savePublicKey(key: String) { + SaveKeyValues.putValue("keyString", key) + } + + val publicKey: String? + get() = SaveKeyValues.getValue("keyString", "") as String? + + fun saveToken(token: String?) { + SaveKeyValues.putValue("token", token!!) + } + + val token: String? + get() = SaveKeyValues.getValue("token", "") as String? + + fun removeToken() { + SaveKeyValues.removeKey("token") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt index ab1a36d..99f0d19 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/LocaleConstant.kt @@ -14,5 +14,9 @@ const val PAGE_LIMIT = 20 const val DEFAULT_SERVER_CONFIG = "defaultServerConfig" - const val DEFAULT_SERVER = "http://111.198.10.15:11308" + const val DEFAULT_SERVER = "http://36.133.189.112:8082" + const val APP_AUTHORITY = "com.casic.smart.town.sanxi.fileprovider" + const val ACCOUNT = "account" + const val PASSWORD = "password" + const val USER_DETAIL_MODEL = "userDetailModel" } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/RSAUtils.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/RSAUtils.kt new file mode 100644 index 0000000..b9216ba --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/RSAUtils.kt @@ -0,0 +1,55 @@ +package com.casic.smart.town.sanxi.util + +import android.util.Base64 +import java.security.* +import java.security.spec.InvalidKeySpecException +import java.security.spec.X509EncodedKeySpec +import javax.crypto.BadPaddingException +import javax.crypto.Cipher +import javax.crypto.IllegalBlockSizeException +import javax.crypto.NoSuchPaddingException + +object RSAUtils { + //构建Cipher实例时所传入的的字符串,默认为"RSA/NONE/PKCS1Padding" + private fun processData(srcData: ByteArray, key: Key): ByteArray? { //用来保存处理结果 + var resultBytes: ByteArray? = null + try { //获取Cipher实例 + val cipher = Cipher.getInstance("RSA/None/PKCS1Padding") + //初始化Cipher,mode指定是加密还是解密,key为公钥或私钥 + cipher.init(Cipher.ENCRYPT_MODE, key) + //处理数据 + resultBytes = cipher.doFinal(srcData) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: NoSuchPaddingException) { + e.printStackTrace() + } catch (e: InvalidKeyException) { + e.printStackTrace() + } catch (e: BadPaddingException) { + e.printStackTrace() + } catch (e: IllegalBlockSizeException) { + e.printStackTrace() + } + return resultBytes + } + + fun encryptDataByPublicKey(srcData: ByteArray, publicKey: PublicKey): String { + val resultBytes = processData(srcData, publicKey) + return Base64.encodeToString(resultBytes, Base64.DEFAULT) + } + + fun keyStrToPublicKey(publicKeyStr: String?): PublicKey? { + var publicKey: PublicKey? = null + val keyBytes = Base64.decode(publicKeyStr, Base64.DEFAULT) + val keySpec = X509EncodedKeySpec(keyBytes) + try { + val keyFactory = KeyFactory.getInstance("RSA") + publicKey = keyFactory.generatePublic(keySpec) + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } catch (e: InvalidKeySpecException) { + e.printStackTrace() + } + return publicKey + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt index 63db0ec..50d1742 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitService.kt @@ -1,12 +1,64 @@ package com.casic.smart.town.sanxi.util.retrofit -import retrofit2.http.GET +import retrofit2.http.* interface RetrofitService { + /** + * PublicKey校验 + */ + @GET("/config/baseConfig") + suspend fun obtainPublicKey(): String + + /** + * 登录并获取Token + * + * @param sid + * @param account 用户名 + * @param secretKey 加密后的密码 + */ + @FormUrlEncoded + @POST("/user/login") + suspend fun login( + @Field("sid") sid: String, + @Field("username") account: String, + @Field("password") secretKey: String + ): String + + /** + * 退出登录 + */ + @GET("/user/logout") + suspend fun loginOut(@Header("token") token: String): String + + /** + * 获取用户信息 + */ + @GET("/user/info") + suspend fun obtainUserDetail(@Header("token") token: String): String + + /** + * 修改密码 + * + * @param oldPwd 旧密码 + * @param newPwd 新密码 + */ + @FormUrlEncoded + @POST("/mgr/changePwd") + suspend fun changePassword( + @Header("token") token: String, + @Field("oldPwd") oldPwd: String, + @Field("newPwd") newPwd: String + ): String /** * 根据布防状态统计闸井数量接口 */ @GET("/well/countByBfzt") - suspend fun countWellByState(): String + suspend fun countWellByState(@Header("token") token: String): String + + /** + * 更新APK版本 + */ + @POST("/app/checkVersion") + suspend fun obtainVersionResult(@Header("token") token: String): String } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt index 46bbf4b..410444b 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/util/retrofit/RetrofitServiceManager.kt @@ -1,5 +1,6 @@ package com.casic.smart.town.sanxi.util.retrofit +import com.casic.smart.town.sanxi.util.AuthenticationHelper import com.casic.smart.town.sanxi.util.LocaleConstant import com.pengxh.kt.lite.utils.RetrofitFactory import com.pengxh.kt.lite.utils.SaveKeyValues @@ -15,9 +16,52 @@ } /** + * 验证PublicKey + */ + suspend fun authenticate(): String { + return api.obtainPublicKey() + } + + /** + * 登录并获取Token + */ + suspend fun login(sid: String, account: String, secretKey: String): String { + return api.login(sid, account, secretKey) + } + + /** + * 退出登录 + */ + suspend fun loginOut(): String { + return api.loginOut(AuthenticationHelper.token!!) + } + + /** + * 获取用户信息 + */ + suspend fun obtainUserDetail(): String { + return api.obtainUserDetail(AuthenticationHelper.token!!) + } + + /** + * 修改密码 + */ + suspend fun changePassword(oldPwd: String, newPwd: String): String { + return api.changePassword(AuthenticationHelper.token!!, oldPwd, newPwd) + } + + /** * 根据布防状态统计闸井数量接口 */ suspend fun countWellByState(): String { - return api.countWellByState() + return api.countWellByState(AuthenticationHelper.token!!) } + + /** + * 更新APK版本 + */ + suspend fun updateVersion(): String { + return api.obtainVersionResult(AuthenticationHelper.token!!) + } + } \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/AboutUsActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/AboutUsActivity.kt new file mode 100644 index 0000000..7e43c47 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/AboutUsActivity.kt @@ -0,0 +1,34 @@ +package com.casic.smart.town.sanxi.view + +import com.casic.smart.town.sanxi.BuildConfig +import com.casic.smart.town.sanxi.R +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.kt.lite.base.KotlinBaseActivity +import com.pengxh.kt.lite.extensions.convertColor +import com.pengxh.kt.lite.utils.ImmerseStatusBarUtil +import kotlinx.android.synthetic.main.activity_about_us.* +import kotlinx.android.synthetic.main.include_base_title.* + +class AboutUsActivity : KotlinBaseActivity() { + + override fun initLayoutView(): Int = R.layout.activity_about_us + + override fun setupTopBarLayout() { + ImmersionBar.with(this).statusBarDarkFont(false).init() + ImmerseStatusBarUtil.setColor(this, R.color.mainThemeColor.convertColor(this)) + titleView.text = "关于我们" + leftBackView.setOnClickListener { finish() } + } + + override fun initData() { + versionCodeView.text = String.format("版本信息:V${BuildConfig.VERSION_NAME}") + } + + override fun observeRequestState() { + + } + + override fun initEvent() { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/LoginActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/LoginActivity.kt new file mode 100644 index 0000000..b5d1d50 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/LoginActivity.kt @@ -0,0 +1,97 @@ +package com.casic.smart.town.sanxi.view + +import androidx.lifecycle.ViewModelProvider +import com.casic.smart.town.sanxi.R +import com.casic.smart.town.sanxi.util.AuthenticationHelper +import com.casic.smart.town.sanxi.util.LoadingDialogHub +import com.casic.smart.town.sanxi.util.LocaleConstant +import com.casic.smart.town.sanxi.util.RSAUtils +import com.casic.smart.town.sanxi.vm.AuthenticateViewModel +import com.casic.smart.town.sanxi.vm.LoginViewModel +import com.casic.smart.town.sanxi.vm.UserViewModel +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.kt.lite.base.KotlinBaseActivity +import com.pengxh.kt.lite.extensions.navigatePageTo +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.utils.SaveKeyValues +import kotlinx.android.synthetic.main.activity_login.* + +class LoginActivity : KotlinBaseActivity() { + + private lateinit var authenticateViewModel: AuthenticateViewModel + private lateinit var loginViewModel: LoginViewModel + private lateinit var userViewModel: UserViewModel + + override fun initLayoutView(): Int = R.layout.activity_login + + override fun setupTopBarLayout() { + ImmersionBar.with(this).statusBarDarkFont(true).init() + } + + override fun initData() { + // 设置默认账号密码 + userNameView.setText(SaveKeyValues.getValue(LocaleConstant.ACCOUNT, "") as String) + userPasswordView.setText(SaveKeyValues.getValue(LocaleConstant.PASSWORD, "") as String) + authenticateViewModel = ViewModelProvider(this)[AuthenticateViewModel::class.java] + loginViewModel = ViewModelProvider(this)[LoginViewModel::class.java] + userViewModel = ViewModelProvider(this)[UserViewModel::class.java] + + authenticateViewModel.keyModel.observe(this, { + if (it.code == 200) {//用code判断,别的判断可能有坑 + val keyString = it.data!!.publicKey!! + /** + * 修改密码需要用到key,先存着 + * */ + AuthenticationHelper.savePublicKey(keyString) + val publicKey = RSAUtils.keyStrToPublicKey(keyString) + + val account = userNameView.text.toString() + val userPassword = userPasswordView.text.toString() + val dataByPublicKey = + RSAUtils.encryptDataByPublicKey(userPassword.toByteArray(), publicKey!!) + //登录并获取Token,POST请求 + loginViewModel.enter(it.data!!.sid!!, account, dataByPublicKey) + loginViewModel.enterResultModel.observe(this, { loginResult -> + if (loginResult.code == 200) { + AuthenticationHelper.saveToken(loginResult.data!!.token!!) + /** + * 获取token之后保存用户信息 + * */ + userViewModel.obtainUserDetail() + //验证成功登录 + navigatePageTo() + finish() + } + }) + } + }) + } + + override fun observeRequestState() { + authenticateViewModel.loadState.observe(this, { + LoadingDialogHub.show(this, "登录中,请稍后") + }) + + loginViewModel.loadState.observe(this, { + LoadingDialogHub.dismiss() + }) + } + + override fun initEvent() { + loginButton.setOnClickListener { + val account = userNameView.text.toString().trim() + val userPassword = userPasswordView.text.toString().trim() + if (account.isBlank()) { + "用户名不能为空".show(this) + return@setOnClickListener + } + if (userPassword.isBlank()) { + "密码不能为空".show(this) + return@setOnClickListener + } + SaveKeyValues.putValue(LocaleConstant.ACCOUNT, account) + SaveKeyValues.putValue(LocaleConstant.PASSWORD, userPassword) + authenticateViewModel.obtainPublicKey() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt index 3bea1c9..a4fa951 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/MainActivity.kt @@ -7,6 +7,7 @@ import com.casic.smart.town.sanxi.R import com.casic.smart.town.sanxi.adapter.ViewPagerAdapter import com.casic.smart.town.sanxi.fragment.AlarmPageFragment +import com.casic.smart.town.sanxi.fragment.MinePageFragment import com.casic.smart.town.sanxi.fragment.MonitorPageFragment import com.casic.smart.town.sanxi.fragment.StatisticsPageFragment import com.gyf.immersionbar.ImmersionBar @@ -33,6 +34,7 @@ fragmentPages.add(StatisticsPageFragment()) fragmentPages.add(MonitorPageFragment()) fragmentPages.add(AlarmPageFragment()) + fragmentPages.add(MinePageFragment()) } override fun initLayoutView(): Int = R.layout.activity_main @@ -68,6 +70,9 @@ R.id.nav_alarm -> { mainViewPager.currentItem = 2 } + R.id.nav_more -> { + mainViewPager.currentItem = 3 + } } false } diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/PermissionActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/PermissionActivity.kt index c478d83..3238f3c 100644 --- a/app/src/main/java/com/casic/smart/town/sanxi/view/PermissionActivity.kt +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/PermissionActivity.kt @@ -32,7 +32,7 @@ //先把导航隐私政策声明,后面导航会用到 MapsInitializer.updatePrivacyAgree(this, true) MapsInitializer.updatePrivacyShow(this, true, true) - this.navigatePageTo() + this.navigatePageTo() finish() } diff --git a/app/src/main/java/com/casic/smart/town/sanxi/view/SplashScreenActivity.kt b/app/src/main/java/com/casic/smart/town/sanxi/view/SplashScreenActivity.kt new file mode 100644 index 0000000..fa5a74d --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/view/SplashScreenActivity.kt @@ -0,0 +1,54 @@ +package com.casic.smart.town.sanxi.view + +import android.annotation.SuppressLint +import android.os.CountDownTimer +import androidx.lifecycle.ViewModelProvider +import com.casic.smart.town.sanxi.R +import com.casic.smart.town.sanxi.vm.UserViewModel +import com.gyf.immersionbar.ImmersionBar +import com.pengxh.kt.lite.base.KotlinBaseActivity +import com.pengxh.kt.lite.extensions.navigatePageTo + +@SuppressLint("CustomSplashScreen") +class SplashScreenActivity : KotlinBaseActivity() { + + private lateinit var userDetailViewModel: UserViewModel + private val countDownTimer = object : CountDownTimer(1000, 500) { + override fun onFinish() { + /** + * 获取token之后保存用户信息 + * */ + userDetailViewModel.obtainUserDetail() + userDetailViewModel.userDetailModel.observe(this@SplashScreenActivity, { + if (it.code == 200) { + navigatePageTo() + } else { + navigatePageTo() + } + finish() + }) + } + + override fun onTick(millisUntilFinished: Long) { + + } + } + + override fun initLayoutView(): Int = R.layout.activity_splash + + override fun setupTopBarLayout() { + ImmersionBar.with(this).statusBarDarkFont(false).init() + } + + override fun initData() { + userDetailViewModel = ViewModelProvider(this).get(UserViewModel::class.java) + } + + override fun observeRequestState() { + + } + + override fun initEvent() { + countDownTimer.start() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/vm/AuthenticateViewModel.kt b/app/src/main/java/com/casic/smart/town/sanxi/vm/AuthenticateViewModel.kt new file mode 100644 index 0000000..87ddbf8 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/vm/AuthenticateViewModel.kt @@ -0,0 +1,35 @@ +package com.casic.smart.town.sanxi.vm + +import androidx.lifecycle.MutableLiveData +import com.casic.smart.town.sanxi.base.BaseApplication +import com.casic.smart.town.sanxi.extensions.separateResponseCode +import com.casic.smart.town.sanxi.extensions.toErrorMessage +import com.casic.smart.town.sanxi.model.PublicKeyModel +import com.casic.smart.town.sanxi.util.retrofit.RetrofitServiceManager +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.launch +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.vm.BaseViewModel +import com.pengxh.kt.lite.vm.LoadState + +class AuthenticateViewModel : BaseViewModel() { + + private val gson = Gson() + val keyModel = MutableLiveData() + + fun obtainPublicKey() = launch({ + loadState.value = LoadState.Loading + val response = RetrofitServiceManager.authenticate() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + keyModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ) + } else { + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + it.printStackTrace() + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/vm/LoginViewModel.kt b/app/src/main/java/com/casic/smart/town/sanxi/vm/LoginViewModel.kt new file mode 100644 index 0000000..c377c61 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/vm/LoginViewModel.kt @@ -0,0 +1,54 @@ +package com.casic.smart.town.sanxi.vm + +import androidx.lifecycle.MutableLiveData +import com.casic.smart.town.sanxi.base.BaseApplication +import com.casic.smart.town.sanxi.extensions.separateResponseCode +import com.casic.smart.town.sanxi.extensions.toErrorMessage +import com.casic.smart.town.sanxi.model.CommonResultModel +import com.casic.smart.town.sanxi.model.LoginResultModel +import com.casic.smart.town.sanxi.util.retrofit.RetrofitServiceManager +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.launch +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.vm.BaseViewModel +import com.pengxh.kt.lite.vm.LoadState + +class LoginViewModel : BaseViewModel() { + + private val gson = Gson() + val enterResultModel = MutableLiveData() + val outResultModel = MutableLiveData() + + fun enter(sid: String, account: String, secretKey: String) = launch({ + val response = RetrofitServiceManager.login(sid, account, secretKey) + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + loadState.value = LoadState.Success + "登录成功".show(BaseApplication.obtainInstance()) + enterResultModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ) + } else { + loadState.value = LoadState.Fail + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + loadState.value = LoadState.Fail + it.printStackTrace() + }) + + fun out() = launch({ + val response = RetrofitServiceManager.loginOut() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + outResultModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ) + } else { + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + it.printStackTrace() + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/vm/UserViewModel.kt b/app/src/main/java/com/casic/smart/town/sanxi/vm/UserViewModel.kt new file mode 100644 index 0000000..29a8756 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/vm/UserViewModel.kt @@ -0,0 +1,57 @@ +package com.casic.smart.town.sanxi.vm + +import androidx.lifecycle.MutableLiveData +import com.casic.smart.town.sanxi.base.BaseApplication +import com.casic.smart.town.sanxi.extensions.separateResponseCode +import com.casic.smart.town.sanxi.extensions.toErrorMessage +import com.casic.smart.town.sanxi.model.UserDetailModel +import com.casic.smart.town.sanxi.util.LocaleConstant +import com.casic.smart.town.sanxi.util.retrofit.RetrofitServiceManager +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.launch +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.utils.SaveKeyValues +import com.pengxh.kt.lite.vm.BaseViewModel +import com.pengxh.kt.lite.vm.LoadState + +class UserViewModel : BaseViewModel() { + + private val gson = Gson() + val userDetailModel = MutableLiveData() + + fun obtainUserDetail() = launch({ + val response = RetrofitServiceManager.obtainUserDetail() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + val userDetail = gson.fromJson( + response, object : TypeToken() {}.type + ) + userDetailModel.value = userDetail + SaveKeyValues.putValue(LocaleConstant.USER_DETAIL_MODEL, gson.toJson(userDetail.data)) + } else { + val errorModel = UserDetailModel() + errorModel.code = responseCode + userDetailModel.value = errorModel + //如果此次获取不到用户信息,那么就清空之前的用户缓存,然后让用户重新登录 + SaveKeyValues.removeKey(LocaleConstant.USER_DETAIL_MODEL) + } + }, { + it.printStackTrace() + }) + + fun changePassword(oldPwd: String, newPwd: String) = launch({ + loadState.value = LoadState.Loading + val response = RetrofitServiceManager.changePassword(oldPwd, newPwd) + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + loadState.value = LoadState.Success + } else { + loadState.value = LoadState.Fail + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + loadState.value = LoadState.Fail + it.printStackTrace() + }) +} \ No newline at end of file diff --git a/app/src/main/java/com/casic/smart/town/sanxi/vm/VersionViewModel.kt b/app/src/main/java/com/casic/smart/town/sanxi/vm/VersionViewModel.kt new file mode 100644 index 0000000..20ea457 --- /dev/null +++ b/app/src/main/java/com/casic/smart/town/sanxi/vm/VersionViewModel.kt @@ -0,0 +1,38 @@ +package com.casic.smart.town.sanxi.vm + +import androidx.lifecycle.MutableLiveData +import com.casic.smart.town.sanxi.base.BaseApplication +import com.casic.smart.town.sanxi.extensions.separateResponseCode +import com.casic.smart.town.sanxi.extensions.toErrorMessage +import com.casic.smart.town.sanxi.model.VersionResultModel +import com.casic.smart.town.sanxi.util.retrofit.RetrofitServiceManager +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken +import com.pengxh.kt.lite.extensions.launch +import com.pengxh.kt.lite.extensions.show +import com.pengxh.kt.lite.vm.BaseViewModel +import com.pengxh.kt.lite.vm.LoadState + +class VersionViewModel : BaseViewModel() { + + private val gson = Gson() + val versionResultModel = MutableLiveData() + + fun updateVersion() = launch({ + loadState.value = LoadState.Loading + val response = RetrofitServiceManager.updateVersion() + val responseCode = response.separateResponseCode() + if (responseCode == 200) { + loadState.value = LoadState.Success + versionResultModel.value = gson.fromJson( + response, object : TypeToken() {}.type + ).data + } else { + loadState.value = LoadState.Fail + response.toErrorMessage().show(BaseApplication.obtainInstance()) + } + }, { + loadState.value = LoadState.Fail + it.printStackTrace() + }) +} \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_solid_layout_gray_radius_5.xml b/app/src/main/res/drawable/bg_solid_layout_gray_radius_5.xml new file mode 100644 index 0000000..96eb93e --- /dev/null +++ b/app/src/main/res/drawable/bg_solid_layout_gray_radius_5.xml @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/button_main_selector.xml b/app/src/main/res/drawable/button_main_selector.xml new file mode 100644 index 0000000..5875998 --- /dev/null +++ b/app/src/main/res/drawable/button_main_selector.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/button_white_selector.xml b/app/src/main/res/drawable/button_white_selector.xml new file mode 100644 index 0000000..4dd3c0b --- /dev/null +++ b/app/src/main/res/drawable/button_white_selector.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/download_progress.xml b/app/src/main/res/drawable/download_progress.xml new file mode 100644 index 0000000..493d923 --- /dev/null +++ b/app/src/main/res/drawable/download_progress.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_about.xml b/app/src/main/res/drawable/ic_about.xml new file mode 100644 index 0000000..8469319 --- /dev/null +++ b/app/src/main/res/drawable/ic_about.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_change_pwd.xml b/app/src/main/res/drawable/ic_change_pwd.xml new file mode 100644 index 0000000..75caa39 --- /dev/null +++ b/app/src/main/res/drawable/ic_change_pwd.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/ic_clean.xml b/app/src/main/res/drawable/ic_clean.xml new file mode 100644 index 0000000..027f809 --- /dev/null +++ b/app/src/main/res/drawable/ic_clean.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_left.xml b/app/src/main/res/drawable/ic_left.xml new file mode 100644 index 0000000..04ee5da --- /dev/null +++ b/app/src/main/res/drawable/ic_left.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_lock.xml b/app/src/main/res/drawable/ic_lock.xml new file mode 100644 index 0000000..f0a5610 --- /dev/null +++ b/app/src/main/res/drawable/ic_lock.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/ic_more.xml b/app/src/main/res/drawable/ic_more.xml new file mode 100644 index 0000000..0794eaa --- /dev/null +++ b/app/src/main/res/drawable/ic_more.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_more_selected.xml b/app/src/main/res/drawable/ic_more_selected.xml new file mode 100644 index 0000000..9b6d3df --- /dev/null +++ b/app/src/main/res/drawable/ic_more_selected.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_phone.xml b/app/src/main/res/drawable/ic_phone.xml new file mode 100644 index 0000000..253f843 --- /dev/null +++ b/app/src/main/res/drawable/ic_phone.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_right.xml b/app/src/main/res/drawable/ic_right.xml new file mode 100644 index 0000000..6020f09 --- /dev/null +++ b/app/src/main/res/drawable/ic_right.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_update_version.xml b/app/src/main/res/drawable/ic_update_version.xml new file mode 100644 index 0000000..3a62d63 --- /dev/null +++ b/app/src/main/res/drawable/ic_update_version.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_user.xml b/app/src/main/res/drawable/ic_user.xml new file mode 100644 index 0000000..8c4a29d --- /dev/null +++ b/app/src/main/res/drawable/ic_user.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/drawable/more_selector.xml b/app/src/main/res/drawable/more_selector.xml new file mode 100644 index 0000000..a150a48 --- /dev/null +++ b/app/src/main/res/drawable/more_selector.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_about_us.xml b/app/src/main/res/layout/activity_about_us.xml new file mode 100644 index 0000000..9b2d9e5 --- /dev/null +++ b/app/src/main/res/layout/activity_about_us.xml @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml new file mode 100644 index 0000000..c7ac8fb --- /dev/null +++ b/app/src/main/res/layout/activity_login.xml @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + + + + + + +