diff --git a/build.gradle.kts b/build.gradle.kts index 163910a..df9e2cb 100644 --- a/build.gradle.kts +++ b/build.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 diff --git a/foreground/build.gradle.kts b/foreground/build.gradle.kts index 612e5f4..2b05dbb 100644 --- a/foreground/build.gradle.kts +++ b/foreground/build.gradle.kts @@ -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 diff --git a/foreground/src/main/AndroidManifest.xml b/foreground/src/main/AndroidManifest.xml index 179e623..36a66d9 100644 --- a/foreground/src/main/AndroidManifest.xml +++ b/foreground/src/main/AndroidManifest.xml @@ -1,11 +1,22 @@ - - - - - + + + + + + + + + + + + + + + + + + + + + + () + 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() + + 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") + + } + } } \ No newline at end of file diff --git a/foreground/src/main/java/com/gyf/csams/account/ui/AccountActivity.kt b/foreground/src/main/java/com/gyf/csams/account/ui/AccountActivity.kt index 91a08f4..80248db 100644 --- a/foreground/src/main/java/com/gyf/csams/account/ui/AccountActivity.kt +++ b/foreground/src/main/java/com/gyf/csams/account/ui/AccountActivity.kt @@ -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 diff --git a/foreground/src/main/java/com/gyf/csams/activity/model/ApplyActViewModel.kt b/foreground/src/main/java/com/gyf/csams/activity/model/ApplyActViewModel.kt new file mode 100644 index 0000000..d27df74 --- /dev/null +++ b/foreground/src/main/java/com/gyf/csams/activity/model/ApplyActViewModel.kt @@ -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() + + val mapView: LiveData = _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() + + 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?) { + _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 = 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?) { + 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() + val showMap: LiveData = _showMap + + private val _mStatusChangeByItemClick = MutableLiveData(false) + + private val _poInfo = MutableLiveData>() + val poiInfo: LiveData> = _poInfo + + private val _selectPoi = MutableLiveData() + val selectPoi: LiveData = _selectPoi + + private val _sugResult = MutableLiveData>() + val sugResult: LiveData> = _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) + } +} \ No newline at end of file diff --git a/foreground/src/main/java/com/gyf/csams/activity/ui/ApplyActActivity.kt b/foreground/src/main/java/com/gyf/csams/activity/ui/ApplyActActivity.kt new file mode 100644 index 0000000..ac4de30 --- /dev/null +++ b/foreground/src/main/java/com/gyf/csams/activity/ui/ApplyActActivity.kt @@ -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 + ) { + 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 + ) + } + } + } +} + diff --git a/foreground/src/main/java/com/gyf/csams/association/model/ExamViewModel.kt b/foreground/src/main/java/com/gyf/csams/association/model/ExamViewModel.kt index fff046e..c2cc191 100644 --- a/foreground/src/main/java/com/gyf/csams/association/model/ExamViewModel.kt +++ b/foreground/src/main/java/com/gyf/csams/association/model/ExamViewModel.kt @@ -91,9 +91,6 @@ const val ANSWER_TEXT_LENGTH = 15 class ExamViewModel : ScrollList() { val questionIsNull: String = "问题不能为空" val deleteLeastOne: String = "至少保留一道题目" - val updateExam = "更新题库" - val postAnswer = "提交答案" - val back = "返回" val deleteTip = "确定删除此题目?" val addTip = "确定添加此题目?" val actionLabel = "确定" diff --git a/foreground/src/main/java/com/gyf/csams/association/model/RegAssociationViewModel.kt b/foreground/src/main/java/com/gyf/csams/association/model/RegAssociationViewModel.kt index 9f8e810..e8ecb55 100644 --- a/foreground/src/main/java/com/gyf/csams/association/model/RegAssociationViewModel.kt +++ b/foreground/src/main/java/com/gyf/csams/association/model/RegAssociationViewModel.kt @@ -25,10 +25,6 @@ class RegAssociationViewModel : ViewModel() { val errorPicture = "图片加载失败,请联系管理员" - val deninedPermission = "拒绝授权" - - val register = "注册" - val back = "返回" fun setPicture(uri: Uri) { _picture.value = uri diff --git a/foreground/src/main/java/com/gyf/csams/association/ui/AssociationActivity.kt b/foreground/src/main/java/com/gyf/csams/association/ui/AssociationActivity.kt index 5f9595f..b1eebb7 100644 --- a/foreground/src/main/java/com/gyf/csams/association/ui/AssociationActivity.kt +++ b/foreground/src/main/java/com/gyf/csams/association/ui/AssociationActivity.kt @@ -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 diff --git a/foreground/src/main/java/com/gyf/csams/association/ui/ExamActivity.kt b/foreground/src/main/java/com/gyf/csams/association/ui/ExamActivity.kt index b20f449..f5a9da6 100644 --- a/foreground/src/main/java/com/gyf/csams/association/ui/ExamActivity.kt +++ b/foreground/src/main/java/com/gyf/csams/association/ui/ExamActivity.kt @@ -23,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) } + } + } + + + } + + } } diff --git a/foreground/src/main/java/com/gyf/csams/association/ui/ReNameActivity.kt b/foreground/src/main/java/com/gyf/csams/association/ui/ReNameActivity.kt index 60b2427..8d2b863 100644 --- a/foreground/src/main/java/com/gyf/csams/association/ui/ReNameActivity.kt +++ b/foreground/src/main/java/com/gyf/csams/association/ui/ReNameActivity.kt @@ -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)) - } - } } \ No newline at end of file diff --git a/foreground/src/main/java/com/gyf/csams/association/ui/RegAssociationActivity.kt b/foreground/src/main/java/com/gyf/csams/association/ui/RegAssociationActivity.kt index bb86cb0..a58b49b 100644 --- a/foreground/src/main/java/com/gyf/csams/association/ui/RegAssociationActivity.kt +++ b/foreground/src/main/java/com/gyf/csams/association/ui/RegAssociationActivity.kt @@ -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 @@ -60,23 +59,31 @@ class RegAssociationActivity : ComponentActivity() { .weight(0.1F) ) Title() - Name() - Desc( - modifier = Modifier - .weight(0.1F) - .fillMaxWidth() - ) - Spacer(modifier = Modifier.weight(0.05F)) - Logo( - modifier = Modifier - .weight(0.2F) - .fillMaxWidth() - ) - Spacer(modifier = Modifier.weight(0.05F)) - BottomButton(modifier = Modifier.fillMaxWidth()) - Spacer(modifier = Modifier.weight(0.05F)) - - ShowSnackbar(scaffoldState = scaffoldState) + Name() + Desc( + modifier = Modifier + .weight(0.1F) + .fillMaxWidth() + ) + Spacer(modifier = Modifier.weight(0.05F)) + Logo( + modifier = Modifier + .weight(0.2F) + .fillMaxWidth() + ) + Spacer(modifier = Modifier.weight(0.05F)) + + val model: RegAssociationViewModel = viewModel() + val scaffoldModel: ScaffoldModel = viewModel() + BottomButton( + modifier = Modifier.fillMaxWidth(), + confirmDesc = R.string.reg_btn + ) { + model.register { scaffoldModel.update(message = it) } + } + Spacer(modifier = Modifier.weight(0.05F)) + + 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) - } - } - - } - /** * 菜单标题 * diff --git a/foreground/src/main/java/com/gyf/csams/uikit/BaseView.kt b/foreground/src/main/java/com/gyf/csams/uikit/BaseView.kt index 0c0dea1..f2895fe 100644 --- a/foreground/src/main/java/com/gyf/csams/uikit/BaseView.kt +++ b/foreground/src/main/java/com/gyf/csams/uikit/BaseView.kt @@ -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)) - Text( - modifier = Modifier.weight(0.65F), - text = content, - overflow = TextOverflow.Ellipsis - ) + if (stringForm != null) { + BaseTextField(modifier = Modifier.weight(0.65F), form = stringForm) + } else { + Text( + modifier = Modifier.weight(0.65F), + text = content ?: "", + overflow = TextOverflow.Ellipsis + ) + } + Spacer(modifier = Modifier.weight(0.15F)) } diff --git a/foreground/src/main/java/com/gyf/csams/uikit/ViewModel.kt b/foreground/src/main/java/com/gyf/csams/uikit/ViewModel.kt index 8e1a607..3db437d 100644 --- a/foreground/src/main/java/com/gyf/csams/uikit/ViewModel.kt +++ b/foreground/src/main/java/com/gyf/csams/uikit/ViewModel.kt @@ -17,11 +17,11 @@ import kotlinx.coroutines.launch class ImageModel(application: Application, private val urlPath: String) : AndroidViewModel(application) { private val _image = MutableLiveData() - private val _imageUrls = MutableLiveData>() + private val _imageUrls = MutableLiveData?>() val image: LiveData = _image private var job: Job? = null - private val _error = MutableLiveData() - val error: LiveData = _error + private val _error = MutableLiveData() + val error: LiveData = _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) diff --git a/foreground/src/main/res/drawable/ic_date.xml b/foreground/src/main/res/drawable/ic_date.xml new file mode 100644 index 0000000..b1e1307 --- /dev/null +++ b/foreground/src/main/res/drawable/ic_date.xml @@ -0,0 +1,12 @@ + + + + diff --git a/foreground/src/main/res/drawable/ic_icon_location.xml b/foreground/src/main/res/drawable/ic_icon_location.xml new file mode 100644 index 0000000..083bcc9 --- /dev/null +++ b/foreground/src/main/res/drawable/ic_icon_location.xml @@ -0,0 +1,9 @@ + + + diff --git a/foreground/src/main/res/drawable/ic_seleted.xml b/foreground/src/main/res/drawable/ic_seleted.xml new file mode 100644 index 0000000..04868a7 --- /dev/null +++ b/foreground/src/main/res/drawable/ic_seleted.xml @@ -0,0 +1,9 @@ + + + diff --git a/foreground/src/main/res/values-en/strings.xml b/foreground/src/main/res/values-en/strings.xml new file mode 100644 index 0000000..b75ce61 --- /dev/null +++ b/foreground/src/main/res/values-en/strings.xml @@ -0,0 +1,15 @@ + + + 申请活动 + 设置题库 + 社团命名 + 申请入团 + 更新题库 + 提交答案 + 相册权限请求失败,无法读取相册 + 部分权限请求失败,无法获取地理位置 + + 城市 + 地点 + 市内找 + \ No newline at end of file diff --git a/foreground/src/main/res/values-night/themes.xml b/foreground/src/main/res/values-night/themes.xml deleted file mode 100644 index 1ff666e..0000000 --- a/foreground/src/main/res/values-night/themes.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - \ No newline at end of file diff --git a/foreground/src/main/res/values-zh/strings.xml b/foreground/src/main/res/values-zh/strings.xml new file mode 100644 index 0000000..b75ce61 --- /dev/null +++ b/foreground/src/main/res/values-zh/strings.xml @@ -0,0 +1,15 @@ + + + 申请活动 + 设置题库 + 社团命名 + 申请入团 + 更新题库 + 提交答案 + 相册权限请求失败,无法读取相册 + 部分权限请求失败,无法获取地理位置 + + 城市 + 地点 + 市内找 + \ No newline at end of file diff --git a/foreground/src/main/res/values/strings.xml b/foreground/src/main/res/values/strings.xml index 6b30d8d..642eed0 100644 --- a/foreground/src/main/res/values/strings.xml +++ b/foreground/src/main/res/values/strings.xml @@ -1,3 +1,15 @@ + 申请活动 + 设置题库 + 社团命名 + 申请入团 + 更新题库 + 提交答案 + 相册权限请求失败,无法读取相册 + 部分权限请求失败,无法获取地理位置 + + 城市 + 地点 + 市内找 \ No newline at end of file diff --git a/foreground/src/test/java/com/gyf/csams/ExampleUnitTest.kt b/foreground/src/test/java/com/gyf/csams/ExampleUnitTest.kt index baed3db..87a7a5c 100644 --- a/foreground/src/test/java/com/gyf/csams/ExampleUnitTest.kt +++ b/foreground/src/test/java/com/gyf/csams/ExampleUnitTest.kt @@ -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) } } diff --git a/jks/csams.jks b/jks/csams.jks new file mode 100644 index 0000000..7d0ee52 Binary files /dev/null and b/jks/csams.jks differ diff --git a/lib/build.gradle.kts b/lib/build.gradle.kts index bec0ea0..ed72278 100644 --- a/lib/build.gradle.kts +++ b/lib/build.gradle.kts @@ -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 */ diff --git a/lib/src/main/AndroidManifest.xml b/lib/src/main/AndroidManifest.xml index 4bfa72b..f92bbcc 100644 --- a/lib/src/main/AndroidManifest.xml +++ b/lib/src/main/AndroidManifest.xml @@ -1,4 +1,3 @@ - \ No newline at end of file diff --git a/lib/src/main/java/com/gyf/lib/uikit/BaseTextField.kt b/lib/src/main/java/com/gyf/lib/uikit/BaseTextField.kt index d649554..cebf138 100644 --- a/lib/src/main/java/com/gyf/lib/uikit/BaseTextField.kt +++ b/lib/src/main/java/com/gyf/lib/uikit/BaseTextField.kt @@ -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 } abstract class FormName(val formDesc: String) { protected val _formValue = MutableLiveData() val formValue: LiveData = _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 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 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 ) -} \ No newline at end of file +} + +/** + * 带字符长度计数的文本编辑框 + * + * @param T 字段类型 + * @param modifier + * @param form 字段 + * @param singeLine + * @param keyboardOptions + * @param isError + * @param visualTransformation + */ +@Composable +fun 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 + ) +} + diff --git a/lib/src/main/java/com/gyf/lib/uikit/MainFrame.kt b/lib/src/main/java/com/gyf/lib/uikit/MainFrame.kt index 3b65e8f..27ec818 100644 --- a/lib/src/main/java/com/gyf/lib/uikit/MainFrame.kt +++ b/lib/src/main/java/com/gyf/lib/uikit/MainFrame.kt @@ -7,7 +7,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 diff --git a/lib/src/main/java/com/gyf/lib/uikit/Snackbar.kt b/lib/src/main/java/com/gyf/lib/uikit/Snackbar.kt index e82cdc9..29a83ea 100644 --- a/lib/src/main/java/com/gyf/lib/uikit/Snackbar.kt +++ b/lib/src/main/java/com/gyf/lib/uikit/Snackbar.kt @@ -27,8 +27,8 @@ data class SnackBar( * */ class ScaffoldModel : ViewModel() { - private val _data = MutableLiveData() - val data: LiveData = _data + private val _data = MutableLiveData() + val data: LiveData = _data fun update(message: String? = null, actionLabel: String? = null, callback: () -> Unit? = {}) { if (message == null) { diff --git a/lib/src/main/java/com/gyf/lib/util/BottomButton.kt b/lib/src/main/java/com/gyf/lib/util/BottomButton.kt new file mode 100644 index 0000000..fdfae33 --- /dev/null +++ b/lib/src/main/java/com/gyf/lib/util/BottomButton.kt @@ -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)) + } + } +} \ No newline at end of file diff --git a/lib/src/main/java/com/gyf/lib/util/RandomUtil.kt b/lib/src/main/java/com/gyf/lib/util/RandomUtil.kt index daa4a52..529a09b 100644 --- a/lib/src/main/java/com/gyf/lib/util/RandomUtil.kt +++ b/lib/src/main/java/com/gyf/lib/util/RandomUtil.kt @@ -50,6 +50,7 @@ fun randomDateTime(): Date { } } + fun Date.format(): String { return FORMAT.format(this) } diff --git a/lib/src/main/res/values-en/integers.xml b/lib/src/main/res/values-en/integers.xml new file mode 100644 index 0000000..c6c1f31 --- /dev/null +++ b/lib/src/main/res/values-en/integers.xml @@ -0,0 +1,4 @@ + + + 20 + \ No newline at end of file diff --git a/lib/src/main/res/values-en/strings.xml b/lib/src/main/res/values-en/strings.xml new file mode 100644 index 0000000..5387c0c --- /dev/null +++ b/lib/src/main/res/values-en/strings.xml @@ -0,0 +1,15 @@ + + + 活动申请书 + 活动名称 + 活动时间 + 提交 + 返回 + 注册 + 活动地点 + 活动人数 + 活动人数要在%1d到%2d之间 + 活动介绍 + 检索 + 未找到结果 + \ No newline at end of file diff --git a/lib/src/main/res/values-zh/integers.xml b/lib/src/main/res/values-zh/integers.xml new file mode 100644 index 0000000..c6c1f31 --- /dev/null +++ b/lib/src/main/res/values-zh/integers.xml @@ -0,0 +1,4 @@ + + + 20 + \ No newline at end of file diff --git a/lib/src/main/res/values-zh/strings.xml b/lib/src/main/res/values-zh/strings.xml new file mode 100644 index 0000000..5387c0c --- /dev/null +++ b/lib/src/main/res/values-zh/strings.xml @@ -0,0 +1,15 @@ + + + 活动申请书 + 活动名称 + 活动时间 + 提交 + 返回 + 注册 + 活动地点 + 活动人数 + 活动人数要在%1d到%2d之间 + 活动介绍 + 检索 + 未找到结果 + \ No newline at end of file diff --git a/lib/src/main/res/values/integers.xml b/lib/src/main/res/values/integers.xml new file mode 100644 index 0000000..c6c1f31 --- /dev/null +++ b/lib/src/main/res/values/integers.xml @@ -0,0 +1,4 @@ + + + 20 + \ No newline at end of file diff --git a/lib/src/main/res/values/strings.xml b/lib/src/main/res/values/strings.xml new file mode 100644 index 0000000..5387c0c --- /dev/null +++ b/lib/src/main/res/values/strings.xml @@ -0,0 +1,15 @@ + + + 活动申请书 + 活动名称 + 活动时间 + 提交 + 返回 + 注册 + 活动地点 + 活动人数 + 活动人数要在%1d到%2d之间 + 活动介绍 + 检索 + 未找到结果 + \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index dc9ec1e..0d296bc 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -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() }