前台申请活动接入百度地图定位

master
pan 4 years ago
parent 24b12f9778
commit d64411fa51
  1. 7
      build.gradle.kts
  2. 10
      foreground/build.gradle.kts
  3. 33
      foreground/src/main/AndroidManifest.xml
  4. 70
      foreground/src/main/java/com/gyf/csams/MainApplication.kt
  5. 1
      foreground/src/main/java/com/gyf/csams/account/ui/AccountActivity.kt
  6. 574
      foreground/src/main/java/com/gyf/csams/activity/model/ApplyActViewModel.kt
  7. 495
      foreground/src/main/java/com/gyf/csams/activity/ui/ApplyActActivity.kt
  8. 3
      foreground/src/main/java/com/gyf/csams/association/model/ExamViewModel.kt
  9. 4
      foreground/src/main/java/com/gyf/csams/association/model/RegAssociationViewModel.kt
  10. 17
      foreground/src/main/java/com/gyf/csams/association/ui/AssociationActivity.kt
  11. 61
      foreground/src/main/java/com/gyf/csams/association/ui/ExamActivity.kt
  12. 42
      foreground/src/main/java/com/gyf/csams/association/ui/ReNameActivity.kt
  13. 52
      foreground/src/main/java/com/gyf/csams/association/ui/RegAssociationActivity.kt
  14. 18
      foreground/src/main/java/com/gyf/csams/uikit/BaseView.kt
  15. 10
      foreground/src/main/java/com/gyf/csams/uikit/ViewModel.kt
  16. 12
      foreground/src/main/res/drawable/ic_date.xml
  17. 9
      foreground/src/main/res/drawable/ic_icon_location.xml
  18. 9
      foreground/src/main/res/drawable/ic_seleted.xml
  19. 15
      foreground/src/main/res/values-en/strings.xml
  20. 16
      foreground/src/main/res/values-night/themes.xml
  21. 15
      foreground/src/main/res/values-zh/strings.xml
  22. 12
      foreground/src/main/res/values/strings.xml
  23. 5
      foreground/src/test/java/com/gyf/csams/ExampleUnitTest.kt
  24. BIN
      jks/csams.jks
  25. 14
      lib/build.gradle.kts
  26. 1
      lib/src/main/AndroidManifest.xml
  27. 63
      lib/src/main/java/com/gyf/lib/uikit/BaseTextField.kt
  28. 2
      lib/src/main/java/com/gyf/lib/uikit/MainFrame.kt
  29. 4
      lib/src/main/java/com/gyf/lib/uikit/Snackbar.kt
  30. 41
      lib/src/main/java/com/gyf/lib/util/BottomButton.kt
  31. 1
      lib/src/main/java/com/gyf/lib/util/RandomUtil.kt
  32. 4
      lib/src/main/res/values-en/integers.xml
  33. 15
      lib/src/main/res/values-en/strings.xml
  34. 4
      lib/src/main/res/values-zh/integers.xml
  35. 15
      lib/src/main/res/values-zh/strings.xml
  36. 4
      lib/src/main/res/values/integers.xml
  37. 15
      lib/src/main/res/values/strings.xml
  38. 6
      settings.gradle.kts

@ -1,7 +1,10 @@
val store_password by extra("123456")
val key_alias by extra("csams")
val key_password by extra("123456")
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
//Jetpack Compose版本
val compose_version by extra("1.0.0-beta06")
val compose_version by extra("1.0.0-beta07")
//生命周期组件版本
val lifecycle_version by extra("2.3.1")
//APP应用名字
@ -17,7 +20,7 @@ buildscript {
mavenCentral()
}
dependencies {
classpath("com.android.tools.build:gradle:7.0.0-alpha15")
classpath("com.android.tools.build:gradle:7.1.0-alpha01")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlin_version}")
// NOTE: Do not place your application dependencies here; they belong

@ -5,6 +5,14 @@ plugins {
}
android {
signingConfigs {
getByName("debug") {
storeFile = file("..\\jks\\csams.jks")
storePassword = rootProject.extra["store_password"] as String
keyAlias = rootProject.extra["key_alias"] as String
keyPassword = rootProject.extra["key_password"] as String
}
}
compileSdk = 30
defaultConfig {
@ -18,6 +26,7 @@ android {
vectorDrawables {
useSupportLibrary = true
}
signingConfig = signingConfigs.getByName("debug")
}
buildTypes {
@ -64,6 +73,7 @@ android {
dependencies {
implementation(project(":lib"))
implementation(files("libs\\BaiduLBS_Android.jar"))
kapt("androidx.room:room-compiler:${rootProject.extra["room_version"]}")
// optional - Test helpers

@ -1,11 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.gyf.csams">
<!--访问网络-->
<uses-permission android:name="android.permission.INTERNET" />
<!-- 这个权限用于进行网络定位-->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<!-- 这个权限用于访问GPS定位-->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<!-- 用于访问wifi网络信息,wifi信息会用于进行网络定位-->
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<!-- 获取运营商信息,用于支持提供运营商信息相关的接口-->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<!-- 这个权限用于获取wifi的获取权限,wifi信息会用来进行网络定位-->
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<!--读写存储-->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<!--已启用分区存储-->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="28" />
<application
@ -18,6 +29,16 @@
android:theme="@style/Theme.CSAMS"
android:name=".MainApplication">
<!--百度SDK AK-->
<meta-data
android:name="com.baidu.lbsapi.API_KEY"
android:value="SeFsmyQxWaVG2aYMLGqVbLaPuWelQrFs" />
<!--使用定位SDK,需在AndroidManifest.xml文件中Application标签中声明service组件,每个App拥有自己单独的定位service-->
<service
android:name="com.baidu.location.f"
android:enabled="true"
android:process=":remote" />
<!--初始化界面-->
<activity
android:name=".InitActivity"
@ -68,6 +89,12 @@
android:name=".activity.ui.ActivityDetailActivity"
android:exported="true" />
<!--申请活动-->
<activity
android:name=".activity.ui.ApplyActActivity"
android:exported="true"
android:theme="@style/Theme.CSAMS.NoActionBar" />
<!--通知-->
<activity
android:name=".message.ui.MessageActivity"

@ -3,24 +3,43 @@ package com.gyf.csams
import android.app.Application
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.asImageBitmap
import androidx.lifecycle.MutableLiveData
import coil.ImageLoader
import coil.ImageLoaderFactory
import coil.imageLoader
import coil.memory.MemoryCache
import coil.request.ImageRequest
import coil.util.CoilUtils
import com.baidu.location.BDAbstractLocationListener
import com.baidu.location.BDLocation
import com.baidu.location.LocationClient
import com.baidu.mapapi.SDKInitializer
import com.gyf.csams.uikit.BackgroundImage
import com.gyf.lib.util.ImageUtil
import com.orhanobut.logger.AndroidLogAdapter
import com.orhanobut.logger.DiskLogAdapter
import com.orhanobut.logger.Logger
import okhttp3.OkHttpClient
class MainApplication : Application(), ImageLoaderFactory {
private val backgroundImage = mutableMapOf<BackgroundImage, MemoryCache.Key?>()
lateinit var mLocationClient: LocationClient
/**
* 获取背景图
*
* @param image
* @return
*/
suspend fun getImage(image: BackgroundImage): ImageBitmap? {
val key = backgroundImage[image]
if (key != null) {
@ -63,14 +82,63 @@ class MainApplication : Application(), ImageLoaderFactory {
}
override fun onCreate() {
super.onCreate()
//初始化日志
Logger.addLogAdapter(AndroidLogAdapter())
Logger.addLogAdapter(DiskLogAdapter())
//初始化百度地图SDK
mLocationClient = LocationClient(applicationContext)
mLocationClient.registerLocationListener(MyLocationListener)
Logger.i("${BuildConfig.foreground_app_name}启动")
SDKInitializer.initialize(this)
preloadImage()
}
}
/**
* 百度地图定位信息监听
*/
object MyLocationListener : BDAbstractLocationListener() {
val location = MutableLiveData<BDLocation>()
override fun onReceiveLocation(bdLocation: BDLocation?) {
bdLocation?.apply {
location.value = this
val sb = StringBuffer()
sb.append("time : ")
sb.append(time)
sb.append("\nerror code : ")
sb.append(locType)
sb.append("\nlatitude : ")
sb.append(latitude)
sb.append("\nlontitude : ")
sb.append(longitude)
sb.append("\nradius : ")
sb.append(radius)
when (locType) {
BDLocation.TypeCacheLocation -> {
sb.append("\nspeed : ")
sb.append(speed)
sb.append("\nsatellite : ")
sb.append(satelliteNumber)
sb.append("\ndirection : ")
sb.append("\naddr : ")
sb.append(addrStr)
sb.append(direction)
}
BDLocation.TypeNetWorkLocation -> {
sb.append("\naddr : ")
sb.append(addrStr)
}
}
Logger.i("BaiduLocationApiDem:$sb")
}
}
}

@ -23,7 +23,6 @@ import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.navigate
import com.gyf.csams.BuildConfig
import com.gyf.csams.account.model.AccountViewModel
import com.gyf.csams.account.model.DialogMessage

@ -0,0 +1,574 @@
package com.gyf.csams.activity.model
import android.app.Activity
import android.app.Application
import android.content.Context
import android.graphics.BitmapFactory
import android.view.inputmethod.InputMethodManager
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.baidu.mapapi.map.*
import com.baidu.mapapi.model.LatLng
import com.baidu.mapapi.model.LatLngBounds
import com.baidu.mapapi.search.core.PoiInfo
import com.baidu.mapapi.search.core.SearchResult
import com.baidu.mapapi.search.geocode.*
import com.baidu.mapapi.search.poi.*
import com.baidu.mapapi.search.sug.SuggestionResult
import com.baidu.mapapi.search.sug.SuggestionSearch
import com.baidu.mapapi.search.sug.SuggestionSearchOption
import com.gyf.csams.MyLocationListener
import com.gyf.csams.NOT_IMPL_TIP
import com.gyf.csams.R
import com.gyf.lib.uikit.ScaffoldModel
import com.gyf.lib.uikit.StringForm
import com.gyf.lib.util.DATETIME_FORMAT
import com.orhanobut.logger.Logger
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import java.util.*
/**
* 申请活动数据状态管理
*
* @constructor
*
*
* @param application
*/
class ApplyActViewModel(application: Application) : AndroidViewModel(application) {
val activityName =
StringForm(formDesc = application.getString(R.string.activity_name), textLength = 10)
val activityTime = StringForm(
formDesc = application.getString(R.string.activity_time),
textLength = DATETIME_FORMAT.length
)
val activityAddress =
StringForm(formDesc = application.getString(R.string.activity_address), textLength = 30)
val activityDesc =
StringForm(formDesc = application.getString(R.string.activity_desc), textLength = 50)
val maxActivitySize = application.resources.getInteger(R.integer.activity_size)
val activitySize = object :
StringForm(formDesc = application.getString(R.string.activity_size), textLength = 2) {
override fun onChange(value: String) {
if (value.length > textLength) {
formError.value = "${formDesc}不能超过最大长度$textLength"
_formValue.value = value.slice(IntRange(0, textLength - 1))
} else if (value.matches(Regex("\\d+")) && value.toInt() !in 1..maxActivitySize) {
Logger.i("活动人数:${value}不合法")
formError.value =
application.getString(R.string.activity_size_error, 1, maxActivitySize)
} else {
_formValue.value = value
formError.value = ""
}
}
}
val city =
object : StringForm(formDesc = application.getString(R.string.city), textLength = 4) {
override val formPlaceholder = ""
}
// 默认逆地理编码半径范围
private val sDefaultRGCRadius = 500
private val mGeoCoder: GeoCoder = GeoCoder.newInstance().apply {
setOnGetGeoCodeResultListener(object : OnGetGeoCoderResultListener {
override fun onGetGeoCodeResult(p0: GeoCodeResult?) {
}
override fun onGetReverseGeoCodeResult(reverseGeoCodeResult: ReverseGeoCodeResult?) {
reverseGeoCodeResult?.let {
GlobalScope.launch {
updateUI(it)
}
}
}
})
}
lateinit var scaffoldModel: ScaffoldModel
private val _mapView = MutableLiveData<MapView>()
val mapView: LiveData<MapView> = _mapView
/**
* 更新UI
*
* @param reverseGeoCodeResult
*/
private fun updateUI(reverseGeoCodeResult: ReverseGeoCodeResult) {
Logger.i("逆编码更新地点信息")
var poiInfo = reverseGeoCodeResult.poiList
val curAddressPoiInfo = PoiInfo()
curAddressPoiInfo.address = reverseGeoCodeResult.address
curAddressPoiInfo.location = reverseGeoCodeResult.location
if (null == poiInfo) {
poiInfo = ArrayList(2)
}
poiInfo.add(0, curAddressPoiInfo)
_poInfo.postValue(poiInfo)
_selectPoi.postValue(null)
}
fun searchPoiInCity() {
if (city.formValue.value?.isEmpty() == true ||
address.formValue.value?.isEmpty() == true
) {
Logger.i("city=${city.formValue.value},keyword=${address.formValue.value}")
return
} else {
Logger.i("${city.formValue.value}市内找${address.formValue.value}")
mPoiSearch.searchInCity(
PoiCitySearchOption()
.city(city.formValue.value)
.keyword(address.formValue.value)
.pageNum(0) // 分页编号
.cityLimit(true)
.scope(1)
)
}
}
/**
* 在地图上定位poi
*
* @param suggestInfo
*/
fun locateSuggestPoi(suggestInfo: SuggestionResult.SuggestionInfo) {
// _sugResult.value?.clear()
_sugResult.value = mutableListOf()
_mapView.value?.apply {
val latLng = suggestInfo.getPt()
_mStatusChangeByItemClick.value = false
// 将地图平移到 latLng 位置
val mapStatusUpdate = MapStatusUpdateFactory.newLatLng(latLng)
map.setMapStatus(mapStatusUpdate)
// 清除之前的
// clearData()
// 显示当前的
// if (!showSuggestMarker(latLng)) {
// searchPoiInCity()
// }
}
}
/**
* 显示定位点
*
* @param latLng
*/
private fun showSuggestMarker(latLng: LatLng?): Boolean {
if (null == latLng) {
return false
}
_mapView.value?.apply {
val markerOptions = MarkerOptions()
.position(latLng)
.icon(mBitmapDescWaterDrop)
.scaleX(1.5f)
.scaleY(1.5f)
.clickable(true)
map.addOverlay(markerOptions)
return true
}
return false
}
val mSuggestionSearch: SuggestionSearch = SuggestionSearch.newInstance().apply {
setOnGetSuggestionResultListener { it ->
if (it == null
|| it.error == SearchResult.ERRORNO.RESULT_NOT_FOUND
) {
scaffoldModel.update(
message = application.getString(
R.string.search_null
)
)
Logger.i("sug检索不到地点")
} else {
it.allSuggestions?.let {
Logger.i("sug检索到${it.size}个地点")
_sugResult.value = it
}
}
}
}
private val mPoiSearch: PoiSearch = PoiSearch.newInstance().apply {
setOnGetPoiSearchResultListener(
object : OnGetPoiSearchResultListener {
override fun onGetPoiResult(
poiResult: PoiResult?
) {
if (poiResult == null || poiResult.error == SearchResult.ERRORNO.RESULT_NOT_FOUND) {
scaffoldModel.update(
message = application.getString(
R.string.search_null
)
)
Logger.i("Poi检索不到地点")
} else {
poiResult.allPoi?.let {
Logger.i("Poi检索到${it.size}个地点")
setPoiResult(it)
}
}
}
@Suppress("DEPRECATION")
override fun onGetPoiDetailResult(p0: PoiDetailResult?) {
Logger.i("$p0")
}
override fun onGetPoiDetailResult(p0: PoiDetailSearchResult?) {
Logger.i("$p0")
}
override fun onGetPoiIndoorResult(p0: PoiIndoorResult?) {
Logger.i("$p0")
}
})
}
private val mBitmapDescWaterDrop = BitmapDescriptorFactory.fromBitmap(
BitmapFactory.decodeResource(
application.resources,
R.drawable.icon_binding_point
)
)
private val mMarkerPoiInfo = mutableMapOf<Marker, PoiInfo>()
private var mPreSelectMarker: Marker? = null
private fun showPoiMarker(poiInfo: PoiInfo?, i: Int) {
poiInfo?.apply {
_mapView.value?.apply {
val markerOptions = MarkerOptions()
.position(location)
.icon(mBitmapDescWaterDrop)
// 第一个poi放大显示
// if (0 == i) {
// val infoWindow: InfoWindow = getPoiInfoWindow(poiInfo)
// markerOptions.scaleX(1.5f).scaleY(1.5f).infoWindow(infoWindow)
// }
val marker = map.addOverlay(markerOptions) as Marker
mMarkerPoiInfo[marker] = poiInfo
if (0 == i) {
mPreSelectMarker = marker
}
}
}
}
private fun setPoiResult(poiInfos: List<PoiInfo>?) {
_mapView.value?.apply {
if (null == poiInfos || poiInfos.isEmpty()) {
return
}
clearData()
// 将地图平移到 latLng 位置
val latLng = poiInfos[0].getLocation()
map.setMapStatus(mapStatus(latLng))
val itr: Iterator<*> = poiInfos.iterator()
val latLngs: MutableList<LatLng> = ArrayList()
var poiInfo: PoiInfo?
var i = 0
while (itr.hasNext()) {
poiInfo = itr.next() as PoiInfo?
if (null == poiInfo) {
continue
}
showPoiMarker(poiInfo, i)
latLngs.add(poiInfo.getLocation())
i++
}
setBounds(latLngs)
}
}
/**
* 最佳视野内显示所有点标记
*/
private fun setBounds(latLngs: List<LatLng>?) {
if (null == latLngs || latLngs.isEmpty()) {
return
}
val horizontalPadding = 80
val verticalPaddingBottom = 400
// 构造地理范围对象
val builder = LatLngBounds.Builder()
// 让该地理范围包含一组地理位置坐标
builder.include(latLngs)
_mapView.value?.apply {
// 设置显示在指定相对于MapView的padding中的地图地理范围
val mapStatusUpdate = MapStatusUpdateFactory.newLatLngBounds(
builder.build(),
horizontalPadding,
verticalPaddingBottom,
horizontalPadding,
verticalPaddingBottom
)
// 更新地图
map.setMapStatus(mapStatusUpdate)
// 设置地图上控件与地图边界的距离,包含比例尺、缩放控件、logo、指南针的位置
map.setViewPadding(
0,
0,
0,
verticalPaddingBottom
)
}
}
private fun clearData() {
Logger.i("清空地图数据")
_mapView.value?.apply {
map.clear()
mMarkerPoiInfo.clear()
mPreSelectMarker = null
}
}
/**
* 创建地图中心点marker
*/
private fun createCenterMarker(mBaiduMap: BaiduMap, mCenter: LatLng) {
Logger.i("标记坐标${mCenter}为地图中心点")
val projection: Projection = mBaiduMap.projection ?: return
val point = projection.toScreenLocation(mCenter)
val markerOptions = MarkerOptions()
.position(mCenter)
.icon(mBitmapDescWaterDrop)
.flat(false)
.fixedScreenPosition(point)
marker = markerOptions
mBaiduMap.addOverlay(markerOptions)
}
fun isLatlngEqual(latLng0: LatLng, latLng1: LatLng): Boolean {
return (latLng0.latitude == latLng1.latitude
&& latLng0.longitude == latLng1.longitude)
}
/**
* 逆地理编码请求
*
* @param latLng
*/
private fun reverseRequest(latLng: LatLng?) {
Logger.i("逆地理编码请求")
latLng?.let {
val reverseGeoCodeOption = ReverseGeoCodeOption().location(it)
.newVersion(1) // 建议请求新版数据
.radius(sDefaultRGCRadius)
mGeoCoder.reverseGeoCode(reverseGeoCodeOption)
}
}
private fun mapStatus(mCenter: LatLng): MapStatusUpdate {
return MapStatusUpdateFactory.newLatLngZoom(
mCenter,
16f
)
}
fun updateMapStatus(mapStatus: MapStatus?) {
mapStatus?.target?.let {
// 如果是点击poi item导致的地图状态更新,则不用做后面的逆地理请求,
if (_mStatusChangeByItemClick.value == true) {
if (!isLatlngEqual(
mCenter,
it
)
) {
mCenter = it
}
_mStatusChangeByItemClick.postValue(false)
return
}
if (!isLatlngEqual(mCenter, it)) {
mCenter = it
reverseRequest(mCenter)
}
}
}
private lateinit var mCenter: LatLng
private lateinit var marker: MarkerOptions
fun hideKeyBoard(activity: Activity?) {
Logger.i("隐藏软键盘")
activity?.apply {
val imm = getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager
window.peekDecorView()?.let {
imm.hideSoftInputFromWindow(it.windowToken, 0)
}
}
}
fun createMapView(context: Context): MapView {
val mapView = MapView(context).apply {
MyLocationListener.location.value?.let { _ ->
// 设置初始中心点为用户附近
// mCenter = LatLng(it.latitude, it.longitude)
mCenter = LatLng(39.963175, 116.400244)
map.setMapStatus(mapStatus(mCenter = mCenter))
map.setOnMapTouchListener {
hideKeyBoard(context as Activity)
}
map.setOnMarkerClickListener {
scaffoldModel.update(message = "确定选中此地址", actionLabel = "确定") {
val poi = (_selectPoi.value ?: poiInfo.value?.get(0))
val address = poi?.address ?: poi?.name ?: "地址获取失败,请再尝试一次"
if (address.isNotEmpty()) {
activityAddress.onChange(address)
} else {
Logger.w("地址获取失败")
}
closeMap()
}
true
}
map.removeMarkerClickListener {
Logger.i("删除标记")
true
}
map.setOnMapLoadedCallback {
Logger.i("地图加载完成!")
// createCenterMarker(map, mCenter)
map.addOverlay(MarkerOptions().position(mCenter).icon(mBitmapDescWaterDrop))
reverseRequest(mCenter)
}
map.setOnMapStatusChangeListener(object :
BaiduMap.OnMapStatusChangeListener {
override fun onMapStatusChangeStart(p0: MapStatus?) {
Logger.i("状态开始改变:$p0")
}
override fun onMapStatusChangeStart(
p0: MapStatus?,
p1: Int
) {
Logger.i("状态改变:$p0,p1=$p1")
}
override fun onMapStatusChange(mapStatus: MapStatus?) {
Logger.i("状态改变:$mapStatus,_mStatusChangeByItemClick.value=${_mStatusChangeByItemClick.value}")
updateMapStatus(mapStatus = mapStatus)
map.clear()
map.addOverlay(MarkerOptions().position(mCenter).icon(mBitmapDescWaterDrop))
}
override fun onMapStatusChangeFinish(
mapStatus: MapStatus?
) {
Logger.i("状态改变完成:$mapStatus")
}
})
}
}
_mapView.value = mapView
return mapView
}
val address = object : StringForm(formDesc = "", textLength = 5) {
override val formPlaceholder = ""
override fun onChange(value: String) {
super.onChange(value)
Logger.i("Sug检索,城市:${city.formValue.value},地点关键词:${value}")
// _sugResult.value?.clear()
_sugResult.value = mutableListOf()
mSuggestionSearch.requestSuggestion(
SuggestionSearchOption()
.city(city.formValue.value)
.keyword(value)
.citylimit(true)
)
}
}
private val _showMap = MutableLiveData<Boolean>()
val showMap: LiveData<Boolean> = _showMap
private val _mStatusChangeByItemClick = MutableLiveData(false)
private val _poInfo = MutableLiveData<List<PoiInfo>>()
val poiInfo: LiveData<List<PoiInfo>> = _poInfo
private val _selectPoi = MutableLiveData<PoiInfo?>()
val selectPoi: LiveData<PoiInfo?> = _selectPoi
private val _sugResult = MutableLiveData<MutableList<SuggestionResult.SuggestionInfo>>()
val sugResult: LiveData<MutableList<SuggestionResult.SuggestionInfo>> = _sugResult
fun updateSelectPoi(poiInfo: PoiInfo?) {
_selectPoi.value = poiInfo
}
fun updateStatusChangeByItemClick(flag: Boolean) {
_mStatusChangeByItemClick.value = flag
}
fun openMap() {
_showMap.value = true
}
private fun closeMap() {
_showMap.value = false
clearData()
}
private fun destroy() {
mPoiSearch.destroy()
mSuggestionSearch.destroy()
mapView.value?.onDestroy()
mBitmapDescWaterDrop?.recycle()
}
/**
* TODO 提交申请
*
*/
fun apply(callback: (message: String) -> Unit) {
callback(NOT_IMPL_TIP)
}
}

@ -0,0 +1,495 @@
package com.gyf.csams.activity.ui
import android.Manifest
import android.content.pm.PackageManager
import android.os.Bundle
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.compose.setContent
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import androidx.core.content.ContextCompat
import androidx.lifecycle.viewmodel.compose.viewModel
import com.baidu.location.LocationClient
import com.baidu.location.LocationClientOption
import com.baidu.mapapi.map.MapStatusUpdateFactory
import com.baidu.mapapi.search.sug.SuggestionResult
import com.google.android.material.datepicker.MaterialDatePicker
import com.gyf.csams.MainApplication
import com.gyf.csams.MyLocationListener
import com.gyf.csams.R
import com.gyf.csams.activity.model.ApplyActViewModel
import com.gyf.csams.uikit.Background
import com.gyf.csams.uikit.BackgroundImage
import com.gyf.csams.uikit.DescCard
import com.gyf.lib.uikit.*
import com.gyf.lib.util.BottomButton
import com.gyf.lib.util.format
import com.orhanobut.logger.Logger
import kotlinx.coroutines.launch
import java.util.*
val location_permissions = arrayOf(
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_WIFI_STATE,
Manifest.permission.ACCESS_NETWORK_STATE,
Manifest.permission.CHANGE_WIFI_STATE
)
/**
* 申请活动
*
*/
class ApplyActActivity : AppCompatActivity() {
private lateinit var mLocationClient: LocationClient
override fun onStop() {
super.onStop()
mLocationClient.stop()
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mLocationClient = (application as MainApplication).mLocationClient
setContent {
Body { scaffoldState ->
MainColumnFrame(background = { Background(image = BackgroundImage.ApplyActivity) }) {
val model: ApplyActViewModel = viewModel()
val scaffoldModel: ScaffoldModel = viewModel()
model.scaffoldModel = scaffoldModel
val showMap by model.showMap.observeAsState(false)
val location by MyLocationListener.location.observeAsState()
when {
showMap && location != null -> {
Box {
val sugResult by model.sugResult.observeAsState()
Column(modifier = Modifier.fillMaxSize()) {
AndroidView(modifier = Modifier
.weight(0.6F)
.fillMaxWidth(),
factory = { context -> model.createMapView(context) })
if (sugResult == null || sugResult?.size == 0) {
PoiItem(
modifier = Modifier
.weight(0.4F)
.fillMaxWidth()
)
}
}
Column(modifier = Modifier.fillMaxSize()) {
Search(
modifier = Modifier
.weight(0.2F)
.fillMaxWidth()
)
sugResult.let {
if (it.isNullOrEmpty()) {
Spacer(
modifier = Modifier
.weight(0.8F)
.fillMaxWidth()
)
} else {
SuggestItem(
modifier = Modifier
.weight(0.8F)
.fillMaxWidth(),
sugResult = it
)
}
}
}
}
}
else -> {
Title(modifier = Modifier.weight(0.2F))
Row(
Modifier
.weight(0.1F)
.fillMaxWidth(), horizontalArrangement = Arrangement.Center
) {
BaseTextField(form = model.activityName)
}
Row(
Modifier
.weight(0.1F)
.fillMaxWidth(),
horizontalArrangement = Arrangement.Center
) {
ActivityTime()
}
ActivityLocation(
modifier = Modifier
.weight(0.15F)
.fillMaxWidth()
)
DescCard(
modifier = Modifier.weight(0.3F),
stringForm = model.activityDesc
)
val error by model.activitySize.formError.observeAsState()
if (error?.isNotEmpty() == true) {
scaffoldModel.update(message = error)
}
Row(
Modifier
.weight(0.1F)
.fillMaxWidth(), horizontalArrangement = Arrangement.Center
) {
BaseTextField(
form = model.activitySize,
keyboardOptions =
KeyboardOptions.Default.copy(keyboardType = KeyboardType.Number)
)
}
Spacer(modifier = Modifier.weight(0.05F))
BottomButton(modifier = Modifier.fillMaxWidth()) {
model.apply { scaffoldModel.update(message = it) }
}
}
}
ShowSnackbar(scaffoldState = scaffoldState)
}
}
}
}
@Composable
private fun SelectIcon(modifier: Modifier = Modifier) {
IconButton(modifier = modifier, onClick = {
}) {
Icon(
painter = painterResource(id = R.drawable.ic_seleted),
contentDescription = null
)
}
}
@Composable
private fun Search(
modifier: Modifier = Modifier,
model: ApplyActViewModel = viewModel()
) {
Column(
modifier = modifier
.fillMaxWidth()
// .border(width = 1.dp, color = MaterialTheme.colors.onBackground)
,
verticalArrangement = Arrangement.SpaceAround
) {
Card(backgroundColor = MaterialTheme.colors.background) {
Row(modifier = Modifier.fillMaxWidth()) {
BaseTextField(modifier = Modifier.weight(0.5F),
form = model.city, leadingIcon = {
Text(
text = stringResource(id = R.string.at),
style = MaterialTheme.typography.subtitle2
)
}, trailingIcon = {
Text(
text = stringResource(id = R.string.incity),
style = MaterialTheme.typography.subtitle2
)
}, textStyle = MaterialTheme.typography.subtitle2
)
BaseTextField(modifier = Modifier.weight(0.5F), form = model.address)
}
}
// Row(
// modifier = Modifier.fillMaxWidth(),
// horizontalArrangement = Arrangement.Center
// ) {
// OutlinedButton(onClick = {
// model.searchPoiInCity()
// }) {
// Text(text = stringResource(id = R.string.search_btn))
// }
// }
}
}
@Composable
private fun SuggestItem(
modifier: Modifier = Modifier,
model: ApplyActViewModel = viewModel(),
sugResult: List<SuggestionResult.SuggestionInfo>
) {
val state = rememberLazyListState()
LazyColumn(modifier = modifier, state = state) {
sugResult.forEach {
item {
Card(backgroundColor = MaterialTheme.colors.background) {
Column(
modifier = Modifier
.fillMaxWidth()
.clickable(onClick = {
model.hideKeyBoard(this@ApplyActActivity)
model.locateSuggestPoi(suggestInfo = it)
})
) {
Text(text = it.key)
it.address?.let {
Text(text = it)
}
}
}
Divider(color = MaterialTheme.colors.onBackground)
}
}
}
}
@Composable
private fun PoiItem(
modifier: Modifier = Modifier,
model: ApplyActViewModel = viewModel()
) {
val state = rememberLazyListState()
val poiInfo by model.poiInfo.observeAsState()
val selectPoi by model.selectPoi.observeAsState()
val mapView by model.mapView.observeAsState()
LazyColumn(modifier = modifier, state = state) {
poiInfo?.withIndex()?.forEach { it ->
if (it.value.name?.isNotEmpty() == true || it.value.address?.isNotEmpty() == true) {
item {
Card(modifier = Modifier.clickable(onClick = {
model.updateStatusChangeByItemClick(true)
val mapStatusUpdate =
MapStatusUpdateFactory.newLatLng(it.value.getLocation())
mapView?.map?.setMapStatus(mapStatusUpdate)
model.updateSelectPoi(it.value)
}), backgroundColor = MaterialTheme.colors.background) {
Row(
modifier = Modifier
.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
if (it.index == 0) {
Text(
text = "${it.value.address}",
color = MaterialTheme.colors.primary,
modifier = Modifier.weight(0.8F)
)
if (selectPoi == null || selectPoi == it.value) {
SelectIcon(modifier = Modifier.weight(0.2F))
} else {
Spacer(modifier = Modifier.weight(0.2F))
}
} else {
Column(
modifier = Modifier.weight(0.8F)
) {
Text(
text = it.value.name,
color = MaterialTheme.colors.primary
)
it.value.address?.let {
Text(text = it, color = MaterialTheme.colors.secondary)
}
}
if (selectPoi == it.value) {
SelectIcon(modifier = Modifier.weight(0.2F))
} else {
Spacer(modifier = Modifier.weight(0.2F))
}
}
}
}
Spacer(modifier = Modifier.height(10.dp))
}
}
}
}
poiInfo?.apply {
LaunchedEffect(poiInfo) {
launch {
if (size > 0) {
state.animateScrollToItem(0)
}
}
}
}
}
@Composable
private fun Title(modifier: Modifier = Modifier) {
Box(modifier = modifier.fillMaxWidth(), contentAlignment = Alignment.Center) {
Text(
text = stringResource(id = R.string.activity_application),
style = MaterialTheme.typography.h4
)
}
}
/**
* 活动时间
*
* @param modifier
* @param model
*/
@Composable
private fun ActivityTime(
modifier: Modifier = Modifier,
model: ApplyActViewModel = viewModel()
) {
val context = LocalContext.current as ApplyActActivity
val scope = rememberCoroutineScope()
BaseTextField(
modifier = modifier,
form = model.activityTime, readOnly = true
) {
IconButton(onClick = {
scope.launch {
val picker = MaterialDatePicker
.Builder
.datePicker()
.setSelection(Date().time)
.build()
picker.show(context.supportFragmentManager, picker.toString())
picker.addOnPositiveButtonClickListener {
model.activityTime.onChange(Date(it).format())
}
}
}) {
Icon(
painter = painterResource(id = R.drawable.ic_date),
contentDescription = null
)
}
}
}
private fun requestLocation() {
//如果想获取地址信息,需在配置LocationClientOption类时做相应的设置
val option = LocationClientOption()
option.locationMode = LocationClientOption.LocationMode.Hight_Accuracy
//可选,是否需要地址信息,默认为不需要,即参数为false
//如果开发者需要获得当前点的地址信息,此处必须为true
option.setIsNeedAddress(true)
//可选,设置是否需要最新版本的地址信息。默认需要,即参数为true
option.setNeedNewVersionRgc(true)
mLocationClient.locOption = option
Logger.i("开始定位")
mLocationClient.start()
}
private fun checkSelfPermissions(): Int {
location_permissions.forEach {
when (val v = ContextCompat.checkSelfPermission(this, it)) {
PackageManager.PERMISSION_GRANTED -> Logger.i("权限${it}已放通")
else -> {
Logger.i("权限${it}检查状态:$v")
return v
}
}
}
Logger.i("已授权")
return PackageManager.PERMISSION_GRANTED
}
/**
* 活动地点
*
* @param modifier
* @param model
*/
@Composable
private fun ActivityLocation(
modifier: Modifier = Modifier,
model: ApplyActViewModel = viewModel(),
scaffoldModel: ScaffoldModel = viewModel()
) {
val launcher = rememberLauncherForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
) { it ->
var isGranted = true
it.entries.forEach {
isGranted = isGranted && it.value
Logger.i("权限:${it.key}授权结果:${it.value}")
}
if (isGranted) {
// Permission Accepted: Do something
requestLocation()
model.openMap()
Logger.i("准备打开地图")
} else {
// Permission Denied: Do something
scaffoldModel.update(message = getString(R.string.denined_location_permission))
}
}
BaseTextField(modifier = modifier, form = model.activityAddress, readOnly = true) {
IconButton(onClick = {
when (PackageManager.PERMISSION_GRANTED) {
checkSelfPermissions() -> {
// Some works that require permission
requestLocation()
model.openMap()
Logger.i("准备打开地图")
}
else -> {
// Asking for permission
Logger.i("询问权限")
launcher.launch(location_permissions)
}
}
}) {
Icon(
painter = painterResource(id = R.drawable.ic_icon_location),
contentDescription = null
)
}
}
}
}

@ -91,9 +91,6 @@ const val ANSWER_TEXT_LENGTH = 15
class ExamViewModel : ScrollList<Exam>() {
val questionIsNull: String = "问题不能为空"
val deleteLeastOne: String = "至少保留一道题目"
val updateExam = "更新题库"
val postAnswer = "提交答案"
val back = "返回"
val deleteTip = "确定删除此题目?"
val addTip = "确定添加此题目?"
val actionLabel = "确定"

@ -25,10 +25,6 @@ class RegAssociationViewModel : ViewModel() {
val errorPicture = "图片加载失败,请联系管理员"
val deninedPermission = "拒绝授权"
val register = "注册"
val back = "返回"
fun setPicture(uri: Uri) {
_picture.value = uri

@ -27,6 +27,7 @@ import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import com.gyf.csams.R
import com.gyf.csams.activity.ui.ActivityDetailActivity
import com.gyf.csams.activity.ui.ApplyActActivity
import com.gyf.csams.association.model.*
import com.gyf.csams.uikit.*
import com.gyf.lib.uikit.*
@ -62,14 +63,14 @@ class AssociationActivity : ComponentActivity() {
DropdownMenu(
expanded = expanded,
onDismissRequest = { /*TODO*/ },
// offset = DpOffset.Zero.copy(x=50.dp),
properties = PopupProperties()
) {
DropdownMenuItem(onClick = {
startActivity(Intent(context, ApplyActActivity::class.java))
model.close()
}) {
Row(verticalAlignment = Alignment.CenterVertically) {
Text(text = "申请活动")
Text(text = getString(R.string.apply_act_menu))
Icon(
painter = painterResource(id = R.drawable.ic_add_fill),
contentDescription = null
@ -83,11 +84,11 @@ class AssociationActivity : ComponentActivity() {
ExamActivityType.SET_EXAM
)
}
context.startActivity(intent)
startActivity(intent)
model.close()
}) {
Row(verticalAlignment = Alignment.CenterVertically) {
Text(text = "设置题库")
Text(text = getString(R.string.set_exam_menu))
Icon(
painter = painterResource(id = R.drawable.ic_editor),
contentDescription = null
@ -95,7 +96,7 @@ class AssociationActivity : ComponentActivity() {
}
}
DropdownMenuItem(onClick = {
context.startActivity(
startActivity(
Intent(
context,
ReNameActivity::class.java
@ -104,7 +105,7 @@ class AssociationActivity : ComponentActivity() {
model.close()
}) {
Row(verticalAlignment = Alignment.CenterVertically) {
Text(text = "社团命名")
Text(text = getString(R.string.rename_menu))
Icon(
painter = painterResource(id = R.drawable.ic_exchange_rate),
contentDescription = null
@ -118,11 +119,11 @@ class AssociationActivity : ComponentActivity() {
ExamActivityType.JOIN_Association
)
}
context.startActivity(intent)
startActivity(intent)
model.close()
}) {
Row(verticalAlignment = Alignment.CenterVertically) {
Text(text = "申请入团")
Text(text = getString(R.string.join_association))
Icon(
painter = painterResource(id = R.drawable.ic_add_account),
contentDescription = null

@ -23,6 +23,7 @@ import com.gyf.csams.association.model.*
import com.gyf.csams.uikit.Background
import com.gyf.csams.uikit.BackgroundImage
import com.gyf.lib.uikit.*
import com.gyf.lib.util.BottomButton
/**
@ -56,41 +57,6 @@ class ExamActivity : ComponentActivity() {
}
/**
* 底部按钮
*
*/
@Composable
private fun BottomButton(
modifier: Modifier = Modifier,
model: ExamViewModel = viewModel(),
scaffoldModel: ScaffoldModel = viewModel()
) {
val context = LocalContext.current as ExamActivity
Row(modifier = modifier, horizontalArrangement = Arrangement.Center) {
when (context.activityType) {
ExamActivityType.SET_EXAM -> OutlinedButton(onClick = {
model.updateExam { scaffoldModel.update(message = it) }
}, modifier = Modifier.background(color = MaterialTheme.colors.primary)) {
Text(text = model.updateExam)
}
ExamActivityType.JOIN_Association -> OutlinedButton(onClick = {
model.postAnswer { scaffoldModel.update(message = it) }
}, modifier = Modifier.background(color = MaterialTheme.colors.primary)) {
Text(text = model.postAnswer)
}
}
Spacer(modifier = Modifier.width(10.dp))
OutlinedButton(onClick = {
context.onBackPressed()
}, modifier = Modifier.background(color = MaterialTheme.colors.secondary)) {
Text(text = model.back)
}
}
}
/**
* 标题
*
@ -160,7 +126,30 @@ class ExamActivity : ComponentActivity() {
Column {
Divider(color = MaterialTheme.colors.background)
Spacer(modifier = Modifier.height(30.dp))
BottomButton(modifier = Modifier.fillMaxWidth())
val context = LocalContext.current as ExamActivity
when (context.activityType) {
ExamActivityType.SET_EXAM -> {
BottomButton(
modifier = Modifier.fillMaxWidth(),
confirmDesc = R.string.update_exam
) {
model.updateExam { scaffoldModel.update(message = it) }
}
}
ExamActivityType.JOIN_Association -> {
BottomButton(
modifier = Modifier.fillMaxWidth(),
confirmDesc = R.string.post_answer
) {
model.postAnswer { scaffoldModel.update(message = it) }
}
}
}
}
}

@ -5,17 +5,16 @@ import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.*
import androidx.compose.material.MaterialTheme
import androidx.compose.material.OutlinedButton
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.gyf.csams.association.model.RenameViewModel
import com.gyf.csams.uikit.Background
import com.gyf.csams.uikit.BackgroundImage
import com.gyf.lib.uikit.*
import com.gyf.lib.util.BottomButton
/**
* 社团重命名
@ -41,7 +40,15 @@ class ReNameActivity : ComponentActivity() {
Cause(modifier = Modifier.weight(0.2F))
Spacer(modifier = Modifier.height(10.dp))
BottomButton(modifier = Modifier.weight(0.1F))
val model: RenameViewModel = viewModel()
val scaffoldModel: ScaffoldModel = viewModel()
BottomButton(
modifier = Modifier
.weight(0.1F)
.fillMaxWidth()
) {
model.post { scaffoldModel.update(message = it) }
}
Spacer(modifier = Modifier.weight(1 - 0.2F * 2 - 0.1F * 4))
ShowSnackbar(scaffoldState = scaffoldState)
@ -87,33 +94,4 @@ class ReNameActivity : ComponentActivity() {
private fun Cause(modifier: Modifier = Modifier, model: RenameViewModel = viewModel()) {
BaseTextField(form = model.cause, modifier = modifier.fillMaxWidth())
}
/**
* 操作按钮
*
* @param modifier
* @param model
*/
@Composable
private fun BottomButton(
modifier: Modifier = Modifier,
model: RenameViewModel = viewModel(),
scaffoldModel: ScaffoldModel = viewModel()
) {
Row(modifier = modifier.fillMaxWidth()) {
val weight = (1 - 0.5F) / 2
val context = LocalContext.current as ReNameActivity
Spacer(modifier = Modifier.weight(weight))
Row(modifier = Modifier.weight(0.5F)) {
OutlinedButton(onClick = { model.post { scaffoldModel.update(message = it) } }) {
Text(text = model.postDesc)
}
Spacer(modifier = Modifier.width(10.dp))
OutlinedButton(onClick = { context.onBackPressed() }) {
Text(text = model.back)
}
}
Spacer(modifier = Modifier.weight(weight))
}
}
}

@ -13,7 +13,6 @@ import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.compose.setContent
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.*
import androidx.compose.material.IconButton
@ -26,7 +25,6 @@ import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
import androidx.core.content.ContextCompat
@ -36,6 +34,7 @@ import com.gyf.csams.association.model.RegAssociationViewModel
import com.gyf.csams.uikit.Background
import com.gyf.csams.uikit.BackgroundImage
import com.gyf.lib.uikit.*
import com.gyf.lib.util.BottomButton
import com.orhanobut.logger.Logger
@ -73,7 +72,15 @@ class RegAssociationActivity : ComponentActivity() {
.fillMaxWidth()
)
Spacer(modifier = Modifier.weight(0.05F))
BottomButton(modifier = Modifier.fillMaxWidth())
val model: RegAssociationViewModel = viewModel()
val scaffoldModel: ScaffoldModel = viewModel()
BottomButton(
modifier = Modifier.fillMaxWidth(),
confirmDesc = R.string.reg_btn
) {
model.register { scaffoldModel.update(message = it) }
}
Spacer(modifier = Modifier.weight(0.05F))
ShowSnackbar(scaffoldState = scaffoldState)
@ -90,7 +97,11 @@ class RegAssociationActivity : ComponentActivity() {
* @param modifier
*/
@Composable
private fun Logo(model: RegAssociationViewModel = viewModel(), modifier: Modifier) {
private fun Logo(
model: RegAssociationViewModel = viewModel(),
scaffoldModel: ScaffoldModel = viewModel(),
modifier: Modifier
) {
val photoIntent = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI)
photoIntent.type = "image/*"
val uri: Uri? by model.picture.observeAsState()
@ -109,7 +120,7 @@ class RegAssociationActivity : ComponentActivity() {
//model.loadPicture(context)
resultLauncher.launch(photoIntent)
}
// val context= LocalContext.current
val launcher = rememberLauncherForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGranted: Boolean ->
@ -118,12 +129,12 @@ class RegAssociationActivity : ComponentActivity() {
loadPicture()
} else {
// Permission Denied: Do something
Logger.w(model.deninedPermission)
scaffoldModel.update(message = getString(R.string.denined_photo_permission))
}
}
val context = LocalContext.current
Box(contentAlignment = Alignment.Center, modifier = modifier) {
Row(
@ -137,7 +148,7 @@ class RegAssociationActivity : ComponentActivity() {
OutlinedButton(onClick = {
when (PackageManager.PERMISSION_GRANTED) {
ContextCompat.checkSelfPermission(
context,
applicationContext,
Manifest.permission.READ_EXTERNAL_STORAGE
) -> {
// Some works that require permission
@ -157,7 +168,7 @@ class RegAssociationActivity : ComponentActivity() {
Row {
Image(
bitmap = BitmapFactory.decodeStream(
context.contentResolver.openInputStream(
contentResolver.openInputStream(
it
)
)
@ -188,29 +199,6 @@ class RegAssociationActivity : ComponentActivity() {
}
@Composable
private fun BottomButton(
modifier: Modifier = Modifier,
scaffoldModel: ScaffoldModel = viewModel(),
model: RegAssociationViewModel = viewModel()
) {
val context = LocalContext.current as RegAssociationActivity
Row(modifier = modifier, horizontalArrangement = Arrangement.Center) {
OutlinedButton(onClick = {
model.register { scaffoldModel.update(message = it) }
}, modifier = Modifier.background(color = MaterialTheme.colors.primary)) {
Text(text = model.register)
}
Spacer(modifier = Modifier.width(10.dp))
OutlinedButton(onClick = {
context.onBackPressed()
}, modifier = Modifier.background(color = MaterialTheme.colors.secondary)) {
Text(text = model.back)
}
}
}
/**
* 菜单标题
*

@ -27,7 +27,6 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavHostController
import androidx.navigation.compose.navigate
import com.gyf.csams.MainApplication
import com.gyf.csams.R
import com.gyf.csams.main.model.MarqueeViewModel
@ -423,9 +422,10 @@ enum class BackgroundImage(@DrawableRes val id: Int) {
ActivityBBS(R.drawable.mb_bg_fb_04),
//系统通知
ActivityMessage(R.drawable.mb_bg_fb_26)
ActivityMessage(R.drawable.mb_bg_fb_26),
//申请活动
ApplyActivity(R.drawable.mb_bg_fb_07)
}
/**
@ -492,7 +492,7 @@ fun Poster(modifier: Modifier = Modifier, imageBitmap: ImageBitmap? = null) {
*
*/
@Composable
fun DescCard(modifier: Modifier, content: String) {
fun DescCard(modifier: Modifier, content: String? = null, stringForm: StringForm? = null) {
Card(
modifier = modifier,
backgroundColor = Color.Transparent
@ -507,14 +507,20 @@ fun DescCard(modifier: Modifier, content: String) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(horizontal = 10.dp)
.padding(horizontal = 10.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Spacer(modifier = Modifier.weight(0.15F))
if (stringForm != null) {
BaseTextField(modifier = Modifier.weight(0.65F), form = stringForm)
} else {
Text(
modifier = Modifier.weight(0.65F),
text = content,
text = content ?: "",
overflow = TextOverflow.Ellipsis
)
}
Spacer(modifier = Modifier.weight(0.15F))
}

@ -17,11 +17,11 @@ import kotlinx.coroutines.launch
class ImageModel(application: Application, private val urlPath: String) :
AndroidViewModel(application) {
private val _image = MutableLiveData<ImageBitmap>()
private val _imageUrls = MutableLiveData<List<String>>()
private val _imageUrls = MutableLiveData<List<String>?>()
val image: LiveData<ImageBitmap> = _image
private var job: Job? = null
private val _error = MutableLiveData<String>()
val error: LiveData<String> = _error
private val _error = MutableLiveData<String?>()
val error: LiveData<String?> = _error
fun clearError() {
_error.value = null
@ -64,7 +64,9 @@ class ImageModel(application: Application, private val urlPath: String) :
data = get(index)
)
Logger.e("成功从image url:${get(index)}解析图片")
_image.postValue(imageBitmap)
imageBitmap?.apply {
_image.postValue(this)
}
delay(5000)
index = if (index == size - 1) 0 else index.plus(1)
} while (job?.isActive == true)

@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="200dp"
android:height="200dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:fillColor="#FF000000"
android:pathData="M789.6,187.35h-77.04c-7.17,0 -13.02,-5.85 -13.02,-13.02v-34.28c0,-13.43 -10.55,-24.88 -24.06,-25.21 -14.01,-0.41 -25.38,10.79 -25.38,24.72v34.85c0,7.17 -5.85,13.02 -13.02,13.02L389.48,187.44c-7.17,0 -13.02,-5.85 -13.02,-13.02v-34.36c0,-13.43 -10.55,-24.88 -24.06,-25.21 -14.01,-0.41 -25.38,10.79 -25.38,24.72v34.85c0,7.17 -5.85,13.02 -13.02,13.02h-79.6c-62.87,-0.08 -113.79,50.84 -113.79,113.71v494.22c0,62.87 50.92,113.79 113.79,113.79h555.11c62.87,0 113.79,-50.92 113.79,-113.79v-494.22c0.08,-62.87 -50.84,-113.79 -113.71,-113.79zM170.05,301.14c0,-35.6 28.84,-64.35 64.35,-64.35h79.6c7.17,0 13.02,5.85 13.02,13.02v34.36c0,13.43 10.55,24.88 24.06,25.21 14.01,0.41 25.38,-10.79 25.38,-24.72v-34.85c0,-7.17 5.85,-13.02 13.02,-13.02h247.6c7.17,0 13.02,5.85 13.02,13.02v34.36c0,13.43 10.55,24.88 24.06,25.21 14.01,0.41 25.38,-10.79 25.38,-24.72v-34.85c0,-7.17 5.85,-13.02 13.02,-13.02h77.04c35.6,0 64.35,28.84 64.35,64.35v37.41a16.95,16.95 0,0 1,-16.97 16.97L187.02,355.53a16.95,16.95 0,0 1,-16.97 -16.97v-37.41zM853.95,795.45c0,35.6 -28.84,64.35 -64.35,64.35L234.4,859.8c-35.51,0 -64.35,-28.84 -64.35,-64.35L170.05,421.94a16.95,16.95 0,0 1,16.97 -16.97h650.03a16.95,16.95 0,0 1,16.97 16.97l-0.08,373.51z" />
<path
android:fillColor="#FF000000"
android:pathData="M288.13,573.06h54.13c13.6,0 24.72,-11.04 24.72,-24.72 0,-13.6 -11.04,-24.72 -24.72,-24.72h-54.13c-13.6,0 -24.72,11.04 -24.72,24.72s11.04,24.72 24.72,24.72zM484.97,573.06h54.13c13.6,0 24.72,-11.04 24.72,-24.72 0,-13.6 -11.04,-24.72 -24.72,-24.72h-54.13c-13.6,0 -24.72,11.04 -24.72,24.72 0.08,13.68 11.12,24.72 24.72,24.72zM681.9,573.06h54.13c13.6,0 24.72,-11.04 24.72,-24.72 0,-13.6 -11.04,-24.72 -24.72,-24.72h-54.13c-13.6,0 -24.72,11.04 -24.72,24.72s11.04,24.72 24.72,24.72zM347.2,691.71L293.07,691.71c-13.6,0 -24.72,11.04 -24.72,24.72 0,13.6 11.04,24.72 24.72,24.72h54.13c13.6,0 24.72,-11.04 24.72,-24.72s-11.04,-24.72 -24.72,-24.72zM544.14,691.71L490,691.71c-13.6,0 -24.72,11.04 -24.72,24.72 0,13.6 11.04,24.72 24.72,24.72h54.13c13.6,0 24.72,-11.04 24.72,-24.72 -0.08,-13.68 -11.12,-24.72 -24.72,-24.72zM740.98,691.71h-54.14c-13.6,0 -24.72,11.04 -24.72,24.72 0,13.6 11.04,24.72 24.72,24.72h54.13c13.6,0 24.72,-11.04 24.72,-24.72s-11.04,-24.72 -24.72,-24.72z" />
</vector>

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="200dp"
android:height="200dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:fillColor="#FF000000"
android:pathData="M698.98,181.33c-136.41,-141.54 -357.56,-141.54 -493.97,0 -136.41,141.57 -136.41,371.1 0,512.65l246.99,254.87 246.99,-254.87c136.41,-141.54 136.41,-371.08 0,-512.65zM451.99,533.84a109.18,109.18 0,0 1,-109.16 -109.22,109.2 109.2,0 0,1 109.16,-109.22 109.2,109.2 0,0 1,109.16 109.22,109.2 109.2,0 0,1 -109.16,109.22z" />
</vector>

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="200dp"
android:height="200dp"
android:viewportWidth="1024"
android:viewportHeight="1024">
<path
android:fillColor="#FF000000"
android:pathData="M235.95,472.94l-45.23,45.31 210.09,209.51 432.36,-427.69 -45.01,-45.48 -387.16,382.98z" />
</vector>

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="apply_act_menu">申请活动</string>
<string name="set_exam_menu">设置题库</string>
<string name="rename_menu">社团命名</string>
<string name="join_association">申请入团</string>
<string name="update_exam">更新题库</string>
<string name="post_answer">提交答案</string>
<string name="denined_photo_permission">相册权限请求失败,无法读取相册</string>
<string name="denined_location_permission">部分权限请求失败,无法获取地理位置</string>
<string name="at"></string>
<string name="city">城市</string>
<string name="address">地点</string>
<string name="incity">市内找</string>
</resources>

@ -1,16 +0,0 @@
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.CSAMS" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_200</item>
<item name="colorPrimaryVariant">@color/purple_700</item>
<item name="colorOnPrimary">@color/black</item>
<!-- Secondary brand color. -->
<item name="colorSecondary">@color/teal_200</item>
<item name="colorSecondaryVariant">@color/teal_200</item>
<item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. -->
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
</style>
</resources>

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="apply_act_menu">申请活动</string>
<string name="set_exam_menu">设置题库</string>
<string name="rename_menu">社团命名</string>
<string name="join_association">申请入团</string>
<string name="update_exam">更新题库</string>
<string name="post_answer">提交答案</string>
<string name="denined_photo_permission">相册权限请求失败,无法读取相册</string>
<string name="denined_location_permission">部分权限请求失败,无法获取地理位置</string>
<string name="at"></string>
<string name="city">城市</string>
<string name="address">地点</string>
<string name="incity">市内找</string>
</resources>

@ -1,3 +1,15 @@
<resources>
<string name="apply_act_menu">申请活动</string>
<string name="set_exam_menu">设置题库</string>
<string name="rename_menu">社团命名</string>
<string name="join_association">申请入团</string>
<string name="update_exam">更新题库</string>
<string name="post_answer">提交答案</string>
<string name="denined_photo_permission">相册权限请求失败,无法读取相册</string>
<string name="denined_location_permission">部分权限请求失败,无法获取地理位置</string>
<string name="at"></string>
<string name="city">城市</string>
<string name="address">地点</string>
<string name="incity">市内找</string>
</resources>

@ -2,7 +2,6 @@ package com.gyf.csams
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import com.gyf.lib.uikit.TestLib
import com.gyf.lib.util.ApiResponse
import com.gyf.lib.util.randomChinese
import org.junit.Assert.assertEquals
@ -47,8 +46,8 @@ class ExampleUnitTest {
@Test
fun testLib() {
TestLib.hello()
Fuck()
println(0 in 1..2)
println(1 in 1..2)
}
}

Binary file not shown.

@ -47,12 +47,12 @@ dependencies {
* 针对最新的平台功能和 API 调整应用,同时还支持旧设备。
* https://developer.android.com/jetpack/androidx/releases/core
*/
api("androidx.core:core-ktx:1.3.2")
api("androidx.core:core-ktx:1.5.0")
/**
* 允许在平台旧版 API 上访问新 API(很多使用 Material Design)。
* https://developer.android.com/jetpack/androidx/releases/appcompat
*/
api("androidx.appcompat:appcompat:1.2.0")
api("androidx.appcompat:appcompat:1.3.0")
/**
* 与设备互动所需的 Compose UI 的基本组件,包括布局、绘图和输入。
* https://developer.android.com/jetpack/androidx/releases/compose-ui
@ -76,12 +76,12 @@ dependencies {
* https://developer.android.com/jetpack/androidx/releases/lifecycle
*/
api("androidx.lifecycle:lifecycle-runtime-ktx:${rootProject.extra["lifecycle_version"]}")
api("androidx.lifecycle:lifecycle-viewmodel-compose:1.0.0-alpha04")
api("androidx.lifecycle:lifecycle-viewmodel-compose:1.0.0-alpha05")
/**
* 访问基于 Activity 构建的可组合 API。
* https://developer.android.com/jetpack/androidx/releases/activity
*/
api("androidx.activity:activity-compose:1.3.0-alpha07")
api("androidx.activity:activity-compose:1.3.0-alpha08")
/**
* Simple, pretty and powerful logger for android
* https://github.com/orhanobut/logger
@ -103,7 +103,11 @@ dependencies {
/**
* https://developer.android.com/jetpack/androidx/releases/navigation
*/
api("androidx.navigation:navigation-compose:1.0.0-alpha10")
api("androidx.navigation:navigation-compose:2.4.0-alpha01")
/**
* https://developer.android.com/jetpack/androidx/releases/fragment
*/
api("androidx.fragment:fragment-ktx:1.3.4")
/**
* https://developer.android.com/jetpack/androidx/releases/room
*/

@ -1,4 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest package="com.gyf.lib">
</manifest>

@ -2,6 +2,7 @@ package com.gyf.lib.uikit
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.LocalTextStyle
import androidx.compose.material.OutlinedTextField
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
@ -9,6 +10,7 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.VisualTransformation
import androidx.lifecycle.LiveData
@ -17,17 +19,15 @@ import com.orhanobut.logger.Logger
interface FormLength {
val nameLengthError: String
val formError: MutableLiveData<String>
}
abstract class FormName<T>(val formDesc: String) {
protected val _formValue = MutableLiveData<T>()
val formValue: LiveData<T> = _formValue
val formPlaceholder = "请输入$formDesc"
open val formPlaceholder = "请输入$formDesc"
abstract fun onChange(value: T)
}
/**
@ -49,13 +49,14 @@ open class StringForm(formDesc: String, val textLength: Int) :
_formValue.value = value
}
override val nameLengthError = "${formDesc}不能超过最大长度$textLength"
override val formError = MutableLiveData("")
override fun onChange(value: String) {
if (value.length > textLength) {
_formValue.value = value.slice(IntRange(0, textLength - 1))
formError.value = "${formDesc}不能超过最大长度$textLength"
_formValue.postValue(value.slice(IntRange(0, textLength - 1)))
} else {
_formValue.value = value
_formValue.postValue(value)
}
Logger.i("${formDesc}更新值:${_formValue.value}")
}
@ -76,7 +77,11 @@ fun <T : StringForm> BaseTextField(
singeLine: Boolean = false,
keyboardOptions: KeyboardOptions = KeyboardOptions.Default.copy(imeAction = ImeAction.Done),
isError: Boolean = false,
visualTransformation: VisualTransformation = VisualTransformation.None
visualTransformation: VisualTransformation = VisualTransformation.None,
readOnly: Boolean = false,
textStyle: TextStyle = LocalTextStyle.current,
leadingIcon: @Composable (() -> Unit)? = null,
trailingIcon: @Composable (() -> Unit)? = null,
) {
val name: String by form.formValue.observeAsState("")
val focusManager = LocalFocusManager.current
@ -89,8 +94,46 @@ fun <T : StringForm> BaseTextField(
singleLine = singeLine,
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
keyboardOptions = keyboardOptions,
trailingIcon = { Text(text = "${name.length}/${form.textLength}") },
leadingIcon = leadingIcon,
trailingIcon = trailingIcon,
isError = isError,
visualTransformation = visualTransformation
visualTransformation = visualTransformation,
readOnly = readOnly,
textStyle = textStyle
)
}
/**
* 带字符长度计数的文本编辑框
*
* @param T 字段类型
* @param modifier
* @param form 字段
* @param singeLine
* @param keyboardOptions
* @param isError
* @param visualTransformation
*/
@Composable
fun <T : StringForm> BaseTextField(
modifier: Modifier = Modifier,
form: T,
singeLine: Boolean = false,
keyboardOptions: KeyboardOptions = KeyboardOptions.Default.copy(imeAction = ImeAction.Done),
isError: Boolean = false,
visualTransformation: VisualTransformation = VisualTransformation.None,
readOnly: Boolean = false
) {
val name: String by form.formValue.observeAsState("")
BaseTextField(
modifier,
form,
singeLine,
keyboardOptions,
isError,
visualTransformation,
trailingIcon = { Text(text = "${name.length}/${form.textLength}") },
readOnly = readOnly
)
}

@ -7,7 +7,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.navigation.NavHostController
import androidx.navigation.compose.navigate
import androidx.navigation.compose.rememberNavController
import com.gyf.lib.uikit.theme.CSAMSTheme

@ -27,8 +27,8 @@ data class SnackBar(
*
*/
class ScaffoldModel : ViewModel() {
private val _data = MutableLiveData<SnackBar>()
val data: LiveData<SnackBar> = _data
private val _data = MutableLiveData<SnackBar?>()
val data: LiveData<SnackBar?> = _data
fun update(message: String? = null, actionLabel: String? = null, callback: () -> Unit? = {}) {
if (message == null) {

@ -0,0 +1,41 @@
package com.gyf.lib.util
import android.app.Activity
import androidx.annotation.StringRes
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.width
import androidx.compose.material.MaterialTheme
import androidx.compose.material.OutlinedButton
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import com.gyf.lib.R
@Composable
fun BottomButton(
modifier: Modifier = Modifier,
@StringRes confirmDesc: Int = R.string.confirm_btn,
@StringRes backDesc: Int = R.string.back_btn,
onConfirm: () -> Unit
) {
val context = LocalContext.current as Activity
Row(modifier = modifier, horizontalArrangement = Arrangement.Center) {
OutlinedButton(
onClick = onConfirm,
modifier = Modifier.background(color = MaterialTheme.colors.primary)
) {
Text(text = context.getString(confirmDesc))
}
Spacer(modifier = Modifier.width(10.dp))
OutlinedButton(onClick = {
context.onBackPressed()
}, modifier = Modifier.background(color = MaterialTheme.colors.secondary)) {
Text(text = context.getString(backDesc))
}
}
}

@ -50,6 +50,7 @@ fun randomDateTime(): Date {
}
}
fun Date.format(): String {
return FORMAT.format(this)
}

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<integer name="activity_size">20</integer>
</resources>

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="activity_application">活动申请书</string>
<string name="activity_name">活动名称</string>
<string name="activity_time">活动时间</string>
<string name="confirm_btn">提交</string>
<string name="back_btn">返回</string>
<string name="reg_btn">注册</string>
<string name="activity_address">活动地点</string>
<string name="activity_size">活动人数</string>
<string name="activity_size_error">活动人数要在%1d到%2d之间</string>
<string name="activity_desc">活动介绍</string>
<string name="search_btn">检索</string>
<string name="search_null">未找到结果</string>
</resources>

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<integer name="activity_size">20</integer>
</resources>

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="activity_application">活动申请书</string>
<string name="activity_name">活动名称</string>
<string name="activity_time">活动时间</string>
<string name="confirm_btn">提交</string>
<string name="back_btn">返回</string>
<string name="reg_btn">注册</string>
<string name="activity_address">活动地点</string>
<string name="activity_size">活动人数</string>
<string name="activity_size_error">活动人数要在%1d到%2d之间</string>
<string name="activity_desc">活动介绍</string>
<string name="search_btn">检索</string>
<string name="search_null">未找到结果</string>
</resources>

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<integer name="activity_size">20</integer>
</resources>

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="activity_application">活动申请书</string>
<string name="activity_name">活动名称</string>
<string name="activity_time">活动时间</string>
<string name="confirm_btn">提交</string>
<string name="back_btn">返回</string>
<string name="reg_btn">注册</string>
<string name="activity_address">活动地点</string>
<string name="activity_size">活动人数</string>
<string name="activity_size_error">活动人数要在%1d到%2d之间</string>
<string name="activity_desc">活动介绍</string>
<string name="search_btn">检索</string>
<string name="search_null">未找到结果</string>
</resources>

@ -1,9 +1,9 @@
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
maven("https://maven.aliyun.com/repository/google")
maven("https://maven.aliyun.com/repository/public")
maven("https://maven.aliyun.com/repository/jcenter")
// maven("https://maven.aliyun.com/repository/google")
// maven("https://maven.aliyun.com/repository/public")
// maven("https://maven.aliyun.com/repository/jcenter")
google()
mavenCentral()
}

Loading…
Cancel
Save